From 919c283e64026dba89292583729858dd89834615 Mon Sep 17 00:00:00 2001 From: Bart Saelen Date: Thu, 14 Apr 2022 10:45:19 +0200 Subject: [PATCH] Upload refactor (#18) * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * upload refactor * cleanup --- ckanext/scheming/fanstatic/scheming-upload.js | 8 + ckanext/scheming/plugins.py | 2 + ckanext/scheming/templates/base.html | 7 + .../scheming/form_snippets/doc_upload.html | 57 +- venv/.Python | 1 + venv/bin/activate | 78 + venv/bin/activate.csh | 42 + venv/bin/activate.fish | 101 + venv/bin/activate.ps1 | 60 + venv/bin/activate_this.py | 46 + venv/bin/chardetect | 10 + venv/bin/ckan-admin | 10 + venv/bin/ckanapi | 10 + venv/bin/easy_install | 10 + venv/bin/easy_install-2.7 | 10 + venv/bin/pip | 10 + venv/bin/pip2 | 10 + venv/bin/pip2.7 | 10 + venv/bin/python | Bin 0 -> 51744 bytes venv/bin/python-config | 78 + venv/bin/python2 | 1 + venv/bin/python2.7 | 1 + venv/bin/slugify | 10 + venv/bin/wheel | 10 + venv/lib/python2.7/LICENSE.txt | 1 + venv/lib/python2.7/UserDict.py | 1 + venv/lib/python2.7/_abcoll.py | 1 + venv/lib/python2.7/_weakrefset.py | 1 + venv/lib/python2.7/abc.py | 1 + venv/lib/python2.7/codecs.py | 1 + venv/lib/python2.7/config | 1 + venv/lib/python2.7/copy_reg.py | 1 + venv/lib/python2.7/distutils/__init__.py | 134 + venv/lib/python2.7/distutils/distutils.cfg | 6 + venv/lib/python2.7/encodings | 1 + venv/lib/python2.7/fnmatch.py | 1 + venv/lib/python2.7/genericpath.py | 1 + venv/lib/python2.7/lib-dynload | 1 + venv/lib/python2.7/linecache.py | 1 + venv/lib/python2.7/locale.py | 1 + .../lib/python2.7/no-global-site-packages.txt | 0 venv/lib/python2.7/ntpath.py | 1 + venv/lib/python2.7/orig-prefix.txt | 1 + venv/lib/python2.7/os.py | 1 + venv/lib/python2.7/posixpath.py | 1 + venv/lib/python2.7/re.py | 1 + .../Werkzeug-0.16.0.dist-info/INSTALLER | 1 + .../Werkzeug-0.16.0.dist-info/LICENSE.rst | 28 + .../Werkzeug-0.16.0.dist-info/METADATA | 128 + .../Werkzeug-0.16.0.dist-info/RECORD | 119 + .../Werkzeug-0.16.0.dist-info/WHEEL | 6 + .../Werkzeug-0.16.0.dist-info/top_level.txt | 1 + .../DESCRIPTION.rst | 50 + .../certifi-2019.9.11.dist-info/INSTALLER | 1 + .../certifi-2019.9.11.dist-info/METADATA | 74 + .../certifi-2019.9.11.dist-info/RECORD | 14 + .../certifi-2019.9.11.dist-info/WHEEL | 6 + .../certifi-2019.9.11.dist-info/metadata.json | 1 + .../certifi-2019.9.11.dist-info/top_level.txt | 1 + .../site-packages/certifi/__init__.py | 3 + .../site-packages/certifi/__main__.py | 2 + .../site-packages/certifi/cacert.pem | 4558 ++ .../python2.7/site-packages/certifi/core.py | 15 + .../chardet-3.0.4.dist-info/DESCRIPTION.rst | 70 + .../chardet-3.0.4.dist-info/INSTALLER | 1 + .../chardet-3.0.4.dist-info/METADATA | 96 + .../chardet-3.0.4.dist-info/RECORD | 91 + .../chardet-3.0.4.dist-info/WHEEL | 6 + .../chardet-3.0.4.dist-info/entry_points.txt | 3 + .../chardet-3.0.4.dist-info/metadata.json | 1 + .../chardet-3.0.4.dist-info/top_level.txt | 1 + .../site-packages/chardet/__init__.py | 39 + .../site-packages/chardet/big5freq.py | 386 + .../site-packages/chardet/big5prober.py | 47 + .../site-packages/chardet/chardistribution.py | 233 + .../chardet/charsetgroupprober.py | 106 + .../site-packages/chardet/charsetprober.py | 145 + .../site-packages/chardet/cli/__init__.py | 1 + .../site-packages/chardet/cli/chardetect.py | 85 + .../chardet/codingstatemachine.py | 88 + .../python2.7/site-packages/chardet/compat.py | 34 + .../site-packages/chardet/cp949prober.py | 49 + .../python2.7/site-packages/chardet/enums.py | 76 + .../site-packages/chardet/escprober.py | 101 + .../python2.7/site-packages/chardet/escsm.py | 246 + .../site-packages/chardet/eucjpprober.py | 92 + .../site-packages/chardet/euckrfreq.py | 195 + .../site-packages/chardet/euckrprober.py | 47 + .../site-packages/chardet/euctwfreq.py | 387 + .../site-packages/chardet/euctwprober.py | 46 + .../site-packages/chardet/gb2312freq.py | 283 + .../site-packages/chardet/gb2312prober.py | 46 + .../site-packages/chardet/hebrewprober.py | 292 + .../site-packages/chardet/jisfreq.py | 325 + .../python2.7/site-packages/chardet/jpcntx.py | 233 + .../chardet/langbulgarianmodel.py | 228 + .../chardet/langcyrillicmodel.py | 333 + .../site-packages/chardet/langgreekmodel.py | 225 + .../site-packages/chardet/langhebrewmodel.py | 200 + .../chardet/langhungarianmodel.py | 225 + .../site-packages/chardet/langthaimodel.py | 199 + .../site-packages/chardet/langturkishmodel.py | 193 + .../site-packages/chardet/latin1prober.py | 145 + .../site-packages/chardet/mbcharsetprober.py | 91 + .../site-packages/chardet/mbcsgroupprober.py | 54 + .../python2.7/site-packages/chardet/mbcssm.py | 572 + .../site-packages/chardet/sbcharsetprober.py | 132 + .../site-packages/chardet/sbcsgroupprober.py | 73 + .../site-packages/chardet/sjisprober.py | 92 + .../chardet/universaldetector.py | 286 + .../site-packages/chardet/utf8prober.py | 82 + .../site-packages/chardet/version.py | 9 + .../site-packages/ckan-2.8.3-py2.7-nspkg.pth | 3 + .../ckan-2.8.3.dist-info/INSTALLER | 1 + .../ckan-2.8.3.dist-info/METADATA | 28 + .../site-packages/ckan-2.8.3.dist-info/RECORD | 2168 + .../site-packages/ckan-2.8.3.dist-info/WHEEL | 5 + .../ckan-2.8.3.dist-info/entry_points.txt | 160 + .../namespace_packages.txt | 2 + .../ckan-2.8.3.dist-info/top_level.txt | 2 + .../python2.7/site-packages/ckan/__init__.py | 22 + .../lib/python2.7/site-packages/ckan/authz.py | 473 + .../site-packages/ckan/ckan_nose_plugin.py | 133 + .../python2.7/site-packages/ckan/common.py | 208 + .../site-packages/ckan/config/__init__.py | 0 .../ckan/config/deployment.ini_tmpl | 228 + .../site-packages/ckan/config/environment.py | 318 + .../site-packages/ckan/config/install.py | 22 + .../ckan/config/middleware/__init__.py | 210 + .../config/middleware/common_middleware.py | 93 + .../ckan/config/middleware/flask_app.py | 444 + .../ckan/config/middleware/pylons_app.py | 286 + .../ckan/config/resource_formats.json | 77 + .../site-packages/ckan/config/routing.py | 347 + .../site-packages/ckan/config/solr/schema.xml | 188 + .../site-packages/ckan/config/who.ini | 37 + .../ckan/controllers/__init__.py | 0 .../site-packages/ckan/controllers/admin.py | 191 + .../site-packages/ckan/controllers/api.py | 624 + .../site-packages/ckan/controllers/error.py | 65 + .../site-packages/ckan/controllers/feed.py | 575 + .../site-packages/ckan/controllers/group.py | 933 + .../site-packages/ckan/controllers/home.py | 99 + .../ckan/controllers/organization.py | 30 + .../site-packages/ckan/controllers/package.py | 1617 + .../ckan/controllers/revision.py | 193 + .../site-packages/ckan/controllers/storage.py | 98 + .../site-packages/ckan/controllers/tag.py | 80 + .../ckan/controllers/template.py | 45 + .../site-packages/ckan/controllers/user.py | 748 + .../site-packages/ckan/controllers/util.py | 42 + .../site-packages/ckan/exceptions.py | 34 + .../site-packages/ckan/i18n/__init__.py | 3 + .../site-packages/ckan/i18n/check_po_files.py | 94 + .../site-packages/ckan/i18n/ckan.pot | 4665 ++ .../site-packages/ckan/include/__init__.py | 0 .../site-packages/ckan/include/rcssmin.py | 360 + .../site-packages/ckan/include/rjsmin.py | 290 + .../site-packages/ckan/lib/__init__.py | 0 .../ckan/lib/activity_streams.py | 262 + .../lib/activity_streams_session_extension.py | 153 + .../ckan/lib/alphabet_paginate.py | 159 + .../site-packages/ckan/lib/app_globals.py | 225 + .../site-packages/ckan/lib/auth_tkt.py | 87 + .../site-packages/ckan/lib/authenticator.py | 32 + .../python2.7/site-packages/ckan/lib/base.py | 259 + .../site-packages/ckan/lib/captcha.py | 41 + .../python2.7/site-packages/ckan/lib/cli.py | 2649 + .../site-packages/ckan/lib/config_tool.py | 261 + .../ckan/lib/create_test_data.py | 933 + .../site-packages/ckan/lib/datapreview.py | 306 + .../ckan/lib/dictization/__init__.py | 156 + .../ckan/lib/dictization/model_dictize.py | 768 + .../ckan/lib/dictization/model_save.py | 614 + .../ckan/lib/email_notifications.py | 226 + .../site-packages/ckan/lib/extract.py | 42 + .../ckan/lib/fanstatic_extensions.py | 116 + .../ckan/lib/fanstatic_resources.py | 247 + .../site-packages/ckan/lib/formatters.py | 203 + .../python2.7/site-packages/ckan/lib/hash.py | 29 + .../site-packages/ckan/lib/helpers.py | 2647 + .../python2.7/site-packages/ckan/lib/i18n.py | 408 + .../python2.7/site-packages/ckan/lib/io.py | 53 + .../ckan/lib/jinja_extensions.py | 364 + .../python2.7/site-packages/ckan/lib/jobs.py | 300 + .../python2.7/site-packages/ckan/lib/jsonp.py | 32 + .../site-packages/ckan/lib/lazyjson.py | 55 + .../site-packages/ckan/lib/mailer.py | 205 + .../site-packages/ckan/lib/maintain.py | 92 + .../python2.7/site-packages/ckan/lib/munge.py | 181 + .../site-packages/ckan/lib/navl/__init__.py | 3 + .../ckan/lib/navl/dictization_functions.py | 471 + .../site-packages/ckan/lib/navl/validators.py | 165 + .../site-packages/ckan/lib/plugins.py | 636 + .../python2.7/site-packages/ckan/lib/redis.py | 64 + .../site-packages/ckan/lib/render.py | 49 + .../site-packages/ckan/lib/search/__init__.py | 307 + .../site-packages/ckan/lib/search/common.py | 101 + .../site-packages/ckan/lib/search/index.py | 327 + .../site-packages/ckan/lib/search/query.py | 421 + .../site-packages/ckan/lib/uploader.py | 317 + .../site-packages/ckan/logic/__init__.py | 717 + .../ckan/logic/action/__init__.py | 67 + .../site-packages/ckan/logic/action/create.py | 1491 + .../site-packages/ckan/logic/action/delete.py | 757 + .../site-packages/ckan/logic/action/get.py | 3544 ++ .../site-packages/ckan/logic/action/patch.py | 132 + .../site-packages/ckan/logic/action/update.py | 1237 + .../site-packages/ckan/logic/auth/__init__.py | 52 + .../site-packages/ckan/logic/auth/create.py | 247 + .../site-packages/ckan/logic/auth/delete.py | 149 + .../site-packages/ckan/logic/auth/get.py | 364 + .../site-packages/ckan/logic/auth/patch.py | 19 + .../site-packages/ckan/logic/auth/update.py | 295 + .../site-packages/ckan/logic/converters.py | 197 + .../site-packages/ckan/logic/schema.py | 779 + .../site-packages/ckan/logic/validators.py | 856 + .../site-packages/ckan/migration/README | 4 + .../site-packages/ckan/migration/__init__.py | 0 .../site-packages/ckan/migration/manage.py | 6 + .../site-packages/ckan/migration/migrate.cfg | 20 + .../versions/001_add_existing_tables.py | 115 + .../versions/002_add_author_and_maintainer.py | 34 + .../migration/versions/003_add_user_object.py | 27 + .../versions/004_add_group_object.py | 38 + .../versions/005_add_authorization_tables.py | 57 + .../migration/versions/006_add_ratings.py | 30 + .../versions/007_add_system_roles.py | 27 + .../migration/versions/008_update_vdm_ids.py | 91 + .../versions/009_add_creation_timestamps.py | 27 + .../migration/versions/010_add_user_about.py | 16 + .../versions/011_add_package_search_vector.py | 25 + .../migration/versions/012_add_resources.py | 80 + .../ckan/migration/versions/013_add_hash.py | 17 + .../ckan/migration/versions/014_hash_2.py | 19 + .../versions/015_remove_state_object.py | 35 + .../versions/016_uuids_everywhere.py | 196 + .../versions/017_add_pkg_relationships.py | 41 + .../migration/versions/018_adjust_licenses.py | 180 + .../versions/019_pkg_relationships_state.py | 26 + .../migration/versions/020_add_changeset.py | 47 + .../versions/021_postgres_downgrade.sql | 1 + .../versions/021_postgres_upgrade.sql | 73 + .../versions/021_postgresql_downgrade.sql | 1 + .../versions/021_postgresql_upgrade.sql | 73 + .../versions/022_add_group_extras.py | 53 + .../migration/versions/023_add_harvesting.py | 42 + .../versions/024_add_harvested_document.py | 22 + .../versions/025_add_authorization_groups.py | 58 + .../026_authorization_group_user_pk.py | 37 + .../versions/027_adjust_harvester.py | 34 + .../028_drop_harvest_source_status.py | 18 + .../migration/versions/029_version_groups.py | 174 + .../030_additional_user_attributes.py | 24 + .../versions/031_move_openid_to_new_field.py | 39 + .../032_add_extra_info_field_to_resources.py | 20 + .../033_auth_group_user_id_add_conditional.py | 32 + .../versions/034_resource_group_table.py | 209 + .../versions/035_harvesting_doc_versioning.py | 50 + .../migration/versions/036_lockdown_roles.py | 37 + .../versions/037_role_anon_editor.py | 50 + .../versions/038_delete_migration_tables.py | 12 + .../versions/039_add_expired_id_and_dates.py | 221 + .../versions/040_reset_key_on_user.py | 15 + .../versions/041_resource_new_fields.py | 34 + .../versions/042_user_revision_indexes.py | 15 + .../versions/043_drop_postgres_search.py | 14 + .../migration/versions/044_add_task_status.py | 26 + .../versions/045_user_name_unique.py | 19 + .../migration/versions/046_drop_changesets.py | 12 + .../047_rename_package_group_member.py | 110 + .../048_add_activity_streams_tables.py | 37 + .../versions/049_add_group_approval_status.py | 19 + .../versions/050_term_translation_table.py | 22 + .../versions/051_add_tag_vocabulary.py | 31 + .../versions/052_update_member_capacities.py | 14 + .../migration/versions/053_add_group_logo.py | 14 + .../versions/054_add_resource_created_date.py | 11 + .../055_update_user_and_activity_detail.py | 11 + .../versions/056_add_related_table.py | 42 + .../ckan/migration/versions/057_tracking.py | 35 + .../versions/058_add_follower_tables.py | 39 + .../059_add_related_count_and_flag.py | 16 + .../versions/060_add_system_info_table.py | 29 + .../versions/061_add_follower__group_table.py | 24 + .../versions/062_add_dashboard_table.py | 18 + .../migration/versions/063_org_changes.py | 33 + .../064_add_email_last_sent_column.py | 12 + .../065_add_email_notifications_preference.py | 12 + .../versions/066_default_package_type.py | 14 + .../versions/067_turn_extras_to_strings.py | 30 + .../versions/068_add_package_extras_index.py | 10 + .../069_resource_url_and_metadata_modified.py | 76 + .../070_add_activity_and_resource_indexes.py | 16 + .../071_add_state_column_to_user_table.py | 11 + .../versions/072_add_resource_view.py | 26 + ...te_resource_view_resource_id_constraint.py | 17 + .../versions/074_remove_resource_groups.py | 31 + .../versions/075_rename_view_plugins.py | 17 + .../versions/076_rename_view_plugins_2.py | 17 + .../077_add_revisions_to_system_info.py | 25 + .../versions/078_remove_old_authz_model.py | 16 + .../versions/079_resource_revision_index.py | 10 + .../versions/080_continuity_id_indexes.py | 16 + .../versions/081_set_datastore_active.py | 67 + .../082_create_index_creator_user_id.py | 10 + .../versions/083_remove_related_items.py | 28 + .../versions/084_add_metadata_created.py | 17 + .../085_adjust_activity_timestamps.py | 23 + .../versions/086_drop_openid_column.py | 12 + .../ckan/migration/versions/__init__.py | 0 .../site-packages/ckan/model/__init__.py | 411 + .../site-packages/ckan/model/activity.py | 353 + .../site-packages/ckan/model/core.py | 50 + .../site-packages/ckan/model/dashboard.py | 45 + .../site-packages/ckan/model/domain_object.py | 117 + .../site-packages/ckan/model/extension.py | 118 + .../site-packages/ckan/model/follower.py | 184 + .../site-packages/ckan/model/group.py | 431 + .../site-packages/ckan/model/group_extra.py | 56 + .../site-packages/ckan/model/license.py | 363 + .../site-packages/ckan/model/meta.py | 147 + .../site-packages/ckan/model/misc.py | 17 + .../site-packages/ckan/model/modification.py | 86 + .../site-packages/ckan/model/package.py | 640 + .../site-packages/ckan/model/package_extra.py | 85 + .../ckan/model/package_relationship.py | 178 + .../site-packages/ckan/model/rating.py | 42 + .../site-packages/ckan/model/resource.py | 242 + .../site-packages/ckan/model/resource_view.py | 69 + .../site-packages/ckan/model/system_info.py | 93 + .../python2.7/site-packages/ckan/model/tag.py | 322 + .../site-packages/ckan/model/task_status.py | 35 + .../ckan/model/term_translation.py | 14 + .../site-packages/ckan/model/tracking.py | 52 + .../site-packages/ckan/model/types.py | 97 + .../site-packages/ckan/model/user.py | 309 + .../site-packages/ckan/model/vocabulary.py | 44 + .../ckan/pastertemplates/__init__.py | 82 + .../site-packages/ckan/plugins/__init__.py | 6 + .../site-packages/ckan/plugins/core.py | 259 + .../site-packages/ckan/plugins/interfaces.py | 1778 + .../site-packages/ckan/plugins/toolkit.py | 500 + .../ckan/plugins/toolkit_sphinx_extension.py | 187 + .../javascript/modules/activity-stream.js | 119 + .../javascript/modules/activity-stream.min.js | 2 + .../base/javascript/modules/api-info.js | 127 + .../base/javascript/modules/api-info.min.js | 4 + .../base/javascript/modules/autocomplete.js | 275 + .../javascript/modules/autocomplete.min.js | 9 + .../base/javascript/modules/basic-form.js | 24 + .../base/javascript/modules/basic-form.min.js | 1 + .../base/javascript/modules/confirm-action.js | 128 + .../javascript/modules/confirm-action.min.js | 2 + .../base/javascript/modules/custom-fields.js | 99 + .../javascript/modules/custom-fields.min.js | 2 + .../base/javascript/modules/dashboard.js | 86 + .../base/javascript/modules/dashboard.min.js | 3 + .../base/javascript/modules/data-viewer.js | 54 + .../javascript/modules/data-viewer.min.js | 1 + .../javascript/modules/dataset-visibility.js | 32 + .../modules/dataset-visibility.min.js | 1 + .../public/base/javascript/modules/follow.js | 78 + .../base/javascript/modules/follow.min.js | 2 + .../javascript/modules/followers-counter.js | 93 + .../modules/followers-counter.min.js | 2 + .../base/javascript/modules/image-upload.js | 280 + .../javascript/modules/image-upload.min.js | 10 + .../base/javascript/modules/media-grid.js | 16 + .../base/javascript/modules/media-grid.min.js | 1 + .../javascript/modules/popover-context.js | 257 + .../javascript/modules/popover-context.min.js | 7 + .../base/javascript/modules/resource-form.js | 55 + .../javascript/modules/resource-form.min.js | 1 + .../javascript/modules/resource-reorder.js | 151 + .../modules/resource-reorder.min.js | 1 + .../modules/resource-upload-field.js | 283 + .../modules/resource-upload-field.min.js | 3 + .../javascript/modules/resource-view-embed.js | 51 + .../modules/resource-view-embed.min.js | 9 + .../modules/resource-view-filters-form.js | 104 + .../modules/resource-view-filters-form.min.js | 5 + .../modules/resource-view-filters.js | 186 + .../modules/resource-view-filters.min.js | 11 + .../modules/resource-view-reorder.js | 140 + .../modules/resource-view-reorder.min.js | 1 + .../base/javascript/modules/select-switch.js | 31 + .../javascript/modules/select-switch.min.js | 1 + .../base/javascript/modules/slug-preview.js | 79 + .../javascript/modules/slug-preview.min.js | 3 + .../modules/table-selectable-rows.js | 95 + .../modules/table-selectable-rows.min.js | 3 + .../javascript/modules/table-toggle-more.js | 61 + .../modules/table-toggle-more.min.js | 1 + .../javascript/plugins/jquery.date-helpers.js | 82 + .../plugins/jquery.date-helpers.min.js | 4 + .../javascript/plugins/jquery.form-warning.js | 41 + .../plugins/jquery.form-warning.min.js | 3 + .../plugins/jquery.images-loaded.js | 496 + .../plugins/jquery.images-loaded.min.js | 37 + .../base/javascript/plugins/jquery.inherit.js | 52 + .../javascript/plugins/jquery.inherit.min.js | 6 + .../base/javascript/plugins/jquery.masonry.js | 2501 + .../javascript/plugins/jquery.masonry.min.js | 114 + .../javascript/plugins/jquery.proxy-all.js | 47 + .../plugins/jquery.proxy-all.min.js | 2 + .../javascript/plugins/jquery.slug-preview.js | 80 + .../plugins/jquery.slug-preview.min.js | 3 + .../base/javascript/plugins/jquery.slug.js | 83 + .../javascript/plugins/jquery.slug.min.js | 5 + .../javascript/plugins/jquery.truncator.js | 145 + .../plugins/jquery.truncator.min.js | 8 + .../javascript/plugins/jquery.url-helpers.js | 167 + .../plugins/jquery.url-helpers.min.js | 3 + .../test/spec/modules/autocomplete.spec.js | 327 + .../spec/modules/autocomplete.spec.min.js | 2 + .../base/test/spec/modules/basic-form.spec.js | 39 + .../test/spec/modules/basic-form.spec.min.js | 4 + .../test/spec/modules/confirm-action.spec.js | 118 + .../spec/modules/confirm-action.spec.min.js | 1 + .../test/spec/modules/custom-fields.spec.js | 168 + .../spec/modules/custom-fields.spec.min.js | 1 + .../spec/modules/followers-counter.spec.js | 186 + .../modules/followers-counter.spec.min.js | 1 + .../test/spec/modules/image-upload.spec.js | 65 + .../spec/modules/image-upload.spec.min.js | 2 + .../test/spec/modules/resource-form.spec.js | 74 + .../spec/modules/resource-form.spec.min.js | 1 + .../modules/resource-upload-field.spec.js | 290 + .../modules/resource-upload-field.spec.min.js | 1 + .../spec/plugins/jquery.date-helpers.spec.js | 37 + .../plugins/jquery.date-helpers.spec.min.js | 1 + .../spec/plugins/jquery.form-warning.spec.js | 42 + .../plugins/jquery.form-warning.spec.min.js | 1 + .../test/spec/plugins/jquery.inherit.spec.js | 46 + .../spec/plugins/jquery.inherit.spec.min.js | 3 + .../spec/plugins/jquery.proxy-all.spec.js | 60 + .../spec/plugins/jquery.proxy-all.spec.min.js | 1 + .../spec/plugins/jquery.slug-preview.spec.js | 65 + .../plugins/jquery.slug-preview.spec.min.js | 1 + .../test/spec/plugins/jquery.slug.spec.js | 34 + .../test/spec/plugins/jquery.slug.spec.min.js | 1 + .../spec/plugins/jquery.url-helpers.spec.js | 52 + .../plugins/jquery.url-helpers.spec.min.js | 1 + .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../fonts/glyphicons-halflings-regular.svg | 288 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes .../base/vendor/bootstrap/js/bootstrap.js | 2377 + .../base/vendor/bootstrap/js/bootstrap.min.js | 7 + .../base/vendor/bootstrap/less/alerts.less | 73 + .../base/vendor/bootstrap/less/badges.less | 66 + .../base/vendor/bootstrap/less/bootstrap.less | 56 + .../vendor/bootstrap/less/breadcrumbs.less | 26 + .../vendor/bootstrap/less/button-groups.less | 244 + .../base/vendor/bootstrap/less/buttons.less | 166 + .../base/vendor/bootstrap/less/carousel.less | 270 + .../base/vendor/bootstrap/less/close.less | 34 + .../base/vendor/bootstrap/less/code.less | 69 + .../bootstrap/less/component-animations.less | 33 + .../base/vendor/bootstrap/less/dropdowns.less | 216 + .../base/vendor/bootstrap/less/forms.less | 613 + .../vendor/bootstrap/less/glyphicons.less | 305 + .../base/vendor/bootstrap/less/grid.less | 84 + .../vendor/bootstrap/less/input-groups.less | 171 + .../base/vendor/bootstrap/less/jumbotron.less | 54 + .../base/vendor/bootstrap/less/labels.less | 64 + .../vendor/bootstrap/less/list-group.less | 130 + .../base/vendor/bootstrap/less/media.less | 66 + .../base/vendor/bootstrap/less/mixins.less | 40 + .../vendor/bootstrap/less/mixins/alerts.less | 14 + .../less/mixins/background-variant.less | 9 + .../bootstrap/less/mixins/border-radius.less | 18 + .../vendor/bootstrap/less/mixins/buttons.less | 65 + .../bootstrap/less/mixins/center-block.less | 7 + .../bootstrap/less/mixins/clearfix.less | 22 + .../vendor/bootstrap/less/mixins/forms.less | 85 + .../bootstrap/less/mixins/gradients.less | 59 + .../bootstrap/less/mixins/grid-framework.less | 91 + .../vendor/bootstrap/less/mixins/grid.less | 122 + .../bootstrap/less/mixins/hide-text.less | 21 + .../vendor/bootstrap/less/mixins/image.less | 33 + .../vendor/bootstrap/less/mixins/labels.less | 12 + .../bootstrap/less/mixins/list-group.less | 30 + .../bootstrap/less/mixins/nav-divider.less | 10 + .../less/mixins/nav-vertical-align.less | 9 + .../vendor/bootstrap/less/mixins/opacity.less | 8 + .../bootstrap/less/mixins/pagination.less | 24 + .../vendor/bootstrap/less/mixins/panels.less | 24 + .../bootstrap/less/mixins/progress-bar.less | 10 + .../bootstrap/less/mixins/reset-filter.less | 8 + .../bootstrap/less/mixins/reset-text.less | 18 + .../vendor/bootstrap/less/mixins/resize.less | 6 + .../less/mixins/responsive-visibility.less | 15 + .../vendor/bootstrap/less/mixins/size.less | 10 + .../bootstrap/less/mixins/tab-focus.less | 9 + .../bootstrap/less/mixins/table-row.less | 28 + .../bootstrap/less/mixins/text-emphasis.less | 9 + .../bootstrap/less/mixins/text-overflow.less | 8 + .../less/mixins/vendor-prefixes.less | 227 + .../base/vendor/bootstrap/less/modals.less | 150 + .../base/vendor/bootstrap/less/navbar.less | 660 + .../base/vendor/bootstrap/less/navs.less | 242 + .../base/vendor/bootstrap/less/normalize.less | 424 + .../base/vendor/bootstrap/less/pager.less | 54 + .../vendor/bootstrap/less/pagination.less | 89 + .../base/vendor/bootstrap/less/panels.less | 271 + .../base/vendor/bootstrap/less/popovers.less | 131 + .../base/vendor/bootstrap/less/print.less | 101 + .../vendor/bootstrap/less/progress-bars.less | 87 + .../bootstrap/less/responsive-embed.less | 35 + .../bootstrap/less/responsive-utilities.less | 194 + .../vendor/bootstrap/less/scaffolding.less | 161 + .../base/vendor/bootstrap/less/tables.less | 234 + .../base/vendor/bootstrap/less/theme.less | 291 + .../vendor/bootstrap/less/thumbnails.less | 36 + .../base/vendor/bootstrap/less/tooltip.less | 101 + .../base/vendor/bootstrap/less/type.less | 302 + .../base/vendor/bootstrap/less/utilities.less | 55 + .../base/vendor/bootstrap/less/variables.less | 869 + .../base/vendor/bootstrap/less/wells.less | 29 + .../vendor/font-awesome/css/font-awesome.css | 2337 + .../font-awesome/css/font-awesome.css.map | 7 + .../font-awesome/css/font-awesome.min.css | 4 + .../vendor/font-awesome/fonts/FontAwesome.otf | Bin 0 -> 134808 bytes .../fonts/fontawesome-webfont.eot | Bin 0 -> 165742 bytes .../fonts/fontawesome-webfont.svg | 2671 + .../fonts/fontawesome-webfont.ttf | Bin 0 -> 165548 bytes .../fonts/fontawesome-webfont.woff | Bin 0 -> 98024 bytes .../fonts/fontawesome-webfont.woff2 | Bin 0 -> 77160 bytes .../vendor/font-awesome/less/animated.less | 34 + .../font-awesome/less/bordered-pulled.less | 25 + .../base/vendor/font-awesome/less/core.less | 12 + .../vendor/font-awesome/less/fixed-width.less | 6 + .../font-awesome/less/font-awesome.less | 18 + .../base/vendor/font-awesome/less/icons.less | 789 + .../base/vendor/font-awesome/less/larger.less | 13 + .../base/vendor/font-awesome/less/list.less | 19 + .../base/vendor/font-awesome/less/mixins.less | 60 + .../base/vendor/font-awesome/less/path.less | 15 + .../font-awesome/less/rotated-flipped.less | 20 + .../font-awesome/less/screen-reader.less | 5 + .../vendor/font-awesome/less/stacked.less | 20 + .../vendor/font-awesome/less/variables.less | 800 + .../jquery-fileupload/jquery.fileupload-ui.js | 702 + .../jquery.fileupload-ui.min.js | 30 + .../jquery-fileupload/jquery.fileupload.js | 968 + .../jquery.fileupload.min.js | 45 + .../jquery.iframe-transport.js | 171 + .../jquery.iframe-transport.min.js | 7 + .../public/base/vendor/select2/.gitignore | 2 + .../base/vendor/select2/CONTRIBUTING.md | 107 + .../ckan/public/base/vendor/select2/README.md | 115 + .../public/base/vendor/select2/bower.json | 8 + .../public/base/vendor/select2/component.json | 66 + .../public/base/vendor/select2/composer.json | 29 + .../public/base/vendor/select2/package.json | 20 + .../public/base/vendor/select2/release.sh | 79 + .../base/vendor/select2/select2-bootstrap.css | 87 + .../base/vendor/select2/select2-spinner.gif | Bin 0 -> 1849 bytes .../public/base/vendor/select2/select2.css | 692 + .../base/vendor/select2/select2.jquery.json | 36 + .../public/base/vendor/select2/select2.js | 3729 ++ .../public/base/vendor/select2/select2.png | Bin 0 -> 613 bytes .../base/vendor/select2/select2_locale_ar.js | 19 + .../base/vendor/select2/select2_locale_az.js | 20 + .../base/vendor/select2/select2_locale_bg.js | 20 + .../base/vendor/select2/select2_locale_ca.js | 19 + .../base/vendor/select2/select2_locale_cs.js | 51 + .../base/vendor/select2/select2_locale_da.js | 19 + .../base/vendor/select2/select2_locale_de.js | 18 + .../base/vendor/select2/select2_locale_es.js | 19 + .../base/vendor/select2/select2_locale_fa.js | 21 + .../base/vendor/select2/select2_locale_fr.js | 18 + .../base/vendor/select2/select2_locale_he.js | 19 + .../base/vendor/select2/select2_locale_hu.js | 17 + .../base/vendor/select2/select2_locale_it.js | 17 + .../base/vendor/select2/select2_locale_ja.js | 17 + .../base/vendor/select2/select2_locale_ka.js | 19 + .../base/vendor/select2/select2_locale_lt.js | 26 + .../base/vendor/select2/select2_locale_nb.js | 22 + .../base/vendor/select2/select2_locale_nl.js | 17 + .../vendor/select2/select2_locale_pt-PT.js | 17 + .../base/vendor/select2/select2_locale_ro.js | 17 + .../base/vendor/select2/select2_locale_rs.js | 19 + .../base/vendor/select2/select2_locale_sk.js | 50 + .../base/vendor/select2/select2_locale_sv.js | 19 + .../base/vendor/select2/select2_locale_tr.js | 20 + .../base/vendor/select2/select2_locale_uk.js | 25 + .../activity_stream_email_notifications.text | 7 + .../ckan/templates/admin/base.html | 12 + .../ckan/templates/admin/config.html | 73 + .../ckan/templates/admin/confirm_reset.html | 13 + .../ckan/templates/admin/index.html | 26 + .../ckan/templates/admin/trash.html | 37 + .../ajax_snippets/custom_fields.html | 4 + .../ajax_snippets/follow_button.html | 1 + .../site-packages/ckan/templates/base.html | 116 + .../ckan/templates/dataviewer/base.html | 17 + .../ckan/templates/development/markup.html | 9 + .../ckan/templates/development/primer.html | 100 + .../ckan/templates/emails/invite_user.txt | 17 + .../templates/emails/invite_user_subject.txt | 1 + .../ckan/templates/emails/reset_password.txt | 12 + .../emails/reset_password_subject.txt | 1 + .../templates/error_document_template.html | 21 + .../site-packages/ckan/templates/footer.html | 39 + .../ckan/templates/group/about.html | 16 + .../ckan/templates/group/activity_stream.html | 10 + .../ckan/templates/group/admins.html | 10 + .../ckan/templates/group/base_form_page.html | 15 + .../ckan/templates/group/confirm_delete.html | 21 + .../group/confirm_delete_member.html | 22 + .../ckan/templates/group/edit.html | 12 + .../ckan/templates/group/edit_base.html | 26 + .../ckan/templates/group/followers.html | 10 + .../ckan/templates/group/history.html | 10 + .../ckan/templates/group/index.html | 43 + .../ckan/templates/group/member_new.html | 93 + .../ckan/templates/group/members.html | 38 + .../ckan/templates/group/new.html | 13 + .../ckan/templates/group/new_group_form.html | 25 + .../ckan/templates/group/read.html | 41 + .../ckan/templates/group/read_base.html | 29 + .../site-packages/ckan/templates/header.html | 103 + .../ckan/templates/home/about.html | 24 + .../ckan/templates/home/index.html | 18 + .../ckan/templates/home/layout1.html | 37 + .../ckan/templates/home/layout2.html | 35 + .../ckan/templates/home/layout3.html | 23 + .../ckan/templates/macros/autoform.html | 70 + .../ckan/templates/macros/form.html | 444 + .../ckan/templates/organization/about.html | 15 + .../organization/activity_stream.html | 10 + .../ckan/templates/organization/admins.html | 10 + .../organization/base_form_page.html | 10 + .../templates/organization/bulk_process.html | 112 + .../organization/confirm_delete.html | 21 + .../organization/confirm_delete_member.html | 22 + .../ckan/templates/organization/edit.html | 6 + .../templates/organization/edit_base.html | 38 + .../ckan/templates/organization/index.html | 43 + .../templates/organization/member_new.html | 97 + .../ckan/templates/organization/members.html | 43 + .../ckan/templates/organization/new.html | 17 + .../organization/new_organization_form.html | 25 + .../ckan/templates/organization/read.html | 46 + .../templates/organization/read_base.html | 30 + .../ckan/templates/package/activity.html | 10 + .../ckan/templates/package/base.html | 25 + .../templates/package/base_form_page.html | 39 + .../templates/package/confirm_delete.html | 22 + .../package/confirm_delete_resource.html | 21 + .../ckan/templates/package/edit.html | 10 + .../ckan/templates/package/edit_base.html | 26 + .../ckan/templates/package/edit_view.html | 24 + .../ckan/templates/package/followers.html | 10 + .../ckan/templates/package/group_list.html | 26 + .../ckan/templates/package/history.html | 10 + .../ckan/templates/package/new.html | 10 + .../templates/package/new_package_form.html | 29 + .../ckan/templates/package/new_resource.html | 24 + .../package/new_resource_not_draft.html | 21 + .../ckan/templates/package/new_view.html | 32 + .../ckan/templates/package/read.html | 48 + .../ckan/templates/package/read_base.html | 62 + .../ckan/templates/package/resource_edit.html | 15 + .../templates/package/resource_edit_base.html | 41 + .../ckan/templates/package/resource_read.html | 213 + .../templates/package/resource_views.html | 29 + .../ckan/templates/package/resources.html | 31 + .../ckan/templates/package/search.html | 82 + .../templates/package/view_edit_base.html | 55 + .../site-packages/ckan/templates/page.html | 137 + .../ckan/templates/revision/__init__.py | 3 + .../ckan/templates/revision/diff.html | 56 + .../ckan/templates/revision/list.html | 19 + .../ckan/templates/revision/read.html | 94 + .../ckan/templates/revision/read_base.html | 19 + .../site-packages/ckan/templates/robots.txt | 11 + .../templates/snippets/activity_item.html | 10 + .../ckan/templates/snippets/add_dataset.html | 9 + .../templates/snippets/additional_info.html | 25 + .../ckan/templates/snippets/context.html | 10 + .../snippets/custom_form_fields.html | 42 + .../templates/snippets/datapusher_status.html | 14 + .../ckan/templates/snippets/debug.html | 63 + .../templates/snippets/disqus_trackback.html | 4 + .../ckan/templates/snippets/facet_list.html | 97 + .../templates/snippets/follow_button.html | 16 + .../ckan/templates/snippets/group.html | 27 + .../ckan/templates/snippets/group_item.html | 33 + .../snippets/home_breadcrumb_item.html | 2 + .../templates/snippets/language_selector.html | 12 + .../ckan/templates/snippets/license.html | 39 + .../snippets/local_friendly_datetime.html | 14 + .../ckan/templates/snippets/organization.html | 73 + .../templates/snippets/organization_item.html | 32 + .../ckan/templates/snippets/package_grid.html | 30 + .../ckan/templates/snippets/package_item.html | 78 + .../ckan/templates/snippets/package_list.html | 27 + .../ckan/templates/snippets/popular.html | 4 + .../ckan/templates/snippets/private.html | 3 + .../ckan/templates/snippets/search_form.html | 90 + .../snippets/search_result_text.html | 58 + .../templates/snippets/simple_search.html | 17 + .../ckan/templates/snippets/social.html | 14 + .../ckan/templates/snippets/sort_by.html | 23 + .../ckan/templates/snippets/subscribe.html | 7 + .../ckan/templates/snippets/tag_list.html | 14 + .../ckan/templates/tag/index.html | 39 + .../tests/broken_helper_as_attribute.html | 5 + .../tests/broken_helper_as_item.html | 5 + .../ckan/templates/tests/flash_messages.html | 14 + .../templates/tests/helper_as_attribute.html | 5 + .../ckan/templates/tests/helper_as_item.html | 5 + .../mock_json_resource_preview_template.html | 17 + .../tests/mock_resource_preview_template.html | 17 + .../ckan/templates/user/activity_stream.html | 10 + .../ckan/templates/user/dashboard.html | 48 + .../templates/user/dashboard_datasets.html | 23 + .../ckan/templates/user/dashboard_groups.html | 26 + .../user/dashboard_organizations.html | 27 + .../ckan/templates/user/edit.html | 25 + .../ckan/templates/user/edit_base.html | 11 + .../ckan/templates/user/edit_user_form.html | 56 + .../ckan/templates/user/followers.html | 12 + .../ckan/templates/user/list.html | 33 + .../ckan/templates/user/login.html | 54 + .../ckan/templates/user/logout.html | 14 + .../ckan/templates/user/logout_first.html | 26 + .../ckan/templates/user/new.html | 33 + .../ckan/templates/user/new_user_form.html | 22 + .../ckan/templates/user/perform_reset.html | 50 + .../ckan/templates/user/read.html | 32 + .../ckan/templates/user/read_base.html | 105 + .../ckan/templates/user/request_reset.html | 44 + .../site-packages/ckan/tests/__init__.py | 0 .../ckan/tests/config/__init__.py | 0 .../ckan/tests/config/test_environment.py | 113 + .../ckan/tests/config/test_middleware.py | 660 + .../ckan/tests/config/test_sessions.py | 127 + .../ckan/tests/controllers/__init__.py | 55 + .../ckan/tests/controllers/test_admin.py | 418 + .../ckan/tests/controllers/test_api.py | 400 + .../ckan/tests/controllers/test_feed.py | 152 + .../ckan/tests/controllers/test_group.py | 782 + .../ckan/tests/controllers/test_home.py | 120 + .../tests/controllers/test_organization.py | 571 + .../ckan/tests/controllers/test_package.py | 1749 + .../ckan/tests/controllers/test_tags.py | 135 + .../ckan/tests/controllers/test_template.py | 19 + .../ckan/tests/controllers/test_user.py | 691 + .../ckan/tests/controllers/test_util.py | 45 + .../site-packages/ckan/tests/factories.py | 423 + .../site-packages/ckan/tests/helpers.py | 678 + .../site-packages/ckan/tests/i18n/__init__.py | 0 .../ckan/tests/i18n/test_check_po_files.py | 125 + .../ckan/tests/legacy/__init__.py | 427 + .../ckan/tests/legacy/ckantestplugins.py | 200 + .../legacy/functional/api/model/__init__.py | 0 .../legacy/functional/api/model/test_group.py | 184 + .../functional/api/model/test_package.py | 564 + .../functional/api/model/test_ratings.py | 100 + .../functional/api/model/test_revisions.py | 63 + .../legacy/functional/api/model/test_tag.py | 57 + .../functional/api/model/test_vocabulary.py | 1126 + .../ckan/tests/legacy/html_check.py | 114 + .../ckan/tests/legacy/mock_mail_server.py | 85 + .../ckan/tests/legacy/mock_plugin.py | 51 + .../ckan/tests/legacy/pylons_controller.py | 71 + .../tests/legacy/test_coding_standards.py | 893 + .../ckan/tests/legacy/test_plugins.py | 197 + .../ckan/tests/legacy/test_versions.py | 14 + .../site-packages/ckan/tests/lib/__init__.py | 19 + .../ckan/tests/lib/test_app_globals.py | 19 + .../ckan/tests/lib/test_auth_tkt.py | 140 + .../site-packages/ckan/tests/lib/test_base.py | 347 + .../site-packages/ckan/tests/lib/test_cli.py | 343 + .../ckan/tests/lib/test_config_tool.py | 186 + .../ckan/tests/lib/test_datapreview.py | 294 + .../ckan/tests/lib/test_helpers.py | 686 + .../site-packages/ckan/tests/lib/test_i18n.py | 177 + .../site-packages/ckan/tests/lib/test_io.py | 42 + .../site-packages/ckan/tests/lib/test_jobs.py | 261 + .../ckan/tests/lib/test_mailer.py | 235 + .../ckan/tests/lib/test_munge.py | 153 + .../site-packages/ckan/tests/lib/test_navl.py | 33 + .../ckan/tests/logic/__init__.py | 0 .../ckan/tests/logic/test_conversion.py | 154 + .../ckan/tests/logic/test_converters.py | 83 + .../ckan/tests/logic/test_schema.py | 13 + .../ckan/tests/logic/test_validators.py | 641 + .../ckan/tests/migration/__init__.py | 10 + .../ckan/tests/model/__init__.py | 11 + .../ckan/tests/model/test_license.py | 104 + .../ckan/tests/model/test_resource.py | 83 + .../ckan/tests/model/test_resource_view.py | 74 + .../ckan/tests/model/test_system_info.py | 72 + .../ckan/tests/model/test_user.py | 150 + .../ckan/tests/plugins/__init__.py | 27 + .../ckan/tests/plugins/test_toolkit.py | 282 + .../site-packages/ckan/tests/test_authz.py | 68 + .../ckan/tests/test_coding_standards.py | 715 + .../site-packages/ckan/tests/test_common.py | 273 + .../ckan/tests/test_factories.py | 69 + .../ckan/tests/test_none_root.py | 16 + .../ckan/tests/test_robots_txt.py | 14 + .../site-packages/ckan/views/__init__.py | 195 + .../site-packages/ckan/views/admin.py | 215 + .../python2.7/site-packages/ckan/views/api.py | 512 + .../site-packages/ckan/views/dashboard.py | 146 + .../site-packages/ckan/views/feed.py | 584 + .../site-packages/ckan/views/home.py | 77 + .../site-packages/ckan/views/user.py | 736 + .../python2.7/site-packages/ckan/websetup.py | 18 + .../ckanapi-4.3.dist-info/INSTALLER | 1 + .../ckanapi-4.3.dist-info/METADATA | 19 + .../ckanapi-4.3.dist-info/RECORD | 62 + .../site-packages/ckanapi-4.3.dist-info/WHEEL | 5 + .../ckanapi-4.3.dist-info/entry_points.txt | 7 + .../ckanapi-4.3.dist-info/pbr.json | 1 + .../ckanapi-4.3.dist-info/top_level.txt | 1 + .../site-packages/ckanapi/__init__.py | 26 + .../site-packages/ckanapi/cli/__init__.py | 0 .../site-packages/ckanapi/cli/action.py | 68 + .../site-packages/ckanapi/cli/delete.py | 232 + .../site-packages/ckanapi/cli/dump.py | 206 + .../site-packages/ckanapi/cli/load.py | 298 + .../site-packages/ckanapi/cli/main.py | 144 + .../site-packages/ckanapi/cli/paster.py | 29 + .../site-packages/ckanapi/cli/utils.py | 62 + .../site-packages/ckanapi/cli/workers.py | 119 + .../python2.7/site-packages/ckanapi/common.py | 131 + .../site-packages/ckanapi/datapackage.py | 327 + .../python2.7/site-packages/ckanapi/errors.py | 66 + .../site-packages/ckanapi/localckan.py | 89 + .../site-packages/ckanapi/remoteckan.py | 114 + .../site-packages/ckanapi/testappckan.py | 56 + .../site-packages/ckanapi/tests/__init__.py | 0 .../ckanapi/tests/mock/mock_ckan.py | 64 + .../site-packages/ckanapi/tests/test_call.py | 29 + .../ckanapi/tests/test_cli_action.py | 158 + .../ckanapi/tests/test_cli_dump.py | 300 + .../ckanapi/tests/test_cli_load.py | 424 + .../ckanapi/tests/test_cli_workers.py | 165 + .../ckanapi/tests/test_datapackage.py | 310 + .../ckanapi/tests/test_remote.py | 124 + .../ckanapi/tests/test_testapp.py | 77 + .../site-packages/ckanapi/version.py | 5 + .../ckanext/datapusher/__init__.py | 0 .../site-packages/ckanext/datapusher/cli.py | 118 + .../ckanext/datapusher/helpers.py | 29 + .../ckanext/datapusher/interfaces.py | 51 + .../ckanext/datapusher/logic/__init__.py | 0 .../ckanext/datapusher/logic/action.py | 317 + .../ckanext/datapusher/logic/auth.py | 11 + .../ckanext/datapusher/logic/schema.py | 30 + .../ckanext/datapusher/plugin.py | 166 + .../templates/datapusher/resource_data.html | 88 + .../templates/package/resource_edit_base.html | 6 + .../ckanext/datapusher/tests/__init__.py | 0 .../ckanext/datapusher/tests/test.py | 269 + .../ckanext/datapusher/tests/test_action.py | 304 + .../datapusher/tests/test_default_views.py | 120 + .../datapusher/tests/test_interfaces.py | 149 + .../ckanext/datastore/__init__.py | 0 .../ckanext/datastore/backend/__init__.py | 229 + .../ckanext/datastore/backend/postgres.py | 1975 + .../ckanext/datastore/commands.py | 89 + .../ckanext/datastore/controller.py | 168 + .../ckanext/datastore/helpers.py | 166 + .../ckanext/datastore/interfaces.py | 172 + .../ckanext/datastore/logic/__init__.py | 0 .../ckanext/datastore/logic/action.py | 638 + .../ckanext/datastore/logic/auth.py | 85 + .../ckanext/datastore/logic/schema.py | 196 + .../site-packages/ckanext/datastore/plugin.py | 256 + .../ckanext/datastore/set_permissions.sql | 108 + .../templates/ajax_snippets/api_info.html | 137 + .../templates/datastore/dictionary.html | 23 + .../datastore/snippets/dictionary_form.html | 25 + .../templates/package/resource_edit_base.html | 8 + .../templates/package/resource_read.html | 37 + .../package/snippets/data_api_button.html | 10 + .../package/snippets/dictionary_table.html | 7 + .../ckanext/datastore/tests/__init__.py | 0 .../ckanext/datastore/tests/helpers.py | 88 + .../tests/sample_datastore_plugin.py | 50 + .../datastore/tests/test_chained_action.py | 69 + .../tests/test_chained_auth_functions.py | 73 + .../ckanext/datastore/tests/test_configure.py | 78 + .../ckanext/datastore/tests/test_create.py | 1112 + .../ckanext/datastore/tests/test_db.py | 234 + .../ckanext/datastore/tests/test_delete.py | 327 + .../ckanext/datastore/tests/test_disable.py | 22 + .../ckanext/datastore/tests/test_dump.py | 185 + .../ckanext/datastore/tests/test_helpers.py | 118 + .../ckanext/datastore/tests/test_info.py | 76 + .../ckanext/datastore/tests/test_interface.py | 172 + .../ckanext/datastore/tests/test_plugin.py | 211 + .../ckanext/datastore/tests/test_search.py | 1191 + .../ckanext/datastore/tests/test_unit.py | 46 + .../ckanext/datastore/tests/test_upsert.py | 586 + .../site-packages/ckanext/datastore/writer.py | 180 + .../ckanext/datatablesview/__init__.py | 0 .../ckanext/datatablesview/controller.py | 90 + .../ckanext/datatablesview/plugin.py | 61 + .../datatablesview/public/datatablesview.js | 7 + .../public/datatablesview.min.js | 1 + .../datatablesview/public/resource.config | 25 + .../Bootstrap-3.3.7/css/bootstrap-theme.css | 587 + .../css/bootstrap-theme.css.map | 1 + .../vendor/Bootstrap-3.3.7/css/bootstrap.css | 6757 ++ .../Bootstrap-3.3.7/css/bootstrap.css.map | 1 + .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../fonts/glyphicons-halflings-regular.svg | 288 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes .../vendor/Bootstrap-3.3.7/js/bootstrap.js | 2377 + .../public/vendor/Bootstrap-3.3.7/js/npm.js | 13 + .../Buttons-1.3.1/css/buttons.bootstrap.css | 158 + .../Buttons-1.3.1/css/buttons.dataTables.css | 354 + .../Buttons-1.3.1/css/buttons.foundation.css | 189 + .../Buttons-1.3.1/css/buttons.jqueryui.css | 218 + .../Buttons-1.3.1/css/buttons.semanticui.css | 171 + .../vendor/Buttons-1.3.1/css/common.scss | 27 + .../vendor/Buttons-1.3.1/css/mixins.scss | 136 + .../Buttons-1.3.1/js/buttons.bootstrap.js | 68 + .../vendor/Buttons-1.3.1/js/buttons.colVis.js | 206 + .../Buttons-1.3.1/js/buttons.foundation.js | 85 + .../Buttons-1.3.1/js/buttons.jqueryui.js | 62 + .../Buttons-1.3.1/js/buttons.semanticui.js | 57 + .../Buttons-1.3.1/js/dataTables.buttons.js | 1705 + .../vendor/Buttons-1.3.1/swf/flashExport.swf | Bin 0 -> 64573 bytes .../css/dataTables.bootstrap.css | 184 + .../css/dataTables.foundation.css | 118 + .../css/dataTables.jqueryui.css | 482 + .../css/dataTables.semanticui.css | 102 + .../css/jquery.dataTables.css | 455 + .../css/jquery.dataTables_themeroller.css | 416 + .../DataTables-1.10.15/images/sort_asc.png | Bin 0 -> 160 bytes .../images/sort_asc_disabled.png | Bin 0 -> 148 bytes .../DataTables-1.10.15/images/sort_both.png | Bin 0 -> 201 bytes .../DataTables-1.10.15/images/sort_desc.png | Bin 0 -> 158 bytes .../images/sort_desc_disabled.png | Bin 0 -> 146 bytes .../js/dataTables.bootstrap.js | 182 + .../js/dataTables.foundation.js | 174 + .../js/dataTables.jqueryui.js | 164 + .../js/dataTables.semanticui.js | 208 + .../js/jquery.dataTables.js | 15345 +++++ .../css/fixedColumns.bootstrap.css | 44 + .../css/fixedColumns.dataTables.css | 18 + .../css/fixedColumns.foundation.css | 27 + .../css/fixedColumns.jqueryui.css | 8 + .../js/dataTables.fixedColumns.js | 1623 + .../css/fixedHeader.bootstrap.css | 20 + .../css/fixedHeader.dataTables.css | 19 + .../css/fixedHeader.foundation.css | 20 + .../css/fixedHeader.jqueryui.css | 15 + .../js/dataTables.fixedHeader.js | 672 + .../KeyTable-2.2.1/css/keyTable.bootstrap.css | 5 + .../css/keyTable.dataTables.css | 5 + .../css/keyTable.foundation.css | 5 + .../KeyTable-2.2.1/css/keyTable.jqueryui.css | 5 + .../css/keyTable.semanticui.css | 5 + .../KeyTable-2.2.1/js/dataTables.keyTable.js | 980 + .../css/responsive.bootstrap.css | 181 + .../css/responsive.dataTables.css | 178 + .../css/responsive.foundation.css | 181 + .../css/responsive.jqueryui.css | 178 + .../js/dataTables.responsive.js | 1255 + .../js/responsive.bootstrap.js | 85 + .../js/responsive.foundation.js | 62 + .../js/responsive.jqueryui.js | 63 + .../Select-1.2.2/css/select.bootstrap.css | 115 + .../Select-1.2.2/css/select.dataTables.css | 105 + .../Select-1.2.2/css/select.foundation.css | 117 + .../Select-1.2.2/css/select.jqueryui.css | 105 + .../Select-1.2.2/css/select.semanticui.css | 110 + .../Select-1.2.2/js/dataTables.select.js | 1144 + .../public/vendor/datatables.css | 7492 +++ .../public/vendor/datatables.js | 51004 ++++++++++++++++ .../templates/datatables/datatables_form.html | 37 + .../templates/datatables/datatables_view.html | 51 + .../example_flask_iblueprint/__init__.py | 0 .../example_flask_iblueprint/plugin.py | 107 + .../templates/about.html | 10 + .../templates/about_base.html | 5 + .../tests/__init__.py | 0 .../tests/test_routes.py | 63 + .../example_flask_streaming/__init__.py | 0 .../ckanext/example_flask_streaming/plugin.py | 98 + .../templates/stream.html | 13 + .../example_flask_streaming/tests/__init__.py | 0 .../tests/test_streaming_responses.py | 71 + .../example_iauthfunctions/__init__.py | 0 .../example_iauthfunctions/plugin_v1.py | 7 + .../example_iauthfunctions/plugin_v2.py | 14 + .../example_iauthfunctions/plugin_v3.py | 37 + .../example_iauthfunctions/plugin_v4.py | 48 + .../plugin_v5_custom_config_setting.py | 31 + .../plugin_v6_parent_auth_functions.py | 18 + .../ckanext/example_iconfigurer/__init__.py | 0 .../ckanext/example_iconfigurer/controller.py | 24 + .../ckanext/example_iconfigurer/plugin.py | 69 + .../ckanext/example_iconfigurer/plugin_v1.py | 30 + .../ckanext/example_iconfigurer/plugin_v2.py | 33 + .../templates/admin/config.html | 27 + .../templates/admin/myext_config.html | 5 + .../example_iconfigurer/tests/__init__.py | 0 .../tests/test_example_iconfigurer.py | 84 + .../tests/test_iconfigurer_toolkit.py | 113 + .../tests/test_iconfigurer_update_config.py | 173 + .../ckanext/example_idatasetform/__init__.py | 0 .../ckanext/example_idatasetform/plugin.py | 176 + .../ckanext/example_idatasetform/plugin_v1.py | 45 + .../ckanext/example_idatasetform/plugin_v2.py | 52 + .../ckanext/example_idatasetform/plugin_v3.py | 44 + .../ckanext/example_idatasetform/plugin_v4.py | 91 + .../templates/package/read.html | 13 + .../templates/package/search.html | 21 + .../package/snippets/additional_info.html | 10 + .../snippets/package_basic_fields.html | 5 + .../snippets/package_metadata_fields.html | 23 + .../package/snippets/resource_form.html | 7 + .../example_idatasetform/tests/__init__.py | 0 .../tests/test_controllers.py | 66 + .../tests/test_example_idatasetform.py | 250 + .../example_idatastorebackend/__init__.py | 0 .../example_sqlite.py | 120 + .../example_idatastorebackend/plugin.py | 16 + .../test/__init__.py | 0 .../test/test_plugin.py | 128 + .../ckanext/example_igroupform/__init__.py | 0 .../ckanext/example_igroupform/plugin.py | 91 + .../example_igroup_form/group_form.html | 7 + .../example_igroupform/tests/__init__.py | 0 .../tests/test_controllers.py | 306 + .../example_ipermissionlabels/__init__.py | 0 .../example_ipermissionlabels/plugin.py | 42 + .../tests/__init__.py | 0 .../tests/test_example_ipermissionlabels.py | 112 + .../example_iresourcecontroller/__init__.py | 0 .../example_iresourcecontroller/plugin.py | 33 + .../example_itemplatehelpers/__init__.py | 0 .../example_itemplatehelpers/plugin.py | 29 + .../templates/home/index.html | 6 + .../ckanext/example_itranslation/__init__.py | 0 .../i18n/ckanext-example_itranslation.pot | 23 + .../ckanext-example_itranslation.mo | Bin 0 -> 542 bytes .../ckanext-example_itranslation.po | 25 + .../ckanext-example_translation.po | 25 + .../ckanext-example_itranslation.mo | Bin 0 -> 559 bytes .../ckanext-example_itranslation.po | 21 + .../ckanext/example_itranslation/plugin.py | 13 + .../ckanext/example_itranslation/plugin_v1.py | 11 + .../templates/home/index.html | 5 + .../example_itranslation/tests/__init__.py | 0 .../example_itranslation/tests/test_plugin.py | 71 + .../ckanext/example_iuploader/__init__.py | 0 .../ckanext/example_iuploader/plugin.py | 23 + .../example_iuploader/test/__init__.py | 0 .../example_iuploader/test/test_plugin.py | 123 + .../ckanext/example_ivalidators/__init__.py | 0 .../ckanext/example_ivalidators/plugin.py | 36 + .../example_ivalidators/tests/__init__.py | 0 .../tests/test_ivalidators.py | 39 + .../ckanext/example_theme_docs/__init__.py | 0 .../custom_config_setting/__init__.py | 0 .../custom_config_setting/plugin.py | 67 + .../custom_emails/__init__.py | 0 .../custom_emails/plugin.py | 17 + .../example_theme_docs/custom_emails/tests.py | 117 + .../v01_empty_extension/__init__.py | 0 .../v01_empty_extension/plugin.py | 10 + .../v02_empty_template/__init__.py | 0 .../v02_empty_template/plugin.py | 23 + .../example_theme_docs/v03_jinja/__init__.py | 0 .../example_theme_docs/v03_jinja/plugin.py | 23 + .../v04_ckan_extends/__init__.py | 0 .../v04_ckan_extends/plugin.py | 23 + .../example_theme_docs/v05_block/__init__.py | 0 .../example_theme_docs/v05_block/plugin.py | 23 + .../example_theme_docs/v06_super/__init__.py | 0 .../example_theme_docs/v06_super/plugin.py | 23 + .../v07_helper_function/__init__.py | 0 .../v07_helper_function/plugin.py | 23 + .../v08_custom_helper_function/__init__.py | 0 .../v08_custom_helper_function/plugin.py | 44 + .../v09_snippet/__init__.py | 0 .../example_theme_docs/v09_snippet/plugin.py | 44 + .../v10_custom_snippet/__init__.py | 0 .../v10_custom_snippet/plugin.py | 44 + .../v11_HTML_and_CSS/__init__.py | 0 .../v11_HTML_and_CSS/plugin.py | 44 + .../v12_extra_public_dir/__init__.py | 0 .../v12_extra_public_dir/plugin.py | 48 + .../v13_custom_css/__init__.py | 0 .../v13_custom_css/plugin.py | 48 + .../v14_more_custom_css/__init__.py | 0 .../v14_more_custom_css/plugin.py | 48 + .../v15_fanstatic/__init__.py | 0 .../v15_fanstatic/plugin.py | 55 + .../__init__.py | 0 .../plugin.py | 16 + .../v17_popover/__init__.py | 0 .../example_theme_docs/v17_popover/plugin.py | 16 + .../v18_snippet_api/__init__.py | 0 .../v18_snippet_api/plugin.py | 16 + .../v19_01_error/__init__.py | 0 .../example_theme_docs/v19_01_error/plugin.py | 16 + .../v19_02_error_handling/__init__.py | 0 .../v19_02_error_handling/plugin.py | 16 + .../example_theme_docs/v20_pubsub/__init__.py | 0 .../example_theme_docs/v20_pubsub/plugin.py | 16 + .../v21_custom_jquery_plugin/__init__.py | 0 .../v21_custom_jquery_plugin/plugin.py | 16 + .../ckanext/imageview/__init__.py | 0 .../site-packages/ckanext/imageview/plugin.py | 43 + .../ckanext/imageview/tests/__init__.py | 0 .../ckanext/imageview/tests/test_view.py | 32 + .../imageview/theme/templates/image_form.html | 3 + .../imageview/theme/templates/image_view.html | 1 + .../ckanext/multilingual/__init__.py | 0 .../ckanext/multilingual/plugin.py | 408 + .../ckanext/reclineview/__init__.py | 0 .../ckanext/reclineview/plugin.py | 260 + .../ckanext/reclineview/tests/__init__.py | 0 .../ckanext/reclineview/tests/test_view.py | 177 + .../reclineview/theme/public/css/recline.css | 380 + .../theme/public/css/recline.min.css | 1 + .../theme/public/img/ajaxload-circle.gif | Bin 0 -> 4176 bytes .../reclineview/theme/public/recline_view.js | 247 + .../theme/public/recline_view.min.js | 15 + .../reclineview/theme/public/resource.config | 53 + .../public/vendor/backbone/1.0.0/backbone.js | 1571 + .../bootstrap/3.2.0/css/bootstrap-theme.css | 442 + .../vendor/bootstrap/3.2.0/css/bootstrap.css | 6203 ++ .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20335 bytes .../fonts/glyphicons-halflings-regular.svg | 229 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 41280 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23320 bytes .../vendor/bootstrap/3.2.0/js/bootstrap.js | 2114 + .../theme/public/vendor/ckan.js/ckan.js | 255 + .../theme/public/vendor/flot/excanvas.js | 1428 + .../theme/public/vendor/flot/excanvas.min.js | 1 + .../theme/public/vendor/flot/jquery.flot.js | 3061 + .../public/vendor/flot/jquery.flot.time.js | 431 + .../theme/public/vendor/flotr2/flotr2.js | 6128 ++ .../theme/public/vendor/flotr2/flotr2.min.js | 489 + .../public/vendor/jquery/1.7.1/jquery.js | 9266 +++ .../public/vendor/jquery/1.7.1/jquery.min.js | 4 + .../theme/public/vendor/json/json2.js | 486 + .../theme/public/vendor/json/json2.min.js | 25 + .../MarkerCluster.Default.css | 60 + .../leaflet.markercluster/MarkerCluster.css | 6 + .../leaflet.markercluster-src.js | 2163 + .../leaflet.markercluster.js | 6 + .../vendor/leaflet/0.7.7/images/layers-2x.png | Bin 0 -> 1585 bytes .../vendor/leaflet/0.7.7/images/layers.png | Bin 0 -> 913 bytes .../leaflet/0.7.7/images/marker-icon-2x.png | Bin 0 -> 4032 bytes .../leaflet/0.7.7/images/marker-icon.png | Bin 0 -> 1747 bytes .../leaflet/0.7.7/images/marker-shadow.png | Bin 0 -> 681 bytes .../vendor/leaflet/0.7.7/leaflet-src.js | 9168 +++ .../public/vendor/leaflet/0.7.7/leaflet.css | 479 + .../public/vendor/leaflet/0.7.7/leaflet.js | 9 + .../public/vendor/moment/2.0.0/moment.js | 1400 + .../vendor/mustache/0.5.0-dev/mustache.js | 536 + .../vendor/mustache/0.5.0-dev/mustache.min.js | 36 + .../theme/public/vendor/recline/flot.css | 26 + .../theme/public/vendor/recline/map.css | 28 + .../theme/public/vendor/recline/recline.css | 644 + .../public/vendor/recline/recline.dataset.js | 896 + .../theme/public/vendor/recline/recline.js | 4453 ++ .../theme/public/vendor/recline/slickgrid.css | 188 + .../vendor/showdown/20120615/showdown.js | 1341 + .../vendor/showdown/20120615/showdown.min.js | 48 + .../vendor/slickgrid/2.2/MIT-LICENSE.txt | 20 + .../public/vendor/slickgrid/2.2/README.md | 25 + .../2.2/controls/slick.columnpicker.css | 31 + .../2.2/controls/slick.columnpicker.js | 152 + .../slickgrid/2.2/controls/slick.pager.css | 41 + .../slickgrid/2.2/controls/slick.pager.js | 154 + .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 86 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 0 -> 74 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 111 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 90 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 102 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 102 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 115 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 86 bytes .../images/ui-icons_222222_256x240.png | Bin 0 -> 3687 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 0 -> 3687 bytes .../images/ui-icons_454545_256x240.png | Bin 0 -> 3687 bytes .../images/ui-icons_888888_256x240.png | Bin 0 -> 3687 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 3687 bytes .../smoothness/jquery-ui-1.8.16.custom.css | 409 + .../vendor/slickgrid/2.2/images/actions.gif | Bin 0 -> 170 bytes .../2.2/images/ajax-loader-small.gif | Bin 0 -> 1849 bytes .../slickgrid/2.2/images/arrow_redo.png | Bin 0 -> 550 bytes .../2.2/images/arrow_right_peppermint.png | Bin 0 -> 126 bytes .../2.2/images/arrow_right_spearmint.png | Bin 0 -> 126 bytes .../slickgrid/2.2/images/arrow_undo.png | Bin 0 -> 555 bytes .../slickgrid/2.2/images/bullet_blue.png | Bin 0 -> 231 bytes .../slickgrid/2.2/images/bullet_star.png | Bin 0 -> 273 bytes .../2.2/images/bullet_toggle_minus.png | Bin 0 -> 154 bytes .../2.2/images/bullet_toggle_plus.png | Bin 0 -> 156 bytes .../vendor/slickgrid/2.2/images/calendar.gif | Bin 0 -> 1035 bytes .../vendor/slickgrid/2.2/images/collapse.gif | Bin 0 -> 846 bytes .../slickgrid/2.2/images/comment_yellow.gif | Bin 0 -> 257 bytes .../vendor/slickgrid/2.2/images/down.gif | Bin 0 -> 59 bytes .../slickgrid/2.2/images/drag-handle.png | Bin 0 -> 98 bytes .../slickgrid/2.2/images/editor-helper-bg.gif | Bin 0 -> 1164 bytes .../vendor/slickgrid/2.2/images/expand.gif | Bin 0 -> 851 bytes .../vendor/slickgrid/2.2/images/header-bg.gif | Bin 0 -> 872 bytes .../2.2/images/header-columns-bg.gif | Bin 0 -> 836 bytes .../2.2/images/header-columns-over-bg.gif | Bin 0 -> 823 bytes .../vendor/slickgrid/2.2/images/help.png | Bin 0 -> 328 bytes .../vendor/slickgrid/2.2/images/info.gif | Bin 0 -> 80 bytes .../vendor/slickgrid/2.2/images/listview.gif | Bin 0 -> 2380 bytes .../vendor/slickgrid/2.2/images/pencil.gif | Bin 0 -> 914 bytes .../slickgrid/2.2/images/row-over-bg.gif | Bin 0 -> 823 bytes .../vendor/slickgrid/2.2/images/sort-asc.gif | Bin 0 -> 830 bytes .../vendor/slickgrid/2.2/images/sort-asc.png | Bin 0 -> 104 bytes .../vendor/slickgrid/2.2/images/sort-desc.gif | Bin 0 -> 833 bytes .../vendor/slickgrid/2.2/images/sort-desc.png | Bin 0 -> 106 bytes .../vendor/slickgrid/2.2/images/stripes.png | Bin 0 -> 94 bytes .../vendor/slickgrid/2.2/images/tag_red.png | Bin 0 -> 529 bytes .../vendor/slickgrid/2.2/images/tick.png | Bin 0 -> 465 bytes .../slickgrid/2.2/images/user_identity.gif | Bin 0 -> 905 bytes .../2.2/images/user_identity_plus.gif | Bin 0 -> 546 bytes .../vendor/slickgrid/2.2/jquery-1.7.min.js | 4 + .../slickgrid/2.2/jquery-ui-1.8.16.custom.js | 611 + .../slickgrid/2.2/jquery.event.drag-2.2.js | 402 + .../slickgrid/2.2/jquery.event.drop-2.2.js | 302 + .../2.2/plugins/slick.autotooltips.js | 83 + .../2.2/plugins/slick.cellcopymanager.js | 86 + .../2.2/plugins/slick.cellrangedecorator.js | 66 + .../2.2/plugins/slick.cellrangeselector.js | 113 + .../2.2/plugins/slick.cellselectionmodel.js | 154 + .../2.2/plugins/slick.checkboxselectcolumn.js | 153 + .../2.2/plugins/slick.headerbuttons.css | 39 + .../2.2/plugins/slick.headerbuttons.js | 177 + .../2.2/plugins/slick.headermenu.css | 59 + .../slickgrid/2.2/plugins/slick.headermenu.js | 275 + .../2.2/plugins/slick.rowmovemanager.js | 138 + .../2.2/plugins/slick.rowselectionmodel.js | 187 + .../slickgrid/2.2/slick-default-theme.css | 118 + .../public/vendor/slickgrid/2.2/slick.core.js | 467 + .../vendor/slickgrid/2.2/slick.dataview.js | 1126 + .../vendor/slickgrid/2.2/slick.editors.js | 512 + .../vendor/slickgrid/2.2/slick.formatters.js | 59 + .../vendor/slickgrid/2.2/slick.grid.css | 157 + .../public/vendor/slickgrid/2.2/slick.grid.js | 3422 ++ .../2.2/slick.groupitemmetadataprovider.js | 158 + .../vendor/slickgrid/2.2/slick.remotemodel.js | 173 + .../theme/public/vendor/timeline/LICENSE | 365 + .../theme/public/vendor/timeline/README | 1 + .../public/vendor/timeline/css/loading.gif | Bin 0 -> 6909 bytes .../public/vendor/timeline/css/timeline.css | 284 + .../public/vendor/timeline/css/timeline.png | Bin 0 -> 14048 bytes .../vendor/timeline/css/timeline@2x.png | Bin 0 -> 36430 bytes .../public/vendor/timeline/js/timeline.js | 10015 +++ .../0.4.0/underscore.deferred.js | 445 + .../0.4.0/underscore.deferred.min.js | 17 + .../vendor/underscore/1.4.4/underscore.js | 1227 + .../theme/public/widget.recordcount.js | 29 + .../theme/public/widget.recordcount.min.js | 3 + .../theme/templates/recline_graph_form.html | 8 + .../theme/templates/recline_map_form.html | 11 + .../theme/templates/recline_view.html | 23 + .../ckanext/resourceproxy/__init__.py | 0 .../ckanext/resourceproxy/controller.py | 91 + .../ckanext/resourceproxy/plugin.py | 75 + .../ckanext/resourceproxy/tests/__init__.py | 0 .../ckanext/resourceproxy/tests/test_proxy.py | 175 + .../site-packages/ckanext/stats/controller.py | 41 + .../site-packages/ckanext/stats/plugin.py | 29 + .../ckanext/stats/public/.gitignore | 2 + .../ckanext/stats/public/__init__.py | 9 + .../ckanext/stats/public/ckanext/__init__.py | 9 + .../stats/public/ckanext/stats/__init__.py | 9 + .../stats/public/ckanext/stats/css/stats.css | 16 + .../ckanext/stats/javascript/modules/plot.js | 209 + .../stats/javascript/modules/stats-nav.js | 36 + .../public/ckanext/stats/resource.config | 12 + .../ckanext/stats/test/fixtures/table.html | 30 + .../public/ckanext/stats/test/index.html | 59 + .../stats/test/spec/modules/plot.spec.js | 136 + .../stats/test/spec/modules/stats-nav.spec.js | 44 + .../public/ckanext/stats/vendor/excanvas.js | 1427 + .../ckanext/stats/vendor/jquery.flot.js | 2599 + .../site-packages/ckanext/stats/stats.py | 372 + .../stats/templates/ckanext/stats/index.html | 193 + .../ckanext/stats/tests/__init__.py | 18 + .../ckanext/stats/tests/test_stats_lib.py | 155 + .../ckanext/stats/tests/test_stats_plugin.py | 17 + .../ckanext/test_tag_vocab_plugin.py | 68 + .../ckanext/textview/__init__.py | 0 .../site-packages/ckanext/textview/plugin.py | 102 + .../ckanext/textview/tests/__init__.py | 0 .../ckanext/textview/tests/test_view.py | 112 + .../ckanext/textview/theme/public/LICENSE | 28 + .../textview/theme/public/css/text.css | 18 + .../textview/theme/public/css/text.min.css | 1 + .../textview/theme/public/resource.config | 12 + .../textview/theme/public/styles/default.css | 135 + .../theme/public/styles/default.min.css | 1 + .../textview/theme/public/styles/github.css | 127 + .../theme/public/styles/github.min.css | 1 + .../textview/theme/public/text_view.js | 77 + .../textview/theme/public/text_view.min.js | 3 + .../theme/public/vendor/highlight.pack.js | 1 + .../textview/theme/templates/text_form.html | 0 .../textview/theme/templates/text_view.html | 24 + .../ckanext/webpageview/__init__.py | 0 .../ckanext/webpageview/plugin.py | 40 + .../ckanext/webpageview/tests/__init__.py | 0 .../ckanext/webpageview/tests/test_view.py | 48 + .../theme/templates/webpage_form.html | 3 + .../theme/templates/webpage_view.html | 3 + .../ckantoolkit-0.0.4.dist-info/INSTALLER | 1 + .../ckantoolkit-0.0.4.dist-info/METADATA | 13 + .../ckantoolkit-0.0.4.dist-info/RECORD | 11 + .../ckantoolkit-0.0.4.dist-info/WHEEL | 5 + .../ckantoolkit-0.0.4.dist-info/top_level.txt | 1 + .../site-packages/ckantoolkit/__init__.py | 56 + .../site-packages/ckantoolkit/shims.py | 22 + .../site-packages/ckantoolkit/tests.py | 13 + .../docopt-0.6.2.dist-info/INSTALLER | 1 + .../docopt-0.6.2.dist-info/LICENSE-MIT | 19 + .../docopt-0.6.2.dist-info/METADATA | 469 + .../docopt-0.6.2.dist-info/RECORD | 8 + .../docopt-0.6.2.dist-info/WHEEL | 6 + .../docopt-0.6.2.dist-info/top_level.txt | 1 + venv/lib/python2.7/site-packages/docopt.py | 579 + .../python2.7/site-packages/easy_install.py | 5 + .../idna-2.8.dist-info/INSTALLER | 1 + .../idna-2.8.dist-info/LICENSE.rst | 80 + .../site-packages/idna-2.8.dist-info/METADATA | 239 + .../site-packages/idna-2.8.dist-info/RECORD | 22 + .../site-packages/idna-2.8.dist-info/WHEEL | 6 + .../idna-2.8.dist-info/top_level.txt | 1 + .../python2.7/site-packages/idna/__init__.py | 2 + .../lib/python2.7/site-packages/idna/codec.py | 118 + .../python2.7/site-packages/idna/compat.py | 12 + venv/lib/python2.7/site-packages/idna/core.py | 396 + .../python2.7/site-packages/idna/idnadata.py | 1979 + .../python2.7/site-packages/idna/intranges.py | 53 + .../site-packages/idna/package_data.py | 2 + .../python2.7/site-packages/idna/uts46data.py | 8205 +++ .../python2.7/site-packages/pip/__init__.py | 1 + .../python2.7/site-packages/pip/__main__.py | 19 + .../site-packages/pip/_internal/__init__.py | 77 + .../site-packages/pip/_internal/build_env.py | 218 + .../site-packages/pip/_internal/cache.py | 224 + .../pip/_internal/cli/__init__.py | 4 + .../pip/_internal/cli/autocompletion.py | 152 + .../pip/_internal/cli/base_command.py | 346 + .../pip/_internal/cli/cmdoptions.py | 931 + .../pip/_internal/cli/main_parser.py | 98 + .../site-packages/pip/_internal/cli/parser.py | 261 + .../pip/_internal/cli/status_codes.py | 8 + .../pip/_internal/commands/__init__.py | 81 + .../pip/_internal/commands/check.py | 41 + .../pip/_internal/commands/completion.py | 94 + .../pip/_internal/commands/configuration.py | 258 + .../pip/_internal/commands/debug.py | 114 + .../pip/_internal/commands/download.py | 168 + .../pip/_internal/commands/freeze.py | 101 + .../pip/_internal/commands/hash.py | 57 + .../pip/_internal/commands/help.py | 37 + .../pip/_internal/commands/install.py | 580 + .../pip/_internal/commands/list.py | 311 + .../pip/_internal/commands/search.py | 139 + .../pip/_internal/commands/show.py | 168 + .../pip/_internal/commands/uninstall.py | 78 + .../pip/_internal/commands/wheel.py | 181 + .../pip/_internal/configuration.py | 417 + .../pip/_internal/distributions/__init__.py | 23 + .../pip/_internal/distributions/base.py | 33 + .../pip/_internal/distributions/installed.py | 15 + .../pip/_internal/distributions/source.py | 80 + .../pip/_internal/distributions/wheel.py | 17 + .../site-packages/pip/_internal/download.py | 1177 + .../site-packages/pip/_internal/exceptions.py | 305 + .../site-packages/pip/_internal/index.py | 1508 + .../pip/_internal/legacy_resolve.py | 457 + .../site-packages/pip/_internal/locations.py | 142 + .../pip/_internal/models/__init__.py | 2 + .../pip/_internal/models/candidate.py | 36 + .../pip/_internal/models/format_control.py | 73 + .../pip/_internal/models/index.py | 31 + .../pip/_internal/models/link.py | 213 + .../pip/_internal/models/search_scope.py | 113 + .../pip/_internal/models/selection_prefs.py | 47 + .../pip/_internal/models/target_python.py | 106 + .../pip/_internal/operations/__init__.py | 0 .../pip/_internal/operations/check.py | 159 + .../pip/_internal/operations/freeze.py | 253 + .../pip/_internal/operations/prepare.py | 287 + .../site-packages/pip/_internal/pep425tags.py | 384 + .../site-packages/pip/_internal/pyproject.py | 171 + .../pip/_internal/req/__init__.py | 78 + .../pip/_internal/req/constructors.py | 349 + .../pip/_internal/req/req_file.py | 399 + .../pip/_internal/req/req_install.py | 1035 + .../pip/_internal/req/req_set.py | 193 + .../pip/_internal/req/req_tracker.py | 96 + .../pip/_internal/req/req_uninstall.py | 633 + .../pip/_internal/utils/__init__.py | 0 .../pip/_internal/utils/appdirs.py | 268 + .../pip/_internal/utils/compat.py | 293 + .../pip/_internal/utils/deprecation.py | 100 + .../pip/_internal/utils/encoding.py | 39 + .../pip/_internal/utils/filesystem.py | 30 + .../pip/_internal/utils/glibc.py | 120 + .../pip/_internal/utils/hashes.py | 128 + .../pip/_internal/utils/logging.py | 394 + .../pip/_internal/utils/marker_files.py | 20 + .../site-packages/pip/_internal/utils/misc.py | 1201 + .../pip/_internal/utils/models.py | 40 + .../pip/_internal/utils/outdated.py | 178 + .../pip/_internal/utils/packaging.py | 94 + .../pip/_internal/utils/setuptools_build.py | 36 + .../pip/_internal/utils/temp_dir.py | 155 + .../pip/_internal/utils/typing.py | 29 + .../site-packages/pip/_internal/utils/ui.py | 424 + .../pip/_internal/utils/virtualenv.py | 34 + .../pip/_internal/vcs/__init__.py | 12 + .../site-packages/pip/_internal/vcs/bazaar.py | 101 + .../site-packages/pip/_internal/vcs/git.py | 358 + .../pip/_internal/vcs/mercurial.py | 103 + .../pip/_internal/vcs/subversion.py | 314 + .../pip/_internal/vcs/versioncontrol.py | 600 + .../site-packages/pip/_internal/wheel.py | 1125 + .../site-packages/pip/_vendor/__init__.py | 109 + .../site-packages/pip/_vendor/appdirs.py | 604 + .../pip/_vendor/cachecontrol/__init__.py | 11 + .../pip/_vendor/cachecontrol/_cmd.py | 57 + .../pip/_vendor/cachecontrol/adapter.py | 133 + .../pip/_vendor/cachecontrol/cache.py | 39 + .../_vendor/cachecontrol/caches/__init__.py | 2 + .../_vendor/cachecontrol/caches/file_cache.py | 146 + .../cachecontrol/caches/redis_cache.py | 33 + .../pip/_vendor/cachecontrol/compat.py | 29 + .../pip/_vendor/cachecontrol/controller.py | 367 + .../pip/_vendor/cachecontrol/filewrapper.py | 80 + .../pip/_vendor/cachecontrol/heuristics.py | 135 + .../pip/_vendor/cachecontrol/serialize.py | 186 + .../pip/_vendor/cachecontrol/wrapper.py | 29 + .../pip/_vendor/certifi/__init__.py | 3 + .../pip/_vendor/certifi/__main__.py | 2 + .../pip/_vendor/certifi/cacert.pem | 4618 ++ .../site-packages/pip/_vendor/certifi/core.py | 15 + .../pip/_vendor/chardet/__init__.py | 39 + .../pip/_vendor/chardet/big5freq.py | 386 + .../pip/_vendor/chardet/big5prober.py | 47 + .../pip/_vendor/chardet/chardistribution.py | 233 + .../pip/_vendor/chardet/charsetgroupprober.py | 106 + .../pip/_vendor/chardet/charsetprober.py | 145 + .../pip/_vendor/chardet/cli/__init__.py | 1 + .../pip/_vendor/chardet/cli/chardetect.py | 85 + .../pip/_vendor/chardet/codingstatemachine.py | 88 + .../pip/_vendor/chardet/compat.py | 34 + .../pip/_vendor/chardet/cp949prober.py | 49 + .../pip/_vendor/chardet/enums.py | 76 + .../pip/_vendor/chardet/escprober.py | 101 + .../pip/_vendor/chardet/escsm.py | 246 + .../pip/_vendor/chardet/eucjpprober.py | 92 + .../pip/_vendor/chardet/euckrfreq.py | 195 + .../pip/_vendor/chardet/euckrprober.py | 47 + .../pip/_vendor/chardet/euctwfreq.py | 387 + .../pip/_vendor/chardet/euctwprober.py | 46 + .../pip/_vendor/chardet/gb2312freq.py | 283 + .../pip/_vendor/chardet/gb2312prober.py | 46 + .../pip/_vendor/chardet/hebrewprober.py | 292 + .../pip/_vendor/chardet/jisfreq.py | 325 + .../pip/_vendor/chardet/jpcntx.py | 233 + .../pip/_vendor/chardet/langbulgarianmodel.py | 228 + .../pip/_vendor/chardet/langcyrillicmodel.py | 333 + .../pip/_vendor/chardet/langgreekmodel.py | 225 + .../pip/_vendor/chardet/langhebrewmodel.py | 200 + .../pip/_vendor/chardet/langhungarianmodel.py | 225 + .../pip/_vendor/chardet/langthaimodel.py | 199 + .../pip/_vendor/chardet/langturkishmodel.py | 193 + .../pip/_vendor/chardet/latin1prober.py | 145 + .../pip/_vendor/chardet/mbcharsetprober.py | 91 + .../pip/_vendor/chardet/mbcsgroupprober.py | 54 + .../pip/_vendor/chardet/mbcssm.py | 572 + .../pip/_vendor/chardet/sbcharsetprober.py | 132 + .../pip/_vendor/chardet/sbcsgroupprober.py | 73 + .../pip/_vendor/chardet/sjisprober.py | 92 + .../pip/_vendor/chardet/universaldetector.py | 286 + .../pip/_vendor/chardet/utf8prober.py | 82 + .../pip/_vendor/chardet/version.py | 9 + .../pip/_vendor/colorama/__init__.py | 6 + .../pip/_vendor/colorama/ansi.py | 102 + .../pip/_vendor/colorama/ansitowin32.py | 257 + .../pip/_vendor/colorama/initialise.py | 80 + .../pip/_vendor/colorama/win32.py | 152 + .../pip/_vendor/colorama/winterm.py | 169 + .../pip/_vendor/distlib/__init__.py | 23 + .../pip/_vendor/distlib/_backport/__init__.py | 6 + .../pip/_vendor/distlib/_backport/misc.py | 41 + .../pip/_vendor/distlib/_backport/shutil.py | 761 + .../_vendor/distlib/_backport/sysconfig.cfg | 84 + .../_vendor/distlib/_backport/sysconfig.py | 788 + .../pip/_vendor/distlib/_backport/tarfile.py | 2607 + .../pip/_vendor/distlib/compat.py | 1120 + .../pip/_vendor/distlib/database.py | 1339 + .../pip/_vendor/distlib/index.py | 516 + .../pip/_vendor/distlib/locators.py | 1295 + .../pip/_vendor/distlib/manifest.py | 393 + .../pip/_vendor/distlib/markers.py | 131 + .../pip/_vendor/distlib/metadata.py | 1096 + .../pip/_vendor/distlib/resources.py | 355 + .../pip/_vendor/distlib/scripts.py | 403 + .../site-packages/pip/_vendor/distlib/t32.exe | Bin 0 -> 92672 bytes .../site-packages/pip/_vendor/distlib/t64.exe | Bin 0 -> 102912 bytes .../site-packages/pip/_vendor/distlib/util.py | 1760 + .../pip/_vendor/distlib/version.py | 736 + .../site-packages/pip/_vendor/distlib/w32.exe | Bin 0 -> 89088 bytes .../site-packages/pip/_vendor/distlib/w64.exe | Bin 0 -> 99840 bytes .../pip/_vendor/distlib/wheel.py | 1004 + .../site-packages/pip/_vendor/distro.py | 1216 + .../pip/_vendor/html5lib/__init__.py | 35 + .../pip/_vendor/html5lib/_ihatexml.py | 288 + .../pip/_vendor/html5lib/_inputstream.py | 923 + .../pip/_vendor/html5lib/_tokenizer.py | 1721 + .../pip/_vendor/html5lib/_trie/__init__.py | 14 + .../pip/_vendor/html5lib/_trie/_base.py | 40 + .../pip/_vendor/html5lib/_trie/datrie.py | 44 + .../pip/_vendor/html5lib/_trie/py.py | 67 + .../pip/_vendor/html5lib/_utils.py | 124 + .../pip/_vendor/html5lib/constants.py | 2947 + .../pip/_vendor/html5lib/filters/__init__.py | 0 .../filters/alphabeticalattributes.py | 29 + .../pip/_vendor/html5lib/filters/base.py | 12 + .../html5lib/filters/inject_meta_charset.py | 73 + .../pip/_vendor/html5lib/filters/lint.py | 93 + .../_vendor/html5lib/filters/optionaltags.py | 207 + .../pip/_vendor/html5lib/filters/sanitizer.py | 896 + .../_vendor/html5lib/filters/whitespace.py | 38 + .../pip/_vendor/html5lib/html5parser.py | 2791 + .../pip/_vendor/html5lib/serializer.py | 409 + .../_vendor/html5lib/treeadapters/__init__.py | 30 + .../_vendor/html5lib/treeadapters/genshi.py | 54 + .../pip/_vendor/html5lib/treeadapters/sax.py | 50 + .../_vendor/html5lib/treebuilders/__init__.py | 88 + .../pip/_vendor/html5lib/treebuilders/base.py | 417 + .../pip/_vendor/html5lib/treebuilders/dom.py | 239 + .../_vendor/html5lib/treebuilders/etree.py | 340 + .../html5lib/treebuilders/etree_lxml.py | 366 + .../_vendor/html5lib/treewalkers/__init__.py | 154 + .../pip/_vendor/html5lib/treewalkers/base.py | 252 + .../pip/_vendor/html5lib/treewalkers/dom.py | 43 + .../pip/_vendor/html5lib/treewalkers/etree.py | 130 + .../html5lib/treewalkers/etree_lxml.py | 213 + .../_vendor/html5lib/treewalkers/genshi.py | 69 + .../pip/_vendor/idna/__init__.py | 2 + .../site-packages/pip/_vendor/idna/codec.py | 118 + .../site-packages/pip/_vendor/idna/compat.py | 12 + .../site-packages/pip/_vendor/idna/core.py | 396 + .../pip/_vendor/idna/idnadata.py | 1979 + .../pip/_vendor/idna/intranges.py | 53 + .../pip/_vendor/idna/package_data.py | 2 + .../pip/_vendor/idna/uts46data.py | 8205 +++ .../site-packages/pip/_vendor/ipaddress.py | 2419 + .../pip/_vendor/lockfile/__init__.py | 347 + .../pip/_vendor/lockfile/linklockfile.py | 73 + .../pip/_vendor/lockfile/mkdirlockfile.py | 84 + .../pip/_vendor/lockfile/pidlockfile.py | 190 + .../pip/_vendor/lockfile/sqlitelockfile.py | 156 + .../pip/_vendor/lockfile/symlinklockfile.py | 70 + .../pip/_vendor/msgpack/__init__.py | 65 + .../pip/_vendor/msgpack/_version.py | 1 + .../pip/_vendor/msgpack/exceptions.py | 48 + .../pip/_vendor/msgpack/fallback.py | 1027 + .../pip/_vendor/packaging/__about__.py | 27 + .../pip/_vendor/packaging/__init__.py | 26 + .../pip/_vendor/packaging/_compat.py | 31 + .../pip/_vendor/packaging/_structures.py | 68 + .../pip/_vendor/packaging/markers.py | 296 + .../pip/_vendor/packaging/requirements.py | 138 + .../pip/_vendor/packaging/specifiers.py | 749 + .../pip/_vendor/packaging/utils.py | 57 + .../pip/_vendor/packaging/version.py | 420 + .../pip/_vendor/pep517/__init__.py | 4 + .../pip/_vendor/pep517/_in_process.py | 207 + .../site-packages/pip/_vendor/pep517/build.py | 108 + .../site-packages/pip/_vendor/pep517/check.py | 202 + .../pip/_vendor/pep517/colorlog.py | 115 + .../pip/_vendor/pep517/compat.py | 23 + .../pip/_vendor/pep517/envbuild.py | 158 + .../pip/_vendor/pep517/wrappers.py | 163 + .../pip/_vendor/pkg_resources/__init__.py | 3286 + .../pip/_vendor/pkg_resources/py31compat.py | 23 + .../pip/_vendor/progress/__init__.py | 177 + .../site-packages/pip/_vendor/progress/bar.py | 91 + .../pip/_vendor/progress/counter.py | 41 + .../pip/_vendor/progress/spinner.py | 43 + .../site-packages/pip/_vendor/pyparsing.py | 6493 ++ .../pip/_vendor/pytoml/__init__.py | 4 + .../site-packages/pip/_vendor/pytoml/core.py | 13 + .../pip/_vendor/pytoml/parser.py | 341 + .../site-packages/pip/_vendor/pytoml/test.py | 30 + .../site-packages/pip/_vendor/pytoml/utils.py | 67 + .../pip/_vendor/pytoml/writer.py | 106 + .../pip/_vendor/requests/__init__.py | 133 + .../pip/_vendor/requests/__version__.py | 14 + .../pip/_vendor/requests/_internal_utils.py | 42 + .../pip/_vendor/requests/adapters.py | 533 + .../site-packages/pip/_vendor/requests/api.py | 158 + .../pip/_vendor/requests/auth.py | 305 + .../pip/_vendor/requests/certs.py | 18 + .../pip/_vendor/requests/compat.py | 74 + .../pip/_vendor/requests/cookies.py | 549 + .../pip/_vendor/requests/exceptions.py | 126 + .../pip/_vendor/requests/help.py | 119 + .../pip/_vendor/requests/hooks.py | 34 + .../pip/_vendor/requests/models.py | 953 + .../pip/_vendor/requests/packages.py | 16 + .../pip/_vendor/requests/sessions.py | 770 + .../pip/_vendor/requests/status_codes.py | 120 + .../pip/_vendor/requests/structures.py | 103 + .../pip/_vendor/requests/utils.py | 977 + .../site-packages/pip/_vendor/retrying.py | 267 + .../site-packages/pip/_vendor/six.py | 952 + .../pip/_vendor/urllib3/__init__.py | 91 + .../pip/_vendor/urllib3/_collections.py | 329 + .../pip/_vendor/urllib3/connection.py | 417 + .../pip/_vendor/urllib3/connectionpool.py | 897 + .../pip/_vendor/urllib3/contrib/__init__.py | 0 .../urllib3/contrib/_appengine_environ.py | 30 + .../contrib/_securetransport/__init__.py | 0 .../contrib/_securetransport/bindings.py | 593 + .../contrib/_securetransport/low_level.py | 346 + .../pip/_vendor/urllib3/contrib/appengine.py | 289 + .../pip/_vendor/urllib3/contrib/ntlmpool.py | 111 + .../pip/_vendor/urllib3/contrib/pyopenssl.py | 485 + .../urllib3/contrib/securetransport.py | 853 + .../pip/_vendor/urllib3/contrib/socks.py | 205 + .../pip/_vendor/urllib3/exceptions.py | 246 + .../pip/_vendor/urllib3/fields.py | 272 + .../pip/_vendor/urllib3/filepost.py | 98 + .../pip/_vendor/urllib3/packages/__init__.py | 5 + .../urllib3/packages/backports/__init__.py | 0 .../urllib3/packages/backports/makefile.py | 53 + .../urllib3/packages/rfc3986/__init__.py | 56 + .../urllib3/packages/rfc3986/_mixin.py | 353 + .../urllib3/packages/rfc3986/abnf_regexp.py | 267 + .../_vendor/urllib3/packages/rfc3986/api.py | 106 + .../urllib3/packages/rfc3986/builder.py | 298 + .../urllib3/packages/rfc3986/compat.py | 54 + .../urllib3/packages/rfc3986/exceptions.py | 118 + .../_vendor/urllib3/packages/rfc3986/iri.py | 147 + .../_vendor/urllib3/packages/rfc3986/misc.py | 124 + .../urllib3/packages/rfc3986/normalizers.py | 167 + .../urllib3/packages/rfc3986/parseresult.py | 385 + .../_vendor/urllib3/packages/rfc3986/uri.py | 153 + .../urllib3/packages/rfc3986/validators.py | 450 + .../pip/_vendor/urllib3/packages/six.py | 868 + .../packages/ssl_match_hostname/__init__.py | 19 + .../ssl_match_hostname/_implementation.py | 156 + .../pip/_vendor/urllib3/poolmanager.py | 455 + .../pip/_vendor/urllib3/request.py | 150 + .../pip/_vendor/urllib3/response.py | 760 + .../pip/_vendor/urllib3/util/__init__.py | 56 + .../pip/_vendor/urllib3/util/connection.py | 134 + .../pip/_vendor/urllib3/util/queue.py | 21 + .../pip/_vendor/urllib3/util/request.py | 125 + .../pip/_vendor/urllib3/util/response.py | 87 + .../pip/_vendor/urllib3/util/retry.py | 412 + .../pip/_vendor/urllib3/util/ssl_.py | 392 + .../pip/_vendor/urllib3/util/timeout.py | 243 + .../pip/_vendor/urllib3/util/url.py | 289 + .../pip/_vendor/urllib3/util/wait.py | 150 + .../pip/_vendor/webencodings/__init__.py | 342 + .../pip/_vendor/webencodings/labels.py | 231 + .../pip/_vendor/webencodings/mklabels.py | 59 + .../pip/_vendor/webencodings/tests.py | 153 + .../_vendor/webencodings/x_user_defined.py | 325 + .../python_slugify-3.0.4.dist-info/INSTALLER | 1 + .../python_slugify-3.0.4.dist-info/LICENSE | 21 + .../python_slugify-3.0.4.dist-info/METADATA | 31 + .../python_slugify-3.0.4.dist-info/RECORD | 12 + .../python_slugify-3.0.4.dist-info/WHEEL | 6 + .../entry_points.txt | 3 + .../top_level.txt | 1 + .../pytz-2019.2.dist-info/DESCRIPTION.rst | 584 + .../pytz-2019.2.dist-info/INSTALLER | 1 + .../pytz-2019.2.dist-info/LICENSE.txt | 19 + .../pytz-2019.2.dist-info/METADATA | 618 + .../pytz-2019.2.dist-info/RECORD | 620 + .../site-packages/pytz-2019.2.dist-info/WHEEL | 6 + .../pytz-2019.2.dist-info/metadata.json | 1 + .../pytz-2019.2.dist-info/top_level.txt | 1 + .../pytz-2019.2.dist-info/zip-safe | 1 + .../python2.7/site-packages/pytz/__init__.py | 1551 + .../site-packages/pytz/exceptions.py | 48 + venv/lib/python2.7/site-packages/pytz/lazy.py | 172 + .../python2.7/site-packages/pytz/reference.py | 140 + .../python2.7/site-packages/pytz/tzfile.py | 134 + .../python2.7/site-packages/pytz/tzinfo.py | 577 + .../pytz/zoneinfo/Africa/Abidjan | Bin 0 -> 148 bytes .../site-packages/pytz/zoneinfo/Africa/Accra | Bin 0 -> 816 bytes .../pytz/zoneinfo/Africa/Addis_Ababa | Bin 0 -> 251 bytes .../pytz/zoneinfo/Africa/Algiers | Bin 0 -> 735 bytes .../site-packages/pytz/zoneinfo/Africa/Asmara | Bin 0 -> 251 bytes .../site-packages/pytz/zoneinfo/Africa/Asmera | Bin 0 -> 251 bytes .../site-packages/pytz/zoneinfo/Africa/Bamako | Bin 0 -> 148 bytes .../site-packages/pytz/zoneinfo/Africa/Bangui | Bin 0 -> 149 bytes .../site-packages/pytz/zoneinfo/Africa/Banjul | Bin 0 -> 148 bytes .../site-packages/pytz/zoneinfo/Africa/Bissau | Bin 0 -> 194 bytes .../pytz/zoneinfo/Africa/Blantyre | Bin 0 -> 149 bytes .../pytz/zoneinfo/Africa/Brazzaville | Bin 0 -> 149 bytes .../pytz/zoneinfo/Africa/Bujumbura | Bin 0 -> 149 bytes .../site-packages/pytz/zoneinfo/Africa/Cairo | Bin 0 -> 1955 bytes .../pytz/zoneinfo/Africa/Casablanca | Bin 0 -> 2429 bytes .../site-packages/pytz/zoneinfo/Africa/Ceuta | Bin 0 -> 2036 bytes .../pytz/zoneinfo/Africa/Conakry | Bin 0 -> 148 bytes .../site-packages/pytz/zoneinfo/Africa/Dakar | Bin 0 -> 148 bytes .../pytz/zoneinfo/Africa/Dar_es_Salaam | Bin 0 -> 251 bytes .../pytz/zoneinfo/Africa/Djibouti | Bin 0 -> 251 bytes .../site-packages/pytz/zoneinfo/Africa/Douala | Bin 0 -> 149 bytes .../pytz/zoneinfo/Africa/El_Aaiun | Bin 0 -> 2295 bytes .../pytz/zoneinfo/Africa/Freetown | Bin 0 -> 148 bytes .../pytz/zoneinfo/Africa/Gaborone | Bin 0 -> 149 bytes .../site-packages/pytz/zoneinfo/Africa/Harare | Bin 0 -> 149 bytes .../pytz/zoneinfo/Africa/Johannesburg | Bin 0 -> 246 bytes .../site-packages/pytz/zoneinfo/Africa/Juba | Bin 0 -> 653 bytes .../pytz/zoneinfo/Africa/Kampala | Bin 0 -> 251 bytes .../pytz/zoneinfo/Africa/Khartoum | Bin 0 -> 679 bytes .../site-packages/pytz/zoneinfo/Africa/Kigali | Bin 0 -> 149 bytes .../pytz/zoneinfo/Africa/Kinshasa | Bin 0 -> 149 bytes .../site-packages/pytz/zoneinfo/Africa/Lagos | Bin 0 -> 149 bytes .../pytz/zoneinfo/Africa/Libreville | Bin 0 -> 149 bytes .../site-packages/pytz/zoneinfo/Africa/Lome | Bin 0 -> 148 bytes .../site-packages/pytz/zoneinfo/Africa/Luanda | Bin 0 -> 149 bytes .../pytz/zoneinfo/Africa/Lubumbashi | Bin 0 -> 149 bytes .../site-packages/pytz/zoneinfo/Africa/Lusaka | Bin 0 -> 149 bytes .../site-packages/pytz/zoneinfo/Africa/Malabo | Bin 0 -> 149 bytes .../site-packages/pytz/zoneinfo/Africa/Maputo | Bin 0 -> 149 bytes .../site-packages/pytz/zoneinfo/Africa/Maseru | Bin 0 -> 246 bytes .../pytz/zoneinfo/Africa/Mbabane | Bin 0 -> 246 bytes .../pytz/zoneinfo/Africa/Mogadishu | Bin 0 -> 251 bytes .../pytz/zoneinfo/Africa/Monrovia | Bin 0 -> 208 bytes .../pytz/zoneinfo/Africa/Nairobi | Bin 0 -> 251 bytes .../pytz/zoneinfo/Africa/Ndjamena | Bin 0 -> 199 bytes .../site-packages/pytz/zoneinfo/Africa/Niamey | Bin 0 -> 149 bytes .../pytz/zoneinfo/Africa/Nouakchott | Bin 0 -> 148 bytes .../pytz/zoneinfo/Africa/Ouagadougou | Bin 0 -> 148 bytes .../pytz/zoneinfo/Africa/Porto-Novo | Bin 0 -> 149 bytes .../pytz/zoneinfo/Africa/Sao_Tome | Bin 0 -> 254 bytes .../pytz/zoneinfo/Africa/Timbuktu | Bin 0 -> 148 bytes .../pytz/zoneinfo/Africa/Tripoli | Bin 0 -> 625 bytes .../site-packages/pytz/zoneinfo/Africa/Tunis | Bin 0 -> 689 bytes .../pytz/zoneinfo/Africa/Windhoek | Bin 0 -> 955 bytes .../site-packages/pytz/zoneinfo/America/Adak | Bin 0 -> 2356 bytes .../pytz/zoneinfo/America/Anchorage | Bin 0 -> 2371 bytes .../pytz/zoneinfo/America/Anguilla | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/Antigua | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/Araguaina | Bin 0 -> 884 bytes .../zoneinfo/America/Argentina/Buenos_Aires | Bin 0 -> 1076 bytes .../pytz/zoneinfo/America/Argentina/Catamarca | Bin 0 -> 1076 bytes .../zoneinfo/America/Argentina/ComodRivadavia | Bin 0 -> 1076 bytes .../pytz/zoneinfo/America/Argentina/Cordoba | Bin 0 -> 1076 bytes .../pytz/zoneinfo/America/Argentina/Jujuy | Bin 0 -> 1048 bytes .../pytz/zoneinfo/America/Argentina/La_Rioja | Bin 0 -> 1090 bytes .../pytz/zoneinfo/America/Argentina/Mendoza | Bin 0 -> 1076 bytes .../zoneinfo/America/Argentina/Rio_Gallegos | Bin 0 -> 1076 bytes .../pytz/zoneinfo/America/Argentina/Salta | Bin 0 -> 1048 bytes .../pytz/zoneinfo/America/Argentina/San_Juan | Bin 0 -> 1090 bytes .../pytz/zoneinfo/America/Argentina/San_Luis | Bin 0 -> 1102 bytes .../pytz/zoneinfo/America/Argentina/Tucuman | Bin 0 -> 1104 bytes .../pytz/zoneinfo/America/Argentina/Ushuaia | Bin 0 -> 1076 bytes .../site-packages/pytz/zoneinfo/America/Aruba | Bin 0 -> 186 bytes .../pytz/zoneinfo/America/Asuncion | Bin 0 -> 2044 bytes .../pytz/zoneinfo/America/Atikokan | Bin 0 -> 336 bytes .../site-packages/pytz/zoneinfo/America/Atka | Bin 0 -> 2356 bytes .../site-packages/pytz/zoneinfo/America/Bahia | Bin 0 -> 1024 bytes .../pytz/zoneinfo/America/Bahia_Banderas | Bin 0 -> 1546 bytes .../pytz/zoneinfo/America/Barbados | Bin 0 -> 314 bytes .../site-packages/pytz/zoneinfo/America/Belem | Bin 0 -> 576 bytes .../pytz/zoneinfo/America/Belize | Bin 0 -> 948 bytes .../pytz/zoneinfo/America/Blanc-Sablon | Bin 0 -> 298 bytes .../pytz/zoneinfo/America/Boa_Vista | Bin 0 -> 632 bytes .../pytz/zoneinfo/America/Bogota | Bin 0 -> 246 bytes .../site-packages/pytz/zoneinfo/America/Boise | Bin 0 -> 2394 bytes .../pytz/zoneinfo/America/Buenos_Aires | Bin 0 -> 1076 bytes .../pytz/zoneinfo/America/Cambridge_Bay | Bin 0 -> 2084 bytes .../pytz/zoneinfo/America/Campo_Grande | Bin 0 -> 1444 bytes .../pytz/zoneinfo/America/Cancun | Bin 0 -> 782 bytes .../pytz/zoneinfo/America/Caracas | Bin 0 -> 264 bytes .../pytz/zoneinfo/America/Catamarca | Bin 0 -> 1076 bytes .../pytz/zoneinfo/America/Cayenne | Bin 0 -> 198 bytes .../pytz/zoneinfo/America/Cayman | Bin 0 -> 182 bytes .../pytz/zoneinfo/America/Chicago | Bin 0 -> 3576 bytes .../pytz/zoneinfo/America/Chihuahua | Bin 0 -> 1484 bytes .../pytz/zoneinfo/America/Coral_Harbour | Bin 0 -> 336 bytes .../pytz/zoneinfo/America/Cordoba | Bin 0 -> 1076 bytes .../pytz/zoneinfo/America/Costa_Rica | Bin 0 -> 316 bytes .../pytz/zoneinfo/America/Creston | Bin 0 -> 208 bytes .../pytz/zoneinfo/America/Cuiaba | Bin 0 -> 1416 bytes .../pytz/zoneinfo/America/Curacao | Bin 0 -> 186 bytes .../pytz/zoneinfo/America/Danmarkshavn | Bin 0 -> 698 bytes .../pytz/zoneinfo/America/Dawson | Bin 0 -> 2084 bytes .../pytz/zoneinfo/America/Dawson_Creek | Bin 0 -> 1050 bytes .../pytz/zoneinfo/America/Denver | Bin 0 -> 2444 bytes .../pytz/zoneinfo/America/Detroit | Bin 0 -> 2174 bytes .../pytz/zoneinfo/America/Dominica | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/Edmonton | Bin 0 -> 2388 bytes .../pytz/zoneinfo/America/Eirunepe | Bin 0 -> 656 bytes .../pytz/zoneinfo/America/El_Salvador | Bin 0 -> 224 bytes .../pytz/zoneinfo/America/Ensenada | Bin 0 -> 2342 bytes .../pytz/zoneinfo/America/Fort_Nelson | Bin 0 -> 2240 bytes .../pytz/zoneinfo/America/Fort_Wayne | Bin 0 -> 1666 bytes .../pytz/zoneinfo/America/Fortaleza | Bin 0 -> 716 bytes .../pytz/zoneinfo/America/Glace_Bay | Bin 0 -> 2192 bytes .../pytz/zoneinfo/America/Godthab | Bin 0 -> 1878 bytes .../pytz/zoneinfo/America/Goose_Bay | Bin 0 -> 3210 bytes .../pytz/zoneinfo/America/Grand_Turk | Bin 0 -> 1848 bytes .../pytz/zoneinfo/America/Grenada | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/Guadeloupe | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/Guatemala | Bin 0 -> 280 bytes .../pytz/zoneinfo/America/Guayaquil | Bin 0 -> 246 bytes .../pytz/zoneinfo/America/Guyana | Bin 0 -> 236 bytes .../pytz/zoneinfo/America/Halifax | Bin 0 -> 3424 bytes .../pytz/zoneinfo/America/Havana | Bin 0 -> 2416 bytes .../pytz/zoneinfo/America/Hermosillo | Bin 0 -> 416 bytes .../zoneinfo/America/Indiana/Indianapolis | Bin 0 -> 1666 bytes .../pytz/zoneinfo/America/Indiana/Knox | Bin 0 -> 2428 bytes .../pytz/zoneinfo/America/Indiana/Marengo | Bin 0 -> 1722 bytes .../pytz/zoneinfo/America/Indiana/Petersburg | Bin 0 -> 1904 bytes .../pytz/zoneinfo/America/Indiana/Tell_City | Bin 0 -> 1726 bytes .../pytz/zoneinfo/America/Indiana/Vevay | Bin 0 -> 1414 bytes .../pytz/zoneinfo/America/Indiana/Vincennes | Bin 0 -> 1694 bytes .../pytz/zoneinfo/America/Indiana/Winamac | Bin 0 -> 1778 bytes .../pytz/zoneinfo/America/Indianapolis | Bin 0 -> 1666 bytes .../pytz/zoneinfo/America/Inuvik | Bin 0 -> 1894 bytes .../pytz/zoneinfo/America/Iqaluit | Bin 0 -> 2032 bytes .../pytz/zoneinfo/America/Jamaica | Bin 0 -> 482 bytes .../site-packages/pytz/zoneinfo/America/Jujuy | Bin 0 -> 1048 bytes .../pytz/zoneinfo/America/Juneau | Bin 0 -> 2353 bytes .../pytz/zoneinfo/America/Kentucky/Louisville | Bin 0 -> 2772 bytes .../pytz/zoneinfo/America/Kentucky/Monticello | Bin 0 -> 2352 bytes .../pytz/zoneinfo/America/Knox_IN | Bin 0 -> 2428 bytes .../pytz/zoneinfo/America/Kralendijk | Bin 0 -> 186 bytes .../pytz/zoneinfo/America/La_Paz | Bin 0 -> 232 bytes .../site-packages/pytz/zoneinfo/America/Lima | Bin 0 -> 406 bytes .../pytz/zoneinfo/America/Los_Angeles | Bin 0 -> 2836 bytes .../pytz/zoneinfo/America/Louisville | Bin 0 -> 2772 bytes .../pytz/zoneinfo/America/Lower_Princes | Bin 0 -> 186 bytes .../pytz/zoneinfo/America/Maceio | Bin 0 -> 744 bytes .../pytz/zoneinfo/America/Managua | Bin 0 -> 430 bytes .../pytz/zoneinfo/America/Manaus | Bin 0 -> 604 bytes .../pytz/zoneinfo/America/Marigot | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/Martinique | Bin 0 -> 232 bytes .../pytz/zoneinfo/America/Matamoros | Bin 0 -> 1390 bytes .../pytz/zoneinfo/America/Mazatlan | Bin 0 -> 1526 bytes .../pytz/zoneinfo/America/Mendoza | Bin 0 -> 1076 bytes .../pytz/zoneinfo/America/Menominee | Bin 0 -> 2274 bytes .../pytz/zoneinfo/America/Merida | Bin 0 -> 1422 bytes .../pytz/zoneinfo/America/Metlakatla | Bin 0 -> 1423 bytes .../pytz/zoneinfo/America/Mexico_City | Bin 0 -> 1584 bytes .../pytz/zoneinfo/America/Miquelon | Bin 0 -> 1666 bytes .../pytz/zoneinfo/America/Moncton | Bin 0 -> 3154 bytes .../pytz/zoneinfo/America/Monterrey | Bin 0 -> 1390 bytes .../pytz/zoneinfo/America/Montevideo | Bin 0 -> 1510 bytes .../pytz/zoneinfo/America/Montreal | Bin 0 -> 3494 bytes .../pytz/zoneinfo/America/Montserrat | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/Nassau | Bin 0 -> 2258 bytes .../pytz/zoneinfo/America/New_York | Bin 0 -> 3536 bytes .../pytz/zoneinfo/America/Nipigon | Bin 0 -> 2122 bytes .../site-packages/pytz/zoneinfo/America/Nome | Bin 0 -> 2367 bytes .../pytz/zoneinfo/America/Noronha | Bin 0 -> 716 bytes .../pytz/zoneinfo/America/North_Dakota/Beulah | Bin 0 -> 2380 bytes .../pytz/zoneinfo/America/North_Dakota/Center | Bin 0 -> 2380 bytes .../zoneinfo/America/North_Dakota/New_Salem | Bin 0 -> 2380 bytes .../pytz/zoneinfo/America/Ojinaga | Bin 0 -> 1484 bytes .../pytz/zoneinfo/America/Panama | Bin 0 -> 182 bytes .../pytz/zoneinfo/America/Pangnirtung | Bin 0 -> 2094 bytes .../pytz/zoneinfo/America/Paramaribo | Bin 0 -> 262 bytes .../pytz/zoneinfo/America/Phoenix | Bin 0 -> 328 bytes .../pytz/zoneinfo/America/Port-au-Prince | Bin 0 -> 1434 bytes .../pytz/zoneinfo/America/Port_of_Spain | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/Porto_Acre | Bin 0 -> 628 bytes .../pytz/zoneinfo/America/Porto_Velho | Bin 0 -> 576 bytes .../pytz/zoneinfo/America/Puerto_Rico | Bin 0 -> 246 bytes .../pytz/zoneinfo/America/Punta_Arenas | Bin 0 -> 1902 bytes .../pytz/zoneinfo/America/Rainy_River | Bin 0 -> 2122 bytes .../pytz/zoneinfo/America/Rankin_Inlet | Bin 0 -> 1892 bytes .../pytz/zoneinfo/America/Recife | Bin 0 -> 716 bytes .../pytz/zoneinfo/America/Regina | Bin 0 -> 980 bytes .../pytz/zoneinfo/America/Resolute | Bin 0 -> 1892 bytes .../pytz/zoneinfo/America/Rio_Branco | Bin 0 -> 628 bytes .../pytz/zoneinfo/America/Rosario | Bin 0 -> 1076 bytes .../pytz/zoneinfo/America/Santa_Isabel | Bin 0 -> 2342 bytes .../pytz/zoneinfo/America/Santarem | Bin 0 -> 602 bytes .../pytz/zoneinfo/America/Santiago | Bin 0 -> 2529 bytes .../pytz/zoneinfo/America/Santo_Domingo | Bin 0 -> 458 bytes .../pytz/zoneinfo/America/Sao_Paulo | Bin 0 -> 1444 bytes .../pytz/zoneinfo/America/Scoresbysund | Bin 0 -> 1916 bytes .../pytz/zoneinfo/America/Shiprock | Bin 0 -> 2444 bytes .../site-packages/pytz/zoneinfo/America/Sitka | Bin 0 -> 2329 bytes .../pytz/zoneinfo/America/St_Barthelemy | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/St_Johns | Bin 0 -> 3655 bytes .../pytz/zoneinfo/America/St_Kitts | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/St_Lucia | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/St_Thomas | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/St_Vincent | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/Swift_Current | Bin 0 -> 560 bytes .../pytz/zoneinfo/America/Tegucigalpa | Bin 0 -> 252 bytes .../site-packages/pytz/zoneinfo/America/Thule | Bin 0 -> 1502 bytes .../pytz/zoneinfo/America/Thunder_Bay | Bin 0 -> 2202 bytes .../pytz/zoneinfo/America/Tijuana | Bin 0 -> 2342 bytes .../pytz/zoneinfo/America/Toronto | Bin 0 -> 3494 bytes .../pytz/zoneinfo/America/Tortola | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/Vancouver | Bin 0 -> 2892 bytes .../pytz/zoneinfo/America/Virgin | Bin 0 -> 148 bytes .../pytz/zoneinfo/America/Whitehorse | Bin 0 -> 2084 bytes .../pytz/zoneinfo/America/Winnipeg | Bin 0 -> 2868 bytes .../pytz/zoneinfo/America/Yakutat | Bin 0 -> 2305 bytes .../pytz/zoneinfo/America/Yellowknife | Bin 0 -> 1966 bytes .../pytz/zoneinfo/Antarctica/Casey | Bin 0 -> 297 bytes .../pytz/zoneinfo/Antarctica/Davis | Bin 0 -> 297 bytes .../pytz/zoneinfo/Antarctica/DumontDUrville | Bin 0 -> 194 bytes .../pytz/zoneinfo/Antarctica/Macquarie | Bin 0 -> 1520 bytes .../pytz/zoneinfo/Antarctica/Mawson | Bin 0 -> 199 bytes .../pytz/zoneinfo/Antarctica/McMurdo | Bin 0 -> 2437 bytes .../pytz/zoneinfo/Antarctica/Palmer | Bin 0 -> 1418 bytes .../pytz/zoneinfo/Antarctica/Rothera | Bin 0 -> 164 bytes .../pytz/zoneinfo/Antarctica/South_Pole | Bin 0 -> 2437 bytes .../pytz/zoneinfo/Antarctica/Syowa | Bin 0 -> 165 bytes .../pytz/zoneinfo/Antarctica/Troll | Bin 0 -> 1162 bytes .../pytz/zoneinfo/Antarctica/Vostok | Bin 0 -> 165 bytes .../pytz/zoneinfo/Arctic/Longyearbyen | Bin 0 -> 2228 bytes .../site-packages/pytz/zoneinfo/Asia/Aden | Bin 0 -> 165 bytes .../site-packages/pytz/zoneinfo/Asia/Almaty | Bin 0 -> 997 bytes .../site-packages/pytz/zoneinfo/Asia/Amman | Bin 0 -> 1853 bytes .../site-packages/pytz/zoneinfo/Asia/Anadyr | Bin 0 -> 1188 bytes .../site-packages/pytz/zoneinfo/Asia/Aqtau | Bin 0 -> 983 bytes .../site-packages/pytz/zoneinfo/Asia/Aqtobe | Bin 0 -> 1011 bytes .../site-packages/pytz/zoneinfo/Asia/Ashgabat | Bin 0 -> 619 bytes .../pytz/zoneinfo/Asia/Ashkhabad | Bin 0 -> 619 bytes .../site-packages/pytz/zoneinfo/Asia/Atyrau | Bin 0 -> 991 bytes .../site-packages/pytz/zoneinfo/Asia/Baghdad | Bin 0 -> 983 bytes .../site-packages/pytz/zoneinfo/Asia/Bahrain | Bin 0 -> 199 bytes .../site-packages/pytz/zoneinfo/Asia/Baku | Bin 0 -> 1227 bytes .../site-packages/pytz/zoneinfo/Asia/Bangkok | Bin 0 -> 199 bytes .../site-packages/pytz/zoneinfo/Asia/Barnaul | Bin 0 -> 1221 bytes .../site-packages/pytz/zoneinfo/Asia/Beirut | Bin 0 -> 2154 bytes .../site-packages/pytz/zoneinfo/Asia/Bishkek | Bin 0 -> 983 bytes .../site-packages/pytz/zoneinfo/Asia/Brunei | Bin 0 -> 203 bytes .../site-packages/pytz/zoneinfo/Asia/Calcutta | Bin 0 -> 285 bytes .../site-packages/pytz/zoneinfo/Asia/Chita | Bin 0 -> 1221 bytes .../pytz/zoneinfo/Asia/Choibalsan | Bin 0 -> 949 bytes .../pytz/zoneinfo/Asia/Chongqing | Bin 0 -> 533 bytes .../pytz/zoneinfo/Asia/Chungking | Bin 0 -> 533 bytes .../site-packages/pytz/zoneinfo/Asia/Colombo | Bin 0 -> 372 bytes .../site-packages/pytz/zoneinfo/Asia/Dacca | Bin 0 -> 337 bytes .../site-packages/pytz/zoneinfo/Asia/Damascus | Bin 0 -> 2294 bytes .../site-packages/pytz/zoneinfo/Asia/Dhaka | Bin 0 -> 337 bytes .../site-packages/pytz/zoneinfo/Asia/Dili | Bin 0 -> 227 bytes .../site-packages/pytz/zoneinfo/Asia/Dubai | Bin 0 -> 165 bytes .../site-packages/pytz/zoneinfo/Asia/Dushanbe | Bin 0 -> 591 bytes .../pytz/zoneinfo/Asia/Famagusta | Bin 0 -> 2028 bytes .../site-packages/pytz/zoneinfo/Asia/Gaza | Bin 0 -> 2316 bytes .../site-packages/pytz/zoneinfo/Asia/Harbin | Bin 0 -> 533 bytes .../site-packages/pytz/zoneinfo/Asia/Hebron | Bin 0 -> 2344 bytes .../pytz/zoneinfo/Asia/Ho_Chi_Minh | Bin 0 -> 351 bytes .../pytz/zoneinfo/Asia/Hong_Kong | Bin 0 -> 1193 bytes .../site-packages/pytz/zoneinfo/Asia/Hovd | Bin 0 -> 891 bytes .../site-packages/pytz/zoneinfo/Asia/Irkutsk | Bin 0 -> 1243 bytes .../site-packages/pytz/zoneinfo/Asia/Istanbul | Bin 0 -> 2143 bytes .../site-packages/pytz/zoneinfo/Asia/Jakarta | Bin 0 -> 355 bytes .../site-packages/pytz/zoneinfo/Asia/Jayapura | Bin 0 -> 221 bytes .../pytz/zoneinfo/Asia/Jerusalem | Bin 0 -> 2288 bytes .../site-packages/pytz/zoneinfo/Asia/Kabul | Bin 0 -> 208 bytes .../pytz/zoneinfo/Asia/Kamchatka | Bin 0 -> 1166 bytes .../site-packages/pytz/zoneinfo/Asia/Karachi | Bin 0 -> 379 bytes .../site-packages/pytz/zoneinfo/Asia/Kashgar | Bin 0 -> 165 bytes .../pytz/zoneinfo/Asia/Kathmandu | Bin 0 -> 212 bytes .../site-packages/pytz/zoneinfo/Asia/Katmandu | Bin 0 -> 212 bytes .../site-packages/pytz/zoneinfo/Asia/Khandyga | Bin 0 -> 1271 bytes .../site-packages/pytz/zoneinfo/Asia/Kolkata | Bin 0 -> 285 bytes .../pytz/zoneinfo/Asia/Krasnoyarsk | Bin 0 -> 1207 bytes .../pytz/zoneinfo/Asia/Kuala_Lumpur | Bin 0 -> 383 bytes .../site-packages/pytz/zoneinfo/Asia/Kuching | Bin 0 -> 483 bytes .../site-packages/pytz/zoneinfo/Asia/Kuwait | Bin 0 -> 165 bytes .../site-packages/pytz/zoneinfo/Asia/Macao | Bin 0 -> 1227 bytes .../site-packages/pytz/zoneinfo/Asia/Macau | Bin 0 -> 1227 bytes .../site-packages/pytz/zoneinfo/Asia/Magadan | Bin 0 -> 1222 bytes .../site-packages/pytz/zoneinfo/Asia/Makassar | Bin 0 -> 254 bytes .../site-packages/pytz/zoneinfo/Asia/Manila | Bin 0 -> 328 bytes .../site-packages/pytz/zoneinfo/Asia/Muscat | Bin 0 -> 165 bytes .../site-packages/pytz/zoneinfo/Asia/Nicosia | Bin 0 -> 2002 bytes .../pytz/zoneinfo/Asia/Novokuznetsk | Bin 0 -> 1165 bytes .../pytz/zoneinfo/Asia/Novosibirsk | Bin 0 -> 1221 bytes .../site-packages/pytz/zoneinfo/Asia/Omsk | Bin 0 -> 1207 bytes .../site-packages/pytz/zoneinfo/Asia/Oral | Bin 0 -> 1005 bytes .../pytz/zoneinfo/Asia/Phnom_Penh | Bin 0 -> 199 bytes .../pytz/zoneinfo/Asia/Pontianak | Bin 0 -> 353 bytes .../pytz/zoneinfo/Asia/Pyongyang | Bin 0 -> 237 bytes .../site-packages/pytz/zoneinfo/Asia/Qatar | Bin 0 -> 199 bytes .../site-packages/pytz/zoneinfo/Asia/Qostanay | Bin 0 -> 1011 bytes .../pytz/zoneinfo/Asia/Qyzylorda | Bin 0 -> 1025 bytes .../site-packages/pytz/zoneinfo/Asia/Rangoon | Bin 0 -> 268 bytes .../site-packages/pytz/zoneinfo/Asia/Riyadh | Bin 0 -> 165 bytes .../site-packages/pytz/zoneinfo/Asia/Saigon | Bin 0 -> 351 bytes .../site-packages/pytz/zoneinfo/Asia/Sakhalin | Bin 0 -> 1202 bytes .../pytz/zoneinfo/Asia/Samarkand | Bin 0 -> 577 bytes .../site-packages/pytz/zoneinfo/Asia/Seoul | Bin 0 -> 493 bytes .../site-packages/pytz/zoneinfo/Asia/Shanghai | Bin 0 -> 533 bytes .../pytz/zoneinfo/Asia/Singapore | Bin 0 -> 383 bytes .../pytz/zoneinfo/Asia/Srednekolymsk | Bin 0 -> 1208 bytes .../site-packages/pytz/zoneinfo/Asia/Taipei | Bin 0 -> 761 bytes .../site-packages/pytz/zoneinfo/Asia/Tashkent | Bin 0 -> 591 bytes .../site-packages/pytz/zoneinfo/Asia/Tbilisi | Bin 0 -> 1035 bytes .../site-packages/pytz/zoneinfo/Asia/Tehran | Bin 0 -> 2582 bytes .../site-packages/pytz/zoneinfo/Asia/Tel_Aviv | Bin 0 -> 2288 bytes .../site-packages/pytz/zoneinfo/Asia/Thimbu | Bin 0 -> 203 bytes .../site-packages/pytz/zoneinfo/Asia/Thimphu | Bin 0 -> 203 bytes .../site-packages/pytz/zoneinfo/Asia/Tokyo | Bin 0 -> 309 bytes .../site-packages/pytz/zoneinfo/Asia/Tomsk | Bin 0 -> 1221 bytes .../pytz/zoneinfo/Asia/Ujung_Pandang | Bin 0 -> 254 bytes .../pytz/zoneinfo/Asia/Ulaanbaatar | Bin 0 -> 891 bytes .../pytz/zoneinfo/Asia/Ulan_Bator | Bin 0 -> 891 bytes .../site-packages/pytz/zoneinfo/Asia/Urumqi | Bin 0 -> 165 bytes .../site-packages/pytz/zoneinfo/Asia/Ust-Nera | Bin 0 -> 1252 bytes .../pytz/zoneinfo/Asia/Vientiane | Bin 0 -> 199 bytes .../pytz/zoneinfo/Asia/Vladivostok | Bin 0 -> 1208 bytes .../site-packages/pytz/zoneinfo/Asia/Yakutsk | Bin 0 -> 1207 bytes .../site-packages/pytz/zoneinfo/Asia/Yangon | Bin 0 -> 268 bytes .../pytz/zoneinfo/Asia/Yekaterinburg | Bin 0 -> 1243 bytes .../site-packages/pytz/zoneinfo/Asia/Yerevan | Bin 0 -> 1151 bytes .../pytz/zoneinfo/Atlantic/Azores | Bin 0 -> 3484 bytes .../pytz/zoneinfo/Atlantic/Bermuda | Bin 0 -> 1978 bytes .../pytz/zoneinfo/Atlantic/Canary | Bin 0 -> 1897 bytes .../pytz/zoneinfo/Atlantic/Cape_Verde | Bin 0 -> 270 bytes .../pytz/zoneinfo/Atlantic/Faeroe | Bin 0 -> 1815 bytes .../pytz/zoneinfo/Atlantic/Faroe | Bin 0 -> 1815 bytes .../pytz/zoneinfo/Atlantic/Jan_Mayen | Bin 0 -> 2228 bytes .../pytz/zoneinfo/Atlantic/Madeira | Bin 0 -> 3475 bytes .../pytz/zoneinfo/Atlantic/Reykjavik | Bin 0 -> 1162 bytes .../pytz/zoneinfo/Atlantic/South_Georgia | Bin 0 -> 164 bytes .../pytz/zoneinfo/Atlantic/St_Helena | Bin 0 -> 148 bytes .../pytz/zoneinfo/Atlantic/Stanley | Bin 0 -> 1214 bytes .../site-packages/pytz/zoneinfo/Australia/ACT | Bin 0 -> 2204 bytes .../pytz/zoneinfo/Australia/Adelaide | Bin 0 -> 2222 bytes .../pytz/zoneinfo/Australia/Brisbane | Bin 0 -> 433 bytes .../pytz/zoneinfo/Australia/Broken_Hill | Bin 0 -> 2243 bytes .../pytz/zoneinfo/Australia/Canberra | Bin 0 -> 2204 bytes .../pytz/zoneinfo/Australia/Currie | Bin 0 -> 2204 bytes .../pytz/zoneinfo/Australia/Darwin | Bin 0 -> 304 bytes .../pytz/zoneinfo/Australia/Eucla | Bin 0 -> 484 bytes .../pytz/zoneinfo/Australia/Hobart | Bin 0 -> 2316 bytes .../site-packages/pytz/zoneinfo/Australia/LHI | Bin 0 -> 1860 bytes .../pytz/zoneinfo/Australia/Lindeman | Bin 0 -> 489 bytes .../pytz/zoneinfo/Australia/Lord_Howe | Bin 0 -> 1860 bytes .../pytz/zoneinfo/Australia/Melbourne | Bin 0 -> 2204 bytes .../site-packages/pytz/zoneinfo/Australia/NSW | Bin 0 -> 2204 bytes .../pytz/zoneinfo/Australia/North | Bin 0 -> 304 bytes .../pytz/zoneinfo/Australia/Perth | Bin 0 -> 460 bytes .../pytz/zoneinfo/Australia/Queensland | Bin 0 -> 433 bytes .../pytz/zoneinfo/Australia/South | Bin 0 -> 2222 bytes .../pytz/zoneinfo/Australia/Sydney | Bin 0 -> 2204 bytes .../pytz/zoneinfo/Australia/Tasmania | Bin 0 -> 2316 bytes .../pytz/zoneinfo/Australia/Victoria | Bin 0 -> 2204 bytes .../pytz/zoneinfo/Australia/West | Bin 0 -> 460 bytes .../pytz/zoneinfo/Australia/Yancowinna | Bin 0 -> 2243 bytes .../site-packages/pytz/zoneinfo/Brazil/Acre | Bin 0 -> 628 bytes .../pytz/zoneinfo/Brazil/DeNoronha | Bin 0 -> 716 bytes .../site-packages/pytz/zoneinfo/Brazil/East | Bin 0 -> 1444 bytes .../site-packages/pytz/zoneinfo/Brazil/West | Bin 0 -> 604 bytes .../python2.7/site-packages/pytz/zoneinfo/CET | Bin 0 -> 2094 bytes .../site-packages/pytz/zoneinfo/CST6CDT | Bin 0 -> 2310 bytes .../pytz/zoneinfo/Canada/Atlantic | Bin 0 -> 3424 bytes .../pytz/zoneinfo/Canada/Central | Bin 0 -> 2868 bytes .../pytz/zoneinfo/Canada/Eastern | Bin 0 -> 3494 bytes .../pytz/zoneinfo/Canada/Mountain | Bin 0 -> 2388 bytes .../pytz/zoneinfo/Canada/Newfoundland | Bin 0 -> 3655 bytes .../pytz/zoneinfo/Canada/Pacific | Bin 0 -> 2892 bytes .../pytz/zoneinfo/Canada/Saskatchewan | Bin 0 -> 980 bytes .../site-packages/pytz/zoneinfo/Canada/Yukon | Bin 0 -> 2084 bytes .../pytz/zoneinfo/Chile/Continental | Bin 0 -> 2529 bytes .../pytz/zoneinfo/Chile/EasterIsland | Bin 0 -> 2233 bytes .../site-packages/pytz/zoneinfo/Cuba | Bin 0 -> 2416 bytes .../python2.7/site-packages/pytz/zoneinfo/EET | Bin 0 -> 1908 bytes .../python2.7/site-packages/pytz/zoneinfo/EST | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/EST5EDT | Bin 0 -> 2310 bytes .../site-packages/pytz/zoneinfo/Egypt | Bin 0 -> 1955 bytes .../site-packages/pytz/zoneinfo/Eire | Bin 0 -> 3492 bytes .../site-packages/pytz/zoneinfo/Etc/GMT | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/Etc/GMT+0 | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/Etc/GMT+1 | Bin 0 -> 116 bytes .../site-packages/pytz/zoneinfo/Etc/GMT+10 | Bin 0 -> 117 bytes .../site-packages/pytz/zoneinfo/Etc/GMT+11 | Bin 0 -> 117 bytes .../site-packages/pytz/zoneinfo/Etc/GMT+12 | Bin 0 -> 117 bytes .../site-packages/pytz/zoneinfo/Etc/GMT+2 | Bin 0 -> 116 bytes .../site-packages/pytz/zoneinfo/Etc/GMT+3 | Bin 0 -> 116 bytes .../site-packages/pytz/zoneinfo/Etc/GMT+4 | Bin 0 -> 116 bytes .../site-packages/pytz/zoneinfo/Etc/GMT+5 | Bin 0 -> 116 bytes .../site-packages/pytz/zoneinfo/Etc/GMT+6 | Bin 0 -> 116 bytes .../site-packages/pytz/zoneinfo/Etc/GMT+7 | Bin 0 -> 116 bytes .../site-packages/pytz/zoneinfo/Etc/GMT+8 | Bin 0 -> 116 bytes .../site-packages/pytz/zoneinfo/Etc/GMT+9 | Bin 0 -> 116 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-0 | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-1 | Bin 0 -> 117 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-10 | Bin 0 -> 118 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-11 | Bin 0 -> 118 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-12 | Bin 0 -> 118 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-13 | Bin 0 -> 118 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-14 | Bin 0 -> 118 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-2 | Bin 0 -> 117 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-3 | Bin 0 -> 117 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-4 | Bin 0 -> 117 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-5 | Bin 0 -> 117 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-6 | Bin 0 -> 117 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-7 | Bin 0 -> 117 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-8 | Bin 0 -> 117 bytes .../site-packages/pytz/zoneinfo/Etc/GMT-9 | Bin 0 -> 117 bytes .../site-packages/pytz/zoneinfo/Etc/GMT0 | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/Etc/Greenwich | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/Etc/UCT | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/Etc/UTC | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/Etc/Universal | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/Etc/Zulu | Bin 0 -> 114 bytes .../pytz/zoneinfo/Europe/Amsterdam | Bin 0 -> 2910 bytes .../pytz/zoneinfo/Europe/Andorra | Bin 0 -> 1742 bytes .../pytz/zoneinfo/Europe/Astrakhan | Bin 0 -> 1165 bytes .../site-packages/pytz/zoneinfo/Europe/Athens | Bin 0 -> 2262 bytes .../pytz/zoneinfo/Europe/Belfast | Bin 0 -> 3648 bytes .../pytz/zoneinfo/Europe/Belgrade | Bin 0 -> 1920 bytes .../site-packages/pytz/zoneinfo/Europe/Berlin | Bin 0 -> 2298 bytes .../pytz/zoneinfo/Europe/Bratislava | Bin 0 -> 2301 bytes .../pytz/zoneinfo/Europe/Brussels | Bin 0 -> 2933 bytes .../pytz/zoneinfo/Europe/Bucharest | Bin 0 -> 2184 bytes .../pytz/zoneinfo/Europe/Budapest | Bin 0 -> 2368 bytes .../pytz/zoneinfo/Europe/Busingen | Bin 0 -> 1909 bytes .../pytz/zoneinfo/Europe/Chisinau | Bin 0 -> 2390 bytes .../pytz/zoneinfo/Europe/Copenhagen | Bin 0 -> 2137 bytes .../site-packages/pytz/zoneinfo/Europe/Dublin | Bin 0 -> 3492 bytes .../pytz/zoneinfo/Europe/Gibraltar | Bin 0 -> 3052 bytes .../pytz/zoneinfo/Europe/Guernsey | Bin 0 -> 3648 bytes .../pytz/zoneinfo/Europe/Helsinki | Bin 0 -> 1900 bytes .../pytz/zoneinfo/Europe/Isle_of_Man | Bin 0 -> 3648 bytes .../pytz/zoneinfo/Europe/Istanbul | Bin 0 -> 2143 bytes .../site-packages/pytz/zoneinfo/Europe/Jersey | Bin 0 -> 3648 bytes .../pytz/zoneinfo/Europe/Kaliningrad | Bin 0 -> 1479 bytes .../site-packages/pytz/zoneinfo/Europe/Kiev | Bin 0 -> 2088 bytes .../site-packages/pytz/zoneinfo/Europe/Kirov | Bin 0 -> 1153 bytes .../site-packages/pytz/zoneinfo/Europe/Lisbon | Bin 0 -> 3469 bytes .../pytz/zoneinfo/Europe/Ljubljana | Bin 0 -> 1920 bytes .../site-packages/pytz/zoneinfo/Europe/London | Bin 0 -> 3648 bytes .../pytz/zoneinfo/Europe/Luxembourg | Bin 0 -> 2946 bytes .../site-packages/pytz/zoneinfo/Europe/Madrid | Bin 0 -> 2614 bytes .../site-packages/pytz/zoneinfo/Europe/Malta | Bin 0 -> 2620 bytes .../pytz/zoneinfo/Europe/Mariehamn | Bin 0 -> 1900 bytes .../site-packages/pytz/zoneinfo/Europe/Minsk | Bin 0 -> 1321 bytes .../site-packages/pytz/zoneinfo/Europe/Monaco | Bin 0 -> 2944 bytes .../site-packages/pytz/zoneinfo/Europe/Moscow | Bin 0 -> 1535 bytes .../pytz/zoneinfo/Europe/Nicosia | Bin 0 -> 2002 bytes .../site-packages/pytz/zoneinfo/Europe/Oslo | Bin 0 -> 2228 bytes .../site-packages/pytz/zoneinfo/Europe/Paris | Bin 0 -> 2962 bytes .../pytz/zoneinfo/Europe/Podgorica | Bin 0 -> 1920 bytes .../site-packages/pytz/zoneinfo/Europe/Prague | Bin 0 -> 2301 bytes .../site-packages/pytz/zoneinfo/Europe/Riga | Bin 0 -> 2198 bytes .../site-packages/pytz/zoneinfo/Europe/Rome | Bin 0 -> 2641 bytes .../site-packages/pytz/zoneinfo/Europe/Samara | Bin 0 -> 1215 bytes .../pytz/zoneinfo/Europe/San_Marino | Bin 0 -> 2641 bytes .../pytz/zoneinfo/Europe/Sarajevo | Bin 0 -> 1920 bytes .../pytz/zoneinfo/Europe/Saratov | Bin 0 -> 1183 bytes .../pytz/zoneinfo/Europe/Simferopol | Bin 0 -> 1453 bytes .../site-packages/pytz/zoneinfo/Europe/Skopje | Bin 0 -> 1920 bytes .../site-packages/pytz/zoneinfo/Europe/Sofia | Bin 0 -> 2077 bytes .../pytz/zoneinfo/Europe/Stockholm | Bin 0 -> 1909 bytes .../pytz/zoneinfo/Europe/Tallinn | Bin 0 -> 2148 bytes .../site-packages/pytz/zoneinfo/Europe/Tirane | Bin 0 -> 2084 bytes .../pytz/zoneinfo/Europe/Tiraspol | Bin 0 -> 2390 bytes .../pytz/zoneinfo/Europe/Ulyanovsk | Bin 0 -> 1267 bytes .../pytz/zoneinfo/Europe/Uzhgorod | Bin 0 -> 2050 bytes .../site-packages/pytz/zoneinfo/Europe/Vaduz | Bin 0 -> 1909 bytes .../pytz/zoneinfo/Europe/Vatican | Bin 0 -> 2641 bytes .../site-packages/pytz/zoneinfo/Europe/Vienna | Bin 0 -> 2200 bytes .../pytz/zoneinfo/Europe/Vilnius | Bin 0 -> 2162 bytes .../pytz/zoneinfo/Europe/Volgograd | Bin 0 -> 1165 bytes .../site-packages/pytz/zoneinfo/Europe/Warsaw | Bin 0 -> 2654 bytes .../site-packages/pytz/zoneinfo/Europe/Zagreb | Bin 0 -> 1920 bytes .../pytz/zoneinfo/Europe/Zaporozhye | Bin 0 -> 2106 bytes .../site-packages/pytz/zoneinfo/Europe/Zurich | Bin 0 -> 1909 bytes .../site-packages/pytz/zoneinfo/Factory | Bin 0 -> 116 bytes .../python2.7/site-packages/pytz/zoneinfo/GB | Bin 0 -> 3648 bytes .../site-packages/pytz/zoneinfo/GB-Eire | Bin 0 -> 3648 bytes .../python2.7/site-packages/pytz/zoneinfo/GMT | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/GMT+0 | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/GMT-0 | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/GMT0 | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/Greenwich | Bin 0 -> 114 bytes .../python2.7/site-packages/pytz/zoneinfo/HST | Bin 0 -> 115 bytes .../site-packages/pytz/zoneinfo/Hongkong | Bin 0 -> 1193 bytes .../site-packages/pytz/zoneinfo/Iceland | Bin 0 -> 1162 bytes .../pytz/zoneinfo/Indian/Antananarivo | Bin 0 -> 251 bytes .../site-packages/pytz/zoneinfo/Indian/Chagos | Bin 0 -> 199 bytes .../pytz/zoneinfo/Indian/Christmas | Bin 0 -> 165 bytes .../site-packages/pytz/zoneinfo/Indian/Cocos | Bin 0 -> 174 bytes .../site-packages/pytz/zoneinfo/Indian/Comoro | Bin 0 -> 251 bytes .../pytz/zoneinfo/Indian/Kerguelen | Bin 0 -> 165 bytes .../site-packages/pytz/zoneinfo/Indian/Mahe | Bin 0 -> 165 bytes .../pytz/zoneinfo/Indian/Maldives | Bin 0 -> 199 bytes .../pytz/zoneinfo/Indian/Mauritius | Bin 0 -> 241 bytes .../pytz/zoneinfo/Indian/Mayotte | Bin 0 -> 251 bytes .../pytz/zoneinfo/Indian/Reunion | Bin 0 -> 165 bytes .../site-packages/pytz/zoneinfo/Iran | Bin 0 -> 2582 bytes .../site-packages/pytz/zoneinfo/Israel | Bin 0 -> 2288 bytes .../site-packages/pytz/zoneinfo/Jamaica | Bin 0 -> 482 bytes .../site-packages/pytz/zoneinfo/Japan | Bin 0 -> 309 bytes .../site-packages/pytz/zoneinfo/Kwajalein | Bin 0 -> 316 bytes .../site-packages/pytz/zoneinfo/Libya | Bin 0 -> 625 bytes .../python2.7/site-packages/pytz/zoneinfo/MET | Bin 0 -> 2094 bytes .../python2.7/site-packages/pytz/zoneinfo/MST | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/MST7MDT | Bin 0 -> 2310 bytes .../pytz/zoneinfo/Mexico/BajaNorte | Bin 0 -> 2342 bytes .../pytz/zoneinfo/Mexico/BajaSur | Bin 0 -> 1526 bytes .../pytz/zoneinfo/Mexico/General | Bin 0 -> 1584 bytes .../python2.7/site-packages/pytz/zoneinfo/NZ | Bin 0 -> 2437 bytes .../site-packages/pytz/zoneinfo/NZ-CHAT | Bin 0 -> 2068 bytes .../site-packages/pytz/zoneinfo/Navajo | Bin 0 -> 2444 bytes .../python2.7/site-packages/pytz/zoneinfo/PRC | Bin 0 -> 533 bytes .../site-packages/pytz/zoneinfo/PST8PDT | Bin 0 -> 2310 bytes .../site-packages/pytz/zoneinfo/Pacific/Apia | Bin 0 -> 1097 bytes .../pytz/zoneinfo/Pacific/Auckland | Bin 0 -> 2437 bytes .../pytz/zoneinfo/Pacific/Bougainville | Bin 0 -> 268 bytes .../pytz/zoneinfo/Pacific/Chatham | Bin 0 -> 2068 bytes .../site-packages/pytz/zoneinfo/Pacific/Chuuk | Bin 0 -> 269 bytes .../pytz/zoneinfo/Pacific/Easter | Bin 0 -> 2233 bytes .../site-packages/pytz/zoneinfo/Pacific/Efate | Bin 0 -> 466 bytes .../pytz/zoneinfo/Pacific/Enderbury | Bin 0 -> 234 bytes .../pytz/zoneinfo/Pacific/Fakaofo | Bin 0 -> 200 bytes .../site-packages/pytz/zoneinfo/Pacific/Fiji | Bin 0 -> 1078 bytes .../pytz/zoneinfo/Pacific/Funafuti | Bin 0 -> 166 bytes .../pytz/zoneinfo/Pacific/Galapagos | Bin 0 -> 238 bytes .../pytz/zoneinfo/Pacific/Gambier | Bin 0 -> 164 bytes .../pytz/zoneinfo/Pacific/Guadalcanal | Bin 0 -> 166 bytes .../site-packages/pytz/zoneinfo/Pacific/Guam | Bin 0 -> 494 bytes .../pytz/zoneinfo/Pacific/Honolulu | Bin 0 -> 329 bytes .../pytz/zoneinfo/Pacific/Johnston | Bin 0 -> 329 bytes .../pytz/zoneinfo/Pacific/Kiritimati | Bin 0 -> 238 bytes .../pytz/zoneinfo/Pacific/Kosrae | Bin 0 -> 351 bytes .../pytz/zoneinfo/Pacific/Kwajalein | Bin 0 -> 316 bytes .../pytz/zoneinfo/Pacific/Majuro | Bin 0 -> 310 bytes .../pytz/zoneinfo/Pacific/Marquesas | Bin 0 -> 173 bytes .../pytz/zoneinfo/Pacific/Midway | Bin 0 -> 175 bytes .../site-packages/pytz/zoneinfo/Pacific/Nauru | Bin 0 -> 252 bytes .../site-packages/pytz/zoneinfo/Pacific/Niue | Bin 0 -> 241 bytes .../pytz/zoneinfo/Pacific/Norfolk | Bin 0 -> 294 bytes .../pytz/zoneinfo/Pacific/Noumea | Bin 0 -> 304 bytes .../pytz/zoneinfo/Pacific/Pago_Pago | Bin 0 -> 175 bytes .../site-packages/pytz/zoneinfo/Pacific/Palau | Bin 0 -> 180 bytes .../pytz/zoneinfo/Pacific/Pitcairn | Bin 0 -> 202 bytes .../pytz/zoneinfo/Pacific/Pohnpei | Bin 0 -> 303 bytes .../pytz/zoneinfo/Pacific/Ponape | Bin 0 -> 303 bytes .../pytz/zoneinfo/Pacific/Port_Moresby | Bin 0 -> 186 bytes .../pytz/zoneinfo/Pacific/Rarotonga | Bin 0 -> 577 bytes .../pytz/zoneinfo/Pacific/Saipan | Bin 0 -> 494 bytes .../site-packages/pytz/zoneinfo/Pacific/Samoa | Bin 0 -> 175 bytes .../pytz/zoneinfo/Pacific/Tahiti | Bin 0 -> 165 bytes .../pytz/zoneinfo/Pacific/Tarawa | Bin 0 -> 166 bytes .../pytz/zoneinfo/Pacific/Tongatapu | Bin 0 -> 372 bytes .../site-packages/pytz/zoneinfo/Pacific/Truk | Bin 0 -> 269 bytes .../site-packages/pytz/zoneinfo/Pacific/Wake | Bin 0 -> 166 bytes .../pytz/zoneinfo/Pacific/Wallis | Bin 0 -> 166 bytes .../site-packages/pytz/zoneinfo/Pacific/Yap | Bin 0 -> 269 bytes .../site-packages/pytz/zoneinfo/Poland | Bin 0 -> 2654 bytes .../site-packages/pytz/zoneinfo/Portugal | Bin 0 -> 3469 bytes .../python2.7/site-packages/pytz/zoneinfo/ROC | Bin 0 -> 761 bytes .../python2.7/site-packages/pytz/zoneinfo/ROK | Bin 0 -> 493 bytes .../site-packages/pytz/zoneinfo/Singapore | Bin 0 -> 383 bytes .../site-packages/pytz/zoneinfo/Turkey | Bin 0 -> 2143 bytes .../python2.7/site-packages/pytz/zoneinfo/UCT | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/US/Alaska | Bin 0 -> 2371 bytes .../site-packages/pytz/zoneinfo/US/Aleutian | Bin 0 -> 2356 bytes .../site-packages/pytz/zoneinfo/US/Arizona | Bin 0 -> 328 bytes .../site-packages/pytz/zoneinfo/US/Central | Bin 0 -> 3576 bytes .../pytz/zoneinfo/US/East-Indiana | Bin 0 -> 1666 bytes .../site-packages/pytz/zoneinfo/US/Eastern | Bin 0 -> 3536 bytes .../site-packages/pytz/zoneinfo/US/Hawaii | Bin 0 -> 329 bytes .../pytz/zoneinfo/US/Indiana-Starke | Bin 0 -> 2428 bytes .../site-packages/pytz/zoneinfo/US/Michigan | Bin 0 -> 2174 bytes .../site-packages/pytz/zoneinfo/US/Mountain | Bin 0 -> 2444 bytes .../site-packages/pytz/zoneinfo/US/Pacific | Bin 0 -> 2836 bytes .../site-packages/pytz/zoneinfo/US/Samoa | Bin 0 -> 175 bytes .../python2.7/site-packages/pytz/zoneinfo/UTC | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/Universal | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/W-SU | Bin 0 -> 1535 bytes .../python2.7/site-packages/pytz/zoneinfo/WET | Bin 0 -> 1905 bytes .../site-packages/pytz/zoneinfo/Zulu | Bin 0 -> 114 bytes .../site-packages/pytz/zoneinfo/iso3166.tab | 274 + .../site-packages/pytz/zoneinfo/leapseconds | 69 + .../site-packages/pytz/zoneinfo/posixrules | Bin 0 -> 3536 bytes .../site-packages/pytz/zoneinfo/tzdata.zi | 4415 ++ .../site-packages/pytz/zoneinfo/zone.tab | 452 + .../site-packages/pytz/zoneinfo/zone1970.tab | 384 + .../requests-2.22.0.dist-info/INSTALLER | 1 + .../requests-2.22.0.dist-info/LICENSE | 13 + .../requests-2.22.0.dist-info/METADATA | 145 + .../requests-2.22.0.dist-info/RECORD | 42 + .../requests-2.22.0.dist-info/WHEEL | 6 + .../requests-2.22.0.dist-info/top_level.txt | 1 + .../site-packages/requests/__init__.py | 131 + .../site-packages/requests/__version__.py | 14 + .../site-packages/requests/_internal_utils.py | 42 + .../site-packages/requests/adapters.py | 533 + .../python2.7/site-packages/requests/api.py | 158 + .../python2.7/site-packages/requests/auth.py | 305 + .../python2.7/site-packages/requests/certs.py | 18 + .../site-packages/requests/compat.py | 70 + .../site-packages/requests/cookies.py | 549 + .../site-packages/requests/exceptions.py | 126 + .../python2.7/site-packages/requests/help.py | 119 + .../python2.7/site-packages/requests/hooks.py | 34 + .../site-packages/requests/models.py | 953 + .../site-packages/requests/packages.py | 14 + .../site-packages/requests/sessions.py | 770 + .../site-packages/requests/status_codes.py | 120 + .../site-packages/requests/structures.py | 103 + .../python2.7/site-packages/requests/utils.py | 977 + .../site-packages/setuptools/__init__.py | 228 + .../setuptools/_deprecation_warning.py | 7 + .../site-packages/setuptools/archive_util.py | 173 + .../site-packages/setuptools/build_meta.py | 257 + .../site-packages/setuptools/cli-32.exe | Bin 0 -> 65536 bytes .../site-packages/setuptools/cli-64.exe | Bin 0 -> 74752 bytes .../site-packages/setuptools/cli.exe | Bin 0 -> 65536 bytes .../site-packages/setuptools/config.py | 656 + .../site-packages/setuptools/dep_util.py | 23 + .../site-packages/setuptools/depends.py | 186 + .../site-packages/setuptools/dist.py | 1280 + .../site-packages/setuptools/extension.py | 57 + .../setuptools/extern/__init__.py | 73 + .../site-packages/setuptools/glibc.py | 86 + .../site-packages/setuptools/glob.py | 174 + .../site-packages/setuptools/gui-32.exe | Bin 0 -> 65536 bytes .../site-packages/setuptools/gui-64.exe | Bin 0 -> 75264 bytes .../site-packages/setuptools/gui.exe | Bin 0 -> 65536 bytes .../site-packages/setuptools/launch.py | 35 + .../site-packages/setuptools/lib2to3_ex.py | 62 + .../site-packages/setuptools/monkey.py | 179 + .../site-packages/setuptools/msvc.py | 1301 + .../site-packages/setuptools/namespaces.py | 107 + .../site-packages/setuptools/package_index.py | 1136 + .../site-packages/setuptools/pep425tags.py | 319 + .../site-packages/setuptools/py27compat.py | 28 + .../site-packages/setuptools/py31compat.py | 32 + .../site-packages/setuptools/py33compat.py | 59 + .../site-packages/setuptools/sandbox.py | 491 + .../setuptools/script (dev).tmpl | 6 + .../site-packages/setuptools/script.tmpl | 3 + .../site-packages/setuptools/site-patch.py | 74 + .../site-packages/setuptools/ssl_support.py | 260 + .../site-packages/setuptools/unicode_utils.py | 44 + .../site-packages/setuptools/version.py | 6 + .../site-packages/setuptools/wheel.py | 211 + .../setuptools/windows_support.py | 29 + .../simplejson-3.16.0.dist-info/INSTALLER | 1 + .../simplejson-3.16.0.dist-info/METADATA | 64 + .../simplejson-3.16.0.dist-info/RECORD | 86 + .../simplejson-3.16.0.dist-info/WHEEL | 5 + .../simplejson-3.16.0.dist-info/top_level.txt | 1 + .../site-packages/simplejson/__init__.py | 577 + .../site-packages/simplejson/_speedups.so | Bin 0 -> 48392 bytes .../site-packages/simplejson/compat.py | 34 + .../site-packages/simplejson/decoder.py | 400 + .../site-packages/simplejson/encoder.py | 722 + .../site-packages/simplejson/errors.py | 53 + .../site-packages/simplejson/ordered_dict.py | 103 + .../site-packages/simplejson/raw_json.py | 9 + .../site-packages/simplejson/scanner.py | 85 + .../simplejson/tests/__init__.py | 74 + .../simplejson/tests/test_bigint_as_string.py | 67 + .../tests/test_bitsize_int_as_string.py | 73 + .../simplejson/tests/test_check_circular.py | 30 + .../simplejson/tests/test_decimal.py | 71 + .../simplejson/tests/test_decode.py | 119 + .../simplejson/tests/test_default.py | 9 + .../simplejson/tests/test_dump.py | 249 + .../tests/test_encode_basestring_ascii.py | 47 + .../simplejson/tests/test_encode_for_html.py | 38 + .../simplejson/tests/test_errors.py | 68 + .../simplejson/tests/test_fail.py | 176 + .../simplejson/tests/test_float.py | 35 + .../simplejson/tests/test_for_json.py | 97 + .../simplejson/tests/test_indent.py | 86 + .../simplejson/tests/test_item_sort_key.py | 27 + .../simplejson/tests/test_iterable.py | 31 + .../simplejson/tests/test_namedtuple.py | 122 + .../simplejson/tests/test_pass1.py | 71 + .../simplejson/tests/test_pass2.py | 14 + .../simplejson/tests/test_pass3.py | 20 + .../simplejson/tests/test_raw_json.py | 47 + .../simplejson/tests/test_recursion.py | 67 + .../simplejson/tests/test_scanstring.py | 196 + .../simplejson/tests/test_separators.py | 42 + .../simplejson/tests/test_speedups.py | 114 + .../simplejson/tests/test_str_subclass.py | 21 + .../simplejson/tests/test_subclass.py | 37 + .../simplejson/tests/test_tool.py | 114 + .../simplejson/tests/test_tuple.py | 47 + .../simplejson/tests/test_unicode.py | 154 + .../site-packages/simplejson/tool.py | 42 + .../six-1.12.0.dist-info/INSTALLER | 1 + .../six-1.12.0.dist-info/LICENSE | 18 + .../six-1.12.0.dist-info/METADATA | 52 + .../site-packages/six-1.12.0.dist-info/RECORD | 8 + .../site-packages/six-1.12.0.dist-info/WHEEL | 6 + .../six-1.12.0.dist-info/top_level.txt | 1 + venv/lib/python2.7/site-packages/six.py | 952 + .../site-packages/slugify/__init__.py | 6 + .../site-packages/slugify/slugify.py | 188 + .../DESCRIPTION.rst | 46 + .../text_unidecode-1.3.dist-info/INSTALLER | 1 + .../text_unidecode-1.3.dist-info/LICENSE.txt | 134 + .../text_unidecode-1.3.dist-info/METADATA | 73 + .../text_unidecode-1.3.dist-info/RECORD | 11 + .../text_unidecode-1.3.dist-info/WHEEL | 6 + .../metadata.json | 1 + .../top_level.txt | 1 + .../site-packages/text_unidecode/__init__.py | 21 + .../site-packages/text_unidecode/data.bin | Bin 0 -> 311077 bytes .../urllib3-1.25.6.dist-info/INSTALLER | 1 + .../urllib3-1.25.6.dist-info/LICENSE.txt | 21 + .../urllib3-1.25.6.dist-info/METADATA | 1243 + .../urllib3-1.25.6.dist-info/RECORD | 78 + .../urllib3-1.25.6.dist-info/WHEEL | 6 + .../urllib3-1.25.6.dist-info/top_level.txt | 1 + .../site-packages/urllib3/__init__.py | 86 + .../site-packages/urllib3/_collections.py | 336 + .../site-packages/urllib3/connection.py | 448 + .../site-packages/urllib3/connectionpool.py | 1051 + .../site-packages/urllib3/contrib/__init__.py | 0 .../urllib3/contrib/_appengine_environ.py | 32 + .../contrib/_securetransport/__init__.py | 0 .../contrib/_securetransport/bindings.py | 492 + .../contrib/_securetransport/low_level.py | 328 + .../urllib3/contrib/appengine.py | 321 + .../site-packages/urllib3/contrib/ntlmpool.py | 123 + .../urllib3/contrib/pyopenssl.py | 498 + .../urllib3/contrib/securetransport.py | 870 + .../site-packages/urllib3/contrib/socks.py | 210 + .../site-packages/urllib3/exceptions.py | 255 + .../python2.7/site-packages/urllib3/fields.py | 273 + .../site-packages/urllib3/filepost.py | 98 + .../urllib3/packages/__init__.py | 5 + .../urllib3/packages/backports/__init__.py | 0 .../urllib3/packages/backports/makefile.py | 52 + .../site-packages/urllib3/packages/six.py | 1021 + .../packages/ssl_match_hostname/__init__.py | 19 + .../ssl_match_hostname/_implementation.py | 162 + .../site-packages/urllib3/poolmanager.py | 470 + .../site-packages/urllib3/request.py | 171 + .../site-packages/urllib3/response.py | 809 + .../site-packages/urllib3/util/__init__.py | 46 + .../site-packages/urllib3/util/connection.py | 138 + .../site-packages/urllib3/util/queue.py | 21 + .../site-packages/urllib3/util/request.py | 135 + .../site-packages/urllib3/util/response.py | 86 + .../site-packages/urllib3/util/retry.py | 450 + .../site-packages/urllib3/util/ssl_.py | 407 + .../site-packages/urllib3/util/timeout.py | 258 + .../site-packages/urllib3/util/url.py | 439 + .../site-packages/urllib3/util/wait.py | 153 + .../site-packages/werkzeug/__init__.py | 221 + .../site-packages/werkzeug/_compat.py | 219 + .../site-packages/werkzeug/_internal.py | 484 + .../site-packages/werkzeug/_reloader.py | 341 + .../werkzeug/contrib/__init__.py | 16 + .../site-packages/werkzeug/contrib/atom.py | 362 + .../site-packages/werkzeug/contrib/cache.py | 933 + .../site-packages/werkzeug/contrib/fixers.py | 262 + .../site-packages/werkzeug/contrib/iterio.py | 358 + .../site-packages/werkzeug/contrib/lint.py | 11 + .../werkzeug/contrib/profiler.py | 42 + .../werkzeug/contrib/securecookie.py | 362 + .../werkzeug/contrib/sessions.py | 389 + .../werkzeug/contrib/wrappers.py | 385 + .../site-packages/werkzeug/datastructures.py | 2852 + .../site-packages/werkzeug/debug/__init__.py | 524 + .../site-packages/werkzeug/debug/console.py | 216 + .../site-packages/werkzeug/debug/repr.py | 297 + .../werkzeug/debug/shared/FONT_LICENSE | 96 + .../werkzeug/debug/shared/console.png | Bin 0 -> 507 bytes .../werkzeug/debug/shared/debugger.js | 210 + .../werkzeug/debug/shared/jquery.js | 2 + .../werkzeug/debug/shared/less.png | Bin 0 -> 191 bytes .../werkzeug/debug/shared/more.png | Bin 0 -> 200 bytes .../werkzeug/debug/shared/source.png | Bin 0 -> 818 bytes .../werkzeug/debug/shared/style.css | 154 + .../werkzeug/debug/shared/ubuntu.ttf | Bin 0 -> 70220 bytes .../site-packages/werkzeug/debug/tbtools.py | 629 + .../site-packages/werkzeug/exceptions.py | 779 + .../site-packages/werkzeug/filesystem.py | 64 + .../site-packages/werkzeug/formparser.py | 584 + .../python2.7/site-packages/werkzeug/http.py | 1259 + .../python2.7/site-packages/werkzeug/local.py | 421 + .../werkzeug/middleware/__init__.py | 25 + .../werkzeug/middleware/dispatcher.py | 66 + .../werkzeug/middleware/http_proxy.py | 219 + .../site-packages/werkzeug/middleware/lint.py | 408 + .../werkzeug/middleware/profiler.py | 132 + .../werkzeug/middleware/proxy_fix.py | 232 + .../werkzeug/middleware/shared_data.py | 253 + .../site-packages/werkzeug/posixemulation.py | 117 + .../site-packages/werkzeug/routing.py | 2039 + .../site-packages/werkzeug/security.py | 249 + .../site-packages/werkzeug/serving.py | 1075 + .../python2.7/site-packages/werkzeug/test.py | 1146 + .../site-packages/werkzeug/testapp.py | 241 + .../python2.7/site-packages/werkzeug/urls.py | 1138 + .../site-packages/werkzeug/useragents.py | 210 + .../python2.7/site-packages/werkzeug/utils.py | 774 + .../werkzeug/wrappers/__init__.py | 36 + .../site-packages/werkzeug/wrappers/accept.py | 50 + .../site-packages/werkzeug/wrappers/auth.py | 33 + .../werkzeug/wrappers/base_request.py | 695 + .../werkzeug/wrappers/base_response.py | 702 + .../werkzeug/wrappers/common_descriptors.py | 322 + .../site-packages/werkzeug/wrappers/etag.py | 304 + .../site-packages/werkzeug/wrappers/json.py | 145 + .../werkzeug/wrappers/request.py | 44 + .../werkzeug/wrappers/response.py | 78 + .../werkzeug/wrappers/user_agent.py | 14 + .../python2.7/site-packages/werkzeug/wsgi.py | 1013 + .../python2.7/site-packages/wheel/__init__.py | 2 + .../python2.7/site-packages/wheel/__main__.py | 19 + .../site-packages/wheel/bdist_wheel.py | 372 + .../site-packages/wheel/cli/__init__.py | 88 + .../site-packages/wheel/cli/convert.py | 269 + .../python2.7/site-packages/wheel/cli/pack.py | 58 + .../site-packages/wheel/cli/unpack.py | 25 + .../python2.7/site-packages/wheel/metadata.py | 141 + .../site-packages/wheel/pep425tags.py | 190 + .../python2.7/site-packages/wheel/pkginfo.py | 43 + .../lib/python2.7/site-packages/wheel/util.py | 46 + .../site-packages/wheel/wheelfile.py | 169 + venv/lib/python2.7/site.py | 851 + venv/lib/python2.7/sre.py | 1 + venv/lib/python2.7/sre_compile.py | 1 + venv/lib/python2.7/sre_constants.py | 1 + venv/lib/python2.7/sre_parse.py | 1 + venv/lib/python2.7/stat.py | 1 + venv/lib/python2.7/types.py | 1 + venv/lib/python2.7/warnings.py | 1 + 2556 files changed, 515164 insertions(+), 4 deletions(-) create mode 100644 ckanext/scheming/fanstatic/scheming-upload.js create mode 100644 ckanext/scheming/templates/base.html create mode 120000 venv/.Python create mode 100644 venv/bin/activate create mode 100644 venv/bin/activate.csh create mode 100644 venv/bin/activate.fish create mode 100644 venv/bin/activate.ps1 create mode 100644 venv/bin/activate_this.py create mode 100755 venv/bin/chardetect create mode 100755 venv/bin/ckan-admin create mode 100755 venv/bin/ckanapi create mode 100755 venv/bin/easy_install create mode 100755 venv/bin/easy_install-2.7 create mode 100755 venv/bin/pip create mode 100755 venv/bin/pip2 create mode 100755 venv/bin/pip2.7 create mode 100755 venv/bin/python create mode 100755 venv/bin/python-config create mode 120000 venv/bin/python2 create mode 120000 venv/bin/python2.7 create mode 100755 venv/bin/slugify create mode 100755 venv/bin/wheel create mode 120000 venv/lib/python2.7/LICENSE.txt create mode 120000 venv/lib/python2.7/UserDict.py create mode 120000 venv/lib/python2.7/_abcoll.py create mode 120000 venv/lib/python2.7/_weakrefset.py create mode 120000 venv/lib/python2.7/abc.py create mode 120000 venv/lib/python2.7/codecs.py create mode 120000 venv/lib/python2.7/config create mode 120000 venv/lib/python2.7/copy_reg.py create mode 100644 venv/lib/python2.7/distutils/__init__.py create mode 100644 venv/lib/python2.7/distutils/distutils.cfg create mode 120000 venv/lib/python2.7/encodings create mode 120000 venv/lib/python2.7/fnmatch.py create mode 120000 venv/lib/python2.7/genericpath.py create mode 120000 venv/lib/python2.7/lib-dynload create mode 120000 venv/lib/python2.7/linecache.py create mode 120000 venv/lib/python2.7/locale.py create mode 100644 venv/lib/python2.7/no-global-site-packages.txt create mode 120000 venv/lib/python2.7/ntpath.py create mode 100644 venv/lib/python2.7/orig-prefix.txt create mode 120000 venv/lib/python2.7/os.py create mode 120000 venv/lib/python2.7/posixpath.py create mode 120000 venv/lib/python2.7/re.py create mode 100644 venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/LICENSE.rst create mode 100644 venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/DESCRIPTION.rst create mode 100644 venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/metadata.json create mode 100644 venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/certifi/__init__.py create mode 100644 venv/lib/python2.7/site-packages/certifi/__main__.py create mode 100644 venv/lib/python2.7/site-packages/certifi/cacert.pem create mode 100644 venv/lib/python2.7/site-packages/certifi/core.py create mode 100644 venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/DESCRIPTION.rst create mode 100644 venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/entry_points.txt create mode 100644 venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/metadata.json create mode 100644 venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/chardet/__init__.py create mode 100644 venv/lib/python2.7/site-packages/chardet/big5freq.py create mode 100644 venv/lib/python2.7/site-packages/chardet/big5prober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/chardistribution.py create mode 100644 venv/lib/python2.7/site-packages/chardet/charsetgroupprober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/charsetprober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/cli/__init__.py create mode 100644 venv/lib/python2.7/site-packages/chardet/cli/chardetect.py create mode 100644 venv/lib/python2.7/site-packages/chardet/codingstatemachine.py create mode 100644 venv/lib/python2.7/site-packages/chardet/compat.py create mode 100644 venv/lib/python2.7/site-packages/chardet/cp949prober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/enums.py create mode 100644 venv/lib/python2.7/site-packages/chardet/escprober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/escsm.py create mode 100644 venv/lib/python2.7/site-packages/chardet/eucjpprober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/euckrfreq.py create mode 100644 venv/lib/python2.7/site-packages/chardet/euckrprober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/euctwfreq.py create mode 100644 venv/lib/python2.7/site-packages/chardet/euctwprober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/gb2312freq.py create mode 100644 venv/lib/python2.7/site-packages/chardet/gb2312prober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/hebrewprober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/jisfreq.py create mode 100644 venv/lib/python2.7/site-packages/chardet/jpcntx.py create mode 100644 venv/lib/python2.7/site-packages/chardet/langbulgarianmodel.py create mode 100644 venv/lib/python2.7/site-packages/chardet/langcyrillicmodel.py create mode 100644 venv/lib/python2.7/site-packages/chardet/langgreekmodel.py create mode 100644 venv/lib/python2.7/site-packages/chardet/langhebrewmodel.py create mode 100644 venv/lib/python2.7/site-packages/chardet/langhungarianmodel.py create mode 100644 venv/lib/python2.7/site-packages/chardet/langthaimodel.py create mode 100644 venv/lib/python2.7/site-packages/chardet/langturkishmodel.py create mode 100644 venv/lib/python2.7/site-packages/chardet/latin1prober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/mbcharsetprober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/mbcsgroupprober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/mbcssm.py create mode 100644 venv/lib/python2.7/site-packages/chardet/sbcharsetprober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/sbcsgroupprober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/sjisprober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/universaldetector.py create mode 100644 venv/lib/python2.7/site-packages/chardet/utf8prober.py create mode 100644 venv/lib/python2.7/site-packages/chardet/version.py create mode 100644 venv/lib/python2.7/site-packages/ckan-2.8.3-py2.7-nspkg.pth create mode 100644 venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/entry_points.txt create mode 100644 venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/namespace_packages.txt create mode 100644 venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/ckan/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/authz.py create mode 100644 venv/lib/python2.7/site-packages/ckan/ckan_nose_plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckan/common.py create mode 100644 venv/lib/python2.7/site-packages/ckan/config/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/config/deployment.ini_tmpl create mode 100644 venv/lib/python2.7/site-packages/ckan/config/environment.py create mode 100644 venv/lib/python2.7/site-packages/ckan/config/install.py create mode 100644 venv/lib/python2.7/site-packages/ckan/config/middleware/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/config/middleware/common_middleware.py create mode 100644 venv/lib/python2.7/site-packages/ckan/config/middleware/flask_app.py create mode 100644 venv/lib/python2.7/site-packages/ckan/config/middleware/pylons_app.py create mode 100644 venv/lib/python2.7/site-packages/ckan/config/resource_formats.json create mode 100644 venv/lib/python2.7/site-packages/ckan/config/routing.py create mode 100644 venv/lib/python2.7/site-packages/ckan/config/solr/schema.xml create mode 100644 venv/lib/python2.7/site-packages/ckan/config/who.ini create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/admin.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/api.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/error.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/feed.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/group.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/home.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/organization.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/package.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/revision.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/storage.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/tag.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/template.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/user.py create mode 100644 venv/lib/python2.7/site-packages/ckan/controllers/util.py create mode 100644 venv/lib/python2.7/site-packages/ckan/exceptions.py create mode 100644 venv/lib/python2.7/site-packages/ckan/i18n/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/i18n/check_po_files.py create mode 100644 venv/lib/python2.7/site-packages/ckan/i18n/ckan.pot create mode 100644 venv/lib/python2.7/site-packages/ckan/include/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/include/rcssmin.py create mode 100644 venv/lib/python2.7/site-packages/ckan/include/rjsmin.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/activity_streams.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/activity_streams_session_extension.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/alphabet_paginate.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/app_globals.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/auth_tkt.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/authenticator.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/base.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/captcha.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/cli.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/config_tool.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/create_test_data.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/datapreview.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/dictization/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/dictization/model_dictize.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/dictization/model_save.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/email_notifications.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/extract.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/fanstatic_extensions.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/fanstatic_resources.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/formatters.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/hash.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/helpers.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/i18n.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/io.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/jinja_extensions.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/jobs.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/jsonp.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/lazyjson.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/mailer.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/maintain.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/munge.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/navl/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/navl/dictization_functions.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/navl/validators.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/plugins.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/redis.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/render.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/search/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/search/common.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/search/index.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/search/query.py create mode 100644 venv/lib/python2.7/site-packages/ckan/lib/uploader.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/action/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/action/create.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/action/delete.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/action/get.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/action/patch.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/action/update.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/auth/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/auth/create.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/auth/delete.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/auth/get.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/auth/patch.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/auth/update.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/converters.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/schema.py create mode 100644 venv/lib/python2.7/site-packages/ckan/logic/validators.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/README create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/manage.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/migrate.cfg create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/001_add_existing_tables.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/002_add_author_and_maintainer.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/003_add_user_object.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/004_add_group_object.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/005_add_authorization_tables.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/006_add_ratings.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/007_add_system_roles.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/008_update_vdm_ids.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/009_add_creation_timestamps.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/010_add_user_about.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/011_add_package_search_vector.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/012_add_resources.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/013_add_hash.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/014_hash_2.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/015_remove_state_object.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/016_uuids_everywhere.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/017_add_pkg_relationships.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/018_adjust_licenses.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/019_pkg_relationships_state.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/020_add_changeset.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/021_postgres_downgrade.sql create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/021_postgres_upgrade.sql create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/021_postgresql_downgrade.sql create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/021_postgresql_upgrade.sql create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/022_add_group_extras.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/023_add_harvesting.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/024_add_harvested_document.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/025_add_authorization_groups.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/026_authorization_group_user_pk.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/027_adjust_harvester.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/028_drop_harvest_source_status.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/029_version_groups.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/030_additional_user_attributes.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/031_move_openid_to_new_field.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/032_add_extra_info_field_to_resources.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/033_auth_group_user_id_add_conditional.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/034_resource_group_table.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/035_harvesting_doc_versioning.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/036_lockdown_roles.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/037_role_anon_editor.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/038_delete_migration_tables.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/039_add_expired_id_and_dates.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/040_reset_key_on_user.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/041_resource_new_fields.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/042_user_revision_indexes.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/043_drop_postgres_search.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/044_add_task_status.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/045_user_name_unique.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/046_drop_changesets.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/047_rename_package_group_member.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/048_add_activity_streams_tables.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/049_add_group_approval_status.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/050_term_translation_table.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/051_add_tag_vocabulary.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/052_update_member_capacities.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/053_add_group_logo.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/054_add_resource_created_date.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/055_update_user_and_activity_detail.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/056_add_related_table.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/057_tracking.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/058_add_follower_tables.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/059_add_related_count_and_flag.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/060_add_system_info_table.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/061_add_follower__group_table.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/062_add_dashboard_table.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/063_org_changes.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/064_add_email_last_sent_column.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/065_add_email_notifications_preference.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/066_default_package_type.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/067_turn_extras_to_strings.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/068_add_package_extras_index.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/069_resource_url_and_metadata_modified.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/070_add_activity_and_resource_indexes.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/071_add_state_column_to_user_table.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/072_add_resource_view.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/073_update_resource_view_resource_id_constraint.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/074_remove_resource_groups.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/075_rename_view_plugins.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/076_rename_view_plugins_2.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/077_add_revisions_to_system_info.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/078_remove_old_authz_model.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/079_resource_revision_index.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/080_continuity_id_indexes.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/081_set_datastore_active.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/082_create_index_creator_user_id.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/083_remove_related_items.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/084_add_metadata_created.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/085_adjust_activity_timestamps.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/086_drop_openid_column.py create mode 100644 venv/lib/python2.7/site-packages/ckan/migration/versions/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/activity.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/core.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/dashboard.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/domain_object.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/extension.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/follower.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/group.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/group_extra.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/license.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/meta.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/misc.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/modification.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/package.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/package_extra.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/package_relationship.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/rating.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/resource.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/resource_view.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/system_info.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/tag.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/task_status.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/term_translation.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/tracking.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/types.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/user.py create mode 100644 venv/lib/python2.7/site-packages/ckan/model/vocabulary.py create mode 100644 venv/lib/python2.7/site-packages/ckan/pastertemplates/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/plugins/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/plugins/core.py create mode 100644 venv/lib/python2.7/site-packages/ckan/plugins/interfaces.py create mode 100644 venv/lib/python2.7/site-packages/ckan/plugins/toolkit.py create mode 100644 venv/lib/python2.7/site-packages/ckan/plugins/toolkit_sphinx_extension.py create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/activity-stream.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/activity-stream.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/api-info.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/api-info.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/autocomplete.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/autocomplete.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/basic-form.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/basic-form.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/confirm-action.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/confirm-action.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/custom-fields.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/custom-fields.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dashboard.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dashboard.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/data-viewer.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/data-viewer.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dataset-visibility.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dataset-visibility.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/follow.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/follow.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/followers-counter.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/followers-counter.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/image-upload.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/image-upload.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/media-grid.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/media-grid.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/popover-context.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/popover-context.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-form.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-form.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-reorder.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-reorder.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-upload-field.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-upload-field.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-embed.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-embed.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters-form.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters-form.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-reorder.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-reorder.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/select-switch.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/select-switch.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/slug-preview.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/slug-preview.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-selectable-rows.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-selectable-rows.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-toggle-more.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-toggle-more.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.date-helpers.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.date-helpers.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.form-warning.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.form-warning.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.images-loaded.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.images-loaded.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.inherit.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.inherit.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.masonry.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.masonry.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.proxy-all.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.proxy-all.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.slug-preview.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.slug-preview.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.slug.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.slug.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.truncator.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.truncator.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.url-helpers.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.url-helpers.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/autocomplete.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/autocomplete.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/basic-form.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/basic-form.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/confirm-action.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/confirm-action.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/custom-fields.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/custom-fields.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/followers-counter.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/followers-counter.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/image-upload.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/image-upload.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-form.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-form.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-upload-field.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-upload-field.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.date-helpers.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.date-helpers.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.form-warning.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.form-warning.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.inherit.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.inherit.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.proxy-all.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.proxy-all.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug-preview.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug-preview.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.url-helpers.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.url-helpers.spec.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.svg create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/js/bootstrap.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/js/bootstrap.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/alerts.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/badges.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/bootstrap.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/breadcrumbs.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/button-groups.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/buttons.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/carousel.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/close.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/code.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/component-animations.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/dropdowns.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/forms.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/glyphicons.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/grid.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/input-groups.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/jumbotron.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/labels.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/list-group.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/media.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/alerts.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/background-variant.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/border-radius.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/buttons.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/center-block.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/clearfix.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/forms.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/gradients.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/grid-framework.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/grid.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/hide-text.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/image.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/labels.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/list-group.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/nav-divider.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/nav-vertical-align.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/opacity.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/pagination.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/panels.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/progress-bar.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/reset-filter.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/reset-text.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/resize.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/responsive-visibility.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/size.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/tab-focus.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/table-row.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/text-emphasis.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/text-overflow.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/vendor-prefixes.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/modals.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/navbar.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/navs.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/normalize.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/pager.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/pagination.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/panels.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/popovers.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/print.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/progress-bars.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/responsive-embed.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/responsive-utilities.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/scaffolding.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/tables.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/theme.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/thumbnails.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/tooltip.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/type.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/utilities.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/variables.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/wells.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/css/font-awesome.css create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/css/font-awesome.css.map create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/css/font-awesome.min.css create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/FontAwesome.otf create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.eot create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.svg create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.ttf create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.woff create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.woff2 create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/animated.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/bordered-pulled.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/core.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/fixed-width.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/font-awesome.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/icons.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/larger.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/list.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/mixins.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/path.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/rotated-flipped.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/screen-reader.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/stacked.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/variables.less create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.fileupload-ui.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.fileupload-ui.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.fileupload.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.fileupload.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.iframe-transport.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.iframe-transport.min.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/.gitignore create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/CONTRIBUTING.md create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/README.md create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/bower.json create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/component.json create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/composer.json create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/package.json create mode 100755 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/release.sh create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2-bootstrap.css create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2-spinner.gif create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.css create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.jquery.json create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.png create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ar.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_az.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_bg.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ca.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_cs.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_da.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_de.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_es.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_fa.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_fr.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_he.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_hu.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_it.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ja.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ka.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_lt.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_nb.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_nl.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_pt-PT.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ro.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_rs.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_sk.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_sv.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_tr.js create mode 100644 venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_uk.js create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/activity_streams/activity_stream_email_notifications.text create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/admin/base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/admin/config.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/admin/confirm_reset.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/admin/index.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/admin/trash.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/ajax_snippets/custom_fields.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/ajax_snippets/follow_button.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/dataviewer/base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/development/markup.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/development/primer.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/emails/invite_user.txt create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/emails/invite_user_subject.txt create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/emails/reset_password.txt create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/emails/reset_password_subject.txt create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/error_document_template.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/footer.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/about.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/activity_stream.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/admins.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/base_form_page.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/confirm_delete.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/confirm_delete_member.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/edit.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/edit_base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/followers.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/history.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/index.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/member_new.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/members.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/new.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/new_group_form.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/read.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/group/read_base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/header.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/home/about.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/home/index.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/home/layout1.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/home/layout2.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/home/layout3.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/macros/autoform.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/macros/form.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/about.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/activity_stream.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/admins.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/base_form_page.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/bulk_process.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/confirm_delete.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/confirm_delete_member.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/edit.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/edit_base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/index.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/member_new.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/members.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/new.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/new_organization_form.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/read.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/organization/read_base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/activity.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/base_form_page.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/confirm_delete.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/confirm_delete_resource.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/edit.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/edit_base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/edit_view.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/followers.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/group_list.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/history.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/new.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/new_package_form.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/new_resource.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/new_resource_not_draft.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/new_view.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/read.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/read_base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/resource_edit.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/resource_edit_base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/resource_read.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/resource_views.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/resources.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/search.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/package/view_edit_base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/page.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/revision/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/revision/diff.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/revision/list.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/revision/read.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/revision/read_base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/robots.txt create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/activity_item.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/add_dataset.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/additional_info.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/context.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/custom_form_fields.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/datapusher_status.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/debug.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/disqus_trackback.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/facet_list.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/follow_button.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/group.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/group_item.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/home_breadcrumb_item.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/language_selector.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/license.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/local_friendly_datetime.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/organization.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/organization_item.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/package_grid.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/package_item.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/package_list.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/popular.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/private.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/search_form.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/search_result_text.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/simple_search.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/social.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/sort_by.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/subscribe.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/snippets/tag_list.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/tag/index.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/tests/broken_helper_as_attribute.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/tests/broken_helper_as_item.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/tests/flash_messages.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/tests/helper_as_attribute.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/tests/helper_as_item.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/tests/mock_json_resource_preview_template.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/tests/mock_resource_preview_template.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/activity_stream.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/dashboard.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/dashboard_datasets.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/dashboard_groups.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/dashboard_organizations.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/edit.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/edit_base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/edit_user_form.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/followers.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/list.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/login.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/logout.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/logout_first.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/new.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/new_user_form.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/perform_reset.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/read.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/read_base.html create mode 100644 venv/lib/python2.7/site-packages/ckan/templates/user/request_reset.html create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/config/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/config/test_environment.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/config/test_middleware.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/config/test_sessions.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/controllers/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/controllers/test_admin.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/controllers/test_api.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/controllers/test_feed.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/controllers/test_group.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/controllers/test_home.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/controllers/test_organization.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/controllers/test_package.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/controllers/test_tags.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/controllers/test_template.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/controllers/test_user.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/controllers/test_util.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/factories.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/helpers.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/i18n/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/i18n/test_check_po_files.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/ckantestplugins.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_group.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_package.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_ratings.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_revisions.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_tag.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_vocabulary.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/html_check.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/mock_mail_server.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/mock_plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/pylons_controller.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/test_coding_standards.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/test_plugins.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/legacy/test_versions.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/test_app_globals.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/test_auth_tkt.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/test_base.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/test_cli.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/test_config_tool.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/test_datapreview.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/test_helpers.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/test_i18n.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/test_io.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/test_jobs.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/test_mailer.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/test_munge.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/lib/test_navl.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/logic/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/logic/test_conversion.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/logic/test_converters.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/logic/test_schema.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/logic/test_validators.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/migration/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/model/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/model/test_license.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/model/test_resource.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/model/test_resource_view.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/model/test_system_info.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/model/test_user.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/plugins/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/plugins/test_toolkit.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/test_authz.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/test_coding_standards.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/test_common.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/test_factories.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/test_none_root.py create mode 100644 venv/lib/python2.7/site-packages/ckan/tests/test_robots_txt.py create mode 100644 venv/lib/python2.7/site-packages/ckan/views/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckan/views/admin.py create mode 100644 venv/lib/python2.7/site-packages/ckan/views/api.py create mode 100644 venv/lib/python2.7/site-packages/ckan/views/dashboard.py create mode 100644 venv/lib/python2.7/site-packages/ckan/views/feed.py create mode 100644 venv/lib/python2.7/site-packages/ckan/views/home.py create mode 100644 venv/lib/python2.7/site-packages/ckan/views/user.py create mode 100644 venv/lib/python2.7/site-packages/ckan/websetup.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi-4.3.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/ckanapi-4.3.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/ckanapi-4.3.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/ckanapi-4.3.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/ckanapi-4.3.dist-info/entry_points.txt create mode 100644 venv/lib/python2.7/site-packages/ckanapi-4.3.dist-info/pbr.json create mode 100644 venv/lib/python2.7/site-packages/ckanapi-4.3.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/ckanapi/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/cli/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/cli/action.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/cli/delete.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/cli/dump.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/cli/load.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/cli/main.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/cli/paster.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/cli/utils.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/cli/workers.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/common.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/datapackage.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/errors.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/localckan.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/remoteckan.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/testappckan.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/tests/mock/mock_ckan.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/tests/test_call.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/tests/test_cli_action.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/tests/test_cli_dump.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/tests/test_cli_load.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/tests/test_cli_workers.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/tests/test_datapackage.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/tests/test_remote.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/tests/test_testapp.py create mode 100644 venv/lib/python2.7/site-packages/ckanapi/version.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/cli.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/helpers.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/interfaces.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/logic/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/logic/action.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/logic/auth.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/logic/schema.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/templates/datapusher/resource_data.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/templates/package/resource_edit_base.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/tests/test.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/tests/test_action.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/tests/test_default_views.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datapusher/tests/test_interfaces.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/backend/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/backend/postgres.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/commands.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/controller.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/helpers.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/interfaces.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/logic/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/logic/action.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/logic/auth.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/logic/schema.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/set_permissions.sql create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/templates/ajax_snippets/api_info.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/templates/datastore/dictionary.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/templates/datastore/snippets/dictionary_form.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/templates/package/resource_edit_base.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/templates/package/resource_read.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/templates/package/snippets/data_api_button.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/templates/package/snippets/dictionary_table.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/helpers.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/sample_datastore_plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_chained_action.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_chained_auth_functions.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_configure.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_create.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_db.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_delete.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_disable.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_dump.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_helpers.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_info.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_interface.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_search.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_unit.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/tests/test_upsert.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datastore/writer.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/controller.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/datatablesview.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/datatablesview.min.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/resource.config create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/css/bootstrap-theme.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/css/bootstrap-theme.css.map create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/css/bootstrap.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/css/bootstrap.css.map create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/fonts/glyphicons-halflings-regular.eot create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/fonts/glyphicons-halflings-regular.svg create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/fonts/glyphicons-halflings-regular.ttf create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/fonts/glyphicons-halflings-regular.woff create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/fonts/glyphicons-halflings-regular.woff2 create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/js/bootstrap.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/js/npm.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/buttons.bootstrap.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/buttons.dataTables.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/buttons.foundation.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/buttons.jqueryui.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/buttons.semanticui.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/common.scss create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/mixins.scss create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/js/buttons.bootstrap.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/js/buttons.colVis.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/js/buttons.foundation.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/js/buttons.jqueryui.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/js/buttons.semanticui.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/js/dataTables.buttons.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Buttons-1.3.1/swf/flashExport.swf create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/css/dataTables.bootstrap.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/css/dataTables.foundation.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/css/dataTables.jqueryui.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/css/dataTables.semanticui.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/css/jquery.dataTables.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/css/jquery.dataTables_themeroller.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/images/sort_asc.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/images/sort_asc_disabled.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/images/sort_both.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/images/sort_desc.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/images/sort_desc_disabled.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/js/dataTables.bootstrap.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/js/dataTables.foundation.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/js/dataTables.jqueryui.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/js/dataTables.semanticui.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/DataTables-1.10.15/js/jquery.dataTables.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/FixedColumns-3.2.2/css/fixedColumns.bootstrap.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/FixedColumns-3.2.2/css/fixedColumns.dataTables.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/FixedColumns-3.2.2/css/fixedColumns.foundation.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/FixedColumns-3.2.2/css/fixedColumns.jqueryui.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/FixedColumns-3.2.2/js/dataTables.fixedColumns.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/FixedHeader-3.1.2/css/fixedHeader.bootstrap.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/FixedHeader-3.1.2/css/fixedHeader.dataTables.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/FixedHeader-3.1.2/css/fixedHeader.foundation.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/FixedHeader-3.1.2/css/fixedHeader.jqueryui.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/FixedHeader-3.1.2/js/dataTables.fixedHeader.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/KeyTable-2.2.1/css/keyTable.bootstrap.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/KeyTable-2.2.1/css/keyTable.dataTables.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/KeyTable-2.2.1/css/keyTable.foundation.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/KeyTable-2.2.1/css/keyTable.jqueryui.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/KeyTable-2.2.1/css/keyTable.semanticui.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/KeyTable-2.2.1/js/dataTables.keyTable.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Responsive-2.1.1/css/responsive.bootstrap.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Responsive-2.1.1/css/responsive.dataTables.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Responsive-2.1.1/css/responsive.foundation.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Responsive-2.1.1/css/responsive.jqueryui.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Responsive-2.1.1/js/dataTables.responsive.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Responsive-2.1.1/js/responsive.bootstrap.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Responsive-2.1.1/js/responsive.foundation.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Responsive-2.1.1/js/responsive.jqueryui.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Select-1.2.2/css/select.bootstrap.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Select-1.2.2/css/select.dataTables.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Select-1.2.2/css/select.foundation.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Select-1.2.2/css/select.jqueryui.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Select-1.2.2/css/select.semanticui.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/Select-1.2.2/js/dataTables.select.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/datatables.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/public/vendor/datatables.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/templates/datatables/datatables_form.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/datatablesview/templates/datatables/datatables_view.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_flask_iblueprint/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_flask_iblueprint/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_flask_iblueprint/templates/about.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_flask_iblueprint/templates/about_base.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_flask_iblueprint/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_flask_iblueprint/tests/test_routes.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_flask_streaming/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_flask_streaming/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_flask_streaming/templates/stream.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_flask_streaming/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_flask_streaming/tests/test_streaming_responses.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iauthfunctions/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iauthfunctions/plugin_v1.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iauthfunctions/plugin_v2.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iauthfunctions/plugin_v3.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iauthfunctions/plugin_v4.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iauthfunctions/plugin_v5_custom_config_setting.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iauthfunctions/plugin_v6_parent_auth_functions.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iconfigurer/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iconfigurer/controller.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iconfigurer/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iconfigurer/plugin_v1.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iconfigurer/plugin_v2.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iconfigurer/templates/admin/config.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iconfigurer/templates/admin/myext_config.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iconfigurer/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iconfigurer/tests/test_example_iconfigurer.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iconfigurer/tests/test_iconfigurer_toolkit.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iconfigurer/tests/test_iconfigurer_update_config.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/plugin_v1.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/plugin_v2.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/plugin_v3.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/plugin_v4.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/templates/package/read.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/templates/package/search.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/templates/package/snippets/additional_info.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/templates/package/snippets/package_basic_fields.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/templates/package/snippets/package_metadata_fields.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/templates/package/snippets/resource_form.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/tests/test_controllers.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatasetform/tests/test_example_idatasetform.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatastorebackend/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatastorebackend/example_sqlite.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatastorebackend/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatastorebackend/test/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_idatastorebackend/test/test_plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_igroupform/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_igroupform/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_igroupform/templates/example_igroup_form/group_form.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_igroupform/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_igroupform/tests/test_controllers.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_ipermissionlabels/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_ipermissionlabels/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_ipermissionlabels/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_ipermissionlabels/tests/test_example_ipermissionlabels.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iresourcecontroller/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iresourcecontroller/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itemplatehelpers/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itemplatehelpers/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itemplatehelpers/templates/home/index.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itranslation/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itranslation/i18n/ckanext-example_itranslation.pot create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itranslation/i18n/en/LC_MESSAGES/ckanext-example_itranslation.mo create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itranslation/i18n/en/LC_MESSAGES/ckanext-example_itranslation.po create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itranslation/i18n/en/LC_MESSAGES/ckanext-example_translation.po create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itranslation/i18n/fr/LC_MESSAGES/ckanext-example_itranslation.mo create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itranslation/i18n/fr/LC_MESSAGES/ckanext-example_itranslation.po create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itranslation/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itranslation/plugin_v1.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itranslation/templates/home/index.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itranslation/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_itranslation/tests/test_plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iuploader/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iuploader/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iuploader/test/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_iuploader/test/test_plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_ivalidators/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_ivalidators/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_ivalidators/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_ivalidators/tests/test_ivalidators.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/custom_config_setting/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/custom_config_setting/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/custom_emails/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/custom_emails/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/custom_emails/tests.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v01_empty_extension/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v01_empty_extension/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v02_empty_template/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v02_empty_template/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v03_jinja/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v03_jinja/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v04_ckan_extends/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v04_ckan_extends/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v05_block/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v05_block/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v06_super/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v06_super/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v07_helper_function/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v07_helper_function/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v08_custom_helper_function/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v08_custom_helper_function/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v09_snippet/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v09_snippet/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v10_custom_snippet/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v10_custom_snippet/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v11_HTML_and_CSS/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v11_HTML_and_CSS/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v12_extra_public_dir/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v12_extra_public_dir/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v13_custom_css/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v13_custom_css/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v14_more_custom_css/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v14_more_custom_css/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v15_fanstatic/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v15_fanstatic/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v16_initialize_a_javascript_module/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v16_initialize_a_javascript_module/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v17_popover/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v17_popover/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v18_snippet_api/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v18_snippet_api/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v19_01_error/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v19_01_error/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v19_02_error_handling/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v19_02_error_handling/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v20_pubsub/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v20_pubsub/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v21_custom_jquery_plugin/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/example_theme_docs/v21_custom_jquery_plugin/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/imageview/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/imageview/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/imageview/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/imageview/tests/test_view.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/imageview/theme/templates/image_form.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/imageview/theme/templates/image_view.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/multilingual/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/multilingual/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/tests/test_view.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/css/recline.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/css/recline.min.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/img/ajaxload-circle.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/recline_view.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/recline_view.min.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/resource.config create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/backbone/1.0.0/backbone.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/css/bootstrap-theme.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/css/bootstrap.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/fonts/glyphicons-halflings-regular.eot create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/fonts/glyphicons-halflings-regular.svg create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/fonts/glyphicons-halflings-regular.ttf create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/fonts/glyphicons-halflings-regular.woff create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/js/bootstrap.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/ckan.js/ckan.js create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/flot/excanvas.js create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/flot/excanvas.min.js create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/flot/jquery.flot.js create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/flot/jquery.flot.time.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/flotr2/flotr2.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/flotr2/flotr2.min.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/jquery/1.7.1/jquery.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/jquery/1.7.1/jquery.min.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/json/json2.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/json/json2.min.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/leaflet.markercluster/MarkerCluster.Default.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/leaflet.markercluster/MarkerCluster.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/leaflet.markercluster/leaflet.markercluster-src.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/leaflet.markercluster/leaflet.markercluster.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/images/layers-2x.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/images/layers.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/images/marker-icon-2x.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/images/marker-icon.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/images/marker-shadow.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/leaflet-src.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/leaflet.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/leaflet.js create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/moment/2.0.0/moment.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/mustache/0.5.0-dev/mustache.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/mustache/0.5.0-dev/mustache.min.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/recline/flot.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/recline/map.css create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/recline/recline.css create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/recline/recline.dataset.js create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/recline/recline.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/recline/slickgrid.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/showdown/20120615/showdown.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/showdown/20120615/showdown.min.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/MIT-LICENSE.txt create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/README.md create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/controls/slick.columnpicker.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/controls/slick.columnpicker.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/controls/slick.pager.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/controls/slick.pager.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-icons_222222_256x240.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-icons_2e83ff_256x240.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-icons_454545_256x240.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-icons_888888_256x240.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-icons_cd0a0a_256x240.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/jquery-ui-1.8.16.custom.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/actions.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/ajax-loader-small.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/arrow_redo.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/arrow_right_peppermint.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/arrow_right_spearmint.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/arrow_undo.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/bullet_blue.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/bullet_star.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/bullet_toggle_minus.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/bullet_toggle_plus.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/calendar.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/collapse.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/comment_yellow.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/down.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/drag-handle.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/editor-helper-bg.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/expand.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/header-bg.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/header-columns-bg.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/header-columns-over-bg.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/help.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/info.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/listview.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/pencil.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/row-over-bg.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/sort-asc.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/sort-asc.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/sort-desc.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/sort-desc.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/stripes.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/tag_red.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/tick.png create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/user_identity.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/user_identity_plus.gif create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery-1.7.min.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery-ui-1.8.16.custom.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery.event.drag-2.2.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery.event.drop-2.2.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.autotooltips.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellcopymanager.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellrangedecorator.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellrangeselector.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellselectionmodel.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.checkboxselectcolumn.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headerbuttons.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headerbuttons.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headermenu.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headermenu.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.rowmovemanager.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.rowselectionmodel.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick-default-theme.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.core.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.dataview.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.editors.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.formatters.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.grid.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.grid.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.groupitemmetadataprovider.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.remotemodel.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/timeline/LICENSE create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/timeline/README create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/timeline/css/loading.gif create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/timeline/css/timeline.css create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/timeline/css/timeline.png create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/timeline/css/timeline@2x.png create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/timeline/js/timeline.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/underscore.deferred/0.4.0/underscore.deferred.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/underscore.deferred/0.4.0/underscore.deferred.min.js create mode 100755 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/underscore/1.4.4/underscore.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/widget.recordcount.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/widget.recordcount.min.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/templates/recline_graph_form.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/templates/recline_map_form.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/reclineview/theme/templates/recline_view.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/resourceproxy/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/resourceproxy/controller.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/resourceproxy/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/resourceproxy/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/resourceproxy/tests/test_proxy.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/controller.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/.gitignore create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/ckanext/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/ckanext/stats/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/ckanext/stats/css/stats.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/ckanext/stats/javascript/modules/plot.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/ckanext/stats/javascript/modules/stats-nav.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/ckanext/stats/resource.config create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/ckanext/stats/test/fixtures/table.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/ckanext/stats/test/index.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/ckanext/stats/test/spec/modules/plot.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/ckanext/stats/test/spec/modules/stats-nav.spec.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/ckanext/stats/vendor/excanvas.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/public/ckanext/stats/vendor/jquery.flot.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/stats.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/templates/ckanext/stats/index.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/tests/test_stats_lib.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/stats/tests/test_stats_plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/test_tag_vocab_plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/tests/test_view.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/theme/public/LICENSE create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/theme/public/css/text.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/theme/public/css/text.min.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/theme/public/resource.config create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/theme/public/styles/default.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/theme/public/styles/default.min.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/theme/public/styles/github.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/theme/public/styles/github.min.css create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/theme/public/text_view.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/theme/public/text_view.min.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/theme/public/vendor/highlight.pack.js create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/theme/templates/text_form.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/textview/theme/templates/text_view.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/webpageview/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/webpageview/plugin.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/webpageview/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/webpageview/tests/test_view.py create mode 100644 venv/lib/python2.7/site-packages/ckanext/webpageview/theme/templates/webpage_form.html create mode 100644 venv/lib/python2.7/site-packages/ckanext/webpageview/theme/templates/webpage_view.html create mode 100644 venv/lib/python2.7/site-packages/ckantoolkit-0.0.4.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/ckantoolkit-0.0.4.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/ckantoolkit-0.0.4.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/ckantoolkit-0.0.4.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/ckantoolkit-0.0.4.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/ckantoolkit/__init__.py create mode 100644 venv/lib/python2.7/site-packages/ckantoolkit/shims.py create mode 100644 venv/lib/python2.7/site-packages/ckantoolkit/tests.py create mode 100644 venv/lib/python2.7/site-packages/docopt-0.6.2.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/docopt-0.6.2.dist-info/LICENSE-MIT create mode 100644 venv/lib/python2.7/site-packages/docopt-0.6.2.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/docopt-0.6.2.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/docopt-0.6.2.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/docopt-0.6.2.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/docopt.py create mode 100644 venv/lib/python2.7/site-packages/easy_install.py create mode 100644 venv/lib/python2.7/site-packages/idna-2.8.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/idna-2.8.dist-info/LICENSE.rst create mode 100644 venv/lib/python2.7/site-packages/idna-2.8.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/idna-2.8.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/idna-2.8.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/idna-2.8.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/idna/__init__.py create mode 100644 venv/lib/python2.7/site-packages/idna/codec.py create mode 100644 venv/lib/python2.7/site-packages/idna/compat.py create mode 100644 venv/lib/python2.7/site-packages/idna/core.py create mode 100644 venv/lib/python2.7/site-packages/idna/idnadata.py create mode 100644 venv/lib/python2.7/site-packages/idna/intranges.py create mode 100644 venv/lib/python2.7/site-packages/idna/package_data.py create mode 100644 venv/lib/python2.7/site-packages/idna/uts46data.py create mode 100644 venv/lib/python2.7/site-packages/pip/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/__main__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/build_env.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/cache.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/cli/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/cli/autocompletion.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/cli/base_command.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/cli/cmdoptions.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/cli/main_parser.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/cli/parser.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/cli/status_codes.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/check.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/completion.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/configuration.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/debug.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/download.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/freeze.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/hash.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/help.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/install.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/list.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/search.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/show.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/uninstall.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/commands/wheel.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/configuration.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/distributions/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/distributions/base.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/distributions/installed.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/distributions/source.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/distributions/wheel.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/download.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/exceptions.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/index.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/legacy_resolve.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/locations.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/models/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/models/candidate.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/models/format_control.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/models/index.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/models/link.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/models/search_scope.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/models/selection_prefs.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/models/target_python.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/operations/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/operations/check.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/operations/freeze.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/operations/prepare.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/pep425tags.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/pyproject.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/req/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/req/constructors.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/req/req_file.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/req/req_install.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/req/req_set.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/req/req_tracker.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/req/req_uninstall.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/appdirs.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/compat.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/deprecation.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/encoding.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/filesystem.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/glibc.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/hashes.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/logging.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/marker_files.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/misc.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/models.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/outdated.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/packaging.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/setuptools_build.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/temp_dir.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/typing.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/ui.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/utils/virtualenv.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/vcs/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/vcs/bazaar.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/vcs/git.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/vcs/mercurial.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/vcs/subversion.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/vcs/versioncontrol.py create mode 100644 venv/lib/python2.7/site-packages/pip/_internal/wheel.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/appdirs.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/cachecontrol/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/cachecontrol/_cmd.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/cachecontrol/adapter.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/cachecontrol/cache.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/cachecontrol/caches/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/cachecontrol/compat.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/cachecontrol/controller.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/cachecontrol/filewrapper.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/cachecontrol/heuristics.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/cachecontrol/serialize.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/cachecontrol/wrapper.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/certifi/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/certifi/__main__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/certifi/cacert.pem create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/certifi/core.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/big5freq.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/big5prober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/chardistribution.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/charsetgroupprober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/charsetprober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/cli/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/cli/chardetect.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/codingstatemachine.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/compat.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/cp949prober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/enums.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/escprober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/escsm.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/eucjpprober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/euckrfreq.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/euckrprober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/euctwfreq.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/euctwprober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/gb2312freq.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/gb2312prober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/hebrewprober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/jisfreq.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/jpcntx.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/langbulgarianmodel.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/langcyrillicmodel.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/langgreekmodel.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/langhebrewmodel.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/langhungarianmodel.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/langthaimodel.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/langturkishmodel.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/latin1prober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/mbcharsetprober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/mbcsgroupprober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/mbcssm.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/sbcharsetprober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/sbcsgroupprober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/sjisprober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/universaldetector.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/utf8prober.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/chardet/version.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/colorama/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/colorama/ansi.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/colorama/ansitowin32.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/colorama/initialise.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/colorama/win32.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/colorama/winterm.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/_backport/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/_backport/misc.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/_backport/shutil.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/_backport/sysconfig.cfg create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/_backport/sysconfig.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/_backport/tarfile.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/compat.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/database.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/index.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/locators.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/manifest.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/markers.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/metadata.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/resources.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/scripts.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/t32.exe create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/t64.exe create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/util.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/version.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/w32.exe create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/w64.exe create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distlib/wheel.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/distro.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/_ihatexml.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/_inputstream.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/_tokenizer.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/_trie/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/_trie/_base.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/_trie/datrie.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/_trie/py.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/_utils.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/constants.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/filters/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/filters/alphabeticalattributes.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/filters/base.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/filters/inject_meta_charset.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/filters/lint.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/filters/optionaltags.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/filters/sanitizer.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/filters/whitespace.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/html5parser.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/serializer.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treeadapters/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treeadapters/genshi.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treeadapters/sax.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treebuilders/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treebuilders/base.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treebuilders/dom.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treebuilders/etree.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treebuilders/etree_lxml.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treewalkers/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treewalkers/base.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treewalkers/dom.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treewalkers/etree.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treewalkers/etree_lxml.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/html5lib/treewalkers/genshi.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/idna/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/idna/codec.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/idna/compat.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/idna/core.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/idna/idnadata.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/idna/intranges.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/idna/package_data.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/idna/uts46data.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/ipaddress.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/lockfile/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/lockfile/linklockfile.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/lockfile/mkdirlockfile.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/lockfile/pidlockfile.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/lockfile/sqlitelockfile.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/lockfile/symlinklockfile.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/msgpack/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/msgpack/_version.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/msgpack/exceptions.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/msgpack/fallback.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/packaging/__about__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/packaging/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/packaging/_compat.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/packaging/_structures.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/packaging/markers.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/packaging/requirements.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/packaging/specifiers.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/packaging/utils.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/packaging/version.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pep517/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pep517/_in_process.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pep517/build.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pep517/check.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pep517/colorlog.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pep517/compat.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pep517/envbuild.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pep517/wrappers.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pkg_resources/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pkg_resources/py31compat.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/progress/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/progress/bar.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/progress/counter.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/progress/spinner.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pyparsing.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pytoml/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pytoml/core.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pytoml/parser.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pytoml/test.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pytoml/utils.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/pytoml/writer.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/__version__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/_internal_utils.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/adapters.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/api.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/auth.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/certs.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/compat.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/cookies.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/exceptions.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/help.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/hooks.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/models.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/packages.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/sessions.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/status_codes.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/structures.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/requests/utils.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/retrying.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/six.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/_collections.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/connection.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/connectionpool.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/contrib/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/contrib/appengine.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/contrib/securetransport.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/contrib/socks.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/exceptions.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/fields.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/filepost.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/rfc3986/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/rfc3986/_mixin.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/rfc3986/abnf_regexp.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/rfc3986/api.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/rfc3986/builder.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/rfc3986/compat.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/rfc3986/exceptions.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/rfc3986/iri.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/rfc3986/misc.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/rfc3986/normalizers.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/rfc3986/parseresult.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/rfc3986/uri.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/rfc3986/validators.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/six.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/poolmanager.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/request.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/response.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/util/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/util/connection.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/util/queue.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/util/request.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/util/response.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/util/retry.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/util/ssl_.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/util/timeout.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/util/url.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/urllib3/util/wait.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/webencodings/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/webencodings/labels.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/webencodings/mklabels.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/webencodings/tests.py create mode 100644 venv/lib/python2.7/site-packages/pip/_vendor/webencodings/x_user_defined.py create mode 100644 venv/lib/python2.7/site-packages/python_slugify-3.0.4.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/python_slugify-3.0.4.dist-info/LICENSE create mode 100644 venv/lib/python2.7/site-packages/python_slugify-3.0.4.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/python_slugify-3.0.4.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/python_slugify-3.0.4.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/python_slugify-3.0.4.dist-info/entry_points.txt create mode 100644 venv/lib/python2.7/site-packages/python_slugify-3.0.4.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/pytz-2019.2.dist-info/DESCRIPTION.rst create mode 100644 venv/lib/python2.7/site-packages/pytz-2019.2.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/pytz-2019.2.dist-info/LICENSE.txt create mode 100644 venv/lib/python2.7/site-packages/pytz-2019.2.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/pytz-2019.2.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/pytz-2019.2.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/pytz-2019.2.dist-info/metadata.json create mode 100644 venv/lib/python2.7/site-packages/pytz-2019.2.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/pytz-2019.2.dist-info/zip-safe create mode 100644 venv/lib/python2.7/site-packages/pytz/__init__.py create mode 100644 venv/lib/python2.7/site-packages/pytz/exceptions.py create mode 100644 venv/lib/python2.7/site-packages/pytz/lazy.py create mode 100644 venv/lib/python2.7/site-packages/pytz/reference.py create mode 100644 venv/lib/python2.7/site-packages/pytz/tzfile.py create mode 100644 venv/lib/python2.7/site-packages/pytz/tzinfo.py create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Abidjan create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Accra create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Addis_Ababa create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Algiers create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Asmara create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Asmera create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Bamako create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Bangui create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Banjul create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Bissau create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Blantyre create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Brazzaville create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Bujumbura create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Cairo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Casablanca create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Ceuta create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Conakry create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Dakar create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Dar_es_Salaam create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Djibouti create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Douala create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/El_Aaiun create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Freetown create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Gaborone create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Harare create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Johannesburg create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Juba create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Kampala create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Khartoum create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Kigali create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Kinshasa create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Lagos create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Libreville create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Lome create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Luanda create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Lubumbashi create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Lusaka create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Malabo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Maputo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Maseru create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Mbabane create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Mogadishu create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Monrovia create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Nairobi create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Ndjamena create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Niamey create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Nouakchott create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Ouagadougou create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Porto-Novo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Sao_Tome create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Timbuktu create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Tripoli create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Tunis create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Africa/Windhoek create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Adak create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Anchorage create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Anguilla create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Antigua create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Araguaina create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Argentina/Buenos_Aires create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Argentina/Catamarca create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Argentina/ComodRivadavia create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Argentina/Cordoba create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Argentina/Jujuy create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Argentina/La_Rioja create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Argentina/Mendoza create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Argentina/Rio_Gallegos create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Argentina/Salta create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Argentina/San_Juan create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Argentina/San_Luis create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Argentina/Tucuman create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Argentina/Ushuaia create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Aruba create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Asuncion create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Atikokan create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Atka create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Bahia create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Bahia_Banderas create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Barbados create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Belem create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Belize create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Blanc-Sablon create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Boa_Vista create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Bogota create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Boise create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Buenos_Aires create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Cambridge_Bay create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Campo_Grande create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Cancun create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Caracas create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Catamarca create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Cayenne create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Cayman create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Chicago create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Chihuahua create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Coral_Harbour create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Cordoba create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Costa_Rica create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Creston create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Cuiaba create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Curacao create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Danmarkshavn create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Dawson create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Dawson_Creek create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Denver create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Detroit create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Dominica create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Edmonton create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Eirunepe create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/El_Salvador create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Ensenada create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Fort_Nelson create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Fort_Wayne create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Fortaleza create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Glace_Bay create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Godthab create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Goose_Bay create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Grand_Turk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Grenada create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Guadeloupe create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Guatemala create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Guayaquil create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Guyana create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Halifax create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Havana create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Hermosillo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Indiana/Indianapolis create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Indiana/Knox create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Indiana/Marengo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Indiana/Petersburg create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Indiana/Tell_City create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Indiana/Vevay create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Indiana/Vincennes create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Indiana/Winamac create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Indianapolis create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Inuvik create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Iqaluit create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Jamaica create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Jujuy create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Juneau create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Kentucky/Louisville create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Kentucky/Monticello create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Knox_IN create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Kralendijk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/La_Paz create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Lima create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Los_Angeles create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Louisville create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Lower_Princes create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Maceio create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Managua create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Manaus create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Marigot create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Martinique create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Matamoros create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Mazatlan create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Mendoza create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Menominee create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Merida create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Metlakatla create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Mexico_City create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Miquelon create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Moncton create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Monterrey create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Montevideo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Montreal create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Montserrat create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Nassau create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/New_York create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Nipigon create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Nome create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Noronha create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/North_Dakota/Beulah create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/North_Dakota/Center create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/North_Dakota/New_Salem create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Ojinaga create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Panama create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Pangnirtung create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Paramaribo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Phoenix create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Port-au-Prince create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Port_of_Spain create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Porto_Acre create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Porto_Velho create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Puerto_Rico create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Punta_Arenas create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Rainy_River create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Rankin_Inlet create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Recife create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Regina create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Resolute create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Rio_Branco create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Rosario create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Santa_Isabel create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Santarem create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Santiago create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Santo_Domingo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Sao_Paulo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Scoresbysund create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Shiprock create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Sitka create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/St_Barthelemy create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/St_Johns create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/St_Kitts create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/St_Lucia create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/St_Thomas create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/St_Vincent create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Swift_Current create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Tegucigalpa create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Thule create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Thunder_Bay create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Tijuana create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Toronto create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Tortola create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Vancouver create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Virgin create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Whitehorse create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Winnipeg create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Yakutat create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/America/Yellowknife create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Antarctica/Casey create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Antarctica/Davis create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Antarctica/DumontDUrville create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Antarctica/Macquarie create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Antarctica/Mawson create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Antarctica/McMurdo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Antarctica/Palmer create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Antarctica/Rothera create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Antarctica/South_Pole create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Antarctica/Syowa create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Antarctica/Troll create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Antarctica/Vostok create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Arctic/Longyearbyen create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Aden create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Almaty create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Amman create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Anadyr create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Aqtau create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Aqtobe create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Ashgabat create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Ashkhabad create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Atyrau create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Baghdad create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Bahrain create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Baku create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Bangkok create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Barnaul create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Beirut create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Bishkek create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Brunei create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Calcutta create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Chita create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Choibalsan create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Chongqing create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Chungking create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Colombo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Dacca create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Damascus create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Dhaka create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Dili create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Dubai create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Dushanbe create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Famagusta create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Gaza create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Harbin create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Hebron create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Ho_Chi_Minh create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Hong_Kong create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Hovd create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Irkutsk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Istanbul create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Jakarta create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Jayapura create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Jerusalem create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Kabul create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Kamchatka create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Karachi create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Kashgar create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Kathmandu create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Katmandu create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Khandyga create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Kolkata create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Krasnoyarsk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Kuala_Lumpur create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Kuching create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Kuwait create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Macao create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Macau create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Magadan create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Makassar create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Manila create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Muscat create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Nicosia create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Novokuznetsk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Novosibirsk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Omsk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Oral create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Phnom_Penh create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Pontianak create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Pyongyang create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Qatar create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Qostanay create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Qyzylorda create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Rangoon create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Riyadh create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Saigon create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Sakhalin create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Samarkand create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Seoul create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Shanghai create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Singapore create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Srednekolymsk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Taipei create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Tashkent create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Tbilisi create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Tehran create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Tel_Aviv create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Thimbu create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Thimphu create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Tokyo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Tomsk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Ujung_Pandang create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Ulaanbaatar create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Ulan_Bator create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Urumqi create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Ust-Nera create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Vientiane create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Vladivostok create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Yakutsk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Yangon create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Yekaterinburg create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Asia/Yerevan create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Atlantic/Azores create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Atlantic/Bermuda create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Atlantic/Canary create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Atlantic/Cape_Verde create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Atlantic/Faeroe create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Atlantic/Faroe create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Atlantic/Jan_Mayen create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Atlantic/Madeira create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Atlantic/Reykjavik create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Atlantic/South_Georgia create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Atlantic/St_Helena create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Atlantic/Stanley create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/ACT create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Adelaide create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Brisbane create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Broken_Hill create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Canberra create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Currie create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Darwin create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Eucla create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Hobart create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/LHI create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Lindeman create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Lord_Howe create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Melbourne create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/NSW create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/North create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Perth create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Queensland create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/South create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Sydney create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Tasmania create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Victoria create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/West create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Australia/Yancowinna create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Brazil/Acre create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Brazil/DeNoronha create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Brazil/East create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Brazil/West create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/CET create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/CST6CDT create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Canada/Atlantic create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Canada/Central create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Canada/Eastern create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Canada/Mountain create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Canada/Newfoundland create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Canada/Pacific create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Canada/Saskatchewan create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Canada/Yukon create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Chile/Continental create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Chile/EasterIsland create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Cuba create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/EET create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/EST create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/EST5EDT create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Egypt create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Eire create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT+0 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT+1 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT+10 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT+11 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT+12 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT+2 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT+3 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT+4 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT+5 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT+6 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT+7 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT+8 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT+9 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-0 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-1 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-10 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-11 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-12 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-13 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-14 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-2 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-3 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-4 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-5 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-6 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-7 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-8 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT-9 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/GMT0 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/Greenwich create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/UCT create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/UTC create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/Universal create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Etc/Zulu create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Amsterdam create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Andorra create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Astrakhan create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Athens create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Belfast create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Belgrade create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Berlin create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Bratislava create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Brussels create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Bucharest create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Budapest create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Busingen create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Chisinau create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Copenhagen create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Dublin create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Gibraltar create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Guernsey create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Helsinki create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Isle_of_Man create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Istanbul create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Jersey create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Kaliningrad create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Kiev create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Kirov create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Lisbon create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Ljubljana create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/London create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Luxembourg create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Madrid create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Malta create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Mariehamn create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Minsk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Monaco create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Moscow create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Nicosia create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Oslo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Paris create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Podgorica create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Prague create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Riga create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Rome create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Samara create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/San_Marino create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Sarajevo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Saratov create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Simferopol create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Skopje create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Sofia create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Stockholm create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Tallinn create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Tirane create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Tiraspol create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Ulyanovsk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Uzhgorod create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Vaduz create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Vatican create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Vienna create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Vilnius create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Volgograd create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Warsaw create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Zagreb create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Zaporozhye create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Europe/Zurich create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Factory create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/GB create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/GB-Eire create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/GMT create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/GMT+0 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/GMT-0 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/GMT0 create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Greenwich create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/HST create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Hongkong create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Iceland create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Indian/Antananarivo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Indian/Chagos create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Indian/Christmas create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Indian/Cocos create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Indian/Comoro create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Indian/Kerguelen create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Indian/Mahe create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Indian/Maldives create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Indian/Mauritius create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Indian/Mayotte create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Indian/Reunion create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Iran create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Israel create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Jamaica create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Japan create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Kwajalein create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Libya create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/MET create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/MST create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/MST7MDT create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Mexico/BajaNorte create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Mexico/BajaSur create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Mexico/General create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/NZ create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/NZ-CHAT create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Navajo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/PRC create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/PST8PDT create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Apia create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Auckland create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Bougainville create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Chatham create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Chuuk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Easter create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Efate create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Enderbury create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Fakaofo create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Fiji create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Funafuti create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Galapagos create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Gambier create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Guadalcanal create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Guam create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Honolulu create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Johnston create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Kiritimati create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Kosrae create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Kwajalein create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Majuro create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Marquesas create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Midway create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Nauru create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Niue create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Norfolk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Noumea create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Pago_Pago create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Palau create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Pitcairn create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Pohnpei create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Ponape create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Port_Moresby create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Rarotonga create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Saipan create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Samoa create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Tahiti create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Tarawa create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Tongatapu create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Truk create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Wake create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Wallis create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Pacific/Yap create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Poland create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Portugal create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/ROC create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/ROK create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Singapore create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Turkey create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/UCT create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/US/Alaska create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/US/Aleutian create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/US/Arizona create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/US/Central create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/US/East-Indiana create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/US/Eastern create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/US/Hawaii create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/US/Indiana-Starke create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/US/Michigan create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/US/Mountain create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/US/Pacific create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/US/Samoa create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/UTC create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Universal create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/W-SU create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/WET create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/Zulu create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/iso3166.tab create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/leapseconds create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/posixrules create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/tzdata.zi create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/zone.tab create mode 100644 venv/lib/python2.7/site-packages/pytz/zoneinfo/zone1970.tab create mode 100644 venv/lib/python2.7/site-packages/requests-2.22.0.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/requests-2.22.0.dist-info/LICENSE create mode 100644 venv/lib/python2.7/site-packages/requests-2.22.0.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/requests-2.22.0.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/requests-2.22.0.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/requests-2.22.0.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/requests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/requests/__version__.py create mode 100644 venv/lib/python2.7/site-packages/requests/_internal_utils.py create mode 100644 venv/lib/python2.7/site-packages/requests/adapters.py create mode 100644 venv/lib/python2.7/site-packages/requests/api.py create mode 100644 venv/lib/python2.7/site-packages/requests/auth.py create mode 100644 venv/lib/python2.7/site-packages/requests/certs.py create mode 100644 venv/lib/python2.7/site-packages/requests/compat.py create mode 100644 venv/lib/python2.7/site-packages/requests/cookies.py create mode 100644 venv/lib/python2.7/site-packages/requests/exceptions.py create mode 100644 venv/lib/python2.7/site-packages/requests/help.py create mode 100644 venv/lib/python2.7/site-packages/requests/hooks.py create mode 100644 venv/lib/python2.7/site-packages/requests/models.py create mode 100644 venv/lib/python2.7/site-packages/requests/packages.py create mode 100644 venv/lib/python2.7/site-packages/requests/sessions.py create mode 100644 venv/lib/python2.7/site-packages/requests/status_codes.py create mode 100644 venv/lib/python2.7/site-packages/requests/structures.py create mode 100644 venv/lib/python2.7/site-packages/requests/utils.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/__init__.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/_deprecation_warning.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/archive_util.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/build_meta.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/cli-32.exe create mode 100644 venv/lib/python2.7/site-packages/setuptools/cli-64.exe create mode 100644 venv/lib/python2.7/site-packages/setuptools/cli.exe create mode 100644 venv/lib/python2.7/site-packages/setuptools/config.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/dep_util.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/depends.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/dist.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/extension.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/extern/__init__.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/glibc.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/glob.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/gui-32.exe create mode 100644 venv/lib/python2.7/site-packages/setuptools/gui-64.exe create mode 100644 venv/lib/python2.7/site-packages/setuptools/gui.exe create mode 100644 venv/lib/python2.7/site-packages/setuptools/launch.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/lib2to3_ex.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/monkey.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/msvc.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/namespaces.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/package_index.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/pep425tags.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/py27compat.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/py31compat.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/py33compat.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/sandbox.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/script (dev).tmpl create mode 100644 venv/lib/python2.7/site-packages/setuptools/script.tmpl create mode 100644 venv/lib/python2.7/site-packages/setuptools/site-patch.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/ssl_support.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/unicode_utils.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/version.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/wheel.py create mode 100644 venv/lib/python2.7/site-packages/setuptools/windows_support.py create mode 100644 venv/lib/python2.7/site-packages/simplejson-3.16.0.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/simplejson-3.16.0.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/simplejson-3.16.0.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/simplejson-3.16.0.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/simplejson-3.16.0.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/simplejson/__init__.py create mode 100755 venv/lib/python2.7/site-packages/simplejson/_speedups.so create mode 100644 venv/lib/python2.7/site-packages/simplejson/compat.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/decoder.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/encoder.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/errors.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/ordered_dict.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/raw_json.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/scanner.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/__init__.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_bigint_as_string.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_bitsize_int_as_string.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_check_circular.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_decimal.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_decode.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_default.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_dump.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_encode_basestring_ascii.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_encode_for_html.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_errors.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_fail.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_float.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_for_json.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_indent.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_item_sort_key.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_iterable.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_namedtuple.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_pass1.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_pass2.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_pass3.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_raw_json.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_recursion.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_scanstring.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_separators.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_speedups.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_str_subclass.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_subclass.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_tool.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_tuple.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tests/test_unicode.py create mode 100644 venv/lib/python2.7/site-packages/simplejson/tool.py create mode 100644 venv/lib/python2.7/site-packages/six-1.12.0.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/six-1.12.0.dist-info/LICENSE create mode 100644 venv/lib/python2.7/site-packages/six-1.12.0.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/six-1.12.0.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/six-1.12.0.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/six-1.12.0.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/six.py create mode 100644 venv/lib/python2.7/site-packages/slugify/__init__.py create mode 100644 venv/lib/python2.7/site-packages/slugify/slugify.py create mode 100644 venv/lib/python2.7/site-packages/text_unidecode-1.3.dist-info/DESCRIPTION.rst create mode 100644 venv/lib/python2.7/site-packages/text_unidecode-1.3.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/text_unidecode-1.3.dist-info/LICENSE.txt create mode 100644 venv/lib/python2.7/site-packages/text_unidecode-1.3.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/text_unidecode-1.3.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/text_unidecode-1.3.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/text_unidecode-1.3.dist-info/metadata.json create mode 100644 venv/lib/python2.7/site-packages/text_unidecode-1.3.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/text_unidecode/__init__.py create mode 100644 venv/lib/python2.7/site-packages/text_unidecode/data.bin create mode 100644 venv/lib/python2.7/site-packages/urllib3-1.25.6.dist-info/INSTALLER create mode 100644 venv/lib/python2.7/site-packages/urllib3-1.25.6.dist-info/LICENSE.txt create mode 100644 venv/lib/python2.7/site-packages/urllib3-1.25.6.dist-info/METADATA create mode 100644 venv/lib/python2.7/site-packages/urllib3-1.25.6.dist-info/RECORD create mode 100644 venv/lib/python2.7/site-packages/urllib3-1.25.6.dist-info/WHEEL create mode 100644 venv/lib/python2.7/site-packages/urllib3-1.25.6.dist-info/top_level.txt create mode 100644 venv/lib/python2.7/site-packages/urllib3/__init__.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/_collections.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/connection.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/connectionpool.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/contrib/__init__.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/contrib/_appengine_environ.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/contrib/_securetransport/__init__.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/contrib/_securetransport/bindings.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/contrib/_securetransport/low_level.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/contrib/appengine.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/contrib/ntlmpool.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/contrib/pyopenssl.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/contrib/securetransport.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/contrib/socks.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/exceptions.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/fields.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/filepost.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/packages/__init__.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/packages/backports/__init__.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/packages/backports/makefile.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/packages/six.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/packages/ssl_match_hostname/__init__.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/packages/ssl_match_hostname/_implementation.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/poolmanager.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/request.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/response.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/util/__init__.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/util/connection.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/util/queue.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/util/request.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/util/response.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/util/retry.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/util/ssl_.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/util/timeout.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/util/url.py create mode 100644 venv/lib/python2.7/site-packages/urllib3/util/wait.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/__init__.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/_compat.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/_internal.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/_reloader.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/contrib/__init__.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/contrib/atom.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/contrib/cache.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/contrib/fixers.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/contrib/iterio.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/contrib/lint.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/contrib/profiler.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/contrib/securecookie.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/contrib/sessions.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/contrib/wrappers.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/datastructures.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/debug/__init__.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/debug/console.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/debug/repr.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/debug/shared/FONT_LICENSE create mode 100644 venv/lib/python2.7/site-packages/werkzeug/debug/shared/console.png create mode 100644 venv/lib/python2.7/site-packages/werkzeug/debug/shared/debugger.js create mode 100644 venv/lib/python2.7/site-packages/werkzeug/debug/shared/jquery.js create mode 100644 venv/lib/python2.7/site-packages/werkzeug/debug/shared/less.png create mode 100644 venv/lib/python2.7/site-packages/werkzeug/debug/shared/more.png create mode 100644 venv/lib/python2.7/site-packages/werkzeug/debug/shared/source.png create mode 100644 venv/lib/python2.7/site-packages/werkzeug/debug/shared/style.css create mode 100644 venv/lib/python2.7/site-packages/werkzeug/debug/shared/ubuntu.ttf create mode 100644 venv/lib/python2.7/site-packages/werkzeug/debug/tbtools.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/exceptions.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/filesystem.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/formparser.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/http.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/local.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/middleware/__init__.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/middleware/dispatcher.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/middleware/http_proxy.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/middleware/lint.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/middleware/profiler.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/middleware/proxy_fix.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/middleware/shared_data.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/posixemulation.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/routing.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/security.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/serving.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/test.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/testapp.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/urls.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/useragents.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/utils.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/wrappers/__init__.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/wrappers/accept.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/wrappers/auth.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/wrappers/base_request.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/wrappers/base_response.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/wrappers/common_descriptors.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/wrappers/etag.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/wrappers/json.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/wrappers/request.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/wrappers/response.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/wrappers/user_agent.py create mode 100644 venv/lib/python2.7/site-packages/werkzeug/wsgi.py create mode 100644 venv/lib/python2.7/site-packages/wheel/__init__.py create mode 100644 venv/lib/python2.7/site-packages/wheel/__main__.py create mode 100644 venv/lib/python2.7/site-packages/wheel/bdist_wheel.py create mode 100644 venv/lib/python2.7/site-packages/wheel/cli/__init__.py create mode 100644 venv/lib/python2.7/site-packages/wheel/cli/convert.py create mode 100644 venv/lib/python2.7/site-packages/wheel/cli/pack.py create mode 100644 venv/lib/python2.7/site-packages/wheel/cli/unpack.py create mode 100644 venv/lib/python2.7/site-packages/wheel/metadata.py create mode 100644 venv/lib/python2.7/site-packages/wheel/pep425tags.py create mode 100644 venv/lib/python2.7/site-packages/wheel/pkginfo.py create mode 100644 venv/lib/python2.7/site-packages/wheel/util.py create mode 100644 venv/lib/python2.7/site-packages/wheel/wheelfile.py create mode 100644 venv/lib/python2.7/site.py create mode 120000 venv/lib/python2.7/sre.py create mode 120000 venv/lib/python2.7/sre_compile.py create mode 120000 venv/lib/python2.7/sre_constants.py create mode 120000 venv/lib/python2.7/sre_parse.py create mode 120000 venv/lib/python2.7/stat.py create mode 120000 venv/lib/python2.7/types.py create mode 120000 venv/lib/python2.7/warnings.py diff --git a/ckanext/scheming/fanstatic/scheming-upload.js b/ckanext/scheming/fanstatic/scheming-upload.js new file mode 100644 index 00000000..7664c017 --- /dev/null +++ b/ckanext/scheming/fanstatic/scheming-upload.js @@ -0,0 +1,8 @@ +$(document).ready(function(){ + setTimeout(function(){ + $( "div[data-module-field_upload|='rtti_upload_doc']>div.form-group>label" ).text( $("div[data-module-field_upload|='rtti_upload_doc']" ).attr("data-module-upload_label") ); + $( "div[data-module-field_upload|='srti_upload_doc']>div.form-group>label" ).text( $("div[data-module-field_upload|='srti_upload_doc']" ).attr("data-module-upload_label") ); + $( "div[data-module-field_upload|='sstp_upload_doc']>div.form-group>label" ).text( $("div[data-module-field_upload|='sstp_upload_doc']" ).attr("data-module-upload_label") ); + $( "div[data-module-field_upload|='image_upload']>div.form-group>label" ).text( $("div[data-module-field_upload|='image_upload']" ).attr("data-module-upload_label") ); + },1000); + }); \ No newline at end of file diff --git a/ckanext/scheming/plugins.py b/ckanext/scheming/plugins.py index 78c79d37..c86a2e46 100644 --- a/ckanext/scheming/plugins.py +++ b/ckanext/scheming/plugins.py @@ -156,6 +156,7 @@ def update_config(self, config): self._store_instance(self) self._add_template_directory(config) self._load_presets(config) + t.add_resource('fanstatic', 'scheming') self._is_fallback = asbool(config.get(self.FALLBACK_OPTION, False)) @@ -166,6 +167,7 @@ def update_config(self, config): ) self._expanded_schemas = _expand_schemas(self._schemas) + def is_fallback(self): return self._is_fallback diff --git a/ckanext/scheming/templates/base.html b/ckanext/scheming/templates/base.html new file mode 100644 index 00000000..1d55fc0d --- /dev/null +++ b/ckanext/scheming/templates/base.html @@ -0,0 +1,7 @@ +{% ckan_extends %} + +{% block styles %} + {{ super() }} + + {% resource 'scheming/scheming-upload.js' %} +{% endblock %} \ No newline at end of file diff --git a/ckanext/scheming/templates/scheming/form_snippets/doc_upload.html b/ckanext/scheming/templates/scheming/form_snippets/doc_upload.html index 3951c004..b6cf3708 100644 --- a/ckanext/scheming/templates/scheming/form_snippets/doc_upload.html +++ b/ckanext/scheming/templates/scheming/form_snippets/doc_upload.html @@ -1,13 +1,61 @@ {% import 'macros/form.html' as form %} -{%- set is_upload = (data.url_type == 'upload') -%} -{{ form.image_upload( +{% macro doc_upload(data, errors, field_url='image_url', field_upload='image_upload', field_clear='clear_upload', + is_url=false, is_upload=false, is_upload_enabled=false, placeholder=false, + url_label='', upload_label='', field_name='image_url') %} + {% set placeholder = placeholder if placeholder else false %} + {% set url_label = url_label or _('Image URL') %} + {% set upload_label = upload_label or '' %} + {% set previous_upload = data['previous_upload'] %} + + {% if is_upload_enabled %} + + +
+ {% endif %} + + + {{ form.input(field_url, label=url_label, id='field-image-url', type='url', placeholder=placeholder, value=data.get(field_url), error=errors.get(field_url), classes=['control-full']) }} + + + {% if is_upload_enabled %} + {{ form.input(field_upload, label=upload_label, id='field-image-upload', type='file', placeholder='', value='', error='', classes=['control-full']) }} + {% if is_upload %} + {{ form.checkbox(field_clear, label=_('Clear Upload'), id='field-clear-upload', value='true', error='', classes=['control-full']) }} + {% endif %} + {% endif %} + + {% if is_upload_enabled %}
{% endif %} + + {% endmacro %} + + +{% if 'rtti_doc_document_upload' in data and field.field_name == 'rtti_doc_document_upload' %} + {%- set is_upload = (data.rtti_doc_document_upload != '') -%} +{% endif %} +{% if 'srti_doc_document_upload' in data and field.field_name == 'srti_doc_document_upload' %} + {%- set is_upload = (data.srti_doc_document_upload != '') -%} +{% endif %} +{% if 'sstp_doc_document_upload' in data and field.field_name == 'sstp_doc_document_upload' %} + {%- set is_upload = (data.sstp_doc_document_upload != '') -%} +{% endif %} + +{{ doc_upload( data, errors, field_url=field.field_name, field_upload=field.upload_field, field_clear=field.upload_clear, - is_upload_enabled=h.uploads_enabled(), + is_upload_enabled=true, is_url=false, is_upload=is_upload, upload_label=h.scheming_language_text(field.upload_label), @@ -15,5 +63,6 @@ placeholder=field.form_placeholder ) }} -{# image_upload macro doesn't support call #} +{# doc_upload macro doesn't support call #} {%- snippet 'scheming/form_snippets/help_text.html', field=field -%} + diff --git a/venv/.Python b/venv/.Python new file mode 120000 index 00000000..cc24a1e9 --- /dev/null +++ b/venv/.Python @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/Python \ No newline at end of file diff --git a/venv/bin/activate b/venv/bin/activate new file mode 100644 index 00000000..0f738249 --- /dev/null +++ b/venv/bin/activate @@ -0,0 +1,78 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + +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="/Users/bart/Projects/ckan/ckanext-scheming/venv" +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/venv/bin/activate.csh b/venv/bin/activate.csh new file mode 100644 index 00000000..bf526cb8 --- /dev/null +++ b/venv/bin/activate.csh @@ -0,0 +1,42 @@ +# 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 "/Users/bart/Projects/ckan/ckanext-scheming/venv" + +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 + +# 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 + +unset env_name + +alias pydoc python -m pydoc + +rehash diff --git a/venv/bin/activate.fish b/venv/bin/activate.fish new file mode 100644 index 00000000..10f3d817 --- /dev/null +++ b/venv/bin/activate.fish @@ -0,0 +1,101 @@ +# 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 | tr "." "\n")[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" + # 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 "/Users/bart/Projects/ckan/ckanext-scheming/venv" + +# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling +if test (echo $FISH_VERSION | tr "." "\n")[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 + # Save the current $status, for fish_prompts that display it. + set -l old_status $status + + # 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 + + # Restore the original $status + echo "exit $old_status" | source + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" +end diff --git a/venv/bin/activate.ps1 b/venv/bin/activate.ps1 new file mode 100644 index 00000000..6d8ae2aa --- /dev/null +++ b/venv/bin/activate.ps1 @@ -0,0 +1,60 @@ +# This file must be dot sourced from PoSh; you cannot run it directly. Do this: . ./activate.ps1 + +$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) + { + $old_env = split-path $env:VIRTUAL_ENV -leaf + 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 + +$global:_OLD_VIRTUAL_PATH = $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 + function global:prompt + { + # Add a prefix to the current prompt, but don't discard it. + write-host "($( split-path $env:VIRTUAL_ENV -leaf )) " -nonewline + & $function:_old_virtual_prompt + } +} diff --git a/venv/bin/activate_this.py b/venv/bin/activate_this.py new file mode 100644 index 00000000..59b5d724 --- /dev/null +++ b/venv/bin/activate_this.py @@ -0,0 +1,46 @@ +"""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: + __file__ +except NameError: + raise AssertionError("You must use exec(open(this_file).read(), {'__file__': this_file}))") + +# prepend bin to PATH (this file is inside the bin directory) +bin_dir = os.path.dirname(os.path.abspath(__file__)) +os.environ["PATH"] = os.pathsep.join([bin_dir] + os.environ.get("PATH", "").split(os.pathsep)) + +base = os.path.dirname(bin_dir) + +# virtual env is right above bin directory +os.environ["VIRTUAL_ENV"] = base + +# add the virtual environments site-package to the host python import mechanism +IS_PYPY = hasattr(sys, "pypy_version_info") +IS_JYTHON = sys.platform.startswith("java") +if IS_JYTHON: + site_packages = os.path.join(base, "Lib", "site-packages") +elif IS_PYPY: + site_packages = os.path.join(base, "site-packages") +else: + IS_WIN = sys.platform == "win32" + if IS_WIN: + site_packages = os.path.join(base, "Lib", "site-packages") + else: + site_packages = os.path.join(base, "lib", "python{}".format(sys.version[:3]), "site-packages") + +prev = set(sys.path) +site.addsitedir(site_packages) +sys.real_prefix = sys.prefix +sys.prefix = base + +# Move the added items to the front of the path, in place +new = list(sys.path) +sys.path[:] = [i for i in new if i not in prev] + [i for i in new if i in prev] diff --git a/venv/bin/chardetect b/venv/bin/chardetect new file mode 100755 index 00000000..e22885d6 --- /dev/null +++ b/venv/bin/chardetect @@ -0,0 +1,10 @@ +#!/Users/bart/Projects/ckan/ckanext-scheming/venv/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/venv/bin/ckan-admin b/venv/bin/ckan-admin new file mode 100755 index 00000000..7188460c --- /dev/null +++ b/venv/bin/ckan-admin @@ -0,0 +1,10 @@ +#!/Users/bart/Projects/ckan/ckanext-scheming/venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys + +from bin.ckan_admin import Command + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(Command()) diff --git a/venv/bin/ckanapi b/venv/bin/ckanapi new file mode 100755 index 00000000..05c33333 --- /dev/null +++ b/venv/bin/ckanapi @@ -0,0 +1,10 @@ +#!/Users/bart/Projects/ckan/ckanext-scheming/venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys + +from ckanapi.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/venv/bin/easy_install b/venv/bin/easy_install new file mode 100755 index 00000000..665e17ae --- /dev/null +++ b/venv/bin/easy_install @@ -0,0 +1,10 @@ +#!/Users/bart/Projects/ckan/ckanext-scheming/venv/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/venv/bin/easy_install-2.7 b/venv/bin/easy_install-2.7 new file mode 100755 index 00000000..665e17ae --- /dev/null +++ b/venv/bin/easy_install-2.7 @@ -0,0 +1,10 @@ +#!/Users/bart/Projects/ckan/ckanext-scheming/venv/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/venv/bin/pip b/venv/bin/pip new file mode 100755 index 00000000..b32c7886 --- /dev/null +++ b/venv/bin/pip @@ -0,0 +1,10 @@ +#!/Users/bart/Projects/ckan/ckanext-scheming/venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys + +from pip._internal import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/pip2 b/venv/bin/pip2 new file mode 100755 index 00000000..b32c7886 --- /dev/null +++ b/venv/bin/pip2 @@ -0,0 +1,10 @@ +#!/Users/bart/Projects/ckan/ckanext-scheming/venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys + +from pip._internal import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/pip2.7 b/venv/bin/pip2.7 new file mode 100755 index 00000000..b32c7886 --- /dev/null +++ b/venv/bin/pip2.7 @@ -0,0 +1,10 @@ +#!/Users/bart/Projects/ckan/ckanext-scheming/venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys + +from pip._internal import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/python b/venv/bin/python new file mode 100755 index 0000000000000000000000000000000000000000..542eddcc6396c5372a2a538133f3b0ec9363c530 GIT binary patch literal 51744 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 (3, 2): + valid_opts.insert(-1, 'extension-suffix') + valid_opts.append('abiflags') +if sys.version_info >= (3, 3): + valid_opts.append('configdir') + + +def exit_with_usage(code=1): + sys.stderr.write("Usage: {0} [{1}]\n".format( + sys.argv[0], '|'.join('--'+opt for opt in valid_opts))) + sys.exit(code) + +try: + opts, args = getopt.getopt(sys.argv[1:], '', valid_opts) +except getopt.error: + exit_with_usage() + +if not opts: + exit_with_usage() + +pyver = sysconfig.get_config_var('VERSION') +getvar = sysconfig.get_config_var + +opt_flags = [flag for (flag, val) in opts] + +if '--help' in opt_flags: + exit_with_usage(code=0) + +for opt in opt_flags: + if opt == '--prefix': + print(sysconfig.get_config_var('prefix')) + + elif opt == '--exec-prefix': + print(sysconfig.get_config_var('exec_prefix')) + + elif opt in ('--includes', '--cflags'): + flags = ['-I' + sysconfig.get_path('include'), + '-I' + sysconfig.get_path('platinclude')] + if opt == '--cflags': + flags.extend(getvar('CFLAGS').split()) + print(' '.join(flags)) + + elif opt in ('--libs', '--ldflags'): + abiflags = getattr(sys, 'abiflags', '') + libs = ['-lpython' + pyver + abiflags] + libs += getvar('LIBS').split() + libs += getvar('SYSLIBS').split() + # add the prefix/lib/pythonX.Y/config dir, but only if there is no + # shared library in prefix/lib/. + if opt == '--ldflags': + if not getvar('Py_ENABLE_SHARED'): + libs.insert(0, '-L' + getvar('LIBPL')) + if not getvar('PYTHONFRAMEWORK'): + libs.extend(getvar('LINKFORSHARED').split()) + print(' '.join(libs)) + + elif opt == '--extension-suffix': + ext_suffix = sysconfig.get_config_var('EXT_SUFFIX') + if ext_suffix is None: + ext_suffix = sysconfig.get_config_var('SO') + print(ext_suffix) + + elif opt == '--abiflags': + if not getattr(sys, 'abiflags', None): + exit_with_usage() + print(sys.abiflags) + + elif opt == '--configdir': + print(sysconfig.get_config_var('LIBPL')) diff --git a/venv/bin/python2 b/venv/bin/python2 new file mode 120000 index 00000000..d8654aa0 --- /dev/null +++ b/venv/bin/python2 @@ -0,0 +1 @@ +python \ No newline at end of file diff --git a/venv/bin/python2.7 b/venv/bin/python2.7 new file mode 120000 index 00000000..d8654aa0 --- /dev/null +++ b/venv/bin/python2.7 @@ -0,0 +1 @@ +python \ No newline at end of file diff --git a/venv/bin/slugify b/venv/bin/slugify new file mode 100755 index 00000000..e7f14f96 --- /dev/null +++ b/venv/bin/slugify @@ -0,0 +1,10 @@ +#!/Users/bart/Projects/ckan/ckanext-scheming/venv/bin/python +# -*- coding: utf-8 -*- +import re +import sys + +from slugify.slugify import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/wheel b/venv/bin/wheel new file mode 100755 index 00000000..d2c606df --- /dev/null +++ b/venv/bin/wheel @@ -0,0 +1,10 @@ +#!/Users/bart/Projects/ckan/ckanext-scheming/venv/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/venv/lib/python2.7/LICENSE.txt b/venv/lib/python2.7/LICENSE.txt new file mode 120000 index 00000000..1a3e1185 --- /dev/null +++ b/venv/lib/python2.7/LICENSE.txt @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/LICENSE.txt \ No newline at end of file diff --git a/venv/lib/python2.7/UserDict.py b/venv/lib/python2.7/UserDict.py new file mode 120000 index 00000000..b735f026 --- /dev/null +++ b/venv/lib/python2.7/UserDict.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/UserDict.py \ No newline at end of file diff --git a/venv/lib/python2.7/_abcoll.py b/venv/lib/python2.7/_abcoll.py new file mode 120000 index 00000000..4a595bc9 --- /dev/null +++ b/venv/lib/python2.7/_abcoll.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/_abcoll.py \ No newline at end of file diff --git a/venv/lib/python2.7/_weakrefset.py b/venv/lib/python2.7/_weakrefset.py new file mode 120000 index 00000000..b8b09b7d --- /dev/null +++ b/venv/lib/python2.7/_weakrefset.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/_weakrefset.py \ No newline at end of file diff --git a/venv/lib/python2.7/abc.py b/venv/lib/python2.7/abc.py new file mode 120000 index 00000000..87956e58 --- /dev/null +++ b/venv/lib/python2.7/abc.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/abc.py \ No newline at end of file diff --git a/venv/lib/python2.7/codecs.py b/venv/lib/python2.7/codecs.py new file mode 120000 index 00000000..b18c8d66 --- /dev/null +++ b/venv/lib/python2.7/codecs.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/codecs.py \ No newline at end of file diff --git a/venv/lib/python2.7/config b/venv/lib/python2.7/config new file mode 120000 index 00000000..88ddfa15 --- /dev/null +++ b/venv/lib/python2.7/config @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/config \ No newline at end of file diff --git a/venv/lib/python2.7/copy_reg.py b/venv/lib/python2.7/copy_reg.py new file mode 120000 index 00000000..8d0265cb --- /dev/null +++ b/venv/lib/python2.7/copy_reg.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/copy_reg.py \ No newline at end of file diff --git a/venv/lib/python2.7/distutils/__init__.py b/venv/lib/python2.7/distutils/__init__.py new file mode 100644 index 00000000..b9b0f24f --- /dev/null +++ b/venv/lib/python2.7/distutils/__init__.py @@ -0,0 +1,134 @@ +import os +import sys +import warnings + +# opcode is not a virtualenv module, so we can use it to find the stdlib +# Important! To work on pypy, this must be a module that resides in the +# lib-python/modified-x.y.z directory +import opcode + +dirname = os.path.dirname + +distutils_path = os.path.join(os.path.dirname(opcode.__file__), "distutils") +if os.path.normpath(distutils_path) == os.path.dirname(os.path.normpath(__file__)): + warnings.warn("The virtualenv distutils package at %s appears to be in the same location as the system distutils?") +else: + __path__.insert(0, distutils_path) # noqa: F821 + if sys.version_info < (3, 4): + import imp + + real_distutils = imp.load_module("_virtualenv_distutils", None, distutils_path, ("", "", imp.PKG_DIRECTORY)) + else: + import importlib.machinery + + distutils_path = os.path.join(distutils_path, "__init__.py") + loader = importlib.machinery.SourceFileLoader("_virtualenv_distutils", distutils_path) + if sys.version_info < (3, 5): + import types + + real_distutils = types.ModuleType(loader.name) + else: + import importlib.util + + spec = importlib.util.spec_from_loader(loader.name, loader) + real_distutils = importlib.util.module_from_spec(spec) + loader.exec_module(real_distutils) + + # Copy the relevant attributes + try: + __revision__ = real_distutils.__revision__ + except AttributeError: + pass + __version__ = real_distutils.__version__ + +from distutils import dist, sysconfig # isort:skip + +try: + basestring +except NameError: + basestring = str + +# patch build_ext (distutils doesn't know how to get the libs directory +# path on windows - it hardcodes the paths around the patched sys.prefix) + +if sys.platform == "win32": + from distutils.command.build_ext import build_ext as old_build_ext + + class build_ext(old_build_ext): + def finalize_options(self): + if self.library_dirs is None: + self.library_dirs = [] + elif isinstance(self.library_dirs, basestring): + self.library_dirs = self.library_dirs.split(os.pathsep) + + self.library_dirs.insert(0, os.path.join(sys.real_prefix, "Libs")) + old_build_ext.finalize_options(self) + + from distutils.command import build_ext as build_ext_module + + build_ext_module.build_ext = build_ext + +# distutils.dist patches: + +old_find_config_files = dist.Distribution.find_config_files + + +def find_config_files(self): + found = old_find_config_files(self) + if os.name == "posix": + user_filename = ".pydistutils.cfg" + else: + user_filename = "pydistutils.cfg" + user_filename = os.path.join(sys.prefix, user_filename) + if os.path.isfile(user_filename): + for item in list(found): + if item.endswith("pydistutils.cfg"): + found.remove(item) + found.append(user_filename) + return found + + +dist.Distribution.find_config_files = find_config_files + +# distutils.sysconfig patches: + +old_get_python_inc = sysconfig.get_python_inc + + +def sysconfig_get_python_inc(plat_specific=0, prefix=None): + if prefix is None: + prefix = sys.real_prefix + return old_get_python_inc(plat_specific, prefix) + + +sysconfig_get_python_inc.__doc__ = old_get_python_inc.__doc__ +sysconfig.get_python_inc = sysconfig_get_python_inc + +old_get_python_lib = sysconfig.get_python_lib + + +def sysconfig_get_python_lib(plat_specific=0, standard_lib=0, prefix=None): + if standard_lib and prefix is None: + prefix = sys.real_prefix + return old_get_python_lib(plat_specific, standard_lib, prefix) + + +sysconfig_get_python_lib.__doc__ = old_get_python_lib.__doc__ +sysconfig.get_python_lib = sysconfig_get_python_lib + +old_get_config_vars = sysconfig.get_config_vars + + +def sysconfig_get_config_vars(*args): + real_vars = old_get_config_vars(*args) + if sys.platform == "win32": + lib_dir = os.path.join(sys.real_prefix, "libs") + if isinstance(real_vars, dict) and "LIBDIR" not in real_vars: + real_vars["LIBDIR"] = lib_dir # asked for all + elif isinstance(real_vars, list) and "LIBDIR" in args: + real_vars = real_vars + [lib_dir] # asked for list + return real_vars + + +sysconfig_get_config_vars.__doc__ = old_get_config_vars.__doc__ +sysconfig.get_config_vars = sysconfig_get_config_vars diff --git a/venv/lib/python2.7/distutils/distutils.cfg b/venv/lib/python2.7/distutils/distutils.cfg new file mode 100644 index 00000000..1af230ec --- /dev/null +++ b/venv/lib/python2.7/distutils/distutils.cfg @@ -0,0 +1,6 @@ +# This is a config file local to this virtualenv installation +# You may include options that will be used by all distutils commands, +# and by easy_install. For instance: +# +# [easy_install] +# find_links = http://mylocalsite diff --git a/venv/lib/python2.7/encodings b/venv/lib/python2.7/encodings new file mode 120000 index 00000000..8732f85c --- /dev/null +++ b/venv/lib/python2.7/encodings @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings \ No newline at end of file diff --git a/venv/lib/python2.7/fnmatch.py b/venv/lib/python2.7/fnmatch.py new file mode 120000 index 00000000..49b6bc07 --- /dev/null +++ b/venv/lib/python2.7/fnmatch.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/fnmatch.py \ No newline at end of file diff --git a/venv/lib/python2.7/genericpath.py b/venv/lib/python2.7/genericpath.py new file mode 120000 index 00000000..7843bce5 --- /dev/null +++ b/venv/lib/python2.7/genericpath.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/genericpath.py \ No newline at end of file diff --git a/venv/lib/python2.7/lib-dynload b/venv/lib/python2.7/lib-dynload new file mode 120000 index 00000000..24c555ed --- /dev/null +++ b/venv/lib/python2.7/lib-dynload @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload \ No newline at end of file diff --git a/venv/lib/python2.7/linecache.py b/venv/lib/python2.7/linecache.py new file mode 120000 index 00000000..1f79a61c --- /dev/null +++ b/venv/lib/python2.7/linecache.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/linecache.py \ No newline at end of file diff --git a/venv/lib/python2.7/locale.py b/venv/lib/python2.7/locale.py new file mode 120000 index 00000000..cc8a5a7d --- /dev/null +++ b/venv/lib/python2.7/locale.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/locale.py \ No newline at end of file diff --git a/venv/lib/python2.7/no-global-site-packages.txt b/venv/lib/python2.7/no-global-site-packages.txt new file mode 100644 index 00000000..e69de29b diff --git a/venv/lib/python2.7/ntpath.py b/venv/lib/python2.7/ntpath.py new file mode 120000 index 00000000..af0bbe70 --- /dev/null +++ b/venv/lib/python2.7/ntpath.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/ntpath.py \ No newline at end of file diff --git a/venv/lib/python2.7/orig-prefix.txt b/venv/lib/python2.7/orig-prefix.txt new file mode 100644 index 00000000..2a451201 --- /dev/null +++ b/venv/lib/python2.7/orig-prefix.txt @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7 \ No newline at end of file diff --git a/venv/lib/python2.7/os.py b/venv/lib/python2.7/os.py new file mode 120000 index 00000000..04db9282 --- /dev/null +++ b/venv/lib/python2.7/os.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.py \ No newline at end of file diff --git a/venv/lib/python2.7/posixpath.py b/venv/lib/python2.7/posixpath.py new file mode 120000 index 00000000..cc89aa2f --- /dev/null +++ b/venv/lib/python2.7/posixpath.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.py \ No newline at end of file diff --git a/venv/lib/python2.7/re.py b/venv/lib/python2.7/re.py new file mode 120000 index 00000000..b1a8e651 --- /dev/null +++ b/venv/lib/python2.7/re.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/re.py \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/INSTALLER b/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/LICENSE.rst b/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/LICENSE.rst new file mode 100644 index 00000000..c37cae49 --- /dev/null +++ b/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/LICENSE.rst @@ -0,0 +1,28 @@ +Copyright 2007 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/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/METADATA b/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/METADATA new file mode 100644 index 00000000..6341603c --- /dev/null +++ b/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/METADATA @@ -0,0 +1,128 @@ +Metadata-Version: 2.1 +Name: Werkzeug +Version: 0.16.0 +Summary: The comprehensive WSGI web application library. +Home-page: https://palletsprojects.com/p/werkzeug/ +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Documentation, https://werkzeug.palletsprojects.com/ +Project-URL: Code, https://github.com/pallets/werkzeug +Project-URL: Issue tracker, https://github.com/pallets/werkzeug/issues +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +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.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +Provides-Extra: dev +Requires-Dist: pytest ; extra == 'dev' +Requires-Dist: coverage ; extra == 'dev' +Requires-Dist: tox ; extra == 'dev' +Requires-Dist: sphinx ; extra == 'dev' +Requires-Dist: pallets-sphinx-themes ; extra == 'dev' +Requires-Dist: sphinx-issues ; extra == 'dev' +Provides-Extra: termcolor +Requires-Dist: termcolor ; extra == 'termcolor' +Provides-Extra: watchdog +Requires-Dist: watchdog ; extra == 'watchdog' + +Werkzeug +======== + +*werkzeug* German noun: "tool". Etymology: *werk* ("work"), *zeug* ("stuff") + +Werkzeug is a comprehensive `WSGI`_ web application library. It began as +a simple collection of various utilities for WSGI applications and has +become one of the most advanced WSGI utility libraries. + +It includes: + +- An interactive debugger that allows inspecting stack traces and + source code in the browser with an interactive interpreter for any + frame in the stack. +- A full-featured request object with objects to interact with + headers, query args, form data, files, and cookies. +- A response object that can wrap other WSGI applications and handle + streaming data. +- A routing system for matching URLs to endpoints and generating URLs + for endpoints, with an extensible system for capturing variables + from URLs. +- HTTP utilities to handle entity tags, cache control, dates, user + agents, cookies, files, and more. +- A threaded WSGI server for use while developing applications + locally. +- A test client for simulating HTTP requests during testing without + requiring running a server. + +Werkzeug is Unicode aware and doesn't enforce any dependencies. It is up +to the developer to choose a template engine, database adapter, and even +how to handle requests. It can be used to build all sorts of end user +applications such as blogs, wikis, or bulletin boards. + +`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while +providing more structure and patterns for defining powerful +applications. + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + pip install -U Werkzeug + + +A Simple Example +---------------- + +.. code-block:: python + + from werkzeug.wrappers import Request, Response + + @Request.application + def application(request): + return Response('Hello, World!') + + if __name__ == '__main__': + from werkzeug.serving import run_simple + run_simple('localhost', 4000, application) + + +Links +----- + +- Website: https://palletsprojects.com/p/werkzeug/ +- Documentation: https://werkzeug.palletsprojects.com/ +- Releases: https://pypi.org/project/Werkzeug/ +- Code: https://github.com/pallets/werkzeug +- Issue tracker: https://github.com/pallets/werkzeug/issues +- Test status: https://dev.azure.com/pallets/werkzeug/_build +- Official chat: https://discord.gg/t6rrQZH + +.. _WSGI: https://wsgi.readthedocs.io/en/latest/ +.. _Flask: https://www.palletsprojects.com/p/flask/ +.. _pip: https://pip.pypa.io/en/stable/quickstart/ + + diff --git a/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/RECORD b/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/RECORD new file mode 100644 index 00000000..8de94dc8 --- /dev/null +++ b/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/RECORD @@ -0,0 +1,119 @@ +Werkzeug-0.16.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +Werkzeug-0.16.0.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475 +Werkzeug-0.16.0.dist-info/METADATA,sha256=BH9_q8z1IK2FbYDS7tSWLsd07z7GDReBgRumclV7T08,4712 +Werkzeug-0.16.0.dist-info/RECORD,, +Werkzeug-0.16.0.dist-info/WHEEL,sha256=8zNYZbwQSXoB9IfXOjPfeNwvAsALAjffgk27FqvCWbo,110 +Werkzeug-0.16.0.dist-info/top_level.txt,sha256=QRyj2VjwJoQkrwjwFIOlB8Xg3r9un0NtqVHQF-15xaw,9 +werkzeug/__init__.py,sha256=tTlHx8lI6FpqB_X9x_zICTy3Rgikur6yIUJr8AE2XTs,7141 +werkzeug/__init__.pyc,, +werkzeug/_compat.py,sha256=oBEVVrJT4sqYdIZbUWmgV9T9w257RhTSDBlTjh0Zbb0,6431 +werkzeug/_compat.pyc,, +werkzeug/_internal.py,sha256=Wx7cpTRWqeBd0LAqobo0lCO4pNUW4oav6XKf7Taumgk,14590 +werkzeug/_internal.pyc,, +werkzeug/_reloader.py,sha256=I3mg3oRQ0lLzl06oEoVopN3bN7CtINuuUQdqDcmTnEs,11531 +werkzeug/_reloader.pyc,, +werkzeug/contrib/__init__.py,sha256=EvNyiiCF49j5P0fZYJ3ZGe82ofXdSBvUNqWFwwBMibQ,553 +werkzeug/contrib/__init__.pyc,, +werkzeug/contrib/atom.py,sha256=KpPJcTfzNW1J0VNQckCbVtVGBe3V8s451tOUya4qByI,15415 +werkzeug/contrib/atom.pyc,, +werkzeug/contrib/cache.py,sha256=AEh5UIw-Ui7sHZnlpvrD7ueOKUhCaAD55FXiPtXbbRs,32115 +werkzeug/contrib/cache.pyc,, +werkzeug/contrib/fixers.py,sha256=peEtAiIWYT5bh00EWEPOGKzGZXivOzVhhzKPvvzk1RM,9193 +werkzeug/contrib/fixers.pyc,, +werkzeug/contrib/iterio.py,sha256=KKHa_8aCF_uhoeQVyPGUwrivuB6y6nNdXYo2D2vzOA8,10928 +werkzeug/contrib/iterio.pyc,, +werkzeug/contrib/lint.py,sha256=NdIxP0E2kVt1xDIxoaIz3Rcl8ZdgmHaFbGTOaybGpN4,296 +werkzeug/contrib/lint.pyc,, +werkzeug/contrib/profiler.py,sha256=k_oMLU-AtsVvQ9TxNdermY6FuzSTYr-WE-ZmWb_DMyU,1229 +werkzeug/contrib/profiler.pyc,, +werkzeug/contrib/securecookie.py,sha256=xbtElskGmtbiApgOJ5WhGgqGDs_68_PcWzqDIAY_QZY,13076 +werkzeug/contrib/securecookie.pyc,, +werkzeug/contrib/sessions.py,sha256=CkJ4IWvNqIaZCP83FMKYFszKL7E6Y1m6YEii7RaTYWs,13040 +werkzeug/contrib/sessions.pyc,, +werkzeug/contrib/wrappers.py,sha256=ZmNk0wpzD66yomPnQxapndZQs4c0kNJaRzqI-BVxeQk,13199 +werkzeug/contrib/wrappers.pyc,, +werkzeug/datastructures.py,sha256=yVH4r-XD8CjOo18tDGVJYiAfezng6pK9hWzzLFy5a94,91761 +werkzeug/datastructures.pyc,, +werkzeug/debug/__init__.py,sha256=Bo3HvgTNY4NQ_2jROTSk3r1ScZcT_g_4EnuHTjKyrKM,18275 +werkzeug/debug/__init__.pyc,, +werkzeug/debug/console.py,sha256=HoBL21bbcmtiCLqiLDJLZi1LYnWMZxjoXYH5WaZB1XY,5469 +werkzeug/debug/console.pyc,, +werkzeug/debug/repr.py,sha256=lIwuhbyrMwVe3P_cFqNyqzHL7P93TLKod7lw9clydEw,9621 +werkzeug/debug/repr.pyc,, +werkzeug/debug/shared/FONT_LICENSE,sha256=LwAVEI1oYnvXiNMT9SnCH_TaLCxCpeHziDrMg0gPkAI,4673 +werkzeug/debug/shared/console.png,sha256=bxax6RXXlvOij_KeqvSNX0ojJf83YbnZ7my-3Gx9w2A,507 +werkzeug/debug/shared/debugger.js,sha256=rOhqZMRfpZnnu6_XCGn6wMWPhtfwRAcyZKksdIxPJas,6400 +werkzeug/debug/shared/jquery.js,sha256=CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo,88145 +werkzeug/debug/shared/less.png,sha256=-4-kNRaXJSONVLahrQKUxMwXGm9R4OnZ9SxDGpHlIR4,191 +werkzeug/debug/shared/more.png,sha256=GngN7CioHQoV58rH6ojnkYi8c_qED2Aka5FO5UXrReY,200 +werkzeug/debug/shared/source.png,sha256=RoGcBTE4CyCB85GBuDGTFlAnUqxwTBiIfDqW15EpnUQ,818 +werkzeug/debug/shared/style.css,sha256=gZ9uhmb5zj3XLuT9RvnMp6jMINgQ-VVBCp-2AZbG3YQ,6604 +werkzeug/debug/shared/ubuntu.ttf,sha256=1eaHFyepmy4FyDvjLVzpITrGEBu_CZYY94jE0nED1c0,70220 +werkzeug/debug/tbtools.py,sha256=SkAAA4KKfwsXJinUbf-AEP4GqONTsR4uU7WPUloXcSE,20318 +werkzeug/debug/tbtools.pyc,, +werkzeug/exceptions.py,sha256=7wl3ufZZU23sASp0ciPe8GJssGND9DX6sDbjxvPuGYU,23437 +werkzeug/exceptions.pyc,, +werkzeug/filesystem.py,sha256=HzKl-j0Hd8Jl66j778UbPTAYNnY6vUZgYLlBZ0e7uw0,2101 +werkzeug/filesystem.pyc,, +werkzeug/formparser.py,sha256=Sto0jZid9im9ZVIf56vilCdyX-arK33wSftkYsLCnzo,21788 +werkzeug/formparser.pyc,, +werkzeug/http.py,sha256=L6r2ehiorjOtsXITW-01zJsvtVa8Emkpkftu9di_cSk,41628 +werkzeug/http.pyc,, +werkzeug/local.py,sha256=USVEcgIg-oCiUJFPIecFIW9jkIejfw4Fjf1u5yN-Np4,14456 +werkzeug/local.pyc,, +werkzeug/middleware/__init__.py,sha256=f1SFZo67IlW4k1uqKzNHxYQlsakUS-D6KK_j0e3jjwQ,549 +werkzeug/middleware/__init__.pyc,, +werkzeug/middleware/dispatcher.py,sha256=_-KoMzHtcISHS7ouWKAOraqlCLprdh83YOAn_8DjLp8,2240 +werkzeug/middleware/dispatcher.pyc,, +werkzeug/middleware/http_proxy.py,sha256=lRjTdMmghHiZuZrS7_UJ3gZc-vlFizhBbFZ-XZPLwIA,7117 +werkzeug/middleware/http_proxy.pyc,, +werkzeug/middleware/lint.py,sha256=ItTwuWJnflF8xMT1uqU_Ty1ryhux-CjeUfskqaUpxsw,12967 +werkzeug/middleware/lint.pyc,, +werkzeug/middleware/profiler.py,sha256=8B_s23d6BGrU_q54gJsm6kcCbOJbTSqrXCsioHON0Xs,4471 +werkzeug/middleware/profiler.pyc,, +werkzeug/middleware/proxy_fix.py,sha256=1hi6AJH-J2uh2hMm1g0u7XfjRiTOoUeIOOmwWZ2n9t0,8670 +werkzeug/middleware/proxy_fix.pyc,, +werkzeug/middleware/shared_data.py,sha256=WtSphPrsUdpEk4E-_09CAILhfOBJ1YtcX1LrxcQfIzw,8224 +werkzeug/middleware/shared_data.pyc,, +werkzeug/posixemulation.py,sha256=gSSiv1SCmOyzOM_nq1ZaZCtxP__C5MeDJl_4yXJmi4Q,3541 +werkzeug/posixemulation.pyc,, +werkzeug/routing.py,sha256=BSgjrYNwj2j5dAHQtK4INEp2TOf4OJP8hBncYSRO2ps,73410 +werkzeug/routing.pyc,, +werkzeug/security.py,sha256=81149MplFq7-hD4RK4sKp9kzXXejjV9D4lWBzaRyeQ8,8106 +werkzeug/security.pyc,, +werkzeug/serving.py,sha256=qqdsTMILMt_B8ffBtROWK3RRpZeyTkQ9g-jhtpJodrY,36607 +werkzeug/serving.pyc,, +werkzeug/test.py,sha256=Cnb5xa3vLDL0hzFCH1fkG_YRpndViGQgCh4D744iSQk,40645 +werkzeug/test.pyc,, +werkzeug/testapp.py,sha256=bHekqMsqRfVxwgFbvOMem-DYa_sdB7R47yUXpt1RUTo,9329 +werkzeug/testapp.pyc,, +werkzeug/urls.py,sha256=hWZMk4ABiJmQPP_B5rRibWTp9gOyNLQpTqq6cmQAfeE,39322 +werkzeug/urls.pyc,, +werkzeug/useragents.py,sha256=0A_Ip74edPv_hy6CouBTpGumi2uyOci01COuzYFOm3U,5622 +werkzeug/useragents.pyc,, +werkzeug/utils.py,sha256=KxCOHhsox7tAVe0m-ZyOGPoCaIbBIy7TxhocaUEHrd4,25050 +werkzeug/utils.pyc,, +werkzeug/wrappers/__init__.py,sha256=S4VioKAmF_av9Ec9zQvG71X1EOkYfPx1TYck9jyDiyY,1384 +werkzeug/wrappers/__init__.pyc,, +werkzeug/wrappers/accept.py,sha256=TIvjUc0g73fhTWX54wg_D9NNzKvpnG1X8u1w26tK1o8,1760 +werkzeug/wrappers/accept.pyc,, +werkzeug/wrappers/auth.py,sha256=Pmn6iaGHBrUyHbJpW0lZhO_q9RVoAa5QalaTqcavdAI,1158 +werkzeug/wrappers/auth.pyc,, +werkzeug/wrappers/base_request.py,sha256=aknREwqVT7WJUxm4weUGdBj90H6rDR3DvsIvmYhaC8A,26943 +werkzeug/wrappers/base_request.pyc,, +werkzeug/wrappers/base_response.py,sha256=ZA1XlxtsbvG4SpbdOEMT5--z7aZM0w6C5y33W8wOXa4,27906 +werkzeug/wrappers/base_response.pyc,, +werkzeug/wrappers/common_descriptors.py,sha256=OJ8jOwMun4L-BxCuFPkK1vaefx_-Y5IndVXvvn_ems4,12089 +werkzeug/wrappers/common_descriptors.pyc,, +werkzeug/wrappers/etag.py,sha256=TwMO1fvluXbBqnFTj2DvrCNa3mYhbHYe1UZAVzfXvuU,12533 +werkzeug/wrappers/etag.pyc,, +werkzeug/wrappers/json.py,sha256=HvK_A4NpO0sLqgb10sTJcoZydYOwyNiPCJPV7SVgcgE,4343 +werkzeug/wrappers/json.pyc,, +werkzeug/wrappers/request.py,sha256=qPo2zmmBv4HxboywtWZb2pJL8OPXo07BUXBKw2j9Fi8,1338 +werkzeug/wrappers/request.pyc,, +werkzeug/wrappers/response.py,sha256=vDZFEGzDOG0jjmS0uVVjeT3hqRt1hFaf15npnx7RD28,2329 +werkzeug/wrappers/response.pyc,, +werkzeug/wrappers/user_agent.py,sha256=YJb-vr12cujG7sQMG9V89VsJa-03SWSenhg1W4cT0EY,435 +werkzeug/wrappers/user_agent.pyc,, +werkzeug/wsgi.py,sha256=iXOR9l1fDd2IgqeTRQZPR6LnBBBx7Xsy97_i2n5HPUo,34666 +werkzeug/wsgi.pyc,, diff --git a/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/WHEEL b/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/WHEEL new file mode 100644 index 00000000..8b701e93 --- /dev/null +++ b/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.33.6) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/top_level.txt b/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/top_level.txt new file mode 100644 index 00000000..6fe8da84 --- /dev/null +++ b/venv/lib/python2.7/site-packages/Werkzeug-0.16.0.dist-info/top_level.txt @@ -0,0 +1 @@ +werkzeug diff --git a/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/DESCRIPTION.rst b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/DESCRIPTION.rst new file mode 100644 index 00000000..0b0953d8 --- /dev/null +++ b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/DESCRIPTION.rst @@ -0,0 +1,50 @@ +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/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/INSTALLER b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/METADATA b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/METADATA new file mode 100644 index 00000000..6ecc70c0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/METADATA @@ -0,0 +1,74 @@ +Metadata-Version: 2.0 +Name: certifi +Version: 2019.9.11 +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/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/RECORD b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/RECORD new file mode 100644 index 00000000..0b727d4d --- /dev/null +++ b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/RECORD @@ -0,0 +1,14 @@ +certifi-2019.9.11.dist-info/DESCRIPTION.rst,sha256=aLNHONztn2ZiBpSTivVFy6EDIWmuNYSsEQwx4NWbvB4,1580 +certifi-2019.9.11.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +certifi-2019.9.11.dist-info/METADATA,sha256=M0Gen7rhgJKIvggZuENXqcZexg74gXdjGC679bMeoDw,2522 +certifi-2019.9.11.dist-info/RECORD,, +certifi-2019.9.11.dist-info/WHEEL,sha256=5wvfB7GvgZAbKBSE9uX9Zbi6LCL-_KgezgHblXhCRnM,113 +certifi-2019.9.11.dist-info/metadata.json,sha256=NppG2TtVr6va5nwyG9pxGhktdvudS-IfpTkaQaWKlBE,1022 +certifi-2019.9.11.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8 +certifi/__init__.py,sha256=WFoavXHhpX-BZ5kbvyinZTbhLsqPJypLKIZu29nUsQg,52 +certifi/__init__.pyc,, +certifi/__main__.py,sha256=FiOYt1Fltst7wk9DRa6GCoBr8qBUxlNQu_MKJf04E6s,41 +certifi/__main__.pyc,, +certifi/cacert.pem,sha256=cVC1b0T-OcQzgdcRql2yMxT7O08O6pcJHnuO9nbLLn0,278533 +certifi/core.py,sha256=EuFc2BsToG5O1-qsx4BSjQ1r1-7WRtH87b1WflZOWhI,218 +certifi/core.pyc,, diff --git a/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/WHEEL b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/WHEEL new file mode 100644 index 00000000..7bf9daa1 --- /dev/null +++ b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.30.0.a0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/metadata.json b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/metadata.json new file mode 100644 index 00000000..7f155f55 --- /dev/null +++ b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/metadata.json @@ -0,0 +1 @@ +{"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7"], "extensions": {"python.details": {"contacts": [{"email": "me@kennethreitz.com", "name": "Kenneth Reitz", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://certifi.io/"}}}, "generator": "bdist_wheel (0.30.0.a0)", "license": "MPL-2.0", "metadata_version": "2.0", "name": "certifi", "summary": "Python package for providing Mozilla's CA Bundle.", "version": "2019.9.11"} \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/top_level.txt b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/top_level.txt new file mode 100644 index 00000000..963eac53 --- /dev/null +++ b/venv/lib/python2.7/site-packages/certifi-2019.9.11.dist-info/top_level.txt @@ -0,0 +1 @@ +certifi diff --git a/venv/lib/python2.7/site-packages/certifi/__init__.py b/venv/lib/python2.7/site-packages/certifi/__init__.py new file mode 100644 index 00000000..8e358e4c --- /dev/null +++ b/venv/lib/python2.7/site-packages/certifi/__init__.py @@ -0,0 +1,3 @@ +from .core import where + +__version__ = "2019.09.11" diff --git a/venv/lib/python2.7/site-packages/certifi/__main__.py b/venv/lib/python2.7/site-packages/certifi/__main__.py new file mode 100644 index 00000000..5f1da0dd --- /dev/null +++ b/venv/lib/python2.7/site-packages/certifi/__main__.py @@ -0,0 +1,2 @@ +from certifi import where +print(where()) diff --git a/venv/lib/python2.7/site-packages/certifi/cacert.pem b/venv/lib/python2.7/site-packages/certifi/cacert.pem new file mode 100644 index 00000000..70fa91f6 --- /dev/null +++ b/venv/lib/python2.7/site-packages/certifi/cacert.pem @@ -0,0 +1,4558 @@ + +# 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----- +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 +-----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----- +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 +-----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----- +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 +-----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----- +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 +-----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----- +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 +mpv0 +-----END CERTIFICATE----- diff --git a/venv/lib/python2.7/site-packages/certifi/core.py b/venv/lib/python2.7/site-packages/certifi/core.py new file mode 100644 index 00000000..7271acf4 --- /dev/null +++ b/venv/lib/python2.7/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 os.path.join(f, 'cacert.pem') diff --git a/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/DESCRIPTION.rst b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/DESCRIPTION.rst new file mode 100644 index 00000000..c0f044d8 --- /dev/null +++ b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/DESCRIPTION.rst @@ -0,0 +1,70 @@ +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/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/INSTALLER b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/METADATA b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/METADATA new file mode 100644 index 00000000..1427867a --- /dev/null +++ b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/METADATA @@ -0,0 +1,96 @@ +Metadata-Version: 2.0 +Name: chardet +Version: 3.0.4 +Summary: Universal encoding detector for Python 2 and 3 +Home-page: https://github.com/chardet/chardet +Author: Daniel Blanchard +Author-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/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/RECORD b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/RECORD new file mode 100644 index 00000000..43e262c7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/RECORD @@ -0,0 +1,91 @@ +../../../bin/chardetect,sha256=rnpVwVV4PWwwKBPlf-mgcy4FvLp8PnvU47OIS_nrV60,266 +chardet-3.0.4.dist-info/DESCRIPTION.rst,sha256=PQ4sBsMyKFZkjC6QpmbpLn0UtCNyeb-ZqvCGEgyZMGk,2174 +chardet-3.0.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +chardet-3.0.4.dist-info/METADATA,sha256=RV_2I4B1Z586DL8oVO5Kp7X5bUdQ5EuKAvNoAEF8wSw,3239 +chardet-3.0.4.dist-info/RECORD,, +chardet-3.0.4.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110 +chardet-3.0.4.dist-info/entry_points.txt,sha256=fAMmhu5eJ-zAJ-smfqQwRClQ3-nozOCmvJ6-E8lgGJo,60 +chardet-3.0.4.dist-info/metadata.json,sha256=0htbRM18ujyGZDdfowgAqj6Hq2eQtwzwyhaEveKntgo,1375 +chardet-3.0.4.dist-info/top_level.txt,sha256=AowzBbZy4x8EirABDdJSLJZMkJ_53iIag8xfKR6D7kI,8 +chardet/__init__.py,sha256=YsP5wQlsHJ2auF1RZJfypiSrCA7_bQiRm3ES_NI76-Y,1559 +chardet/__init__.pyc,, +chardet/big5freq.py,sha256=D_zK5GyzoVsRes0HkLJziltFQX0bKCLOrFe9_xDvO_8,31254 +chardet/big5freq.pyc,, +chardet/big5prober.py,sha256=kBxHbdetBpPe7xrlb-e990iot64g_eGSLd32lB7_h3M,1757 +chardet/big5prober.pyc,, +chardet/chardistribution.py,sha256=3woWS62KrGooKyqz4zQSnjFbJpa6V7g02daAibTwcl8,9411 +chardet/chardistribution.pyc,, +chardet/charsetgroupprober.py,sha256=6bDu8YIiRuScX4ca9Igb0U69TA2PGXXDej6Cc4_9kO4,3787 +chardet/charsetgroupprober.pyc,, +chardet/charsetprober.py,sha256=KSmwJErjypyj0bRZmC5F5eM7c8YQgLYIjZXintZNstg,5110 +chardet/charsetprober.pyc,, +chardet/cli/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +chardet/cli/__init__.pyc,, +chardet/cli/chardetect.py,sha256=YBO8L4mXo0WR6_-Fjh_8QxPBoEBNqB9oNxNrdc54AQs,2738 +chardet/cli/chardetect.pyc,, +chardet/codingstatemachine.py,sha256=VYp_6cyyki5sHgXDSZnXW4q1oelHc3cu9AyQTX7uug8,3590 +chardet/codingstatemachine.pyc,, +chardet/compat.py,sha256=PKTzHkSbtbHDqS9PyujMbX74q1a8mMpeQTDVsQhZMRw,1134 +chardet/compat.pyc,, +chardet/cp949prober.py,sha256=TZ434QX8zzBsnUvL_8wm4AQVTZ2ZkqEEQL_lNw9f9ow,1855 +chardet/cp949prober.pyc,, +chardet/enums.py,sha256=Aimwdb9as1dJKZaFNUH2OhWIVBVd6ZkJJ_WK5sNY8cU,1661 +chardet/enums.pyc,, +chardet/escprober.py,sha256=kkyqVg1Yw3DIOAMJ2bdlyQgUFQhuHAW8dUGskToNWSc,3950 +chardet/escprober.pyc,, +chardet/escsm.py,sha256=RuXlgNvTIDarndvllNCk5WZBIpdCxQ0kcd9EAuxUh84,10510 +chardet/escsm.pyc,, +chardet/eucjpprober.py,sha256=iD8Jdp0ISRjgjiVN7f0e8xGeQJ5GM2oeZ1dA8nbSeUw,3749 +chardet/eucjpprober.pyc,, +chardet/euckrfreq.py,sha256=-7GdmvgWez4-eO4SuXpa7tBiDi5vRXQ8WvdFAzVaSfo,13546 +chardet/euckrfreq.pyc,, +chardet/euckrprober.py,sha256=MqFMTQXxW4HbzIpZ9lKDHB3GN8SP4yiHenTmf8g_PxY,1748 +chardet/euckrprober.pyc,, +chardet/euctwfreq.py,sha256=No1WyduFOgB5VITUA7PLyC5oJRNzRyMbBxaKI1l16MA,31621 +chardet/euctwfreq.pyc,, +chardet/euctwprober.py,sha256=13p6EP4yRaxqnP4iHtxHOJ6R2zxHq1_m8hTRjzVZ95c,1747 +chardet/euctwprober.pyc,, +chardet/gb2312freq.py,sha256=JX8lsweKLmnCwmk8UHEQsLgkr_rP_kEbvivC4qPOrlc,20715 +chardet/gb2312freq.pyc,, +chardet/gb2312prober.py,sha256=gGvIWi9WhDjE-xQXHvNIyrnLvEbMAYgyUSZ65HUfylw,1754 +chardet/gb2312prober.pyc,, +chardet/hebrewprober.py,sha256=c3SZ-K7hvyzGY6JRAZxJgwJ_sUS9k0WYkvMY00YBYFo,13838 +chardet/hebrewprober.pyc,, +chardet/jisfreq.py,sha256=vpmJv2Bu0J8gnMVRPHMFefTRvo_ha1mryLig8CBwgOg,25777 +chardet/jisfreq.pyc,, +chardet/jpcntx.py,sha256=PYlNqRUQT8LM3cT5FmHGP0iiscFlTWED92MALvBungo,19643 +chardet/jpcntx.pyc,, +chardet/langbulgarianmodel.py,sha256=1HqQS9Pbtnj1xQgxitJMvw8X6kKr5OockNCZWfEQrPE,12839 +chardet/langbulgarianmodel.pyc,, +chardet/langcyrillicmodel.py,sha256=LODajvsetH87yYDDQKA2CULXUH87tI223dhfjh9Zx9c,17948 +chardet/langcyrillicmodel.pyc,, +chardet/langgreekmodel.py,sha256=8YAW7bU8YwSJap0kIJSbPMw1BEqzGjWzqcqf0WgUKAA,12688 +chardet/langgreekmodel.pyc,, +chardet/langhebrewmodel.py,sha256=JSnqmE5E62tDLTPTvLpQsg5gOMO4PbdWRvV7Avkc0HA,11345 +chardet/langhebrewmodel.pyc,, +chardet/langhungarianmodel.py,sha256=RhapYSG5l0ZaO-VV4Fan5sW0WRGQqhwBM61yx3yxyOA,12592 +chardet/langhungarianmodel.pyc,, +chardet/langthaimodel.py,sha256=8l0173Gu_W6G8mxmQOTEF4ls2YdE7FxWf3QkSxEGXJQ,11290 +chardet/langthaimodel.pyc,, +chardet/langturkishmodel.py,sha256=W22eRNJsqI6uWAfwXSKVWWnCerYqrI8dZQTm_M0lRFk,11102 +chardet/langturkishmodel.pyc,, +chardet/latin1prober.py,sha256=S2IoORhFk39FEFOlSFWtgVybRiP6h7BlLldHVclNkU8,5370 +chardet/latin1prober.pyc,, +chardet/mbcharsetprober.py,sha256=AR95eFH9vuqSfvLQZN-L5ijea25NOBCoXqw8s5O9xLQ,3413 +chardet/mbcharsetprober.pyc,, +chardet/mbcsgroupprober.py,sha256=h6TRnnYq2OxG1WdD5JOyxcdVpn7dG0q-vB8nWr5mbh4,2012 +chardet/mbcsgroupprober.pyc,, +chardet/mbcssm.py,sha256=SY32wVIF3HzcjY3BaEspy9metbNSKxIIB0RKPn7tjpI,25481 +chardet/mbcssm.pyc,, +chardet/sbcharsetprober.py,sha256=LDSpCldDCFlYwUkGkwD2oFxLlPWIWXT09akH_2PiY74,5657 +chardet/sbcharsetprober.pyc,, +chardet/sbcsgroupprober.py,sha256=1IprcCB_k1qfmnxGC6MBbxELlKqD3scW6S8YIwdeyXA,3546 +chardet/sbcsgroupprober.pyc,, +chardet/sjisprober.py,sha256=IIt-lZj0WJqK4rmUZzKZP4GJlE8KUEtFYVuY96ek5MQ,3774 +chardet/sjisprober.pyc,, +chardet/universaldetector.py,sha256=qL0174lSZE442eB21nnktT9_VcAye07laFWUeUrjttY,12485 +chardet/universaldetector.pyc,, +chardet/utf8prober.py,sha256=IdD8v3zWOsB8OLiyPi-y_fqwipRFxV9Nc1eKBLSuIEw,2766 +chardet/utf8prober.pyc,, +chardet/version.py,sha256=sp3B08mrDXB-pf3K9fqJ_zeDHOCLC8RrngQyDFap_7g,242 +chardet/version.pyc,, diff --git a/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/WHEEL b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/WHEEL new file mode 100644 index 00000000..8b6dd1b5 --- /dev/null +++ b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.29.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/entry_points.txt b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/entry_points.txt new file mode 100644 index 00000000..a884269e --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/metadata.json b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/metadata.json new file mode 100644 index 00000000..8cdf0256 --- /dev/null +++ b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/metadata.json @@ -0,0 +1 @@ +{"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing :: Linguistic"], "extensions": {"python.commands": {"wrap_console": {"chardetect": "chardet.cli.chardetect:main"}}, "python.details": {"contacts": [{"email": "dan.blanchard@gmail.com", "name": "Daniel Blanchard", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/chardet/chardet"}}, "python.exports": {"console_scripts": {"chardetect": "chardet.cli.chardetect:main"}}}, "generator": "bdist_wheel (0.29.0)", "keywords": ["encoding", "i18n", "xml"], "license": "LGPL", "metadata_version": "2.0", "name": "chardet", "summary": "Universal encoding detector for Python 2 and 3", "test_requires": [{"requires": ["hypothesis", "pytest"]}], "version": "3.0.4"} \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/top_level.txt b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/top_level.txt new file mode 100644 index 00000000..79236f25 --- /dev/null +++ b/venv/lib/python2.7/site-packages/chardet-3.0.4.dist-info/top_level.txt @@ -0,0 +1 @@ +chardet diff --git a/venv/lib/python2.7/site-packages/chardet/__init__.py b/venv/lib/python2.7/site-packages/chardet/__init__.py new file mode 100644 index 00000000..0f9f820e --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/big5freq.py b/venv/lib/python2.7/site-packages/chardet/big5freq.py new file mode 100644 index 00000000..38f32517 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/big5prober.py b/venv/lib/python2.7/site-packages/chardet/big5prober.py new file mode 100644 index 00000000..98f99701 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/chardistribution.py b/venv/lib/python2.7/site-packages/chardet/chardistribution.py new file mode 100644 index 00000000..c0395f4a --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/charsetgroupprober.py b/venv/lib/python2.7/site-packages/chardet/charsetgroupprober.py new file mode 100644 index 00000000..8b3738ef --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/charsetprober.py b/venv/lib/python2.7/site-packages/chardet/charsetprober.py new file mode 100644 index 00000000..eac4e598 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/cli/__init__.py b/venv/lib/python2.7/site-packages/chardet/cli/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/venv/lib/python2.7/site-packages/chardet/cli/__init__.py @@ -0,0 +1 @@ + diff --git a/venv/lib/python2.7/site-packages/chardet/cli/chardetect.py b/venv/lib/python2.7/site-packages/chardet/cli/chardetect.py new file mode 100644 index 00000000..f0a4cc5d --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/codingstatemachine.py b/venv/lib/python2.7/site-packages/chardet/codingstatemachine.py new file mode 100644 index 00000000..68fba44f --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/compat.py b/venv/lib/python2.7/site-packages/chardet/compat.py new file mode 100644 index 00000000..ddd74687 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/cp949prober.py b/venv/lib/python2.7/site-packages/chardet/cp949prober.py new file mode 100644 index 00000000..efd793ab --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/enums.py b/venv/lib/python2.7/site-packages/chardet/enums.py new file mode 100644 index 00000000..04512072 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/escprober.py b/venv/lib/python2.7/site-packages/chardet/escprober.py new file mode 100644 index 00000000..c70493f2 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/escsm.py b/venv/lib/python2.7/site-packages/chardet/escsm.py new file mode 100644 index 00000000..0069523a --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/eucjpprober.py b/venv/lib/python2.7/site-packages/chardet/eucjpprober.py new file mode 100644 index 00000000..20ce8f7d --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/euckrfreq.py b/venv/lib/python2.7/site-packages/chardet/euckrfreq.py new file mode 100644 index 00000000..b68078cb --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/euckrprober.py b/venv/lib/python2.7/site-packages/chardet/euckrprober.py new file mode 100644 index 00000000..345a060d --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/euctwfreq.py b/venv/lib/python2.7/site-packages/chardet/euctwfreq.py new file mode 100644 index 00000000..ed7a995a --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/euctwprober.py b/venv/lib/python2.7/site-packages/chardet/euctwprober.py new file mode 100644 index 00000000..35669cc4 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/gb2312freq.py b/venv/lib/python2.7/site-packages/chardet/gb2312freq.py new file mode 100644 index 00000000..697837bd --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/gb2312prober.py b/venv/lib/python2.7/site-packages/chardet/gb2312prober.py new file mode 100644 index 00000000..8446d2dd --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/hebrewprober.py b/venv/lib/python2.7/site-packages/chardet/hebrewprober.py new file mode 100644 index 00000000..b0e1bf49 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/jisfreq.py b/venv/lib/python2.7/site-packages/chardet/jisfreq.py new file mode 100644 index 00000000..83fc082b --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/jpcntx.py b/venv/lib/python2.7/site-packages/chardet/jpcntx.py new file mode 100644 index 00000000..20044e4b --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/langbulgarianmodel.py b/venv/lib/python2.7/site-packages/chardet/langbulgarianmodel.py new file mode 100644 index 00000000..2aa4fb2e --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/langcyrillicmodel.py b/venv/lib/python2.7/site-packages/chardet/langcyrillicmodel.py new file mode 100644 index 00000000..e5f9a1fd --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/langgreekmodel.py b/venv/lib/python2.7/site-packages/chardet/langgreekmodel.py new file mode 100644 index 00000000..53322216 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/langhebrewmodel.py b/venv/lib/python2.7/site-packages/chardet/langhebrewmodel.py new file mode 100644 index 00000000..58f4c875 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/langhungarianmodel.py b/venv/lib/python2.7/site-packages/chardet/langhungarianmodel.py new file mode 100644 index 00000000..bb7c095e --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/langthaimodel.py b/venv/lib/python2.7/site-packages/chardet/langthaimodel.py new file mode 100644 index 00000000..15f94c2d --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/langturkishmodel.py b/venv/lib/python2.7/site-packages/chardet/langturkishmodel.py new file mode 100644 index 00000000..a427a457 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/latin1prober.py b/venv/lib/python2.7/site-packages/chardet/latin1prober.py new file mode 100644 index 00000000..7d1e8c20 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/mbcharsetprober.py b/venv/lib/python2.7/site-packages/chardet/mbcharsetprober.py new file mode 100644 index 00000000..6256ecfd --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/mbcsgroupprober.py b/venv/lib/python2.7/site-packages/chardet/mbcsgroupprober.py new file mode 100644 index 00000000..530abe75 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/mbcssm.py b/venv/lib/python2.7/site-packages/chardet/mbcssm.py new file mode 100644 index 00000000..8360d0f2 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/sbcharsetprober.py b/venv/lib/python2.7/site-packages/chardet/sbcharsetprober.py new file mode 100644 index 00000000..0adb51de --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/sbcsgroupprober.py b/venv/lib/python2.7/site-packages/chardet/sbcsgroupprober.py new file mode 100644 index 00000000..98e95dc1 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/sjisprober.py b/venv/lib/python2.7/site-packages/chardet/sjisprober.py new file mode 100644 index 00000000..9e29623b --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/universaldetector.py b/venv/lib/python2.7/site-packages/chardet/universaldetector.py new file mode 100644 index 00000000..7b4e92d6 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/utf8prober.py b/venv/lib/python2.7/site-packages/chardet/utf8prober.py new file mode 100644 index 00000000..6c3196cc --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/chardet/version.py b/venv/lib/python2.7/site-packages/chardet/version.py new file mode 100644 index 00000000..bb2a34a7 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/ckan-2.8.3-py2.7-nspkg.pth b/venv/lib/python2.7/site-packages/ckan-2.8.3-py2.7-nspkg.pth new file mode 100644 index 00000000..d52397c9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan-2.8.3-py2.7-nspkg.pth @@ -0,0 +1,3 @@ +import sys, types, os;has_mfs = sys.version_info > (3, 5);p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('ckanext',));importlib = has_mfs and __import__('importlib.util');has_mfs and __import__('importlib.machinery');m = has_mfs and sys.modules.setdefault('ckanext', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('ckanext', [os.path.dirname(p)])));m = m or sys.modules.setdefault('ckanext', types.ModuleType('ckanext'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p) +import sys, types, os;has_mfs = sys.version_info > (3, 5);p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('ckanext',));importlib = has_mfs and __import__('importlib.util');has_mfs and __import__('importlib.machinery');m = has_mfs and sys.modules.setdefault('ckanext', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('ckanext', [os.path.dirname(p)])));m = m or sys.modules.setdefault('ckanext', types.ModuleType('ckanext'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p) +import sys, types, os;has_mfs = sys.version_info > (3, 5);p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('ckanext', 'stats'));importlib = has_mfs and __import__('importlib.util');has_mfs and __import__('importlib.machinery');m = has_mfs and sys.modules.setdefault('ckanext.stats', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('ckanext.stats', [os.path.dirname(p)])));m = m or sys.modules.setdefault('ckanext.stats', types.ModuleType('ckanext.stats'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p);m and setattr(sys.modules['ckanext'], 'stats', m) diff --git a/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/INSTALLER b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/METADATA b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/METADATA new file mode 100644 index 00000000..4b4dec49 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/METADATA @@ -0,0 +1,28 @@ +Metadata-Version: 2.1 +Name: ckan +Version: 2.8.3 +Summary: CKAN Software +Home-page: http://ckan.org/ +Author: https://github.com/ckan/ckan/graphs/contributors +Author-email: info@ckan.org +License: AGPL +Keywords: data packaging component tool server +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+) +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 :: Only +Classifier: Programming Language :: Python :: 2.7 + +CKAN is the world's leading Open Source data portal platform. + +It powers dozens of Open Data portals around the world, including +data.gov, open.canada.ca and europeandataportal.eu but also regional, +research and community organizations. + +It makes easy to publish, share and find data online and is fully +customizable via extensions and plugins. + +Check https://ckan.org to know more. + + diff --git a/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/RECORD b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/RECORD new file mode 100644 index 00000000..72ac05ec --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/RECORD @@ -0,0 +1,2168 @@ +../../../bin/ckan-admin,sha256=B7xL7hMk85K2o2xukZ8clVM05K91T0xz78E59x8ffHo,264 +ckan-2.8.3-py2.7-nspkg.pth,sha256=ftGZP8zPfCAFKHkqMmz47kdo7Swh181qt6ukOhMGzTg,1714 +ckan-2.8.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +ckan-2.8.3.dist-info/METADATA,sha256=RPlDH8wJ4pm1Y1WE0EXqD2Q4YX0e3ArksX7GPwlNRgw,949 +ckan-2.8.3.dist-info/RECORD,, +ckan-2.8.3.dist-info/WHEEL,sha256=rhIUU3k_gV2iCypONLq-yw8hAvGuX-BoDgL_ovBiHW0,93 +ckan-2.8.3.dist-info/entry_points.txt,sha256=Yl340yylgYchQlMtACP0sQ-plcyjahFSqRhmvJy7-4M,10231 +ckan-2.8.3.dist-info/namespace_packages.txt,sha256=CWaQZM32iYP8oZYNwA9d6CCwRo-6mLH0tGd4FH9yt-s,22 +ckan-2.8.3.dist-info/top_level.txt,sha256=EBG8IrGCqTAz8Zjnp7bGtT-yxuhhyfAwiig5555X4Yw,13 +ckan/__init__.py,sha256=1AxdYA-ng25csE3I5Zagzz8aaTIkgjT0p_LDaaQz6ME,628 +ckan/__init__.pyc,, +ckan/authz.py,sha256=6InbXP1CgCg0BRcG6qVcPylmBQdfWNHjMQzBZosVZjw,15782 +ckan/authz.pyc,, +ckan/ckan_nose_plugin.py,sha256=V_v3MQ1RtnEL6HTjF5R8dACl64tYuc03uMT0xjVLoEs,4503 +ckan/ckan_nose_plugin.pyc,, +ckan/common.py,sha256=kTAO9GtKAJxSCS9qyMpXO_mQq-2Jycc7zguQcriNeaU,5526 +ckan/common.pyc,, +ckan/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/config/__init__.pyc,, +ckan/config/deployment.ini_tmpl,sha256=yI_Ppa_YGGEF7P5wrzFpiCPgQYOQRF71Er9kXPbU_Qc,6327 +ckan/config/environment.py,sha256=2M_H_gN6RoxR5iT23XLvYGj5ff6JzxRgf8eIv0Mr1_s,12027 +ckan/config/environment.pyc,, +ckan/config/install.py,sha256=NVF9iOthW9vc8PfXWvmtW0gWJPmZhTwZNKvgQOT9r7Y,568 +ckan/config/install.pyc,, +ckan/config/middleware/__init__.py,sha256=Y2yhQxhxVh_JAsNkFVdTlcwJpYGF8G0T1kRdJddGT58,7867 +ckan/config/middleware/__init__.pyc,, +ckan/config/middleware/common_middleware.py,sha256=PC-RB_AP08X4lxTIo_UkQZoBO8-B2tX4R67OllgExTE,3351 +ckan/config/middleware/common_middleware.pyc,, +ckan/config/middleware/flask_app.py,sha256=MuIMcuW2xQS6ryH1bJvV4nIJotJtEeOcr70RwSSqy4Y,14567 +ckan/config/middleware/flask_app.pyc,, +ckan/config/middleware/pylons_app.py,sha256=6MKTtBfNihL6A66xUYFngRxjnvQA9jJ4UzRFB_u0m6o,9757 +ckan/config/middleware/pylons_app.pyc,, +ckan/config/resource_formats.json,sha256=w5wjg-ARxnc6sbpYwUV3YO5NVOHbNFZv8nvJ8ofllow,5606 +ckan/config/routing.py,sha256=kjwTrmwjzJ9itIE2-2HyvDv4q0gESwVrP1ktWttv3lo,14076 +ckan/config/routing.pyc,, +ckan/config/solr/schema.xml,sha256=bmuwe_oz1PCSs29FqNA3AriDHQRgMTgZ4kEAbcQzBZ8,11943 +ckan/config/who.ini,sha256=TXZU1RZicA40zii0K7M0q4CEPT30kOyKDGVgkobJum8,897 +ckan/controllers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/controllers/__init__.pyc,, +ckan/controllers/admin.py,sha256=L800_qCp1xDQyaGpGNw_DRcTo9nRCrCVoAbQ1FaFrsY,9068 +ckan/controllers/admin.pyc,, +ckan/controllers/api.py,sha256=9zZBD9rgVFs_gP03-igj5Sk78R2V4ZFIdPUsw3_GZRc,25554 +ckan/controllers/api.pyc,, +ckan/controllers/error.py,sha256=WRQYnPBwcLcK8-1a097WQBMGlGO6R_22TIeNXs-aCXw,2609 +ckan/controllers/error.pyc,, +ckan/controllers/feed.py,sha256=0ti-sNRwk1mUkDUlzvNkgaxgAcm56xxTAcjq3hmEQjA,22497 +ckan/controllers/feed.pyc,, +ckan/controllers/group.py,sha256=IBnz4Fxivu6Gp2R14yOFcUtrcg-qw0nn0O-7U0KF3W4,37028 +ckan/controllers/group.pyc,, +ckan/controllers/home.py,sha256=jB1ty0TzQFRZrvJTw0pyF266S-ZUzprWurbJKrs0vSo,3417 +ckan/controllers/home.pyc,, +ckan/controllers/organization.py,sha256=9wfrOEGdUkk60Q0BYrp8U0QSFSuNyEOr6hc3HLEfuFY,1054 +ckan/controllers/organization.pyc,, +ckan/controllers/package.py,sha256=EolPH2Sx6iDBSXpegbCFCIqTFC3i8ybKZTntflWG0fo,67137 +ckan/controllers/package.pyc,, +ckan/controllers/revision.py,sha256=OWsENfkOVrCI_gmLu-s8TP-xT0mSnAWVNbPrunPTggc,8157 +ckan/controllers/revision.pyc,, +ckan/controllers/storage.py,sha256=uHL_du_Kz6oHUHLuk8kUEgbOpyVfnVsaoDqNo7aUeCU,2908 +ckan/controllers/storage.pyc,, +ckan/controllers/tag.py,sha256=5lBPNcRmOGek7xEvZPrmFmzdPlUEJ5FIQNQu6ohOo2k,2436 +ckan/controllers/tag.pyc,, +ckan/controllers/template.py,sha256=gv5rMaCiiSVj9larcJLjIm3ZNIMfeFcHoAmLzUZo5Ww,1485 +ckan/controllers/template.pyc,, +ckan/controllers/user.py,sha256=MdJztiVOs5UJSkAWDfVpfnwYiIraXPUCHvKd3Fizh6U,29156 +ckan/controllers/user.pyc,, +ckan/controllers/util.py,sha256=QSv7PPQWIVhP-8qlCxuykQLnc_pGGJ8FJriPuFHgGAE,1378 +ckan/controllers/util.pyc,, +ckan/exceptions.py,sha256=IVX4ArLOr_WEbBebk-oGPDGVs11qL4znl9fCOVZsa7w,673 +ckan/exceptions.pyc,, +ckan/i18n/__init__.py,sha256=O2SB6K6Dm0bQVTtWNsr1I36va9h7mFwWYZlRoGwxxXE,84 +ckan/i18n/__init__.pyc,sha256=9A7WfE9mbhj45MQd9QGlFqOCjb_F4OxYRxyqAMXwVD4,145 +ckan/i18n/am/LC_MESSAGES/ckan.po,sha256=pWJyjekDOA6Uxj9fhwok119ZBlF6B19FjVS7PEBuLYc,184124 +ckan/i18n/ar/LC_MESSAGES/ckan.mo,sha256=KWYZY9CotfMOL5rM-nZd8vRwHC5m4VgdJ2VasxFSQnQ,91619 +ckan/i18n/ar/LC_MESSAGES/ckan.po,sha256=Kll0XC4ZapYmji0C0LnENe80-PqTeg5Bg4NIsNBS7zk,171798 +ckan/i18n/bg/LC_MESSAGES/ckan.mo,sha256=UX5RkgrBLwDSx6MkIwd99Zfgp56JcXUCr8rgEPnLK20,98178 +ckan/i18n/bg/LC_MESSAGES/ckan.po,sha256=xfaJNHSRZR-2AK17Z5lEYJ1_928_vAKcGkg1cEiFeCU,177970 +ckan/i18n/ca/LC_MESSAGES/ckan.mo,sha256=ptFp1vPlzZKY7SP_0zBX6tzad9Ihfr_0syhKoFk0epM,80076 +ckan/i18n/ca/LC_MESSAGES/ckan.po,sha256=JzBV3hGmkVMlW1bn2hvLDWt7fQmFK2tvL5MQe3pmfgg,166507 +ckan/i18n/check_po_files.py,sha256=uBqhZVTmQ9FE0R4SxaVPYpprMVyWd3RWBDxXeggGZVg,2819 +ckan/i18n/check_po_files.pyc,, +ckan/i18n/ckan.pot,sha256=5CO_ufVjyMIt9fjGPAn_dX5vPS-4__z4_K1n77opm10,131460 +ckan/i18n/cs_CZ/LC_MESSAGES/ckan.mo,sha256=uArNEMEubi87r7DhxNe2ZKwpgtJoIFqKbWa2fuu3Nq8,81908 +ckan/i18n/cs_CZ/LC_MESSAGES/ckan.po,sha256=DKVAJnoM8IrU96xEYZEhKfMNWE-XgMlVT-cI_oBaQ9s,168903 +ckan/i18n/da_DK/LC_MESSAGES/ckan.mo,sha256=n5JoKJ8py4sIw0u-rYY1MRjHPm1dEwNzypbEiYhNYyA,76823 +ckan/i18n/da_DK/LC_MESSAGES/ckan.po,sha256=6nFIt-ki5vsJK8yooD_pDm3HkqC_DslX36VSJj4FLGI,156271 +ckan/i18n/de/LC_MESSAGES/ckan.mo,sha256=oyb2x_h8RpnMgzr3gfOGkSoLeaEUPd6bgD_DUArv9_k,81029 +ckan/i18n/de/LC_MESSAGES/ckan.po,sha256=zMyaRASu-YIklxpF2J9Lh1z_8O0UYJOkKCaIRA7JUxo,167524 +ckan/i18n/el/LC_MESSAGES/ckan.mo,sha256=ZG9Mmkg16TGi1xRXllTcvBkOoNQ3dETLJUkeWwb2HlA,109880 +ckan/i18n/el/LC_MESSAGES/ckan.po,sha256=at_KpxKz0EEzcxalzdOkWYftKeWOTo0jPlt98wJqEQc,196499 +ckan/i18n/en_AU/LC_MESSAGES/ckan.mo,sha256=6scyOEZ_rW9kv2T4q3tMw5vGDMRnBGD5Pjv8tx_Llws,74532 +ckan/i18n/en_AU/LC_MESSAGES/ckan.po,sha256=ud8uaIGR_oAIG2E4VHjMLDTkiciguf_0W8982FIZF2o,158458 +ckan/i18n/en_GB/LC_MESSAGES/ckan.mo,sha256=auKRNc45Sd81gnUmnRRG3GAQatBG2t3gn-tyZosbvBc,74536 +ckan/i18n/en_GB/LC_MESSAGES/ckan.po,sha256=6hd-6qx9bh3rkPfuCY6sLbcbqpuy-fE8cqpAQsrzg9Q,159812 +ckan/i18n/es/LC_MESSAGES/ckan.mo,sha256=6g3RYsuNBYQVXOilGg-56Oqo6VfcDdGFGEqiZqmgWYg,81445 +ckan/i18n/es/LC_MESSAGES/ckan.po,sha256=q83wLWFWK-6v_b7EDrilaBLcrVQUqfFWns8NdA5sVbs,168091 +ckan/i18n/es_AR/LC_MESSAGES/ckan.mo,sha256=llFSqr164m8v_mJWRrV_I3RSkP3OZAllMbQgR9D6LoE,81364 +ckan/i18n/es_AR/LC_MESSAGES/ckan.po,sha256=EwBtll0DlHHuZD9LmDJnBzO-tlmNnd0Qdpo2Gqn71iM,167856 +ckan/i18n/eu/LC_MESSAGES/ckan.po,sha256=pkfz4y3DpafNatbhMlU9S-0krtxtwKKYeCUtv1XzvXk,164338 +ckan/i18n/fa_IR/LC_MESSAGES/ckan.mo,sha256=w4GgCyVnntp9mbUbcRmNakpLibmCXsVqS5npwrJlId4,75570 +ckan/i18n/fa_IR/LC_MESSAGES/ckan.po,sha256=Zww9VsQUJ5Mf17yTVV0v3ykZZ2zhhCnYUj1Swq1mjU4,134145 +ckan/i18n/fi/LC_MESSAGES/ckan.mo,sha256=T-G5YENDskDViNxDEcbnf-1-r0LtNNrUT3SOYhUfG0Y,78742 +ckan/i18n/fi/LC_MESSAGES/ckan.po,sha256=RNipfCiMALuykzaahF1IDs3rx_7X9h8qE7NpZVQI_F4,164178 +ckan/i18n/fr/LC_MESSAGES/ckan.mo,sha256=cOSGWABdP5Wvrxrk5d79z38X9sOtxELUBCavEKCQ-4U,84083 +ckan/i18n/fr/LC_MESSAGES/ckan.po,sha256=VzR32pDdx_soXZwlZnnQIusajfCnvmP4bEYoba8B-WA,169914 +ckan/i18n/gl/LC_MESSAGES/ckan.mo,sha256=zfxzhRy-_l-hchKsMkE8qnTyewGYl8ru1XPwbxOiv98,76430 +ckan/i18n/gl/LC_MESSAGES/ckan.po,sha256=kfct68QdkD8QkvUGLSEpKOw97fzR-yBUlDxhhkrbKz4,139494 +ckan/i18n/he/LC_MESSAGES/ckan.mo,sha256=0GH4JKRKH-lHc1Zv3CiO9TnrR8erB5NK51I843CYGwQ,87583 +ckan/i18n/he/LC_MESSAGES/ckan.po,sha256=x5UBAbyXicS2Ys-PYqMgNTShID6UAWe9syujur4Kj4w,169927 +ckan/i18n/hr/LC_MESSAGES/ckan.mo,sha256=NZWwGXfwLVU3BPFiJBc5ENX5jEp-WSawQC9pR5kzLek,78398 +ckan/i18n/hr/LC_MESSAGES/ckan.po,sha256=TkbeVwRqcN0MJ2y123-GjJnZstaLF700pmX80NCqXr0,159092 +ckan/i18n/hu/LC_MESSAGES/ckan.mo,sha256=lJLg1mDemVTjkA2HJVKUgTXmWjSlW8Ho6yU5KP_5BCE,76155 +ckan/i18n/hu/LC_MESSAGES/ckan.po,sha256=FBMtasiBjqp7qfXeetPccys2L13Fk7woIaGol_PUzFc,139567 +ckan/i18n/id/LC_MESSAGES/ckan.mo,sha256=e9Uhzbp2dXR7Pc6mHjaekW4YGSC-U1oBn--vtFzqvDk,75167 +ckan/i18n/id/LC_MESSAGES/ckan.po,sha256=7n6QJx3cq3VJojGcEcqtmg-Iszo_gE4rNeByT86OUFc,140511 +ckan/i18n/is/LC_MESSAGES/ckan.mo,sha256=p9kj4NwbCqEeACW7R-0ZvORwoz7Ug4ibtYmXCVZOnJg,79412 +ckan/i18n/is/LC_MESSAGES/ckan.po,sha256=LdiFKT-nPzVUXtXGfeKtxfpxaLLeLkSw2OUwbex6uFA,163307 +ckan/i18n/it/LC_MESSAGES/ckan.mo,sha256=B0ZdBGejWMOg9T4qLWojjtkDDBntKobBTaXt2jvLvRs,79331 +ckan/i18n/it/LC_MESSAGES/ckan.po,sha256=fKDBkRSAF6iq-7nZPLQfp6cZBboQ7TOHJyRSaQVTqtI,163605 +ckan/i18n/ja/LC_MESSAGES/ckan.mo,sha256=NB_8oVKv3anYCy638ZbDg5r6IXQA9QytUdLxgMw67BI,86998 +ckan/i18n/ja/LC_MESSAGES/ckan.po,sha256=ipS2mKcb7lcWJ1tsPPiCdQLHMqpdmvMmwM-g9WBMHiM,172825 +ckan/i18n/km/LC_MESSAGES/ckan.mo,sha256=YLCKtV2OPDvWK3gr_rjCm_IPorpjrOMt8wVKth4ViEo,84289 +ckan/i18n/km/LC_MESSAGES/ckan.po,sha256=vW5jtWaq50TmKFDj6SAKP-E-eWIsotrYC0Et14DeayY,145810 +ckan/i18n/ko_KR/LC_MESSAGES/ckan.mo,sha256=u5HzGNlaaeOvsiGY6aPJ6FoiI5TEjIhadhHyDTzGirk,80671 +ckan/i18n/ko_KR/LC_MESSAGES/ckan.po,sha256=yWZYuAJIH6rQmYt3vAoN4_M_ai7DnRdJi9kFynTU3W4,165609 +ckan/i18n/lt/LC_MESSAGES/ckan.mo,sha256=5ZgWkepjxfGpUM1eLMwK4i0uLkXrKmCBTCW16Y0l6ec,78221 +ckan/i18n/lt/LC_MESSAGES/ckan.po,sha256=9iEzFvj2sT_PMVFI-LIkilsuDEM0TTzTzBuepfXZ-V0,150164 +ckan/i18n/lv/LC_MESSAGES/ckan.mo,sha256=j3wqA_q3YQCHYyYLPX4ySOBG4z1Oi5T_FdNBkxdqQY4,78878 +ckan/i18n/lv/LC_MESSAGES/ckan.po,sha256=vuJasQCOKYrUS0txX7DWJVGQUltvjarrGzaPjJFLh1w,163717 +ckan/i18n/mk/LC_MESSAGES/ckan.mo,sha256=mdxouqDg2Fz0fxr_4_xPLRzpvmTCXQXq_NG4RhHlE1A,105301 +ckan/i18n/mk/LC_MESSAGES/ckan.po,sha256=PBsNJE__G2_iJ3kmLx6oH0kze_VurYvtZ27YCVKGACM,191834 +ckan/i18n/mn_MN/LC_MESSAGES/ckan.mo,sha256=oEtJjvNS1c32QWAKEfniaMWYx2BstyNAmQUa_dL-_Bg,100782 +ckan/i18n/mn_MN/LC_MESSAGES/ckan.po,sha256=2HjCbQk42jeOlJ0PrfZHmNekUAGARmXIX6vN81V_LXI,185965 +ckan/i18n/ne/LC_MESSAGES/ckan.mo,sha256=qLOO4DdLAGBhWnGxuix_KLjh1iJXr4xgQ8bgq2tovfw,74796 +ckan/i18n/ne/LC_MESSAGES/ckan.po,sha256=dt3wkYGaQgE07WCtNs0BJzRsr_vtXGm0Nqhutg0Edcg,133463 +ckan/i18n/nl/LC_MESSAGES/ckan.mo,sha256=vq41NYCep9mlmA1fv7H_5zkL7kbefrwh2IxJFXYRDnM,77487 +ckan/i18n/nl/LC_MESSAGES/ckan.po,sha256=6nPK75sKzN5Q69UaJXrAOfhyZr7b-zxBeqEeHeIioSo,161878 +ckan/i18n/no/LC_MESSAGES/ckan.mo,sha256=gBExb7blOQ2_NWoIaESrWswOnCn4tgE3u5D1xsXFfps,76782 +ckan/i18n/no/LC_MESSAGES/ckan.po,sha256=OtkU1kYpo_FVGHqlr0F-hYuLO0i2MLJhwuSK_bMIxfE,162205 +ckan/i18n/pl/LC_MESSAGES/ckan.mo,sha256=k_Mhz6vk-QhRhrGGa1R_2rqONQs-RCLhsNg-kLEAh2U,77222 +ckan/i18n/pl/LC_MESSAGES/ckan.po,sha256=U_Ihx67ti-p1M4zpIdZw1ucBa32-YPdmviUVw2ekahg,139447 +ckan/i18n/pt_BR/LC_MESSAGES/ckan.mo,sha256=hvMluKvU3pCggxyTskUzs31SfQYQtKTnCboxVdZVOtk,81020 +ckan/i18n/pt_BR/LC_MESSAGES/ckan.po,sha256=SvmguZpbXuTdpxclrBDkYRqZeKaglijlMdBR2Y5QRRA,166808 +ckan/i18n/pt_PT/LC_MESSAGES/ckan.mo,sha256=R3JuIrO9eVcj7iP1SHjGITEw01VzkCrwppku8EWiuR0,81428 +ckan/i18n/pt_PT/LC_MESSAGES/ckan.po,sha256=mAO-KVCHqol7BlJOwHV54gRSUH1eK7EIeQSer33b2Po,165886 +ckan/i18n/ro/LC_MESSAGES/ckan.mo,sha256=qKzVfWUPbp0U1aQjs-WMNgHhW2QeVVa38Zf7Ss21f4o,78826 +ckan/i18n/ro/LC_MESSAGES/ckan.po,sha256=4mJE8eH1vfKLc-zEGm2npSrpBQWO42avKP3DcEwoxTw,151860 +ckan/i18n/ru/LC_MESSAGES/ckan.mo,sha256=2PImFqBt-zdqoNJy2VIp70ddkZdUuTRKXiCek_shAwM,97589 +ckan/i18n/ru/LC_MESSAGES/ckan.po,sha256=XlnqT6yJyVxwAXtYbB8vvl9zYesNV0Z_XqUIlQ5nMZ0,177080 +ckan/i18n/sk/LC_MESSAGES/ckan.mo,sha256=8IOstmUgtdK3hqUGnbJgT-6zZbrnWS6HrOUUTwWJzTw,80257 +ckan/i18n/sk/LC_MESSAGES/ckan.po,sha256=v6RigUjXS7Kyhb3UXJS4rRay9_SMgMtx9p8Bv8bHZ6k,163901 +ckan/i18n/sl/LC_MESSAGES/ckan.mo,sha256=KdVYXV1bnXCySQ-2FNjGjtg18uZWyen0ch9KSvj4iMA,77969 +ckan/i18n/sl/LC_MESSAGES/ckan.po,sha256=AW1WCAXYM1TKLdLw5zUC4e_O82_bFuPOSGeP8fZVvaI,162102 +ckan/i18n/sq/LC_MESSAGES/ckan.mo,sha256=po-7-D9qspZseCQXncX_kR_sglshHrCqSTLGSg4TZvU,81052 +ckan/i18n/sq/LC_MESSAGES/ckan.po,sha256=ijMxi6tyrs6l3mgtt_CJzjEPW8Whs0aHldYmJA8Hm7o,161400 +ckan/i18n/sr/LC_MESSAGES/ckan.mo,sha256=8N9HmxZKhRddLpiY4U6u8SpmJSs1jQbIpSBhzxceyqo,80697 +ckan/i18n/sr/LC_MESSAGES/ckan.po,sha256=Koa0JkpTDTTkJoO_W1dF8jrL2UXCnVvkNT_XB1iMNsE,143655 +ckan/i18n/sr_Latn/LC_MESSAGES/ckan.mo,sha256=vuA1E-60jZehQRCZTu9dLzPXFkJPOXQWlrk3ZF1Z01U,76577 +ckan/i18n/sr_Latn/LC_MESSAGES/ckan.po,sha256=VfXakyd9VdsjOOpD8bC6IDZpyI9YF5QIlvnmEePznC8,139534 +ckan/i18n/sv/LC_MESSAGES/ckan.mo,sha256=Nbl_Xn_HPNVmGQwnVcGDyDCrPlZ59ZLtT2M_NjciBWE,77194 +ckan/i18n/sv/LC_MESSAGES/ckan.po,sha256=LfPvn8-MnB7Af3HsXuPqhlWcihXJ7P2Ab_H2cEPdzvI,161395 +ckan/i18n/th/LC_MESSAGES/ckan.mo,sha256=LE_8zkE05GCbAHlcxH9zFHgdkzGHFhMBeJj43Ux0fXQ,105640 +ckan/i18n/th/LC_MESSAGES/ckan.po,sha256=qMjzygIP6rlchb0hOs0cC4gx844Vx8drmkh0Uhr1iak,185892 +ckan/i18n/tl/LC_MESSAGES/ckan.mo,sha256=waAtN2h9OSmhrVFSsaUrttpwAQ2-otqy_brgBnLX--g,74591 +ckan/i18n/tl/LC_MESSAGES/ckan.po,sha256=FVbEt9qDVYZUmCTp15Cb7BdHlbYWDItdRkO7raxGpWY,133329 +ckan/i18n/tr/LC_MESSAGES/ckan.mo,sha256=v_zEiFV35b_aJz7wblJxXJIBZlabBkWkLTxEW8G5bx0,74920 +ckan/i18n/tr/LC_MESSAGES/ckan.po,sha256=8U-WFOivoSN8-cUMx3CJ-bVcq3rk8R0zGULxRlNy9VA,137888 +ckan/i18n/uk/LC_MESSAGES/ckan.mo,sha256=w0NLw4GaO48DnAm75glYejT4f8U1PhrJ-VWLEZO_dYA,76420 +ckan/i18n/uk/LC_MESSAGES/ckan.po,sha256=qfAS1SmEd5R7Wz0Bxa40gU3NQnOFithAn5pyMKHz-TE,133619 +ckan/i18n/uk_UA/LC_MESSAGES/ckan.mo,sha256=DTPZ7Rt104cCfU5Q860NVXjk9lDWa9OQeO-iDcyKjYY,102594 +ckan/i18n/uk_UA/LC_MESSAGES/ckan.po,sha256=uMzzr_y715KryViWLQqQAKdeO0620UI2jrDHOiC_P_I,188306 +ckan/i18n/vi/LC_MESSAGES/ckan.mo,sha256=wSkFaeYbQxWID_bISEPz-S03vuNHtr2Nyg0-mNGllmQ,82473 +ckan/i18n/vi/LC_MESSAGES/ckan.po,sha256=wNWpG5Pui59YnLBevS9qRoLL5e9GwgWSUq3R2Cq1O5M,162278 +ckan/i18n/zh_CN/LC_MESSAGES/ckan.mo,sha256=1kdy97k-4_fxKEcg11TTHL1L0tv4gsDjKeE0h9yzj6A,72187 +ckan/i18n/zh_CN/LC_MESSAGES/ckan.po,sha256=cgWJ7k1Q7nTzSyKyW4dgLV2o_sFKb8UjhkHWWOxtI8w,153418 +ckan/i18n/zh_TW/LC_MESSAGES/ckan.mo,sha256=GXRyonm9jbhQEviiJXCNPB6iwb8DHh24BS_hVgfj4DM,72165 +ckan/i18n/zh_TW/LC_MESSAGES/ckan.po,sha256=kapVSAwdGxt9oUQOq4PksMt9Y5lehPJVvXDPEOiUhpc,157917 +ckan/include/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/include/__init__.pyc,, +ckan/include/rcssmin.py,sha256=6vmNEWx7Y2O-ReyyJiA7iLn4RiGFnjwX6nx2-khmzns,12506 +ckan/include/rcssmin.pyc,, +ckan/include/rjsmin.py,sha256=bHaKYZL2vqa5jSSM9DiEVCgn7RHsJE47VkelB-6YT64,10719 +ckan/include/rjsmin.pyc,, +ckan/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/lib/__init__.pyc,, +ckan/lib/activity_streams.py,sha256=1V80WZ4-n6mLoXNUSSxRfPjglPDaedmS7crWEiyPSAM,10374 +ckan/lib/activity_streams.pyc,, +ckan/lib/activity_streams_session_extension.py,sha256=ciBGqv5aeGPD1YyMEjgWEKMXHXZBSJaq_PPMBhyxss8,5881 +ckan/lib/activity_streams_session_extension.pyc,, +ckan/lib/alphabet_paginate.py,sha256=y21HmOJxtDv7_epu0nl-gKiGiNR_3ceuk0fq9rsi9P8,6795 +ckan/lib/alphabet_paginate.pyc,, +ckan/lib/app_globals.py,sha256=ahCUpVDjmBZ4imUums9Qq8u5uXQ33quriberH2ayc50,7034 +ckan/lib/app_globals.pyc,, +ckan/lib/auth_tkt.py,sha256=PO2BTMrgus0exfz4ds2ngefnJt6JEBJJufUHRI8XsDk,3177 +ckan/lib/auth_tkt.pyc,, +ckan/lib/authenticator.py,sha256=5dSSl8l3gVlp_twvQ8afFpLAJnUCCejV_9vlQFJqUhk,872 +ckan/lib/authenticator.pyc,, +ckan/lib/base.py,sha256=-6E1PtLpdzZjFN4i7WN7i41JI130qciNMvSAplWzo5g,8733 +ckan/lib/base.pyc,, +ckan/lib/captcha.py,sha256=NPFUYsPQ0XD65QyDyF2gRaYALBOKBFrR08H7zmCEtaA,1343 +ckan/lib/captcha.pyc,, +ckan/lib/cli.py,sha256=hMzACF-TqyPlKKPzjvFJltLb1pOp_UqNvhfhARPOhu4,91669 +ckan/lib/cli.pyc,, +ckan/lib/config_tool.py,sha256=1js4d1FzqdMLHQRuCweYNGzuXTxUQw4bVLT25rGiOa0,9997 +ckan/lib/config_tool.pyc,, +ckan/lib/create_test_data.py,sha256=HdiB6IDwq-cSiuQa05WEh_bo9KqvWFROlPEHwPmD-Es,38277 +ckan/lib/create_test_data.pyc,, +ckan/lib/datapreview.py,sha256=v8G88tYQgY3HzpqH6YAjYFk-rAhSDXUeHtoDZ3H9hf0,10604 +ckan/lib/datapreview.pyc,, +ckan/lib/dictization/__init__.py,sha256=3AsA1VurrWi3WpqdOuJ9DS_HgsCi-TWtmvu6PRtj0xA,4575 +ckan/lib/dictization/__init__.pyc,, +ckan/lib/dictization/model_dictize.py,sha256=gdRzSdbmAaspwYnEZ6Nrz5uU4_43AuPrRMoHe9Egnhc,28313 +ckan/lib/dictization/model_dictize.pyc,, +ckan/lib/dictization/model_save.py,sha256=DCSaLfJyhGLxomRUqvxWaO_CEUTIi5-5DS-nJhM5Cy8,20725 +ckan/lib/dictization/model_save.pyc,, +ckan/lib/email_notifications.py,sha256=Sj6hG96_tLL5s7f947J1BEmmdlg0f_unnsS3Yz2i8-Q,7863 +ckan/lib/email_notifications.pyc,, +ckan/lib/extract.py,sha256=cSZspjEPyruNeDVDlBN2cUelCzpoD0hmp6pduCIt2ZQ,1514 +ckan/lib/extract.pyc,, +ckan/lib/fanstatic_extensions.py,sha256=6Gwt8qp9M5PkhC70thW6B0il6raL90kg9mYbq_-O7ws,4005 +ckan/lib/fanstatic_extensions.pyc,, +ckan/lib/fanstatic_resources.py,sha256=sXZsCZeovRO7omV6mnbfNWHx0IduyTOM7ioIkVijvrw,9279 +ckan/lib/fanstatic_resources.pyc,, +ckan/lib/formatters.py,sha256=oddF2JQMzYiKxcN8tgXWg4-X7cyX2z7bjMSq6rFso14,6182 +ckan/lib/formatters.pyc,, +ckan/lib/hash.py,sha256=ysSZdnshyQY1tTymWmPsbBavmAehK_NZ7xSEKg5eE_o,860 +ckan/lib/hash.pyc,, +ckan/lib/helpers.py,sha256=UIVNe-wS7b4-0qjvUG2mBijzuaaA3rdRZ0DRr9t_x7k,85364 +ckan/lib/helpers.pyc,, +ckan/lib/i18n.py,sha256=y6GRFnGxdoBXOCmIUnGgsDAmN9JMqsAILSOYBmjvMlQ,15057 +ckan/lib/i18n.pyc,, +ckan/lib/io.py,sha256=KWFBQulebbYeULDOnt2_2xDGPNFG_McD2Z48Fxad-fw,1759 +ckan/lib/io.pyc,, +ckan/lib/jinja_extensions.py,sha256=O6CuVm2qSx9OoKkg4h34SbRjqEOpWLVbZ0Etta2dOd4,11753 +ckan/lib/jinja_extensions.pyc,, +ckan/lib/jobs.py,sha256=kUsqP36ZHwFzS56kOyvyyITDIDmesPt7wwimJd4MFGA,9626 +ckan/lib/jobs.pyc,, +ckan/lib/jsonp.py,sha256=8CYQ6jphHrKXEdKLLvUjksVcYWyXKb1euMipNdZ5uvs,877 +ckan/lib/jsonp.pyc,, +ckan/lib/lazyjson.py,sha256=byNRmCLjz6cMnYEEr3Y7GSMy6g9IRAz43U4tOcwPWW0,1768 +ckan/lib/lazyjson.pyc,, +ckan/lib/mailer.py,sha256=YEJ_rkbjZfH1YZG4naYUO4I9QZ6lHGuzPq1zGDkda_Y,6445 +ckan/lib/mailer.pyc,, +ckan/lib/maintain.py,sha256=NjnMlACEH8wzmL9etxL1tz1QECGUmTFAZK36TCUaEj0,3605 +ckan/lib/maintain.pyc,, +ckan/lib/munge.py,sha256=KMJ8iFxQ0Co-nEvs1h-9epRj-ctBzU6Ey4_rm5otIrg,6672 +ckan/lib/munge.pyc,, +ckan/lib/navl/__init__.py,sha256=QX2OvSEWwTQKYNfdD9FGwotDgNic7S-SXXTiy68IFso,39 +ckan/lib/navl/__init__.pyc,, +ckan/lib/navl/dictization_functions.py,sha256=Xb7Tuo12oRoyjGPFM8ureDeFodDMqXcBmT3R9X2JllE,13700 +ckan/lib/navl/dictization_functions.pyc,, +ckan/lib/navl/validators.py,sha256=4YXO6C1ZXRspKH-A62qeIn4_6uc0BezpZAwh5AgA6tw,4832 +ckan/lib/navl/validators.pyc,, +ckan/lib/plugins.py,sha256=ewi0-66Sp6hxpvUsz-YTq8dQIri6HKJmRXzOw1wgg1I,23247 +ckan/lib/plugins.pyc,, +ckan/lib/redis.py,sha256=HAKDAhsTZ26Fx7G9xlxRuIX0QBuPLsk6XCdkq4twyQg,1428 +ckan/lib/redis.pyc,, +ckan/lib/render.py,sha256=Fia-cEk75bOvxcRa463QaAoC7k_YG5DcJgV4VwlsoUY,1484 +ckan/lib/render.pyc,, +ckan/lib/search/__init__.py,sha256=KY4dxo5Kw0ava_JiBV1XAqwVqYE2gIpA0Iu0xPqeOaY,10620 +ckan/lib/search/__init__.pyc,, +ckan/lib/search/common.py,sha256=NxfSkXYDRwt9dMObJ4-l1JpXziP_rbkcZb6rA9ffEBY,2763 +ckan/lib/search/common.pyc,, +ckan/lib/search/index.py,sha256=rtbAbCgSr-sCKMcUpuUilGhStsYwWiLYaQXxfOxSvWg,12460 +ckan/lib/search/index.pyc,, +ckan/lib/search/query.py,sha256=AF6HYZz5zOtfLjnO2atVI7NKr6MkExGtGHLeruq_PO0,15548 +ckan/lib/search/query.pyc,, +ckan/lib/uploader.py,sha256=mv7GqRR_7Z3PPKXBCkHAq2Xh0MrGW6FhCXG0u5PPwTY,11356 +ckan/lib/uploader.pyc,, +ckan/logic/__init__.py,sha256=HwexJrqfAvr0D4Yq0jHUdSl5MkvtsL6RIBGQPiD3XoE,24236 +ckan/logic/__init__.pyc,, +ckan/logic/action/__init__.py,sha256=sFHQdyi5TEUif2AILsYXwKaLGngm1fkIbuwdsRrDgBU,2105 +ckan/logic/action/__init__.pyc,, +ckan/logic/action/create.py,sha256=lWU2OtuP3AgXzSIYmSBCjRNpq4VW8JgoWvEJajbnjZw,53038 +ckan/logic/action/create.pyc,, +ckan/logic/action/delete.py,sha256=GTWKpd6WC4cLPBCS_R93tBWH0BIS9WiV1iG0EagvyR8,22411 +ckan/logic/action/delete.pyc,, +ckan/logic/action/get.py,sha256=qINnSh_YN_nb8kpyW7oVui627r1szZ7R-194jlRRiRQ,120618 +ckan/logic/action/get.pyc,, +ckan/logic/action/patch.py,sha256=y5MjGnq5weHhdVIqXi9bG2mhMNQpcF8vnGx6U6oDy-4,4064 +ckan/logic/action/patch.pyc,, +ckan/logic/action/update.py,sha256=4hSDoeiIwW8lBCZmUR7zj5nn-OucxyoeQxZHQc2Hvz4,40636 +ckan/logic/action/update.pyc,, +ckan/logic/auth/__init__.py,sha256=g_cz05G4oW1bP_TZUH4RUCTdjAD4LnBRZ-lTbZx0TgE,1437 +ckan/logic/auth/__init__.pyc,, +ckan/logic/auth/create.py,sha256=WOtfEm45zPtpbYl9KRnQhY8eFH9i1XB8LY9VPeVAgas,8640 +ckan/logic/auth/create.pyc,, +ckan/logic/auth/delete.py,sha256=XnOAW0aQD8-4zcTmmPGLr4qn66UKYuG_N7Q16eN1N_M,5190 +ckan/logic/auth/delete.pyc,, +ckan/logic/auth/get.py,sha256=-J3d-zjNKL1SNVG7jW8vYN2BvbMNs2pwvN5UCgYMtFY,10640 +ckan/logic/auth/get.pyc,, +ckan/logic/auth/patch.py,sha256=iE4agE2vOik0eUyVPWbOBS8CultpREavYdSigJAG1Kg,494 +ckan/logic/auth/patch.pyc,, +ckan/logic/auth/update.py,sha256=Q08TsYCJ6F4D5chX62UH324x01AKtOI1ZKslc9X_t3I,9755 +ckan/logic/auth/update.pyc,, +ckan/logic/converters.py,sha256=71fqrpMSjEez6vT_RaUS2F8FmN8JFaaNF2E6A3Le-4U,6342 +ckan/logic/converters.pyc,, +ckan/logic/schema.py,sha256=BerGflfTAt0l7sFeaIgQmy4YMTz8yBVI9cYoJKIO9MU,26376 +ckan/logic/schema.pyc,, +ckan/logic/validators.py,sha256=oM8VOJrguJCnCtcZfvSTlzO3tMhjwGcyH7WEMpUfbFY,28463 +ckan/logic/validators.pyc,, +ckan/migration/README,sha256=9TeIkkVZ1spFtsf8u17Yd07cEOk8cDBz0f3Qci6P6cc,107 +ckan/migration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/migration/__init__.pyc,, +ckan/migration/manage.py,sha256=BEXzjzTHGcQLIjRMShxUs1ZXActrYEmOYrGO5U02G3k,118 +ckan/migration/manage.pyc,, +ckan/migration/migrate.cfg,sha256=EC2J9yey2mtqihKbDv3Z3PbB7ok72IGT7xtSMZKdO1g,987 +ckan/migration/versions/001_add_existing_tables.py,sha256=gOXorziQyisByPYInV-5qVtnVx07OuIEe8Ns2aZa3S4,4717 +ckan/migration/versions/001_add_existing_tables.pyc,, +ckan/migration/versions/002_add_author_and_maintainer.py,sha256=PZnqQ9JqqCWv7Sg2CeIGLyhG0R5HKiqKv3lXSABSveE,1068 +ckan/migration/versions/002_add_author_and_maintainer.pyc,, +ckan/migration/versions/003_add_user_object.py,sha256=y77gVIi9bRGizOHClsdDTgMj1o8cf6gtdheG4IeLGtE,629 +ckan/migration/versions/003_add_user_object.pyc,, +ckan/migration/versions/004_add_group_object.py,sha256=UkFRvGdmiLfGoj3dZKaumrnZ9YZ8Rg35EgaEG6gg0BA,1060 +ckan/migration/versions/004_add_group_object.pyc,, +ckan/migration/versions/005_add_authorization_tables.py,sha256=zVZeJdNWlv-yI-NpEKFV8zUT819MGD5gZlaTWjy_VRg,2043 +ckan/migration/versions/005_add_authorization_tables.pyc,, +ckan/migration/versions/006_add_ratings.py,sha256=mbGa6yNxDzKh0fVfMjYBzQQaUCyN17OUdrKYw1acQqM,970 +ckan/migration/versions/006_add_ratings.pyc,, +ckan/migration/versions/007_add_system_roles.py,sha256=XsRytaiRzl8vmoe2GNlRX_7nbUqm-0fvFRpvfzz0A9E,674 +ckan/migration/versions/007_add_system_roles.pyc,, +ckan/migration/versions/008_update_vdm_ids.py,sha256=IPAufBPhQXZX5VxUWV3MftzhteHRRp73Dd2M-F8drUU,3701 +ckan/migration/versions/008_update_vdm_ids.pyc,, +ckan/migration/versions/009_add_creation_timestamps.py,sha256=p_oRFgEcloKfnlvN_ZcUAMv9GrwLpA3MVS5FeacMGs8,841 +ckan/migration/versions/009_add_creation_timestamps.pyc,, +ckan/migration/versions/010_add_user_about.py,sha256=n5Tido-zbncwP6rzUGF0YaPWJnk3elS_smQ-etbkGGA,402 +ckan/migration/versions/010_add_user_about.pyc,, +ckan/migration/versions/011_add_package_search_vector.py,sha256=kqEE5gKAOG_DVcEjsI8Pm116hq8Hf0WzIZeJ2ogvz3Q,749 +ckan/migration/versions/011_add_package_search_vector.pyc,, +ckan/migration/versions/012_add_resources.py,sha256=bs1tnAwzhTn31cXfqEnbDuVOUN07tSTvHw0AnC5wjEo,3269 +ckan/migration/versions/012_add_resources.pyc,, +ckan/migration/versions/013_add_hash.py,sha256=X07LBa5VAok3REvWaPfovOUsXlfq3OQpY-EBMnRnss0,399 +ckan/migration/versions/013_add_hash.pyc,, +ckan/migration/versions/014_hash_2.py,sha256=RE0B2cSZNgWbqb17jwW_k6oLuQo_p1M-tqbSqpMH5Lo,597 +ckan/migration/versions/014_hash_2.pyc,, +ckan/migration/versions/015_remove_state_object.py,sha256=cgQi8tLdmmsXGCsJUZ9mHNsMphlMSaPwv8dViGAlS2w,1147 +ckan/migration/versions/015_remove_state_object.pyc,, +ckan/migration/versions/016_uuids_everywhere.py,sha256=LdKB_3AkLdV07zXt3s1Kh7rnZ-i3mkg9bemK4V-Btz8,8474 +ckan/migration/versions/016_uuids_everywhere.pyc,, +ckan/migration/versions/017_add_pkg_relationships.py,sha256=3tvwLoNrxYxEJ8gCt7VhT2ox1PG4HSS8JNQ6jWji_Cs,1490 +ckan/migration/versions/017_add_pkg_relationships.pyc,, +ckan/migration/versions/018_adjust_licenses.py,sha256=MYR_HQW3xe2FEEvX37qKewX3MeODAKGjmkCns_4QjsI,9677 +ckan/migration/versions/018_adjust_licenses.pyc,, +ckan/migration/versions/019_pkg_relationships_state.py,sha256=Ld6jNZvd-lKgU06amzUamoJNwkwnrI6msr1_hBF3jkY,842 +ckan/migration/versions/019_pkg_relationships_state.pyc,, +ckan/migration/versions/020_add_changeset.py,sha256=2HanlBfl8M30K5gDbzgkEWBeJDrTkxel64nixWyJh8g,1609 +ckan/migration/versions/020_add_changeset.pyc,, +ckan/migration/versions/021_postgres_downgrade.sql,sha256=UnHeXBxLprvGMkyUpnWs3W3zNJkOmrvAt4V15hZg_zw,6 +ckan/migration/versions/021_postgres_upgrade.sql,sha256=n9vJ9p-s8Wk1mEKW26_X3kxW7xeRyHS2-g_C4ywnpzs,3435 +ckan/migration/versions/021_postgresql_downgrade.sql,sha256=UnHeXBxLprvGMkyUpnWs3W3zNJkOmrvAt4V15hZg_zw,6 +ckan/migration/versions/021_postgresql_upgrade.sql,sha256=n9vJ9p-s8Wk1mEKW26_X3kxW7xeRyHS2-g_C4ywnpzs,3435 +ckan/migration/versions/022_add_group_extras.py,sha256=DpnUnOt7fS0DVicm2Nyp_ZfYZ-EELmZimVdWObLQlaU,1437 +ckan/migration/versions/022_add_group_extras.pyc,, +ckan/migration/versions/023_add_harvesting.py,sha256=a0bW5teiqkxRp3ZAc3qZVZWGthFnqEjXQcRcsVz_WcM,1589 +ckan/migration/versions/023_add_harvesting.pyc,, +ckan/migration/versions/024_add_harvested_document.py,sha256=JZkt4DUstm19dJyhKaLKchGOHnvaEGeo-BkXYoPuVmI,605 +ckan/migration/versions/024_add_harvested_document.pyc,, +ckan/migration/versions/025_add_authorization_groups.py,sha256=5DcPIc_qGeZDQxrXwDBQjR0vEw-dBZPbQ15rUB_-P0g,1963 +ckan/migration/versions/025_add_authorization_groups.pyc,, +ckan/migration/versions/026_authorization_group_user_pk.py,sha256=7ZTXeYvr32Q4ufV72AMD8SV9D3NEYvMYlPfEZcGGGJI,1052 +ckan/migration/versions/026_authorization_group_user_pk.pyc,, +ckan/migration/versions/027_adjust_harvester.py,sha256=JmLGYu_LO0JFSs4y-SUEADeMKR1ASuQ6QbIVPK8fYRs,1215 +ckan/migration/versions/027_adjust_harvester.pyc,, +ckan/migration/versions/028_drop_harvest_source_status.py,sha256=dtWwgrHYkRn2IJJklZH11x6GmhI70StrDJvWbN4RckE,409 +ckan/migration/versions/028_drop_harvest_source_status.pyc,, +ckan/migration/versions/029_version_groups.py,sha256=63s7EcbQTWWUanXpuvKGAqpMKnscUOWTHOsrOwFvCM0,7243 +ckan/migration/versions/029_version_groups.pyc,, +ckan/migration/versions/030_additional_user_attributes.py,sha256=rGeoT9Rgi2MKkD8rSLgNetUMZAU0F98LXLjmfjJzg9I,685 +ckan/migration/versions/030_additional_user_attributes.pyc,, +ckan/migration/versions/031_move_openid_to_new_field.py,sha256=xZXvvORiWtjvnutVlRBzyd6DNMiCkvLUp-sIz3faq2c,1238 +ckan/migration/versions/031_move_openid_to_new_field.pyc,, +ckan/migration/versions/032_add_extra_info_field_to_resources.py,sha256=J4_JeTCbu-P5JBgoDtHCKtSYP9QAZZLb7BEMcaUw2RM,509 +ckan/migration/versions/032_add_extra_info_field_to_resources.pyc,, +ckan/migration/versions/033_auth_group_user_id_add_conditional.py,sha256=12EoV53PXtNfQNUkUzEfmv0seqd6d1oEsggQlybe5PE,848 +ckan/migration/versions/033_auth_group_user_id_add_conditional.pyc,, +ckan/migration/versions/034_resource_group_table.py,sha256=fLLg8_4scWNHplp2lCetW27ugMBE44ImPjQyx3ITVZg,7357 +ckan/migration/versions/034_resource_group_table.pyc,, +ckan/migration/versions/035_harvesting_doc_versioning.py,sha256=7Fmlg30PzKtmn6nvZZUvI4G7GuwL1CFTh7rtXCjPtzY,1697 +ckan/migration/versions/035_harvesting_doc_versioning.pyc,, +ckan/migration/versions/036_lockdown_roles.py,sha256=wqecZ_xuu-bzqxwBWv1gd8DKfis_ExZS35uwZERGqm8,1485 +ckan/migration/versions/036_lockdown_roles.pyc,, +ckan/migration/versions/037_role_anon_editor.py,sha256=7Ao1aji1KySedA7d7axFXdb43EN1XkbocF2mKi6lCf8,1747 +ckan/migration/versions/037_role_anon_editor.pyc,, +ckan/migration/versions/038_delete_migration_tables.py,sha256=00xoOZGAdqjenS6bijdVaeGNNftttvzRZKcJEgTDHrk,203 +ckan/migration/versions/038_delete_migration_tables.pyc,, +ckan/migration/versions/039_add_expired_id_and_dates.py,sha256=ljGpjZScOkjFSCN9CBdGZ43SgC6j9jRRlwTRdnLol8c,16108 +ckan/migration/versions/039_add_expired_id_and_dates.pyc,, +ckan/migration/versions/040_reset_key_on_user.py,sha256=KE8BlyFsWsg55dt-nxoha3z4lLrjyQDfHVRnO0aSX_Y,368 +ckan/migration/versions/040_reset_key_on_user.pyc,, +ckan/migration/versions/041_resource_new_fields.py,sha256=28LKwKozbehky3ygFlAe-ecQyxFbz8Sf-GWBsR1YsgY,1022 +ckan/migration/versions/041_resource_new_fields.pyc,, +ckan/migration/versions/042_user_revision_indexes.py,sha256=LCvuErMC7DihLAQPAZohA5lkgyZfLhSLbDPbhSQTiWo,405 +ckan/migration/versions/042_user_revision_indexes.pyc,, +ckan/migration/versions/043_drop_postgres_search.py,sha256=afTyJ5UwshswGNnAoCJgP7BToa7IKIcKpoVL7kdTJSE,359 +ckan/migration/versions/043_drop_postgres_search.pyc,, +ckan/migration/versions/044_add_task_status.py,sha256=3NT20XYNz-m-chG107zh6rsqc2a2Pzwa564ORVgNNNg,734 +ckan/migration/versions/044_add_task_status.pyc,, +ckan/migration/versions/045_user_name_unique.py,sha256=u8lIPozgSfinNtRhl7PLtnIlVdsJkrm0PGXZevbZpq4,630 +ckan/migration/versions/045_user_name_unique.pyc,, +ckan/migration/versions/046_drop_changesets.py,sha256=kIGdKdizIZEccVjhiFHnga9MS7FzGcjTbB9nLHxr5RE,323 +ckan/migration/versions/046_drop_changesets.pyc,, +ckan/migration/versions/047_rename_package_group_member.py,sha256=Z3itsUZzIyDN3UyQaVNM3GsNJOS6Ce4q_pFshNGYx1U,2949 +ckan/migration/versions/047_rename_package_group_member.pyc,, +ckan/migration/versions/048_add_activity_streams_tables.py,sha256=jEwrd6y5lL4phDttf4NK9xN7eJ_XsFdc7-4h1JCSFA0,799 +ckan/migration/versions/048_add_activity_streams_tables.pyc,, +ckan/migration/versions/049_add_group_approval_status.py,sha256=KMozKhz7vp1SnUbUw7amcVDRMZPBKf9S7DXggpWBrNk,354 +ckan/migration/versions/049_add_group_approval_status.pyc,, +ckan/migration/versions/050_term_translation_table.py,sha256=Cl_TQs6OIL3-029kGAzMx0pWJBp6tSZKz-9E7kvpvmQ,434 +ckan/migration/versions/050_term_translation_table.pyc,, +ckan/migration/versions/051_add_tag_vocabulary.py,sha256=_VpPSVpfDiaRVcjxogZT7VpvW2BBWrlSmQD03Z9BFts,844 +ckan/migration/versions/051_add_tag_vocabulary.pyc,, +ckan/migration/versions/052_update_member_capacities.py,sha256=lby7QNBf_qZ6fNV8Rznsi8Ahd1U1TowXf9TVCri8QYg,435 +ckan/migration/versions/052_update_member_capacities.pyc,, +ckan/migration/versions/053_add_group_logo.py,sha256=AqGTWVBJcAtWHteJJYT9TZaqkn7RpFZQMBvqwTIOPn0,283 +ckan/migration/versions/053_add_group_logo.pyc,, +ckan/migration/versions/054_add_resource_created_date.py,sha256=ZEqIyGJu61q9GJvp9253h5Kz5egsZYAna_mwOtDui54,281 +ckan/migration/versions/054_add_resource_created_date.pyc,, +ckan/migration/versions/055_update_user_and_activity_detail.py,sha256=1MdzvDuauxfAAPYe4-QWKj7oakVq8AXL_KHJVt4Wip8,253 +ckan/migration/versions/055_update_user_and_activity_detail.pyc,, +ckan/migration/versions/056_add_related_table.py,sha256=7i8OqdTLvqK1xKVdPqCM2dmWR9RPHgvskX9_0yKV7mM,921 +ckan/migration/versions/056_add_related_table.pyc,, +ckan/migration/versions/057_tracking.py,sha256=BDDyCmQUOKHC0RQW4bh_545NyCtS8i-wTXWm7viKddc,1223 +ckan/migration/versions/057_tracking.pyc,, +ckan/migration/versions/058_add_follower_tables.py,sha256=tRVN8Bn0Wjd8S7ZzsJKvtx1Rm7EXRALnG__9lmqbVS4,1434 +ckan/migration/versions/058_add_follower_tables.pyc,, +ckan/migration/versions/059_add_related_count_and_flag.py,sha256=5UvDwncpS5X4fi6yZiYv4Hc2h1hOhk65Hqf70gfrz-U,395 +ckan/migration/versions/059_add_related_count_and_flag.pyc,, +ckan/migration/versions/060_add_system_info_table.py,sha256=wm8-GVRREb1MlX3x--basegaPYAYzPA3V4_hks53kIM,993 +ckan/migration/versions/060_add_system_info_table.pyc,, +ckan/migration/versions/061_add_follower__group_table.py,sha256=3NbaEvyH9Jc1MXNW1YbIlYxFv-mi8CvC8kVH08u4Bec,807 +ckan/migration/versions/061_add_follower__group_table.pyc,, +ckan/migration/versions/062_add_dashboard_table.py,sha256=ELR7Va1Ozs4trk00I3mn3EF5vffcnC0eW7BxLeNVAE4,545 +ckan/migration/versions/062_add_dashboard_table.pyc,, +ckan/migration/versions/063_org_changes.py,sha256=8c662V43rExwn7DpORQFz9GHOITQTQBUGrAwfbiTjGo,715 +ckan/migration/versions/063_org_changes.pyc,, +ckan/migration/versions/064_add_email_last_sent_column.py,sha256=Up_f1MY2I5LZAtUV5iCxqI0Q7pq0QP8AJwC7F6YjtUQ,311 +ckan/migration/versions/064_add_email_last_sent_column.pyc,, +ckan/migration/versions/065_add_email_notifications_preference.py,sha256=MCFruPqMvlW-e4ZJR-1PByPBKg-eAh0d8-p9SL_mh84,296 +ckan/migration/versions/065_add_email_notifications_preference.pyc,, +ckan/migration/versions/066_default_package_type.py,sha256=wdVnhb1MUjZKAkjJ4rPjl2l62zTb3e2hPaSCWTmI1Qw,200 +ckan/migration/versions/066_default_package_type.pyc,, +ckan/migration/versions/067_turn_extras_to_strings.py,sha256=ZUnAURpSXwTRUUmTKtge-MilE_9rbLyB8EhIeA5mlpI,1216 +ckan/migration/versions/067_turn_extras_to_strings.pyc,, +ckan/migration/versions/068_add_package_extras_index.py,sha256=tGmBs3hSc92QsEgfZ0XJuvbPK6_im9CEAdCPKJOzCpY,226 +ckan/migration/versions/068_add_package_extras_index.pyc,, +ckan/migration/versions/069_resource_url_and_metadata_modified.py,sha256=NF1hrNRTvm_fA__P8nAu3OjcSeD0evkD0bYgsTzFKnw,2109 +ckan/migration/versions/069_resource_url_and_metadata_modified.pyc,, +ckan/migration/versions/070_add_activity_and_resource_indexes.py,sha256=8Oo9Qde1rInF7_IV1__p2KLYsZG2eIQwpSxxg-P6i3A,485 +ckan/migration/versions/070_add_activity_and_resource_indexes.pyc,, +ckan/migration/versions/071_add_state_column_to_user_table.py,sha256=Udk07aws0VfFUyQv9ZWIq9pJU92sBjcrQWRJnw0DYmk,225 +ckan/migration/versions/071_add_state_column_to_user_table.pyc,, +ckan/migration/versions/072_add_resource_view.py,sha256=I-jEDZ5N3QtXF28EhK8FLtZftMLveiT0fIBJhroqv5I,638 +ckan/migration/versions/072_add_resource_view.pyc,, +ckan/migration/versions/073_update_resource_view_resource_id_constraint.py,sha256=AXsUtccc1rlI5Crq81kokI8S56PO0QJWT2WijlzXc1o,420 +ckan/migration/versions/073_update_resource_view_resource_id_constraint.pyc,, +ckan/migration/versions/074_remove_resource_groups.py,sha256=kAZwbPJy68GNHE73Ot0PLsOUPZxMVvgLEnik3XsE2dY,970 +ckan/migration/versions/074_remove_resource_groups.pyc,, +ckan/migration/versions/075_rename_view_plugins.py,sha256=Q1oEYztsIMqJT79LaBJI6nxkqvVd81Cs_vVgNfNOy_0,331 +ckan/migration/versions/075_rename_view_plugins.pyc,, +ckan/migration/versions/076_rename_view_plugins_2.py,sha256=iK4te0tZI4f-P9layhtu3_QV3kSyfCe2ZVNjxNv6zY0,321 +ckan/migration/versions/076_rename_view_plugins_2.pyc,, +ckan/migration/versions/077_add_revisions_to_system_info.py,sha256=vrvD-bpKgVv2SDSOg_vymKYjv-2x1s6qGzS9N42k7b8,744 +ckan/migration/versions/077_add_revisions_to_system_info.pyc,, +ckan/migration/versions/078_remove_old_authz_model.py,sha256=GPSyz3itkHF2ONqY7HeL-Itu378vjJmpYKfkxMvbdiA,348 +ckan/migration/versions/078_remove_old_authz_model.pyc,, +ckan/migration/versions/079_resource_revision_index.py,sha256=9F7bkNYehbKgRwIESiQgRLSF9qR-8-1ZZ2zng2kczWI,205 +ckan/migration/versions/079_resource_revision_index.pyc,, +ckan/migration/versions/080_continuity_id_indexes.py,sha256=EmYDMSOu7jviHhU-Wu2I-IHT1m5mwaPc1MIVvChv1-I,509 +ckan/migration/versions/080_continuity_id_indexes.pyc,, +ckan/migration/versions/081_set_datastore_active.py,sha256=AGrESSmjzB7Os488CWFtSQr02WAgoOLwm3JWM_tnno4,1977 +ckan/migration/versions/081_set_datastore_active.pyc,, +ckan/migration/versions/082_create_index_creator_user_id.py,sha256=Yf0JVdViZxtIQaScj4lFGN79tMcg3cCa5vl2QFaBE9M,206 +ckan/migration/versions/082_create_index_creator_user_id.pyc,, +ckan/migration/versions/083_remove_related_items.py,sha256=wdX42yw95FFv8TvBmtMC80S5uO0Wlb47wlYF5cschaU,660 +ckan/migration/versions/083_remove_related_items.pyc,, +ckan/migration/versions/084_add_metadata_created.py,sha256=sn_vgrOsHNWnbK_vLwrPWBsWKi3YRmlc8YsWiCCN2TQ,513 +ckan/migration/versions/084_add_metadata_created.pyc,, +ckan/migration/versions/085_adjust_activity_timestamps.py,sha256=-wdxkvUFlup2kGEFHzuTNYi_w2jUTwIemrnNxOw_dTI,739 +ckan/migration/versions/085_adjust_activity_timestamps.pyc,, +ckan/migration/versions/086_drop_openid_column.py,sha256=EuB_oocufCY6JYUD38qh35BbkXbkecWdsweXQZpGLMI,226 +ckan/migration/versions/086_drop_openid_column.pyc,, +ckan/migration/versions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/migration/versions/__init__.pyc,, +ckan/model/__init__.py,sha256=vwBqkOKKZv_fJDTHXlCZN8-wywVec5NrdGRIousmRB8,13124 +ckan/model/__init__.pyc,, +ckan/model/activity.py,sha256=VBt8Nss02Bfm-vpQZS4oYdI9nVfgO3balUR3Gim99MI,11436 +ckan/model/activity.pyc,, +ckan/model/core.py,sha256=a2v-5nAi7uYmgYoKe6kOEwL5GeJZOtmhFy8gV3CTlpc,1480 +ckan/model/core.pyc,, +ckan/model/dashboard.py,sha256=hlK812-u0VNh6AVGdVkY5z5naLupdNVIefQ0sX7kkh0,1415 +ckan/model/dashboard.pyc,, +ckan/model/domain_object.py,sha256=yqyoT_W85YwdzxgbOG6MmEK41cZLvYx828X6Z391P2E,3030 +ckan/model/domain_object.pyc,, +ckan/model/extension.py,sha256=91zS2sZg8poDJfeH4uPWSu6_hDWO6bckqP06HsUSZSI,3552 +ckan/model/extension.pyc,, +ckan/model/follower.py,sha256=IscZ5ObEQDIKAbfFNyZN6B10ilFEWf2Ge5-9i89fXAE,6057 +ckan/model/follower.pyc,, +ckan/model/group.py,sha256=Lkk_vRyzAqWk1wQ3V_VdmDO-RZTJRff1ONaLAzbZ3ZM,17172 +ckan/model/group.pyc,, +ckan/model/group_extra.py,sha256=nqEwxXfaEd1f-VEa8rL3Wu2e6oJoUWBkKD0aCYf0mw4,1934 +ckan/model/group_extra.pyc,, +ckan/model/license.py,sha256=2cWRsDpEhTIIX-g6nsAWTUZZydkK9z4BxTPORu39rsY,11360 +ckan/model/license.pyc,, +ckan/model/meta.py,sha256=BmVHubesoJjWlH7f5VetNhKAiSh85mW8QBG0Bwt_75Y,5188 +ckan/model/meta.pyc,, +ckan/model/misc.py,sha256=5WslnW5eesD-mM45JKsK0XKmMFJdUzqw1R4FSLOJLjU,429 +ckan/model/misc.pyc,, +ckan/model/modification.py,sha256=nAK0WGSciv6bcngpuOn8WPrrqT3m7_FLAD9pGwxTg-4,3107 +ckan/model/modification.pyc,, +ckan/model/package.py,sha256=BbB47p8AgNQG6V4I9bwjOSy80KqICqQbyM6zv6ktjaM,25795 +ckan/model/package.pyc,, +ckan/model/package_extra.py,sha256=YL6aLVgAEMjWHi6QFfjs1Kn1zH2SFTLEt-V-Qp5TXOY,3157 +ckan/model/package_extra.pyc,, +ckan/model/package_relationship.py,sha256=183xgbVnhXUZcPFzfBaQ85e9HG0IQJo12OvCQmMzf-0,6882 +ckan/model/package_relationship.pyc,, +ckan/model/rating.py,sha256=L7jW6Up5JwGzrz-6ph0EVMf5wyhkSWYAYRlLyVE-QXE,1334 +ckan/model/rating.pyc,, +ckan/model/resource.py,sha256=shfiDrS1GqJgW3XHHjdCKuT83nV_HlAL4oQVRJa9pNc,8103 +ckan/model/resource.pyc,, +ckan/model/resource_view.py,sha256=DwEZqK66Rm95I8NStqSYecNgTvW5ZZim0t40aPkacN4,2245 +ckan/model/resource_view.pyc,, +ckan/model/system_info.py,sha256=k1JphhINqin3e12BJIaqRzg-EKghJTO8aCyfYf3ADrI,2710 +ckan/model/system_info.pyc,, +ckan/model/tag.py,sha256=uQiWoKN-zi5ElGO9AQKzU1d91IrcgXk51IZMIJZQL2c,12085 +ckan/model/tag.pyc,, +ckan/model/task_status.py,sha256=3bR6y6HnAsUTTyKjBVGXQX6p2ib-l1qnQrc6z9XHckQ,1174 +ckan/model/task_status.pyc,, +ckan/model/term_translation.py,sha256=lsmfxHHv1I-KRIrnic5bn7yPnFho0hXI8MCZniojMQo,381 +ckan/model/term_translation.pyc,, +ckan/model/tracking.py,sha256=acusfFlez9RtLQcwF9j-siaK76eB7goYrFUgzPV_ngo,1837 +ckan/model/tracking.pyc,, +ckan/model/types.py,sha256=Ft5o52rbSeCxrpY9dlQAKTVng_lXPt_7QAK9zshQcG4,2970 +ckan/model/types.pyc,, +ckan/model/user.py,sha256=A7CwMrOvo0b7hqM69xumIl4yNLQs7GguO2muTBJfZMg,10087 +ckan/model/user.pyc,, +ckan/model/vocabulary.py,sha256=BXykfaJi4_OKgTtDANqXIolfdKo3KhgT81k7i4JCJyg,1201 +ckan/model/vocabulary.pyc,, +ckan/pastertemplates/__init__.py,sha256=YBbvD4mXIekZjqZfI2NEeuDGHAVpyahmfB_almUnsLI,2848 +ckan/pastertemplates/__init__.pyc,, +ckan/plugins/__init__.py,sha256=zt93vqVcvEXrkUJa3fhEceXVzMPVFy9gTlOm0AY2GLI,105 +ckan/plugins/__init__.pyc,, +ckan/plugins/core.py,sha256=__mwtOax91XghC5rqmTeendHdRI8huaPnJrajEyY0RE,7051 +ckan/plugins/core.pyc,, +ckan/plugins/interfaces.py,sha256=L5_-_v_OHMWoiVsFai2X0fVGPOuQ4aCnK9anA5UfdIQ,62005 +ckan/plugins/interfaces.pyc,, +ckan/plugins/toolkit.py,sha256=z5mph_v2w3ROLkAtRxq43ErW7qBbTCG92nHaGDw7_Nk,18251 +ckan/plugins/toolkit.pyc,, +ckan/plugins/toolkit_sphinx_extension.py,sha256=ayritBF1mYao4KNqA8Tm0zuPF60pt-A2fmoj1v9sCMc,6869 +ckan/plugins/toolkit_sphinx_extension.pyc,, +ckan/public/base/css/.gitignore,sha256=y3VD3WrpEWcMroJCrBLOZYLatV8xoWkNBqdF3H-oDuU,11 +ckan/public/base/css/fuchsia.css,sha256=oWVfLF88FWpEcmQgdePcaXGpLTAUxCgBUbGfmx7Q9_M,223119 +ckan/public/base/css/fuchsia.min.css,sha256=UcDtJBty4ttcxU6Q0LWSMfFHwhzVjo37z4sVGQNNzYQ,186626 +ckan/public/base/css/green.css,sha256=-swgE1K1UCuDNfEUBsR0alrYHY8HWoX8cZLK3ruGjCI,223119 +ckan/public/base/css/green.min.css,sha256=prXt0FL-A5XbvcmV5AulSFabNWmTqC63Hqj8a7GvfEo,186626 +ckan/public/base/css/main.css,sha256=5X-nlVM9DJNrpaU7l6X1CrpCARDnxSCcUC3tUE2ThcM,223119 +ckan/public/base/css/main.min.css,sha256=wtcjUme5oT15DZrS7OQChzzPQzXILNV81KzqjWG4OY0,186626 +ckan/public/base/css/maroon.css,sha256=pbq7E14LEk3F-RrVfp3IPvAvwOfwIfItKU9f9Hbumoc,223119 +ckan/public/base/css/maroon.min.css,sha256=pXw9ZEMBzeYQpf9PNFB6iqTW0iCQDnOmgSxE_4o-kcs,186626 +ckan/public/base/css/red.css,sha256=DZTmyk3MSkqZFhH9HvcgXdrbmznPwfJlVA1Y3zqps04,223119 +ckan/public/base/css/red.min.css,sha256=EXdYz9G4LqRc5plR188Rls6PJ9Q4e3VHRfb1hpoiZcQ,186626 +ckan/public/base/i18n/.gitignore,sha256=JWxB4_pQtrgM2VB9fxjD894xitN0Aa1OQrikbTW0m9I,186 +ckan/public/base/images/background-tag-ie7.png,sha256=M_T2sA0sMesVAVhY79Q8vrV17_i6NzqBidsbDGrgdbI,1271 +ckan/public/base/images/background-tag.png,sha256=1emlRVPGbaVPSJ7Mh1yJRvife2_RxDrXNHMrlPlGbVM,993 +ckan/public/base/images/background-tile.png,sha256=U6THus5r7mR85S5ooK_1X8w0McWp9DlzEFlJJE5skaE,225 +ckan/public/base/images/bg.png,sha256=OSMYFIcPHY1_71gzJtsB1EWrJdkqbAqgIt7wivU42i8,3911 +ckan/public/base/images/breadcrumb-slash-ie7.png,sha256=enUOOy3YjbKpWrH2IgbPqjdywxKzaTfSpgxd89XIGvQ,3050 +ckan/public/base/images/ckan-logo-footer.png,sha256=ayH7sASl4nuKnKq-Kv2lMhuDyMjiy4gxKbFxBqJv6OQ,435 +ckan/public/base/images/ckan-logo-white.svg,sha256=zirSc3Ew-Ym_6iwmZuBfcqfiomyOTD4lZJONNO4tB-I,5054 +ckan/public/base/images/ckan-logo.png,sha256=SShg2YAhgrQTIw8YyXAkU_NomNMetP1FJJsTr21BE_w,626 +ckan/public/base/images/ckan-logo.svg,sha256=KWAqN37l9X6g6tdJBEhLHAIxH56IfmWvkwvNlkYn8cU,5006 +ckan/public/base/images/ckan.ico,sha256=NprQjzhUirkb8p55gd5SovyMafbf3D7pdpNASVF-HYQ,1150 +ckan/public/base/images/dashboard-followee-related.png,sha256=LlCEu-xg_3HFHl2xlT4S7gDeXntW16C1OZBBNpDwWNI,2794 +ckan/public/base/images/dotted.png,sha256=TOPWzPIWZ7wIdMvJLhTO8tKaBnPgq0hYJrtr-NXx7BE,74 +ckan/public/base/images/editing.png,sha256=2rf-ZJkAp0CSywqhyC1Pi3qBQfKUvOCsQRqE8fJSUNk,203 +ckan/public/base/images/full-width-nav-right.png,sha256=K9kG_OrKjBSzvUMvqEA-lCE_qtnZYpK-HklrNswpJow,99 +ckan/public/base/images/icon-search-27x26.png,sha256=C8GaP6UD1TzS9kLfyD7CuVt-hE1faj7zp-dr9IawKDg,318 +ckan/public/base/images/loading-spinner.gif,sha256=bxi_mwJVVMMgYfh6NBrEqtm--O33X8--jNmVWcrbzTc,1849 +ckan/public/base/images/nav-active.png,sha256=xVXRk9kD59AG_Nx_ptlhRZP5ICnCegpFB1qmh5ie6Pk,227 +ckan/public/base/images/nav.png,sha256=1-AB0l3NAz0pIckmymFZ380sBxZnIpIdcEf2mIbtTis,2742 +ckan/public/base/images/od_80x15_blue.png,sha256=wvVJilQ4NEejavw8ks95s71_TzVETZDQxrVc4IUGaJ0,163 +ckan/public/base/images/placeholder-200x125.png,sha256=G0JROPU-Rv4k2M8NNk2b5EkZa93KOFn__0wvQCaA8IM,435 +ckan/public/base/images/placeholder-420x220.png,sha256=Evw6jX2fh3t-ul3rCUG7TY-AS77idbgU_AfUPs3tUeo,895 +ckan/public/base/images/placeholder-680x400.png,sha256=qefDPyJ0z1UKxlKbLw9D-YRCnvrA0w5VPAyFmhM83_0,1532 +ckan/public/base/images/placeholder-application.png,sha256=wu7Qx_u3jLnpPjOqc2wXG_LMIUsa6hrYcOKIAx-DoZ8,3772 +ckan/public/base/images/placeholder-group.png,sha256=veTiST4xs6jMkVHkkEfA5KAwVzaFdbM8P-1rdmIc7xw,4033 +ckan/public/base/images/placeholder-image.png,sha256=EMUIkKWLqS5-QyeT9386cSz3P6y2tugQW0f6S-R9Uy0,4663 +ckan/public/base/images/placeholder-organization.png,sha256=4_hMD3tmfUNnSglo6OUqUGJiTbE_ELRw5oXE50GnPSU,3861 +ckan/public/base/images/sprite-ckan-icons.png,sha256=zZ9ZIXLEX7_-Qui-t-2w_wGFMzBJ_c-s_AQen-ujBqw,13523 +ckan/public/base/images/sprite-resource-icons.png,sha256=oUqzLo6VuVNmquf9SP5dh46_dL9D212rZK0ie-A-EYY,44581 +ckan/public/base/images/table-seperator.png,sha256=mM4k6W79y6I5phF8OPEy9M961bDP3Xt1J51CVOAzbgc,82 +ckan/public/base/javascript/client.js,sha256=EoKZRU4cAXN6EqhMFjYhGBR-UQWX5MvhmnJ5CFcRYyQ,11976 +ckan/public/base/javascript/client.min.js,sha256=UFxmDq9A8RUQdaWAT0-lmBYn8-wjEYPeJjnWVCaoGTU,3979 +ckan/public/base/javascript/i18n.js,sha256=70roPNwsUxx_ewsdI3z6f20SbBB7rlVuMgk_bPFDwPQ,2953 +ckan/public/base/javascript/i18n.min.js,sha256=FKUf_GAsK9poli7oJYIx542hGG8dmKQtnl38-0idbQk,707 +ckan/public/base/javascript/main.js,sha256=sWEKVlnr2JFaWKU0iARDD__NxGvwQV21LYF-8YvbZuw,2798 +ckan/public/base/javascript/main.min.js,sha256=rRw_XMPHcWVykCQbTQQ2nnaLdqXzIm27ZSYGig0mXTU,1391 +ckan/public/base/javascript/module.js,sha256=rn5B2vyp9OGXPtsBg9HILJ-RLhF3N0ut8CfvEQPZp6k,13150 +ckan/public/base/javascript/module.min.js,sha256=VJci4--C8jNqOTL3s0MyvGJvwZlUT8TZr-PEF_4gUh0,2732 +ckan/public/base/javascript/modules/activity-stream.js,sha256=DL2HvZe1wLHYgRmQLtzxW5EXKMNztLPKpygyWfUVwJ0,3263 +ckan/public/base/javascript/modules/activity-stream.min.js,sha256=_bVuYRAui9yREMWB-fR_CAxEVz-P-IAa4bDLZJNj9kE,1706 +ckan/public/base/javascript/modules/api-info.js,sha256=HBqBzeDKJdRMVEjpFrefOkbv-Hz89R9tD0iEjUPT_wg,3108 +ckan/public/base/javascript/modules/api-info.min.js,sha256=p8NjYHwFBMwBOhMU60hvfle_7JEIngLQL7q174MdB3Y,1231 +ckan/public/base/javascript/modules/autocomplete.js,sha256=nz-PO62o2tjgL78u57x1zEu8ZGMKCnZvnxRJjOMtjlg,8786 +ckan/public/base/javascript/modules/autocomplete.min.js,sha256=xoXSHnjAwAsHq81ls759_zyZ5RLKNNhEdjxKTePiTAo,3118 +ckan/public/base/javascript/modules/basic-form.js,sha256=-Sh42Eep5W_OsRVhBWd26Ksmjqyy92pyIMl1B3iu_fY,759 +ckan/public/base/javascript/modules/basic-form.min.js,sha256=z1cvttE14wYnUEjljf4QK5yzKnbTv2KZsyr_szHq1iE,366 +ckan/public/base/javascript/modules/confirm-action.js,sha256=jVda-wxYC4g8RbKQxYpUlsU51TGrOxIBvynbLmb9N5s,3860 +ckan/public/base/javascript/modules/confirm-action.min.js,sha256=WIufOTUOD_6kx2wPOF6loccZDJkSGdf3fyBr2P-Iaxs,1690 +ckan/public/base/javascript/modules/custom-fields.js,sha256=0vpyZfXYTVGIwVRe4u134GtJZp_hogbe51JxIWzLg7w,3167 +ckan/public/base/javascript/modules/custom-fields.min.js,sha256=mcQj3GI6bBdGipLT4vKHW1QsB19S84tBpMTHooD8OzU,1150 +ckan/public/base/javascript/modules/dashboard.js,sha256=dimUSGtTxVDuiDn75tWmbt0JV1slL7Af0XMib40KJbo,2273 +ckan/public/base/javascript/modules/dashboard.min.js,sha256=Zim9xsH9XKwJ2TqTebeOl0fcnmgBJPvc2NT-4EIscjs,1151 +ckan/public/base/javascript/modules/data-viewer.js,sha256=qXOqmhObR3BeBLvetri7dWTuHyA7tY6n_H15OflH6Rg,1574 +ckan/public/base/javascript/modules/data-viewer.min.js,sha256=JXG7NV6EwMkHSYjb3-VLb84GZxzDKpuqKe3L5yC9yiI,1036 +ckan/public/base/javascript/modules/dataset-visibility.js,sha256=9jnS49Bv6WNsFFzgubucddWEMkpGjID4Zs_gFecOzQY,968 +ckan/public/base/javascript/modules/dataset-visibility.min.js,sha256=mE2y_thnsd7l1NXfuPSLb4oTTwcQTyhmZvtBO2xqE28,610 +ckan/public/base/javascript/modules/follow.js,sha256=OWOe2lbeWw8nb7gNDlYTEbnjkY7Qd-KU7olayX2anss,2276 +ckan/public/base/javascript/modules/follow.min.js,sha256=URLMDg4d5I3qprZ_zbDHCcsgdW7tQYf2_3NVyfQ80Xk,1049 +ckan/public/base/javascript/modules/followers-counter.js,sha256=RAIRbRv12G0JjTcZdEV1NSo2K_XEM-J9Nj6qU0ydPG0,2708 +ckan/public/base/javascript/modules/followers-counter.min.js,sha256=cMkbFav5j31wrGJbun8eq2RvBVjyKxPBQpSylOJA3Mc,1179 +ckan/public/base/javascript/modules/image-upload.js,sha256=l-6718ZOJeg10kIXB3ZlKqSq3JdD9okxqE8e_fYec6A,8509 +ckan/public/base/javascript/modules/image-upload.min.js,sha256=6HkmbBfUTn3Rozc0n1Oz4Mf_5PVU_7EQzXQemklLJBs,4228 +ckan/public/base/javascript/modules/media-grid.js,sha256=YuCvpI-mkpoVlVb0vrCGhnjrL6HZbz7N917ZT54eEow,406 +ckan/public/base/javascript/modules/media-grid.min.js,sha256=LIi1_0nASYQuGZQEVkh2uTPjO1RDKbUnRo61yLIRchI,178 +ckan/public/base/javascript/modules/popover-context.js,sha256=Fce2V2Qb3PbngN3dprFjE07pnSS4ELCFUlfXm4D5euo,7359 +ckan/public/base/javascript/modules/popover-context.min.js,sha256=U5XJQvXYdyswscLUdmf-YTwiX5ozwrZJw9TY8oKp31g,4247 +ckan/public/base/javascript/modules/resource-form.js,sha256=zFjeqIr0Yb_tJbcsoiAfKAC-BCg7KABhuBYx20BtiCg,1688 +ckan/public/base/javascript/modules/resource-form.min.js,sha256=GQ-p0Ah9-WsrMIegvV5HJ8IDjmPBIh8RzBzgdr-p8EA,614 +ckan/public/base/javascript/modules/resource-reorder.js,sha256=uOpDqKqIrcVYwqwzQ7Tk-Y0ZcGtv2RiVGgsHkFqEXq4,4395 +ckan/public/base/javascript/modules/resource-reorder.min.js,sha256=vGVM-rqzZuKRjRB7CFfKJnEVo9XKFImr4ZgCafUvS9U,3188 +ckan/public/base/javascript/modules/resource-upload-field.js,sha256=KyR2sVSslcVPZnXjHZzaAmvSoytF9Uj-Q0APat45Ias,9093 +ckan/public/base/javascript/modules/resource-upload-field.min.js,sha256=4aVwN9eD6XVxgBMUdax5GHt7SZ1ls_UFd-VmMB8Up9U,3490 +ckan/public/base/javascript/modules/resource-view-embed.js,sha256=Mn6sHppN22hHlcr8858BDAFHjKjrSm1fZx_Gh8SlSdo,1207 +ckan/public/base/javascript/modules/resource-view-embed.min.js,sha256=nRgcnv_vf3x2mQKG42gFX2_2MF3GhwU0_R4xZa8recc,974 +ckan/public/base/javascript/modules/resource-view-filters-form.js,sha256=nNwlj9AsIBg49n99ShzRMXx-95OvxaPrLvNLQn1jIxk,3155 +ckan/public/base/javascript/modules/resource-view-filters-form.min.js,sha256=NjYeRjW692DiFHghTtC_VSvjo4nhcj751VOuPx6ewbY,2190 +ckan/public/base/javascript/modules/resource-view-filters.js,sha256=NPY2rhTbbBuXoIkzYTb_FGVFn6xAp6Dwjp5pzNzI_sg,5908 +ckan/public/base/javascript/modules/resource-view-filters.min.js,sha256=IshGclhMybi0aMRxP_DNysyFMVPI-Ls-qCY6aohDO9s,3763 +ckan/public/base/javascript/modules/resource-view-reorder.js,sha256=Ky4yu7zXK4OuB8mNCT8Dwfny3Eq2Kxhksk2zlwxShck,3947 +ckan/public/base/javascript/modules/resource-view-reorder.min.js,sha256=MEUCm-RL0tq7L5OWhw_zSo3h6e4p27kUNbmFejAKcgQ,2813 +ckan/public/base/javascript/modules/select-switch.js,sha256=IL3L9r3IqySE94ARDxc-yg8K2MaRugdhUl5sMbGznE0,761 +ckan/public/base/javascript/modules/select-switch.min.js,sha256=uAsnaZo41HvC5uV_akMczn0l4_1qMaTii8WEZ-tAJV0,173 +ckan/public/base/javascript/modules/slug-preview.js,sha256=Bs3qH_lvvtSK4I-_UohGm8svuYMakzTjkbeeuKdNLLY,2312 +ckan/public/base/javascript/modules/slug-preview.min.js,sha256=MZvahH0Qvm-_P3bcvleemATPgclrFTAKDoA05vhooFQ,1152 +ckan/public/base/javascript/modules/table-selectable-rows.js,sha256=d1VM9pPyKm_SUeTH4hggnOobRfTuGVdvJLma7tdDx0Q,2805 +ckan/public/base/javascript/modules/table-selectable-rows.min.js,sha256=-Rl-nEWBV7UkdsM4tzen8qVTejJ4OzzDaAJRtF_gO9Y,1545 +ckan/public/base/javascript/modules/table-toggle-more.js,sha256=EFRrTig8pTrJnJKIT2T7pYR5AoY6fZPcSrDVlBwHMK4,1853 +ckan/public/base/javascript/modules/table-toggle-more.min.js,sha256=Ks6D7OowQAzzRpnKkHq5us7gBCOZAIauXznPQqUFMSs,1079 +ckan/public/base/javascript/notify.js,sha256=dqIyniHUAhX_i-w4aleAnDv2jzK08T7DZdc8RAOMTfk,2109 +ckan/public/base/javascript/notify.min.js,sha256=tpWdwd4-Z3ojI74jgP8uk3U3tQU9T9fRCYuXmZ_lFQA,854 +ckan/public/base/javascript/plugins/jquery.date-helpers.js,sha256=b53eM0yPZGHKybk7Pids1kzwj9k7ut44hlVCyFcFsbU,1947 +ckan/public/base/javascript/plugins/jquery.date-helpers.min.js,sha256=rqDf5a8_0LnypB482ns5OqJ6X_o1KLoDt5Zw-yIAbLw,805 +ckan/public/base/javascript/plugins/jquery.form-warning.js,sha256=pxFemRfVZu7EZsVaAzwQsttZ-a8DdBXWmFfxkV8jC1o,1307 +ckan/public/base/javascript/plugins/jquery.form-warning.min.js,sha256=J5wVbkBv1up6nBrrCRZOcVML1TSjHbGIFBP-Ptq5N5Q,494 +ckan/public/base/javascript/plugins/jquery.images-loaded.js,sha256=RizjuVTfpUciEsR-N30fYe4xlT6lnf5v2a6Eyecsm8c,12785 +ckan/public/base/javascript/plugins/jquery.images-loaded.min.js,sha256=EZ_4jshE9WhaZjLK_JDjmdeOrJntkUNvV127g47pfoY,7691 +ckan/public/base/javascript/plugins/jquery.inherit.js,sha256=_GDgUOOvsZToQI7HEEbd5Qg2Xho7ANFReHtr62axMnA,1369 +ckan/public/base/javascript/plugins/jquery.inherit.min.js,sha256=tcdQQ0l9-7CYG4wUXzjvw8A_K21p1inX7_Pzq5mlSRs,615 +ckan/public/base/javascript/plugins/jquery.masonry.js,sha256=G-xiI68F0F8p6YYhbdGjrUEP5VpsHzHAIUAa2c4rHNk,63257 +ckan/public/base/javascript/plugins/jquery.masonry.min.js,sha256=Cz-ckYXdnM7BTIz5XQi3p2qvekyUBIVWpWRdz9d8xTU,34859 +ckan/public/base/javascript/plugins/jquery.proxy-all.js,sha256=oC0qQKcHpMu6O22drwbDKHbjgIffq7r3pwqZPtjKzz8,1302 +ckan/public/base/javascript/plugins/jquery.proxy-all.min.js,sha256=rZpPqS29DojNhlQk1FGy7lJY03q_pVrbTMqTX-b8hnY,472 +ckan/public/base/javascript/plugins/jquery.slug-preview.js,sha256=B-fiMMNzo8XrDaXdHNnuV5dQCZTrFx0wrxWiYcNIPpU,2556 +ckan/public/base/javascript/plugins/jquery.slug-preview.min.js,sha256=Ug00-p0zlWgvJ-UKmlEnK4BAXeSHVoQBgvgqllV_ltQ,1182 +ckan/public/base/javascript/plugins/jquery.slug.js,sha256=tSwM_kDRUge9NUPdp0RjOAQGgP71w4hvaEVc8U0_olc,2221 +ckan/public/base/javascript/plugins/jquery.slug.min.js,sha256=bKQDeqWevClrTXkH831K1ycq1NfsKWSyMfd94AAdC6o,981 +ckan/public/base/javascript/plugins/jquery.truncator.js,sha256=oGGzUaZdT3POtRpdAuUweznf0F-ihR5X7BZDlqZu1bs,5219 +ckan/public/base/javascript/plugins/jquery.truncator.min.js,sha256=_9FPabiQU-CocMLxpBo4ktLVsBnenxn-jktz9z1-iv4,2628 +ckan/public/base/javascript/plugins/jquery.url-helpers.js,sha256=Sa-DD6-LQPcajYvvn1EAbeEQfypE-AEvk0e2G3kX5FQ,9554 +ckan/public/base/javascript/plugins/jquery.url-helpers.min.js,sha256=dUrlDQ7hmye1wh1HQZNw6uaqS0JZrJWWIwHHFUBhJVI,7203 +ckan/public/base/javascript/pubsub.js,sha256=A6N-YSt00GCrVE6A0Q2xGPU_HksZ38jELUwx_mT7nHs,4388 +ckan/public/base/javascript/pubsub.min.js,sha256=xwR3cY3GTZ7Bhe1ctgOPyQGnscfyG2-jfmo7dLbb2I0,1050 +ckan/public/base/javascript/resource.config,sha256=c_mBlL3AN_8QxnsdW5Nq9chNnGdOe8wtvW_taGICvWs,1498 +ckan/public/base/javascript/sandbox.js,sha256=ne4x6EMn6D55PEaVTxBjRIm6P3wmSY-bT4GGTEBnmHk,3589 +ckan/public/base/javascript/sandbox.min.js,sha256=rKMhSqlbLBaDew0J5hTICBQwY1kYvSMHYnnO_rbm4sw,782 +ckan/public/base/javascript/tracking.js,sha256=wKo5kYGwm7XZ02JCaX4U7Rf5uY5-uC8VmEZ5DmkB22Q,733 +ckan/public/base/javascript/tracking.min.js,sha256=sygaE56JWfyWzCbqIpVt_zLoasZTYvEsUS8xBBRwQhQ,498 +ckan/public/base/javascript/view-filters.js,sha256=y5cz1NFBVhjCO5VEegRjnXagO9E2WZOjcVjqMgaF4hI,7148 +ckan/public/base/javascript/view-filters.min.js,sha256=Hy0ni4UrAFzLd9YvabRC_DBA_JUBGArG1Q34tAG02N8,3902 +ckan/public/base/less/activity.less,sha256=fBXXBjfa7VCikWMAylzTUExkiBUBnNqIf_ASvuvFjyI,4757 +ckan/public/base/less/alerts.less,sha256=K92N9DgJQEYlAv2K-b97LqX_RU0KgmzziYO_NRsrTIQ,252 +ckan/public/base/less/bootstrap-variables.less,sha256=P77d949htYvM7Bf4tEDP5gTRQuuH-cqpyxsth1LcxDI,27464 +ckan/public/base/less/bootstrap.less,sha256=dd7FI7CK8kz3fmxZTHraM0zuGnhNKSMMJMiyYSZ4XEg,1994 +ckan/public/base/less/ckan.less,sha256=binjenFtklq9nMc2Xt58spcJ-KzdqCKFD6OqofEngSk,2202 +ckan/public/base/less/custom.less,sha256=9NGeROksN2gKEomzhKU11vpTs0c4pVVe68ByzqNHDHI,73 +ckan/public/base/less/dashboard.less,sha256=-pjysaKb5R6gEpmpOnmR7yFUTF-1o9FyH0WFXjvtUMo,3086 +ckan/public/base/less/datapusher.less,sha256=XuqHILhPi6Cf0ahQgzMd8K5Q2Mgbe9TUazeKsREPhyw,334 +ckan/public/base/less/dataset.less,sha256=7v_mQX3g6YuqwBUaaQZrzOuxehmXnQFuOz9bmbuBPUQ,7288 +ckan/public/base/less/dropdown.less,sha256=ujRbpypnEp5y06W0tQsNezICKilyHpG5CyMQkRqSOnE,325 +ckan/public/base/less/footer.less,sha256=tJz93D8PbrGvmgBSuWJ1CgkKhMu2EnFPGacmLPSGvzM,755 +ckan/public/base/less/forms.less,sha256=C8j5NT-CHUH22PWQu2ILwZypMWwhlaxPbAFSXV75K4g,14910 +ckan/public/base/less/group.less,sha256=6YPTHKJT3_bE2NbYDVNX2MYnhHj6xYAZ7ev3_qnOAkg,642 +ckan/public/base/less/homepage.less,sha256=QG-0QaTmGIYaHovvOr-9hLr8Di7cfeGuPZ7EoXFsbaU,2334 +ckan/public/base/less/icons.less,sha256=J_QIcLci2trulncyQ8Yyka3dTKSbVkSHG2o2Rf97nLA,6021 +ckan/public/base/less/input-groups.less,sha256=iw0NS7AzlPlw467E2Hjq5jT1QbPiAX6g-ZWhSscAxjk,135 +ckan/public/base/less/layout.less,sha256=XTPQ1FiRpLq1n6yOFr-fDXY2ea2OvdCSo0yFUD_9sj8,5774 +ckan/public/base/less/main.less,sha256=MEo5-KSB9AWKQYYiiC9EWtuJsJ_hzdr6McQxIu4jiA8,75 +ckan/public/base/less/masthead.less,sha256=PnNJlWiUWnEW89tS13Wv8IVfw2taYMB1P_rgtZBCk1g,5893 +ckan/public/base/less/media.less,sha256=VeqUW1rPa3x-2XrwBsIiq76DK-8TpOfLuBCOfr5qOJY,2860 +ckan/public/base/less/mixins.less,sha256=PfJx9JK1JyRrNZOo4zqHF2oiihBAGQXHQRu8KizjrWc,4143 +ckan/public/base/less/module.less,sha256=SKBBAc2L49Kgo1AIIMLFF6PPdSrFfEMe-9CawiN9iTg,4469 +ckan/public/base/less/nav.less,sha256=Y7LhPLwstlzizF8YGViRIDF7gXfrezCzC8IROYevlL0,3890 +ckan/public/base/less/profile.less,sha256=AEn7q55AI-OYdj9bmTeVa1ij2IpKUkXiVhHBA_FnXg8,79 +ckan/public/base/less/prose.less,sha256=PW1fYZSwQk6_y5YsWXVEXHN2zb1RDL8zjt4PSukw02c,2310 +ckan/public/base/less/resource-view.less,sha256=Xe6LIl5gqePSeJ8h8juQvrENq-YnPrIrfnO3bSPzdoo,2170 +ckan/public/base/less/search.less,sha256=61mXUKw6JMzlC25qM-jFGRztdsKsUxBxC-ILmGQZjnI,2924 +ckan/public/base/less/tables.less,sha256=V5kiGoqt4KN6LA0WQFjDbrn-SbV5C-SXhGN7FclLf_I,1268 +ckan/public/base/less/toolbar.less,sha256=zPBJlgcUZZLtWyLHPOrzDJR_0v3hJQ2bhRZgVruzxgQ,2853 +ckan/public/base/less/variables.less,sha256=bJf0n3wtaMYjqkqzYEPB16ZFnV4Ls9V1VBDHRSsbS2E,3416 +ckan/public/base/test/index.html,sha256=K9AlF8G1GT99_AIUb-weqdRVqYbXhNG9VhCkfE_u69A,6510 +ckan/public/base/test/primer/index.html,sha256=4bYbyH39b1tNChVRqSZa4rOJbNFdKm7RaR79OqfXBBU,37967 +ckan/public/base/test/spec/ckan.spec.js,sha256=0EkTMR_Qvamg7qPJOpRaqplVzlhsszNNLRfbcvX5phY,1896 +ckan/public/base/test/spec/ckan.spec.min.js,sha256=ICXCgHbKOpO4u80KCCKG8XTMYVkTfAVwdw_ACD8lpyY,1588 +ckan/public/base/test/spec/client.spec.js,sha256=FEMw8xdTCDFmUYwcuxq2TuFn0BWRnY73b7IKVDheM7M,13365 +ckan/public/base/test/spec/client.spec.min.js,sha256=yVkyUB-JHK3zyQkbsIjbNan9hXGMqu3IDZyvsOhF2tM,10617 +ckan/public/base/test/spec/i18n.spec.js,sha256=6DI8-jc_nRCC1R_L1pk9ZsCYWwCTvvHlKoRysudPb74,3707 +ckan/public/base/test/spec/i18n.spec.min.js,sha256=Bex_IIQc6aDYM69QjJyvJd8ALjfQ-ILZTGMf67dHTXo,2815 +ckan/public/base/test/spec/module.spec.js,sha256=ZCR6xxnQ_nzUzxtBVCjU-6q7jixbiIsPYdFYngnEg_8,13434 +ckan/public/base/test/spec/module.spec.min.js,sha256=X1VQc6sAdeD_0zUieXMZBlXrsjYkqclAJEauUu4Rk7E,10045 +ckan/public/base/test/spec/modules/autocomplete.spec.js,sha256=W2j6se1Kzjn-c8JOu67sl6vEuIZOaxH5P6oAU7bI1I8,11185 +ckan/public/base/test/spec/modules/autocomplete.spec.min.js,sha256=g3udJuoKDn1XKA0FgofPrLxYbRBYeeyEn97K82RLCa4,8966 +ckan/public/base/test/spec/modules/basic-form.spec.js,sha256=LmqoqSeXZPjz9AnUG5tHsXDr2D_FA2A_TSylTHProQg,1277 +ckan/public/base/test/spec/modules/basic-form.spec.min.js,sha256=nZYailOr2yi7jnXAyr_K_wiCEP9kQZ6zuKhkRXFtDxQ,1009 +ckan/public/base/test/spec/modules/confirm-action.spec.js,sha256=GidaWE9lQJ7JOjr8JbOwqNvrQVE44pL39zzMr0FQBJs,3678 +ckan/public/base/test/spec/modules/confirm-action.spec.min.js,sha256=3x1cVWZXQfeKoXeifmwjStkMf3WE0EXyLDGlIDqsRmQ,2895 +ckan/public/base/test/spec/modules/custom-fields.spec.js,sha256=WMDC4QoAkC76BJ9AOjKCKhACbAR4Hzn2fraRMPxCjbI,5350 +ckan/public/base/test/spec/modules/custom-fields.spec.min.js,sha256=rnIJ7Q_zbS161Xwucy11yGmzMGVOKhZh0aAuODs_Akw,4353 +ckan/public/base/test/spec/modules/followers-counter.spec.js,sha256=KR_ttKkVfUNeCn1A9CI7S4s8byZZlXh5TqP6wXf5Gok,5591 +ckan/public/base/test/spec/modules/followers-counter.spec.min.js,sha256=1TAcgSePqw1Log8NXbl9blJOfYRazclsT_9FK5DdnXY,4539 +ckan/public/base/test/spec/modules/image-upload.spec.js,sha256=ckJkV1cp1k9p_u_UWK-C5HnNIo_lB-EedLWi4CKlvOQ,2378 +ckan/public/base/test/spec/modules/image-upload.spec.min.js,sha256=Q4s4s3zYp7pEfxEoYV6ElW4vC9xy7gZVSsqAJX9A4yc,1970 +ckan/public/base/test/spec/modules/resource-form.spec.js,sha256=Dw0NYj0FPtdvHWW6lF3tVVBqFR1YAPivm6vnRT7xDpo,2185 +ckan/public/base/test/spec/modules/resource-form.spec.min.js,sha256=v0Lqw_z4sPkb1cB1FSV4SyBFSGbRRmZbOMjZdEfgxqM,1681 +ckan/public/base/test/spec/modules/resource-upload-field.spec.js,sha256=dZusozTW8gwa1Uh4HXAvVnfSju9wlCQFBVNhhA5bckc,8979 +ckan/public/base/test/spec/modules/resource-upload-field.spec.min.js,sha256=BVOVGu-EVcax2qzWvlSuho31CSm8T4GEXkK_pVcq5LU,7150 +ckan/public/base/test/spec/notify.spec.js,sha256=A0Quw3TSwszQDhO7XUw3_usxJIOfRZGfjxemmwdOTlQ,1565 +ckan/public/base/test/spec/notify.spec.min.js,sha256=dmX0uOR2YGEZNJOXAGJlhlqcUo1UjtPO1MVo9isdskg,1298 +ckan/public/base/test/spec/plugins/jquery.date-helpers.spec.js,sha256=UYQ3Hjkwy1BAEmHzqBY2SudKI8OsNZ2QO4FkplLz44E,1160 +ckan/public/base/test/spec/plugins/jquery.date-helpers.spec.min.js,sha256=gg4vU8emfzC9-aCjvB6he_7y6JqtYWoGPK-ETGQ9BpY,899 +ckan/public/base/test/spec/plugins/jquery.form-warning.spec.js,sha256=CZNpNpfO8l_W4jt3juFzjSQZCH8UxQwgVzwEa61cw8I,1107 +ckan/public/base/test/spec/plugins/jquery.form-warning.spec.min.js,sha256=6sHCX1tKsUPymzO8SjPvq2t5ZleJK0ztKDPXt4Qes9Y,938 +ckan/public/base/test/spec/plugins/jquery.inherit.spec.js,sha256=NtwEw1MHRd31RqBVnSkph2MucwBt19WzrvSZxFSSAAU,1498 +ckan/public/base/test/spec/plugins/jquery.inherit.spec.min.js,sha256=wwO3-pCdfqBYnULvCdmpTLRzdRv2ViWVeoAYLJFCqH4,1218 +ckan/public/base/test/spec/plugins/jquery.proxy-all.spec.js,sha256=MERz9WMFUMyXMHdpFhj-8b5706Vz5p8pmiD3XOU5Fd8,1917 +ckan/public/base/test/spec/plugins/jquery.proxy-all.spec.min.js,sha256=H57W5u1UgLrw9L6PugOk9-AT44y_zn4zolRBJtPJ5LU,1582 +ckan/public/base/test/spec/plugins/jquery.slug-preview.spec.js,sha256=-jHysRZXHOKcJqJAP0Hnahd1DbH2HdnmA4ylCE40ahg,2306 +ckan/public/base/test/spec/plugins/jquery.slug-preview.spec.min.js,sha256=Xc2oNNZC-uLM2tYIemg2scumovEppkElQDFQBPmaZCM,1968 +ckan/public/base/test/spec/plugins/jquery.slug.spec.js,sha256=UvXA-R4bFHIy1p1_ujcCUwKK2CuGGQOwW10qYVJ9mcM,1180 +ckan/public/base/test/spec/plugins/jquery.slug.spec.min.js,sha256=hNPxZr6-8bxh3hzcW4Uk022yuRAaH5H6YA5vXEMfaqU,969 +ckan/public/base/test/spec/plugins/jquery.url-helpers.spec.js,sha256=0MNef9EHdVeJO89mytT1GZO9dp6sNl4DqHYXD0rh1ps,1775 +ckan/public/base/test/spec/plugins/jquery.url-helpers.spec.min.js,sha256=JtVX422K_lpTiRAdAnP6XErT7r5DEUUcZd3wXYTBoHs,1429 +ckan/public/base/test/spec/pubsub.spec.js,sha256=cBMzRWGOJzILCI1WEan3R43n3N-qzZh9NH4lDPG4l5U,2356 +ckan/public/base/test/spec/pubsub.spec.min.js,sha256=GqCXl6jPj5_GAvZ-10KpYU5N8vlqExU32Vpc2wGsv9M,1832 +ckan/public/base/test/spec/sandbox.spec.js,sha256=_7Qwp_scg3SVHen-V8lfaUTLoeqV7OkWYIG5PA-01MA,3228 +ckan/public/base/test/spec/sandbox.spec.min.js,sha256=YzHc3MKNWwJZhAA4WZ0OfFihlNgJoi23DbIVeFHKSps,2462 +ckan/public/base/test/spec/view-filters.spec.js,sha256=Xgh3gWVEkvFwwyF96A9he4anlOuU4mHyUq_07EvWg0Y,7063 +ckan/public/base/test/spec/view-filters.spec.min.js,sha256=jlvkmrAKCFs_0RWgPtS8F8N5XSF8iJAvagFnCNoXrO8,5608 +ckan/public/base/test/vendor/chai.js,sha256=_m6PG8u4InMC4MpgwD0IjGYaz_7TQQ329ANSb6ujGUA,88179 +ckan/public/base/test/vendor/less.js,sha256=s9DH5vNHQLw3xitupAAEdWs1xy32bmhrhiIKYqunVZA,117172 +ckan/public/base/test/vendor/mocha.css,sha256=KWAIE2b-T9RCaTdHsD8Ufo3ei-m-CtzMzBBXNmHY7c4,3832 +ckan/public/base/test/vendor/mocha.js,sha256=aJrBmgehLL3ngu7NIARX8uSFU8t_24GjPQdtiJNE0Mg,112554 +ckan/public/base/test/vendor/sinon.js,sha256=T-lVh_jIV1to-4LwvorgfSIB-iadqhnhL3A1OvSLX-Q,126539 +ckan/public/base/vendor/bootstrap.js,sha256=IJ3GeSUv7KJyXK-26PwxTyYYvXSNuEa-a04MpxxVozA,50089 +ckan/public/base/vendor/bootstrap.min.js,sha256=L3PqFB4jXJpcAZrzGZ8fC89bsclPSWAENhgBK2RqyUQ,26990 +ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot,sha256=E2NNqH2eI_jD7ZEIzhck0YOjmtBy5z4bPYy_ZG0tBAc,20127 +ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.svg,sha256=QvYGWdJlwaPDD5-kKry7Vr1KU69Ng9MW1t16NpA8Q-U,108738 +ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf,sha256=45UEQJN1fYKvyxOJV9BqHqk2G9zwtELQahioBRr1dFY,45404 +ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff,sha256=omOU9-3hAMoRjv8u2ghZYnWpg5uVnCJuFUOVV6WoB0I,23424 +ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2,sha256=_hhdEaSWdokNR7t4MxKgzaWkTEA5IUCU55V7TAQO8Rw,18028 +ckan/public/base/vendor/bootstrap/js/bootstrap.js,sha256=Cr6N6zNN4bp0OwTQOZ6Z66M2r-2dpy_EwKMCyZ-SOMg,69707 +ckan/public/base/vendor/bootstrap/js/bootstrap.min.js,sha256=U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8,37045 +ckan/public/base/vendor/bootstrap/less/alerts.less,sha256=h1LLa2gLDo-iheSug1rdcr8aMvnsUJgf5UDfop78vVk,1518 +ckan/public/base/vendor/bootstrap/less/badges.less,sha256=UL0A0APjH7sv46F2Zq9Cw4e1X9dBAMCIzPeokEOdDIs,1199 +ckan/public/base/vendor/bootstrap/less/bootstrap.less,sha256=MknnJiU31MOZYu-H6cwh6C05FApC_Ejee_ReKk_79SY,1291 +ckan/public/base/vendor/bootstrap/less/breadcrumbs.less,sha256=HMMzyQRrnU6cd_APTlUFGVT6X09-Dl4xOj-GocEQW_w,594 +ckan/public/base/vendor/bootstrap/less/button-groups.less,sha256=QwqQzJH-tb82j_9v7O1Pxi7OcV9b-RWGuqZgezFUnbQ,5680 +ckan/public/base/vendor/bootstrap/less/buttons.less,sha256=HS_ZhgmGnoMYEjQKQQqSuQBP6-unJTO77dL1eoN4KB8,3662 +ckan/public/base/vendor/bootstrap/less/carousel.less,sha256=cikdWCG283MHUmVKSvX8jO-mxwd9VMqVTunnksV32Ys,5651 +ckan/public/base/vendor/bootstrap/less/close.less,sha256=L9bzc_8Bsqk5DAGI384bXa-ana3j0t071UyOFitVIWI,764 +ckan/public/base/vendor/bootstrap/less/code.less,sha256=yVJk_HXy6wjHnbe4l6nRJ2GB2aRMAPCgPlLgGzLwXuU,1401 +ckan/public/base/vendor/bootstrap/less/component-animations.less,sha256=6Tn02TYdvaiC_ptVMhp2If1rneWIJWsnnaDjGVH1Bjo,666 +ckan/public/base/vendor/bootstrap/less/dropdowns.less,sha256=yQlVtqmqZHjnn305VDSnLUVWAbhvgKqLmgu9OMVcztY,4876 +ckan/public/base/vendor/bootstrap/less/forms.less,sha256=O542zM68aRn3yzAwoI_6s7JD_aC55dz3Xu1KPl1nWtU,15822 +ckan/public/base/vendor/bootstrap/less/glyphicons.less,sha256=BJIB-IT9o90RHwljnMbqyabJUDwTjKYJs4hhzuelMgU,19807 +ckan/public/base/vendor/bootstrap/less/grid.less,sha256=5gDviOvGjdp2LuugebG5NcAQ_UBwu2UIyd1xVfjDlaE,1387 +ckan/public/base/vendor/bootstrap/less/input-groups.less,sha256=KjzqCNsWXh-wLWmbYrOHgGDHvxhjLIBnbtqbLy3nBjk,4285 +ckan/public/base/vendor/bootstrap/less/jumbotron.less,sha256=rTXCpHugcyLr05OFa9BCowcKrJr0j1c0VJWGuKdpbPo,1152 +ckan/public/base/vendor/bootstrap/less/labels.less,sha256=-q_opgt0kgkaZKKr6VetFLMyx1B5T8qA5PDKXtLbY28,1079 +ckan/public/base/vendor/bootstrap/less/list-group.less,sha256=1-KF8ugjxyJ2ZiBfO6ZtM8Gsa38ju8Vp9dRri7O-2iE,3124 +ckan/public/base/vendor/bootstrap/less/media.less,sha256=8XLBFkRkmcfnI8u_jJ1QRbyDZ7IyGSftOK3L6kmGB8k,900 +ckan/public/base/vendor/bootstrap/less/mixins.less,sha256=IZ1qX52iWsdN69k55TsERQwIiSJMy9X1WC2AXe5wqo0,1136 +ckan/public/base/vendor/bootstrap/less/mixins/alerts.less,sha256=ccjAtuIqLUFnpu9UB8Dv30XePUYfxXfbdozW7AYq2tQ,257 +ckan/public/base/vendor/bootstrap/less/mixins/background-variant.less,sha256=qTUzCLgsjFZSIfxMkzZ9z9FxP10IH_TPGyNfCEuFZPc,151 +ckan/public/base/vendor/bootstrap/less/mixins/border-radius.less,sha256=5phxtYA4Nbtn30DZ4XKhVI45A0iMoxr8ZWhoPlmFySg,468 +ckan/public/base/vendor/bootstrap/less/mixins/buttons.less,sha256=4hzfvGxUycTiUCiF2jskq8HRQfQ5koSU79NMQMk6I-4,1441 +ckan/public/base/vendor/bootstrap/less/mixins/center-block.less,sha256=QqW_0ecqBM5i3Cy58vD1_CRoiTy8vH65z8B9i-cgUhM,120 +ckan/public/base/vendor/bootstrap/less/mixins/clearfix.less,sha256=dV5nU6gmB4k9ZeQANroevGmlW73L05JagmlnrPLtcls,605 +ckan/public/base/vendor/bootstrap/less/mixins/forms.less,sha256=iKxcuMW6X8AJ2dZaty3s0NNM-HBuuDMnVmdhrmrs0ZI,2641 +ckan/public/base/vendor/bootstrap/less/mixins/gradients.less,sha256=1bFXuLiqt4oGx8QTWuRg6_pBxbGJEPUgEicHLMvjS8A,4388 +ckan/public/base/vendor/bootstrap/less/mixins/grid-framework.less,sha256=FOJSzZrTwV0W1gmIDQk6eHr868ZH6fR8kMFmO1NT1Gg,2797 +ckan/public/base/vendor/bootstrap/less/mixins/grid.less,sha256=gmCDaZt0uTZgNdJLjNaesES7k-3jtGDt_idyqrWS7RI,3120 +ckan/public/base/vendor/bootstrap/less/mixins/hide-text.less,sha256=AOmV-Wv0dQ8WVW3YCOEXaANS5FaolvyLN6xLjM_i3bE,575 +ckan/public/base/vendor/bootstrap/less/mixins/image.less,sha256=W8Nvi2RRrAY9R8LAtBxveIQH3SoAKutYticahpg4UJ8,1062 +ckan/public/base/vendor/bootstrap/less/mixins/labels.less,sha256=tDTYl6yNOOAdj0izsuHXsf5OWDGnbuh00h09ievhZQw,161 +ckan/public/base/vendor/bootstrap/less/mixins/list-group.less,sha256=F7p8klBFns9mjRzAavTZd1OosCPcJiqqdkvo-93Zkxs,546 +ckan/public/base/vendor/bootstrap/less/mixins/nav-divider.less,sha256=gEP406tNjTzYY193fVG9gQG58NUBf5kgwQ2it7r9jU4,232 +ckan/public/base/vendor/bootstrap/less/mixins/nav-vertical-align.less,sha256=qza9x_uGsYunebuABo064WFOlvaIj-jwbr-yA4p8qEg,364 +ckan/public/base/vendor/bootstrap/less/mixins/opacity.less,sha256=fkPPW9eVhGdFHG4cdCDORX8nD9seCrf3A3GPRVmlv54,148 +ckan/public/base/vendor/bootstrap/less/mixins/pagination.less,sha256=SuKtj_KSUyx_r3F_Lf1IohrgN_slcMKmgHJjESEdHRg,485 +ckan/public/base/vendor/bootstrap/less/mixins/panels.less,sha256=MV3D-DaL1C6j0qYNAgr_LDt7bfnF2bTN4PXj3lInS0o,537 +ckan/public/base/vendor/bootstrap/less/mixins/progress-bar.less,sha256=4-RByBv-HzzjGe5dQMjJMxmtFpkToxVc7Ibi5qQAu-Q,191 +ckan/public/base/vendor/bootstrap/less/mixins/reset-filter.less,sha256=aZ7Xk1MMzxGxcispQ1lsVx00FDTGQZK0UEMFE36aIMk,248 +ckan/public/base/vendor/bootstrap/less/mixins/reset-text.less,sha256=G3X9_NSJhFvgkkwXkc9mm2U1HNd_6FTa7ANMvaO1WS0,470 +ckan/public/base/vendor/bootstrap/less/mixins/resize.less,sha256=thMoFPVqj0OF6dNWTwbjGFR8ozD1x8c0SIb1U9ZL2rk,196 +ckan/public/base/vendor/bootstrap/less/mixins/responsive-visibility.less,sha256=eADeI9Ay88UMazqwiRwcXx6QeOd2zM2YyVw9ne3R0yE,354 +ckan/public/base/vendor/bootstrap/less/mixins/size.less,sha256=cxJzrUFG5cyNzQxCAFtyIB6bcgmpJgUEJiuWde-DOqM,127 +ckan/public/base/vendor/bootstrap/less/mixins/tab-focus.less,sha256=jtnEMETpJYF4q04XwDR_mjVUmA5sy9mC60Jv9u8fIDU,332 +ckan/public/base/vendor/bootstrap/less/mixins/table-row.less,sha256=-8CnPqKo4jL2b7xmhPKdnS82H5-_4hkTGGBiCSMPwbg,700 +ckan/public/base/vendor/bootstrap/less/mixins/text-emphasis.less,sha256=yUbEfQrs6RoQh8dHyz1LGLiC4mh9Bpa1Wy9Fach3f5o,128 +ckan/public/base/vendor/bootstrap/less/mixins/text-overflow.less,sha256=aq3mm09gb_AJmkQI_aT156wNvZbCNzmwwgGscgDoqPw,162 +ckan/public/base/vendor/bootstrap/less/mixins/vendor-prefixes.less,sha256=G1ByBJNeA5VeM14ooExuiWe5nGbzY1Xi8wk_EtEIB98,6653 +ckan/public/base/vendor/bootstrap/less/modals.less,sha256=ddhNsHoylIBm8U9Hh_xc_R7EMcaBs0CMqZNpqzAykJw,3527 +ckan/public/base/vendor/bootstrap/less/navbar.less,sha256=2IWGva0s05ehpiPqCu6nrQGeOiDo-PoEv25OPkXywuk,14628 +ckan/public/base/vendor/bootstrap/less/navs.less,sha256=6pm16GbnRkKjuhq1WgpT4OBxHmeoko6KJHG4Ttw-BFY,4930 +ckan/public/base/vendor/bootstrap/less/normalize.less,sha256=zwEJ3Qj9sk1eCcmXM33uaj3yB1NfUm6pLQPT1e3p6Sc,7559 +ckan/public/base/vendor/bootstrap/less/pager.less,sha256=jnkEl5QlyzVrUMvJ7a-uX7iF5cQxB4GoG1vVBvBzwJ0,861 +ckan/public/base/vendor/bootstrap/less/pagination.less,sha256=0Gnbvm06Q5Dh6dVc-igM65JcK-_y3zhH3eRTRjdnhfs,2059 +ckan/public/base/vendor/bootstrap/less/panels.less,sha256=xQRnJcq3rkJWlyv2A6LgK5ZCJm13Gn45W7A8JfmyZ4E,6279 +ckan/public/base/vendor/bootstrap/less/popovers.less,sha256=pAdT-qYJ3GD71pKe3U4iWdm5qxmo0QsaFe2m1nt1Djk,3488 +ckan/public/base/vendor/bootstrap/less/print.less,sha256=s2V5jMPMwagbEa-zrufJIlbLwhmxQDK1fdSLL7BFvfg,1939 +ckan/public/base/vendor/bootstrap/less/progress-bars.less,sha256=gUF9BqZe5Zufq8RBzU8Zf-fHtooR1VloYPmDQaLz4WU,1925 +ckan/public/base/vendor/bootstrap/less/responsive-embed.less,sha256=2Y5RRJFxRr-BnubGEHynGBL9OejDWitROiIc7k8MfLY,546 +ckan/public/base/vendor/bootstrap/less/responsive-utilities.less,sha256=gvrvN9ve1ab0KOcW4Lj5h5eSeRc7G_VpV4B1aZB7F9c,4262 +ckan/public/base/vendor/bootstrap/less/scaffolding.less,sha256=1-LKklUroKDqVYe_o08e35xpG9Xdp_yflNU6y094nQ4,2987 +ckan/public/base/vendor/bootstrap/less/tables.less,sha256=4_ZGmP0K7q1zTQljVm-DMUFPnyV8ybqJY22tCf3u52Q,4612 +ckan/public/base/vendor/bootstrap/less/theme.less,sha256=7w95LWqtTZwS0xK7mfCrs3hpy8xw5Pxunj2jDnOihSU,8197 +ckan/public/base/vendor/bootstrap/less/thumbnails.less,sha256=h-mHnH5t7HFU2AqkKobEXtiHa2ioaGRMy5zWtKbFPg4,753 +ckan/public/base/vendor/bootstrap/less/tooltip.less,sha256=UvPzAgabp-ok_Zbh0X32KD3qAXsdCUyVS32e8LbD3qc,2985 +ckan/public/base/vendor/bootstrap/less/type.less,sha256=uUNK2iDU-15FC6pm9KPgVYmv-DHpEpQMHFMGZ394cGU,5954 +ckan/public/base/vendor/bootstrap/less/utilities.less,sha256=9gNMHjgL-UQX_cU_nJO7xndixlcWiejpdNu_PGyXOYo,747 +ckan/public/base/vendor/bootstrap/less/variables.less,sha256=OYUMl1K6lgvDKT1H1SBWpX78o2uatc8w-qXY_rsfkZM,27472 +ckan/public/base/vendor/bootstrap/less/wells.less,sha256=SOizNFBNIk6AnGD-9m_Cjh9UErreSyGZUrNB0MS319s,527 +ckan/public/base/vendor/font-awesome/css/font-awesome.css,sha256=NuCn4IvuZXdBaFKJOAcsU2Q3ZpwbdFisd5dux4jkQ5w,37414 +ckan/public/base/vendor/font-awesome/css/font-awesome.css.map,sha256=OnWOmMndQbWhR065-2rXauW_hVr2psKMCgrws0DJhg8,21778 +ckan/public/base/vendor/font-awesome/css/font-awesome.min.css,sha256=eZrrJcwDc_3uDhsdt61sL2oOBY362qM3lon1gyExkL0,31000 +ckan/public/base/vendor/font-awesome/fonts/FontAwesome.otf,sha256=RE3UNmYV_8ShbQErL6kBNwZdPMtBD6b9Xk3de15P_NU,134808 +ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.eot,sha256=e_yrbbmdXPvxcFygU23ceFhUMsxfpBu9etDwCQM7KXk,165742 +ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.svg,sha256=rWFXkmwWIrpOHQPUePFUE2hSS_xG9R5C_g2UX37zI-Q,444379 +ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.ttf,sha256=qljzPyOaD7AvXHpsRcBD16msmgkzNYBmlOzW1O3A1qg,165548 +ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.woff,sha256=ugxZ3rVFD1y0Gz-TYJ7i0NmVQVh33foiPoqKdTNHTwc,98024 +ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.woff2,sha256=Kt78vAQefRj88tQXh53FoJmXqmTWdbejxLbOM9oT8_4,77160 +ckan/public/base/vendor/font-awesome/less/animated.less,sha256=xwNUI9Wh4D02vP1kZUgeTE1ckfMW-oeKblEDr-Gwu_Y,713 +ckan/public/base/vendor/font-awesome/less/bordered-pulled.less,sha256=49FoopK6M9TjIVkZljowTcJXMqc8693m5-vk1Hq0PrY,585 +ckan/public/base/vendor/font-awesome/less/core.less,sha256=pKk9WYE09lasbHoKXzda-pQeceNI5o7PPNb-t2Fqz54,452 +ckan/public/base/vendor/font-awesome/less/fixed-width.less,sha256=DvhToxknQtIH6g55Pci8xjIty55Q1B9LmBxOusLax6w,119 +ckan/public/base/vendor/font-awesome/less/font-awesome.less,sha256=LfYjBaihTgnsrVihVaR48cjBExj0BTYL5oMhn2Kz6iQ,495 +ckan/public/base/vendor/font-awesome/less/icons.less,sha256=TVhe7umPSCbFR-AwppBpDqVqTe3oBvyBdjQqvs0G_qE,49712 +ckan/public/base/vendor/font-awesome/less/larger.less,sha256=38edQyWATpreIatlFFsjscQZPSSok2kLxHtcBznKPAo,370 +ckan/public/base/vendor/font-awesome/less/list.less,sha256=Akq7kw5mafIV3e4Z9YB3Vx8cq7eu4SdL-WsibClrc9M,377 +ckan/public/base/vendor/font-awesome/less/mixins.less,sha256=ZcS7cTh3IEP6_SFnt0wP16wV5Xunh3rezeSwmSlQZww,1603 +ckan/public/base/vendor/font-awesome/less/path.less,sha256=oNy7zTl29ZEWJowQl2fDuY_jWIcvbp_BoNJqM30nK48,771 +ckan/public/base/vendor/font-awesome/less/rotated-flipped.less,sha256=1n35VIjuyE0tDK95cnglqP9LLfkMYEFReD4-wjiNrTg,622 +ckan/public/base/vendor/font-awesome/less/screen-reader.less,sha256=sy_3ghp7CmSfkgKgLuuM6I_mcetS1h7OUKq9vOIREMY,118 +ckan/public/base/vendor/font-awesome/less/stacked.less,sha256=P53mw-DxorxxV5pBfXxBX4Lyo9P0eSFhqFiLuL3XVFA,476 +ckan/public/base/vendor/font-awesome/less/variables.less,sha256=43F0Ipdiktj9xLKp7QK40L5VrVC4bpv_dHYeXM-UuDk,22563 +ckan/public/base/vendor/jed.js,sha256=wPSuYNFQube-W7b2nG0EJmXjPFjafsaD44eUkn8xY5E,37198 +ckan/public/base/vendor/jed.min.js,sha256=CT6B8rF6DPKeRDP_4cjqXQK0Itj95IF5JRNs5cUIsbg,21119 +ckan/public/base/vendor/jquery-fileupload/jquery.fileupload-ui.js,sha256=6DtGLN3tv1hrOLifK8gEzn4hQ52Me6j6T-Xt1LuIEH0,28532 +ckan/public/base/vendor/jquery-fileupload/jquery.fileupload-ui.min.js,sha256=3Px08ZRveZucO5CXRDAcnZnZqCk6KvGw-unaSeui6-U,13097 +ckan/public/base/vendor/jquery-fileupload/jquery.fileupload.js,sha256=Zufkvh2B13hX06Gcb8AuG3OQ9L36_prJugOpIN8jAfs,42985 +ckan/public/base/vendor/jquery-fileupload/jquery.fileupload.min.js,sha256=XcE3hLiNl4nRLoB1UB4f0av8h_5oSWuiH59H6jUMB08,15360 +ckan/public/base/vendor/jquery-fileupload/jquery.iframe-transport.js,sha256=PcU7ijJbcUZSno17apjnr5v5u3noRAHXRLl4cXO-2bY,8089 +ckan/public/base/vendor/jquery-fileupload/jquery.iframe-transport.min.js,sha256=mr2y8AtM_aSmz33XKSMK0CLoXcuPG26Qd2YBO6DxDnM,2293 +ckan/public/base/vendor/jquery.js,sha256=hipe5Z0PQxI5mbdNjHJIpVgyiirQM0vO5SvI-WEulJ0,276280 +ckan/public/base/vendor/jquery.min.js,sha256=hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4,86659 +ckan/public/base/vendor/jquery.ui.core.js,sha256=q-tGbI-PS8cDpu5sJmxF8WzfWKSK81WYAUYIu-EE1EY,8198 +ckan/public/base/vendor/jquery.ui.mouse.js,sha256=KDOSMpUKj443n-CjjvY3FVAeBg9VYL9ciAnsUmviubM,4561 +ckan/public/base/vendor/jquery.ui.sortable.js,sha256=aqR-6shGXAFPXIiGF_HQNsnyJWHMFC8ayOMbvvBqo_4,42704 +ckan/public/base/vendor/jquery.ui.widget.js,sha256=JWVTvlTW2PnGhHvs-9zaxmc7IbqNfe5Xlw5hRajWM18,15069 +ckan/public/base/vendor/moment-with-locales.js,sha256=S6pRrDGCqIDstuJtFMpTwmONwfbyvjuYlmQqGrbdIvU,225232 +ckan/public/base/vendor/resource.config,sha256=25TukSfUnEWFxN_6PcaF3L9xFJgQeiMo7IjHjAuJFLo,619 +ckan/public/base/vendor/select2/.gitignore,sha256=Qbrl57I3qwWYreawt9s8rP1gnwtHR8WA8MUwBPvlG3M,7 +ckan/public/base/vendor/select2/CONTRIBUTING.md,sha256=diLCJIiRzxqoOzpQysxuzInnJdD8btIKygTT6fuNdY8,4858 +ckan/public/base/vendor/select2/LICENSE,sha256=p45JZfhkZCvhn_uUQyz9hOb3KyJ2-XHSp8GaINR1RJ0,938 +ckan/public/base/vendor/select2/README.md,sha256=1I5caCVxUX75E3ggCIMK0i-zsCzm1Qch_jATqCfIKXI,4774 +ckan/public/base/vendor/select2/bower.json,sha256=oo07-V02eJEMqWCIv0ibxYaa2CZn7P-WwTCHCG-jOuE,206 +ckan/public/base/vendor/select2/component.json,sha256=a4Z0Qe-99dDSE_tD-pogjZxk7ibxv_eRLD4CcX0V0J0,1732 +ckan/public/base/vendor/select2/composer.json,sha256=rYLxBXC_jMX6c56wZyq9t0YfuJD2ujM2ZICV_F2xSaQ,637 +ckan/public/base/vendor/select2/package.json,sha256=V93PqTDLbmqsWd7iUnwC9mIqAMlL1NRBmOYkUnT9mfg,678 +ckan/public/base/vendor/select2/release.sh,sha256=dZPsgE8q9kewQVegDO9cHbelRAXMdvfu_vAF3XsUm70,1490 +ckan/public/base/vendor/select2/select2-bootstrap.css,sha256=famh_fYVCsn7aElzQQSquoRIcf8RnaQ-oM7MyTDEdq8,3347 +ckan/public/base/vendor/select2/select2-spinner.gif,sha256=9uz_YX7Cun9Vnm9TXK2bcKP5ESBzdTXatNRUimyDV2w,1849 +ckan/public/base/vendor/select2/select2.css,sha256=ZODrOgaVxHouOJ4nu2EkvszH71GOyMSwtRidv0CM6KI,19223 +ckan/public/base/vendor/select2/select2.jquery.json,sha256=eZIBF7DuQULrD9JcC1IH2b7hG_sDtws9yPgqMSilIkw,1030 +ckan/public/base/vendor/select2/select2.js,sha256=yiksMVUgkOIV0EUNBs6o6TF67k2uPYp1cJrTza-M7DU,156367 +ckan/public/base/vendor/select2/select2.min.js,sha256=7A2MDY2eGSSUvgfbuH1IdzYk8qkEd3uzwiXADqPDdtY,70142 +ckan/public/base/vendor/select2/select2.png,sha256=1rXY2D28GPuNd8h2HTMc2eUSPJaElQurBAbpiiSsWug,613 +ckan/public/base/vendor/select2/select2_locale_ar.js,sha256=h5CbdT-HqSJ0TDSKk1z1mbehvRWKAlffLWmOhTPElWo,1389 +ckan/public/base/vendor/select2/select2_locale_az.js,sha256=OY5BiLCpjc-cEz3TKJsiqo8kLMicoOP8VGutl-yPw-M,1003 +ckan/public/base/vendor/select2/select2_locale_bg.js,sha256=GfIvKvshdfw8a7W6Exige7AERlnA8xL5Crv_bGoSzqI,1081 +ckan/public/base/vendor/select2/select2_locale_ca.js,sha256=KrfJIfhZDYShgDLIMnTTb8B3GplrHvlhh2rDTAMKO0A,952 +ckan/public/base/vendor/select2/select2_locale_cs.js,sha256=ZqEocqKZTIpKcRbfmE0mRplG15wwZVsAbJG4TJUKMek,1988 +ckan/public/base/vendor/select2/select2_locale_da.js,sha256=wNcE-4Ch3upkz88v2GQbdVZHlXB6c_PVjn9MWj6EE90,853 +ckan/public/base/vendor/select2/select2_locale_de.js,sha256=6NNs6So6FsnOgqRRWJFFLG45RxluvcjdDrk0DyuLkAY,1014 +ckan/public/base/vendor/select2/select2_locale_el.js,sha256=cYvwGq1Avn5h1nHE-9wfWJ2LA0jjuFdZBz9MqF3aZO0,1128 +ckan/public/base/vendor/select2/select2_locale_en.js.template,sha256=hPOQ5dyVAoFk-gei9L6rdC-IyeSM6scd_KsqJemwjak,1102 +ckan/public/base/vendor/select2/select2_locale_es.js,sha256=d5R_SSZ5J2ak5s638iz078M1RVLgqnVfrDWwLjau_n0,1177 +ckan/public/base/vendor/select2/select2_locale_et.js,sha256=79G0dGGe8qL0i9RlYCG8tc2Lf9w2KrQCLzBmNQ1vrO8,886 +ckan/public/base/vendor/select2/select2_locale_eu.js,sha256=wVVjZbSiVJXZmFiM4EU61cT7TSl6RVNKgqmxXfxcD0A,1313 +ckan/public/base/vendor/select2/select2_locale_fa.js,sha256=H2LWp8rpCfB0sRN7F1DMgUfGrDTOHRbY_bOQ1TwflB4,1207 +ckan/public/base/vendor/select2/select2_locale_fi.js,sha256=mX17g9dQ2wZyHGUs29Fz4TnXL8VsvLrzAGngSrAHsFE,940 +ckan/public/base/vendor/select2/select2_locale_fr.js,sha256=6EfvDfmTdq6ddqDyYURYP-xRHDhCM7Z1yV3J16kTw9o,1077 +ckan/public/base/vendor/select2/select2_locale_gl.js,sha256=JkJ2nGH0PQGVr7lBzH83JUVh6oeo3FgrBi9yPIHVA7A,1339 +ckan/public/base/vendor/select2/select2_locale_he.js,sha256=gpmWloS5UhRm5A5TkNVL_WjyE_cZlOJ5l98QG_AK4mw,885 +ckan/public/base/vendor/select2/select2_locale_hr.js,sha256=VkDX5yVGyYRN4K4Ef7JkFKF_gCUwqPTejYP7CvqpvU4,1002 +ckan/public/base/vendor/select2/select2_locale_hu.js,sha256=_YoJfVPKaGozxREp2ZvFTSJUhiKBkBtVbYLvMqCNhbA,802 +ckan/public/base/vendor/select2/select2_locale_id.js,sha256=6gGM1UXa49XaMglffo-QKPUHWvUsxCO4Dt1ESMKNWAI,1111 +ckan/public/base/vendor/select2/select2_locale_is.js,sha256=urhHO3L_4CPbUVlYkmqnTMSpHrwOZ3GFCqELXJjodXw,855 +ckan/public/base/vendor/select2/select2_locale_it.js,sha256=Lc5KEaccVMgbP5HtNQnYFZwDY0mMK22RZ9vdts5lngc,866 +ckan/public/base/vendor/select2/select2_locale_ja.js,sha256=7p7rcqLEejo5p_B4ZkWQ2WXPrtPI7ARLbJKjjAW8uKA,812 +ckan/public/base/vendor/select2/select2_locale_ka.js,sha256=k2D1HThxHQZIdIgBjC3Ktxpeb-XTlHJI7WINYxGPtFM,1078 +ckan/public/base/vendor/select2/select2_locale_ko.js,sha256=P_dSnVFTe6nb1b4pjcai0DTYSDrBR6NrfYJOBNKEPJ4,871 +ckan/public/base/vendor/select2/select2_locale_lt.js,sha256=072Jg8_JLRLqMJoicRIy1ymB_O2bWFbP4TOqZKIt_vY,1144 +ckan/public/base/vendor/select2/select2_locale_lv.js,sha256=duiEcPhTE9V_bfzDlce2v92KrEkDtQxiDUC93f0McXY,999 +ckan/public/base/vendor/select2/select2_locale_mk.js,sha256=F3wf-nKtRAljips5b0Pxlx7ShT6v1g2fzaKDTYT6mXg,1064 +ckan/public/base/vendor/select2/select2_locale_ms.js,sha256=6m99LVqIo6H8dpvp5b3WN3SBLUXE-WwrNaZUUSPhr7w,1122 +ckan/public/base/vendor/select2/select2_locale_nb.js,sha256=hFR8EnOlGt_5yz2Mr1fboy2WQ_2RZ_fi7hdIF7iLseU,1145 +ckan/public/base/vendor/select2/select2_locale_nl.js,sha256=Pv7QlFd_8UQaVRZF6FLql6_6jdh6y6jyn7kfEDqvYJU,846 +ckan/public/base/vendor/select2/select2_locale_pl.js,sha256=zIOCf5pC0dHhgnUUr9312ofo4ejGvIEGY2_85dcCFcY,2015 +ckan/public/base/vendor/select2/select2_locale_pt-BR.js,sha256=sDm05maHvLzH3AJY63MK7aYlQ7tuphxKQ70579J8pvY,969 +ckan/public/base/vendor/select2/select2_locale_pt-PT.js,sha256=RPB6YqeKMjwnIwLNb_kEYBw3DvfYH3rt8fwlWEWHIKo,891 +ckan/public/base/vendor/select2/select2_locale_ro.js,sha256=2JsNfTjPHO2DMSN9asein_HTU_dctKICudOgtuO2z0Q,902 +ckan/public/base/vendor/select2/select2_locale_rs.js,sha256=6lSzwPepW7v8-WW5hJcLGdWEBN4w8AC09wftW6dX5lg,1058 +ckan/public/base/vendor/select2/select2_locale_ru.js,sha256=EJ8o7X2Rn7gTr1VTWB7Y9qvO8Q_cqre6vGAtXPztdLU,1171 +ckan/public/base/vendor/select2/select2_locale_sk.js,sha256=udt0WqxRU4kxFXcZfzsRxcubJUQsXLl2yhvLkSgbu_w,1948 +ckan/public/base/vendor/select2/select2_locale_sv.js,sha256=-BTB0g5PbkcP0Zm4FJy3HIaN1tmMWXkoo_0pl-_vUsI,847 +ckan/public/base/vendor/select2/select2_locale_th.js,sha256=L9oUzCADYaKQYfOZd5UFWK6YSE-7tGRq4H71ojpWRjM,1063 +ckan/public/base/vendor/select2/select2_locale_tr.js,sha256=yi5ZztLSBvzRF-LZhNyQqAoH8WgumAu6P0AWcVI7Lcw,1070 +ckan/public/base/vendor/select2/select2_locale_ug-CN.js,sha256=HpPOKKJbwrTVNmx8GrpEXRKihBOLbUMSCk4rRynehzY,904 +ckan/public/base/vendor/select2/select2_locale_uk.js,sha256=o7lB9Pb4Q7oPxkatWGSe05FfWurKaiV2mlrmpz_2qCs,1415 +ckan/public/base/vendor/select2/select2_locale_vi.js,sha256=_zuZkTwaXIu8MVn3lq-YuGZyHn0jdu8gUQLNm_HHThg,910 +ckan/public/base/vendor/select2/select2_locale_zh-CN.js,sha256=3o9Kg18uzans1ISeEQwbKrrQgNoPwxKQ1KaqlXLIpso,762 +ckan/public/base/vendor/select2/select2_locale_zh-TW.js,sha256=cSm7y_vDWirDG5X6v3Q61kBXqnJlpNCvzmlNSXSh5qU,774 +ckan/public/base/vendor/select2/select2x2.png,sha256=b-KNaH3A7U2WAWI4xgi6HnGYycmsz6CzYLeAGLn7m8I,845 +ckan/templates/activity_streams/activity_stream_email_notifications.text,sha256=0tLPeu2vfSmBCpFthZUYcThtoE2jy_TefEWrgSwKWq4,511 +ckan/templates/activity_streams/activity_stream_items.html,sha256=GgNxf22NUdDi5rrNGGGstNJLzoACN1r4kbga2dBcOGw,1307 +ckan/templates/admin/base.html,sha256=q74gtOBhntEAhZmm-dplhV_p-ceMD51Wz7cZWvmyYq0,430 +ckan/templates/admin/config.html,sha256=E7K_bKwqQVHr6YL28gdxa8wJ-8KrigoW8SuJArvfLpY,4238 +ckan/templates/admin/confirm_reset.html,sha256=LCY83d46_pDzwrzPYpCDLQsADBd8XXEF4sSXLdZ5ubo,516 +ckan/templates/admin/index.html,sha256=j3okIR7AYhGyoe8oMBZufEGgMQ3tc_Nq52v9R2I6stc,845 +ckan/templates/admin/trash.html,sha256=rHOhYXj2wNOpET_yKb4tnsocnSIYlsTwpQHuWbW7PJY,1018 +ckan/templates/ajax_snippets/custom_fields.html,sha256=w20xZxuWs7A1FN6LJuMlkmgpXVRpykU1FAOnGqBI2Ss,166 +ckan/templates/ajax_snippets/follow_button.html,sha256=daiFDtABR0fjKzhBIRW0zLPt1wodLtV_rfzE3tyC8FE,31 +ckan/templates/base.html,sha256=mNiz35gvXPzjshO7OObCUFn7edbeTxBUeKZsm02ZF58,3545 +ckan/templates/dataviewer/base.html,sha256=Navum8cEEe_mY8POL2xjBGZKyToDNj29Fudy8bYcel0,540 +ckan/templates/dataviewer/snippets/data_preview.html,sha256=0vex_3j45EGYxmjc6XWo6n5n_fq8yJEDzUeV4NvmpVs,1016 +ckan/templates/dataviewer/snippets/no_preview.html,sha256=5rDYeH0hvERYoYSNeEpHSZ63a_YxU_V0YF-N7wueViQ,577 +ckan/templates/development/markup.html,sha256=PEI-fyOyfqnTwYfW5dORbmn5yM52r6OvhYG_8N7s3Os,196 +ckan/templates/development/primer.html,sha256=DQ0bLi7g1Ql0Ys1MeADN1QOP3W2KISVP_V230fkAyaE,3692 +ckan/templates/development/snippets/actions.html,sha256=0RaKXvgusmLjlwzseP4qjDLt0yJ7UKK6ohXKVPcRl_E,178 +ckan/templates/development/snippets/breadcrumb.html,sha256=rwr2EK4rB2BJINen-POSvWnOena75zRiPTVtCVZxhcU,370 +ckan/templates/development/snippets/context.html,sha256=6IeWjA8JbpfRf6bPD3V9DILWdo2KTf4hnDe8CK7M8zI,633 +ckan/templates/development/snippets/facet.html,sha256=YC7ssCuYfZZ3ChSvdQj7BSD5JoazIwnBJwpHVKA9mvU,583 +ckan/templates/development/snippets/form.html,sha256=FqWZWWGDTy2l040v-6uMXeadEAAP_z46zBz9hCi9_G4,1659 +ckan/templates/development/snippets/form_stages.html,sha256=sq3MSqFEICZlI-WS7cta59syl0AyAp_mwL6mych_5R4,912 +ckan/templates/development/snippets/list.html,sha256=Fepat1N8sHZElHI_VsvBdVRkCOPo6leXPOAPCSpr6nA,476 +ckan/templates/development/snippets/markup.html,sha256=ZzMyZ3BlTGvwde2La8rwp7zyWtD1DQGTG2TGvNAKc_Y,25362 +ckan/templates/development/snippets/media_grid.html,sha256=jHv8R_HYUJJDikL2h0WCnBjh0p2RJn5OmnVZy_N-uLM,142 +ckan/templates/development/snippets/module.html,sha256=aVCDtV5S6N4z3pQ3H46sDlvScnzf3DFxGqwFvHO-I_k,778 +ckan/templates/development/snippets/nav.html,sha256=ZvJQCwMVy2g1CPBTHaiEMChmiPDohqHfyPqNXsMFiWA,553 +ckan/templates/development/snippets/page_header.html,sha256=tQ1DBgcFOHo4fi4hjjPWJjtF8WxjcL3rV3d5UHjHIIs,364 +ckan/templates/development/snippets/pagination.html,sha256=dnEeKy7XelVM0yeOU6Tn-lODNU-Yp3Z1-IPVGVv29tw,487 +ckan/templates/development/snippets/simple-input.html,sha256=s7ZL0RZG6soXZ-dMYx2WBjcuTzOVKbOgs2ZfywbB4to,265 +ckan/templates/emails/invite_user.txt,sha256=rIv6fjZLbERgM_qPHvh9k4I3_2vW1CwbkJ2nLBDpubM,421 +ckan/templates/emails/invite_user_subject.txt,sha256=7vR2iqpsWCP_dvTxvz3tYclNtRAUIAvEYPO1F065U_Q,28 +ckan/templates/emails/reset_password.txt,sha256=mghDCdJWzK5ytON0oLV6sAAh8N-33s9GsEs2_M_DVe4,240 +ckan/templates/emails/reset_password_subject.txt,sha256=KrR_LAQz9fONMuK5cc3dIUm5dSymg2PYDWDdNASTBwI,39 +ckan/templates/error_document_template.html,sha256=b_Bv6BUxXFJH-iWGHI0JGVAPA8N7ET-s69uV5O1io5Q,469 +ckan/templates/footer.html,sha256=wPIMnOzg6cQ44XOrW7BDlwdMMIb7yQZOf9_2rs3t2pg,1538 +ckan/templates/group/about.html,sha256=Em_xQ6Rar2Dk_9U7ze6HGyt5nC-Gv5wbeOBLTJyHGh8,536 +ckan/templates/group/activity_stream.html,sha256=0rsy0QsOlXLw1m-v8_a4K8EhNkrIzKYjKdcZESJ0eFs,350 +ckan/templates/group/admins.html,sha256=dFS3KWBvHpHuExEvJHZsQK8LjiXwia2QWTQZXeOGMZg,404 +ckan/templates/group/base_form_page.html,sha256=W6acAho8Xy3r-3P7yzwjWkne2DoE0geAiiWUyrDdnUc,590 +ckan/templates/group/confirm_delete.html,sha256=LCrHHkWzJTC16qTa_XGKlfYMT65wA36l7EY58-4LjZM,864 +ckan/templates/group/confirm_delete_member.html,sha256=e8wnPTqmVlcTrX90BlP9AVSfguidMs3MDR7rDpaAWes,905 +ckan/templates/group/edit.html,sha256=FJd0x9SXpE556mjJ8yEyuxxtEYqTUJfY0EVCRAzsXus,552 +ckan/templates/group/edit_base.html,sha256=O8-Wweiq094kqSEQXqHF7IVQQsY5nNuO5f-oMHTiJWM,1199 +ckan/templates/group/followers.html,sha256=S7HAVhldvCixyRd42ZoB7vN5wnzKR-AjzWZibHCBa5g,400 +ckan/templates/group/history.html,sha256=f5qtWLq3Ofzc6ByKp6UddQVbcSgAyg8SxIPWFvnIlss,399 +ckan/templates/group/index.html,sha256=LoC4HEqUtM357bHlB7dAImEqntY7eZ2vWKgTUm1jRq8,1744 +ckan/templates/group/member_new.html,sha256=qjrndIo9Z5CC9UXauo3aRfHd8syLFy2mFxICsaG-Ovs,3567 +ckan/templates/group/members.html,sha256=L7YymlmabZMflneQvkSgc5_OBlgc_ZQPZkCcywzZB64,1613 +ckan/templates/group/new.html,sha256=Oh-yND_Y3Z7jS8pMUflDhiX2C7p2ubiXnD-0LCZefBg,430 +ckan/templates/group/new_group_form.html,sha256=ZaSed0_B_wb9Nmzb68OVwIouwqJu6UhLP3JTuPGlWFs,623 +ckan/templates/group/read.html,sha256=K6Mm6xtHyClxRRkvaVVrjH8skaqUF5arUpErVBD3Pd4,1620 +ckan/templates/group/read_base.html,sha256=_ZKDwBNosYCyZDIMyl1IosdZNAAymOEcar8i8qmNiOY,1265 +ckan/templates/group/snippets/feeds.html,sha256=JT4O1cVUwXqbpath8eDFPMTR5cVlMym6aX8KbLw_p2A,500 +ckan/templates/group/snippets/group_form.html,sha256=5d5exl-S8yvvytiQXga_4t2GBzLZCh8uypg7MarzUC0,2354 +ckan/templates/group/snippets/group_item.html,sha256=MQLH9j-9gQdB6ImI7DbiMs8ga9TnGjp3yN524yKUvAc,1790 +ckan/templates/group/snippets/group_list.html,sha256=79MZUr4xfSTSE2QyO1TtEqRAUkkVWBcN3vh6Z_tAEk8,396 +ckan/templates/group/snippets/helper.html,sha256=su_yvd1Crc39ra6osjZaakcWHSFEyQNuqLJJWmk3jzs,542 +ckan/templates/group/snippets/history_revisions.html,sha256=lrgv-XeU8H22_-wh3GowK14tHAdsuSSGY5u43rcrBG0,468 +ckan/templates/group/snippets/info.html,sha256=DrVeEjaV6qKjcOOG3lfAdcQOxTbLr2QuopGEO5feSZE,1537 +ckan/templates/group/snippets/revisions_table.html,sha256=Gu1ClNH99bLKZmYDWtzrdcn0hxeKm3aaQZy0qOjebw4,967 +ckan/templates/header.html,sha256=IuaP3puASbrvamSvUPtS8yjRPYdIWwolTVy3IKuZbSQ,4819 +ckan/templates/home/about.html,sha256=AF9zgln0ln53bXhxsjyBt1tM6mTji_hIMfGDp3IRcS4,623 +ckan/templates/home/index.html,sha256=V-OhnL9-cGYxV-wGFLxngUll4rBgNoyBpnEodsp2oCI,482 +ckan/templates/home/layout1.html,sha256=MKLgz_YW0kV5Sd_ygHQ_hVfh1Si1jFhIaHbCk4C-fmo,1147 +ckan/templates/home/layout2.html,sha256=s1dohvQs54BDRDYEQb55LH4VMfjOxykJ5u6S8PPEm08,955 +ckan/templates/home/layout3.html,sha256=_SfoqN1v0w9QiyaEhojfhDRRJNrUSkCqakcpw9pY63o,561 +ckan/templates/home/snippets/about_text.html,sha256=I8m9BnJFUePu-e79F4DCbcktWeCNaJaAatSaGtPoXDM,1234 +ckan/templates/home/snippets/featured_group.html,sha256=96WQY0XDKdyAc9g78HRmHCOboUXSlGYLSD6FVKK7wOI,202 +ckan/templates/home/snippets/featured_organization.html,sha256=lVnuKkLQZ1t1drMXyYJx25e1z439gWAh6OCy6qG8Aik,251 +ckan/templates/home/snippets/promoted.html,sha256=tInuyZql45nda_DXn-h9FDWIWneVONdqWxugr6G25aY,946 +ckan/templates/home/snippets/search.html,sha256=F4ZIlcVUEppr-7BsuBoBT3q19iXX8t_087KRrawB9Yg,1085 +ckan/templates/home/snippets/stats.html,sha256=liYGCOVOghar5kITZmd-WR7Bv73DQ1HbU5grkQl4phI,1019 +ckan/templates/macros/autoform.html,sha256=W54A-Z1Ff25mGi_WwZz_NZLRUZ_rHeuvMTC4ZSNHKc0,3339 +ckan/templates/macros/form.html,sha256=pnan2opvTcx6uEBovX1UC_Og9Q59N_GEEGM2jH3cLJE,19517 +ckan/templates/organization/about.html,sha256=DYEqvhV1nfw9wSdNIz-g2bAxda5A0B-85D5sQk1LGhc,557 +ckan/templates/organization/activity_stream.html,sha256=cqWK5bt4C8ZSNQH8S7LHjBB07e1oIgVWd0GQLILwZZk,357 +ckan/templates/organization/admins.html,sha256=AaJy-mg4_zvWsYKbLtIk4q3aMWDhvBN_7HJRgZU4iJQ,411 +ckan/templates/organization/base_form_page.html,sha256=tWQ_sxP-UueOMSutgUEh90B7M8CTq1dBAymjUeUrZCY,305 +ckan/templates/organization/bulk_process.html,sha256=gZRtBPwvRfi47wypu1LE-gHJ79zg2C5Ma8p-hRyqQT4,5187 +ckan/templates/organization/confirm_delete.html,sha256=bDKQSPWpGD58SvPhpG8akFYSWfabbQxYtmMZlaQ5RJI,885 +ckan/templates/organization/confirm_delete_member.html,sha256=mWMr738LVAhmZ-S5OLx2Wau342J7RxsiVglQE9U1tpg,912 +ckan/templates/organization/edit.html,sha256=vTUby80rZecKs3ZIoddfnldMRAg2gwyyIlJ1xXSYTc0,241 +ckan/templates/organization/edit_base.html,sha256=RtzJ91-C0TkK7j-M6ngLoqbzihlxmb8qC5TFSxL87Cw,1612 +ckan/templates/organization/index.html,sha256=AdHezmdAOY8o6SIcHa--eTpshrCtaV3lMus6UezGzo0,1915 +ckan/templates/organization/member_new.html,sha256=Pwk27ElBdNpU8CChT6O8qr-o7Zy2UO7wSUKNRDpeWzI,3812 +ckan/templates/organization/members.html,sha256=uBC2Sm2wu28dPApDrJpnV8MNAAiXjS0vY8PbIW3UEd8,1809 +ckan/templates/organization/new.html,sha256=i2x31-KFTKDAg-FWwvPR2sdvTMSrSHZQDeewFlGjH_o,611 +ckan/templates/organization/new_organization_form.html,sha256=P_PCj2F2He_jcNOFxmCGjgcP4AaEweBSLeXjf5Yh2s4,651 +ckan/templates/organization/read.html,sha256=A3u7VS32TnLuQ_ygRfYBKoy73Pqndo_laYl7qNuL3wM,1861 +ckan/templates/organization/read_base.html,sha256=f9n8SvoReVuW1l5ljx07DwfaOXVbtwa3GY-47BIhx-o,1395 +ckan/templates/organization/snippets/feeds.html,sha256=2YyDgiF-Y1h1fy0-aeVPAIXmPIgOC2sL-w9VwrgkK0U,515 +ckan/templates/organization/snippets/help.html,sha256=wolWRhcfr81hJg4JwJ7XicZIonrpCZ0ZCPY3PrvLzeM,721 +ckan/templates/organization/snippets/helper.html,sha256=xGlqPM7cyn00egaUYGHNerOkAuEUp5BTX3Kk2SrUB8U,500 +ckan/templates/organization/snippets/info.html,sha256=L87u6G13vRds6qg5GkccLbKfR2OQXEU28ed97a_hESM,688 +ckan/templates/organization/snippets/organization_form.html,sha256=TSf0iTPJHNvb4M5XvWi27ZTgZ1Pzid90AmaEhm1RPxQ,2506 +ckan/templates/organization/snippets/organization_item.html,sha256=9j44qfoKcP3G9ax_RpxoUzV2a1CzBCAPvrRrlHsD6-c,1954 +ckan/templates/organization/snippets/organization_list.html,sha256=AfnlOLOMYms94hsdG4YPTGYEcIkuUcFR7DeYFlxn4O0,518 +ckan/templates/package/activity.html,sha256=oL60_4T_RuJbpf1AOwLNjWlJtGZVz7fFBa7fdoxEPNY,354 +ckan/templates/package/base.html,sha256=Alx7eNpW-WNjY7NKC1-Qn00vhDLWs1FBo7yWR6fbz0I,1201 +ckan/templates/package/base_form_page.html,sha256=nByjImBJCpltfrcb7wrJDfyJ5CF7uwJspG5DaVAdV4I,1391 +ckan/templates/package/confirm_delete.html,sha256=yc7QOJ4bPn-qVkbd8hNg1FzJvEWOzeua5dET9nhZYSw,921 +ckan/templates/package/confirm_delete_resource.html,sha256=s4ccB0e0VQEeY7oxxf70Pe1TGc2JtxcW0D_VMnwkwrw,927 +ckan/templates/package/edit.html,sha256=9CvtNDZ9suRPUx_C3jPQg4hqhS7LAusY6cMpUM42k-s,342 +ckan/templates/package/edit_base.html,sha256=NspYmdFcscGJxNqhtnrzxcAr5_Z_6y0ZGBoHz4zaOTo,832 +ckan/templates/package/edit_view.html,sha256=dBfpCLtCh7aiKTs7ZhVzGTp_aJXWxCR1NxPDkYia1Hg,1099 +ckan/templates/package/followers.html,sha256=kj3iJ3o9uJ-5h_B8yd0UrEoIb4owQTlqS3OiIJb7KmU,397 +ckan/templates/package/group_list.html,sha256=MB3rhXq2q1Fn6Jmj2j-XYgbDy1ERIXuifDbUvUwrD2o,914 +ckan/templates/package/history.html,sha256=tK2CYztKV-gow3oCu4HblkJlyRc7tMl8DcMYryqjoQg,399 +ckan/templates/package/new.html,sha256=Y73FOziR2ro6--Vd9yakEvclwpwS1wu9lZl1XJ5O5WQ,337 +ckan/templates/package/new_package_form.html,sha256=_k9F8Y-AeldJIEEkf2tcShUAafxIaALJGFSQzOFRh50,623 +ckan/templates/package/new_resource.html,sha256=8C0siS8EmZ5ht3BNhtbRmcXMFs_sjBxe2cjbNJfoZ3g,817 +ckan/templates/package/new_resource_not_draft.html,sha256=rWcWw1dmEwAath4mCydVWkqe2J3MtLFuoqohcMeLCUE,836 +ckan/templates/package/new_view.html,sha256=uCqKe_NqcgUrxLL3Si2VVLyMY5RmLwCLRguFu8TOrr4,1537 +ckan/templates/package/read.html,sha256=HH6S99qBR-TWjSJ8jx6f2l4h9vMHSL3upawygBX9GIw,1358 +ckan/templates/package/read_base.html,sha256=cXCFkWTBPJg_bnV7O2px6Izx8nv5A-eIBzwDgXLInlE,2254 +ckan/templates/package/resource_edit.html,sha256=0zVC-iud5PtF3G_I3qQbu-sDCFx2xHL4mnXgqO-nuIU,508 +ckan/templates/package/resource_edit_base.html,sha256=Ddb5AqtmvHACbi9dDxqhzy8Vhm2oHPBmwQ8q_DRYrs8,1523 +ckan/templates/package/resource_read.html,sha256=nrONPddl_zucq7DUoOCwkhx1AGkKoxngBaKuOGSEXq8,10200 +ckan/templates/package/resource_views.html,sha256=SncRsxv6cek0D7BtOgvFgWQfBWw-UhdC9LLBPmaecU4,1260 +ckan/templates/package/resources.html,sha256=WpV1hKD8jEOU7or0hrh0wepS_oOLd3V2PBN3Xzb3Iqk,1238 +ckan/templates/package/search.html,sha256=fu5sT88OEoGgNZ6qWu_E57ED8R9t_f1JG4NWHymb9GQ,3221 +ckan/templates/package/snippets/additional_info.html,sha256=mdyYvpM2OGbhcvDpYVcY2tYj5HC9YH8zCeJnXOp8No0,3575 +ckan/templates/package/snippets/cannot_create_package.html,sha256=P_aAPn2o7ZIKb0TshVHecJPrVl0iJfM5T3QuyBOEG5g,1037 +ckan/templates/package/snippets/history_revisions.html,sha256=FAZGiX-qx4MLaWTxRLqyC6QGLuG-hiQJ89KETYo9gpo,458 +ckan/templates/package/snippets/info.html,sha256=4jaY0Pm0-GyjtpaxKSpTERBHSYzvH3QNlq767vIkVTc,1329 +ckan/templates/package/snippets/new_package_breadcrumb.html,sha256=M-_dFKc_Tk4htvjoRRovGOQd7D0ziVdFM_FevllT7gU,205 +ckan/templates/package/snippets/package_basic_fields.html,sha256=VPAPi-ospl_6TQivhzztg8wDGRUN7Qp5W_v7gwxczV8,6286 +ckan/templates/package/snippets/package_context.html,sha256=RJQgs67W38LDvhBIcpYu9s_JirHY1sY_vmslYCNJM60,507 +ckan/templates/package/snippets/package_form.html,sha256=0n_EHDkitCyd-27HutY0FC3DrCyTq-6e1OKzoFIb-Jk,2322 +ckan/templates/package/snippets/package_metadata_fields.html,sha256=JMRgr18hXOilWFxeoDhOOdBcTxv9YZPD-GZrbcRWsqM,1713 +ckan/templates/package/snippets/resource_edit_form.html,sha256=WxbEjUXwhstsEafz7iQhGAZxt88MjojF5kBMqvX6wew,308 +ckan/templates/package/snippets/resource_form.html,sha256=jPrFvON7nOCf7x8GSgar7Al0nbZjl3coIbKx3XfxV9c,4725 +ckan/templates/package/snippets/resource_help.html,sha256=D997Msr8RRiS3hzWpiqHvVsXtGWn21kaSLJ0OnavtHw,295 +ckan/templates/package/snippets/resource_info.html,sha256=ftA6t-KTmO6BK3-MOSplvRSxj7yrPVS7rTYK1AZySYg,475 +ckan/templates/package/snippets/resource_item.html,sha256=MXAHt8L3ng2Ep4C77tvhrsmq8m49NZBdGGGMvl9O-Ik,2330 +ckan/templates/package/snippets/resource_view.html,sha256=hdyWV1tiq0K0dycBxsFLMG2fZoTT-FoytDaMQW_Ld9c,4481 +ckan/templates/package/snippets/resource_view_embed.html,sha256=sPwBaj28nFWt_moPd2UC8nZqvf6sBU72dMwXWjj9c8c,181 +ckan/templates/package/snippets/resource_view_filters.html,sha256=WI3bSazH_EDblnGgDvKfkrapB5vowGqBHeG8nE46xKc,214 +ckan/templates/package/snippets/resource_views_list.html,sha256=nqAL5NxHs3sSA2zHlaEUJkHqehtpZ6fMvWKCxvlfSiw,852 +ckan/templates/package/snippets/resource_views_list_item.html,sha256=uikyOvJQcCAtd_AT7tuzCavtg9VHnOgRoLWRZG0hoAE,685 +ckan/templates/package/snippets/resources.html,sha256=HdquTZ9vEPhuIRCBVozsftaOoF_SYEq3cAVTvpk_-IY,1287 +ckan/templates/package/snippets/resources_list.html,sha256=NSGQCElNPQE-ZWMbIhWH2ZCTDI9sHyfQrv4UxdfULw8,1265 +ckan/templates/package/snippets/revisions_table.html,sha256=YNMrayj2sYQouiXNV1HMNRMxeCIaCDPG9KZy75NBWa8,1129 +ckan/templates/package/snippets/stages.html,sha256=q62jjQ7ruqq2nEr5aH0XM43Xg3QVtklj2qk_1ibgVA4,1463 +ckan/templates/package/snippets/tags.html,sha256=FaW9teJB4rWQinRE4KoWxa5NZNdwsHDIiNm_yhOUhWY,142 +ckan/templates/package/snippets/view_form.html,sha256=DojuRHD0R6lRSOIRROYln26yo0ITe_Wqa0GmshE2v9U,789 +ckan/templates/package/snippets/view_form_filters.html,sha256=cTyKGQoh9jk4MV_oqcQPhcKBOJh8jp1GL1kmvxFOVww,2167 +ckan/templates/package/snippets/view_help.html,sha256=IWq1Ce3g3KEhUoT7AtxyBdyT4X1d3CA9RdnzP0eDKiA,285 +ckan/templates/package/view_edit_base.html,sha256=DKFbwNdxu6bokt4FiCkNfA9nv3qJsBff8HYQgKaF55Y,1814 +ckan/templates/page.html,sha256=LhVjKxF6kO8wsBhMXk9TYQ3xIGZvN2cWTOwc8rpYG4E,4811 +ckan/templates/revision/__init__.py,sha256=VwEQEuqq0IrZBz1sFEXxzlmhD-N5PUCsFLyDPEt-mFs,86 +ckan/templates/revision/__init__.pyc,, +ckan/templates/revision/diff.html,sha256=th0DG_v3UKWrTNANekCApuxW2-SyZIWEZKLn89DMCC0,2159 +ckan/templates/revision/list.html,sha256=kkxp7EaisIRgUz8jtSJcCeSBkJRiBBOb8-WAYuCJkA4,514 +ckan/templates/revision/read.html,sha256=DqUEpUGVE1JsgLpaDB672yu80vmsp6ZPLa12TD50Fb8,2773 +ckan/templates/revision/read_base.html,sha256=nQA9RMaA5Kncp3mYA2DbWV4T_vqIY65DZsBQei3Vg8I,408 +ckan/templates/revision/snippets/revisions_list.html,sha256=1dUxnr5WNPttrebN90-NxUJh36TTLbJlIUA8Ulb9jAU,1173 +ckan/templates/robots.txt,sha256=Fh8J8kR6O8oXGfFjW6QAIJtqo-EApB92i2ZTYMdfHPI,219 +ckan/templates/snippets/activity_item.html,sha256=61nilsWej005JVl9KBtenCPRhAQrD80EhdxWgWTmk9Y,457 +ckan/templates/snippets/add_dataset.html,sha256=WPHuNihoQMw2O1fItznk06yJuhMTakUZ66_uNyF5ft8,441 +ckan/templates/snippets/additional_info.html,sha256=xzwMtb3x4wRKyWrfUrKDALpLu9brYZeqeBOeeNR9FLo,740 +ckan/templates/snippets/context.html,sha256=ZANtQ7SUGn_NFF48yG5hgZVAqX4k8AiKzo62-WfOGHE,728 +ckan/templates/snippets/context/dataset.html,sha256=imDFtY96GVwu7G8PlwBC3Oo7p9cWJrfL9l-CNBpnwKY,531 +ckan/templates/snippets/context/group.html,sha256=2Q9KN47CAKUIaDYwwVmKOA6D9Py0s30nIPaj6dzx7wk,546 +ckan/templates/snippets/context/user.html,sha256=sCKt2MfuHoPKHR2H-UGRAgmjV_svunRmPf9T_ilDM18,685 +ckan/templates/snippets/custom_form_fields.html,sha256=iSMEsaSmqiN54oSx_cbNK046fWG-iDmRyqmhNRKLKFI,1404 +ckan/templates/snippets/datapusher_status.html,sha256=1etUzcfkXlPvilUXkqizU-1kJzEnMw8-MseA8pMW8fo,571 +ckan/templates/snippets/debug.html,sha256=b9D2UegpCDn_J6LD4cI--BtdCEDcCreUvMzlmg9u-NE,2008 +ckan/templates/snippets/disqus_trackback.html,sha256=xcvs__xEg6s_qwR7a8SkKLb2HF-yGE7NG4C_Cs5rASY,274 +ckan/templates/snippets/facet_list.html,sha256=BcHxUUNmgfH07rYBOWgxtufXZZPoh8XfyxXhwm3mYQI,4309 +ckan/templates/snippets/follow_button.html,sha256=r_iR4bSsOFTp3ZGSIJquAKA9WOvEbdESNGqk2csqZt0,813 +ckan/templates/snippets/group.html,sha256=hfd8cxyQEAqV475nNAO4tMmOc96y8QXhi7fDfonNZwQ,1018 +ckan/templates/snippets/group_item.html,sha256=TT8lHW0BEHwi2ow1PEMbpmDkUnNNgVAZv8IM8y-zEnA,1593 +ckan/templates/snippets/home_breadcrumb_item.html,sha256=a2Dlono4DBGqSYsuIKeUT1yS7w-lj61ydWD1A5eogUo,175 +ckan/templates/snippets/language_selector.html,sha256=z7SzeszW5KDN1yjDr2Qvn7Uza2Qpsoi7TH1Mq58H8Zw,816 +ckan/templates/snippets/license.html,sha256=50wCVm2CcqKDahUyj2ZiIAv0SDfSzeS25wHUqx6LcUQ,1426 +ckan/templates/snippets/local_friendly_datetime.html,sha256=jPDKsKjeHPmiCydszcB76poC5nqeqhyoANAoaVF9gnc,504 +ckan/templates/snippets/organization.html,sha256=DzD48_zNSJscvaZI-loDrsf8FwEe-o8vdYAgtn7_fbw,2624 +ckan/templates/snippets/organization_item.html,sha256=HaRUF9AHNhcL29XwbAW1dl9i15H52GeOFED7I8_1hh4,1551 +ckan/templates/snippets/package_grid.html,sha256=oYzzSVS7u7IcpRqDz5kV4x3zAyG2X8kxHnCWUBaenCY,1147 +ckan/templates/snippets/package_item.html,sha256=vMF2XIyRwF_qZr16eRbGKJZwl8-znp20PjMPYUJwxts,3076 +ckan/templates/snippets/package_list.html,sha256=80wSnyxwlk35HxJLCH2SXhlBAhoCCYBkbH8L-jmrm8Q,990 +ckan/templates/snippets/popular.html,sha256=SsLjoMiIUkZoFLrJ05413zVM7OQkDn1VND-3gC_FIQU,219 +ckan/templates/snippets/private.html,sha256=4X2lEHejEXbk3rlSOgQQLSqKWrh3r3kBQkMfNtXh2f0,125 +ckan/templates/snippets/search_form.html,sha256=S91KAo6wz_Z4fU9HW6LGUsTlIrQ8M-Lm-L66bcDMtQY,3585 +ckan/templates/snippets/search_result_text.html,sha256=qtLvEteyd2P6w4YN08q00ENRn2NQhQJNOzbeUJBWRHg,2627 +ckan/templates/snippets/simple_search.html,sha256=aUj3_O8T-n6hxYy8z1Jr1DZK-w6AjpLodoix8fEqPC0,1010 +ckan/templates/snippets/social.html,sha256=6VQBdi4TGvibHNmVZhcSdjyOYg2ezqARHDoBHE0-L00,697 +ckan/templates/snippets/sort_by.html,sha256=YdSookzUglNX5bKUSC4f40xOM-RFik14JTiqRV_zk_0,1219 +ckan/templates/snippets/subscribe.html,sha256=wFKJ1R_coyXevEe3BmgpCNmFlxEznWu4Y3_llprb2U0,384 +ckan/templates/snippets/tag_list.html,sha256=HQOsH2fplOh3509oCUPzqKRLO_b73YOAnA48sW51hH8,445 +ckan/templates/tag/index.html,sha256=D1hdUKKpSDvwe08atE5ljGysM2D_U3w0WjaSnWlO0Ds,1424 +ckan/templates/tests/broken_helper_as_attribute.html,sha256=8S9L9K8hlGC5HC8fi_gAHZOpVPaeQ6X64jQjQr0nXkM,82 +ckan/templates/tests/broken_helper_as_item.html,sha256=IF7hYrDnMl9FBdl34JybYcsTLlXjDezf2_98JWvhIrI,85 +ckan/templates/tests/flash_messages.html,sha256=er3DLL7onldw14uJrRZb8kL_O6P5sCV0XTF6qZ4Acs4,304 +ckan/templates/tests/helper_as_attribute.html,sha256=i8tpJTcn0LPc6icSsEVMcwZLf5UH8yr9crL0uGypHww,90 +ckan/templates/tests/helper_as_item.html,sha256=Zxg4hR5OTss8Ltc438XaXwGtbsdg7b91cGXTTcyGdCk,93 +ckan/templates/tests/mock_json_resource_preview_template.html,sha256=F6vCSr4BiCbYbwCw82c4QSmU_z96-DCbK7n0PDce4J4,311 +ckan/templates/tests/mock_resource_preview_template.html,sha256=tcBQg-jLmV0zioVklijhLsIDJT13Iwve03VGYXyMsBI,301 +ckan/templates/user/activity_stream.html,sha256=E25tTmqEKwcVaP3BDL5ILOwCRIvelnXchlPwl1cwJ-0,343 +ckan/templates/user/dashboard.html,sha256=_scuGSrvHLUYt99zkyuKQ2yoNPsx4fYd8da5dy6wP7U,1760 +ckan/templates/user/dashboard_datasets.html,sha256=NCZu9jhCCXJPs1X9q0JDFWB2-CAn96pxVvH0RVDggFI,716 +ckan/templates/user/dashboard_groups.html,sha256=OzhZGeNOoCgqpjRn5OzSrp9GfB-QNztkr0JFa__wP7g,847 +ckan/templates/user/dashboard_organizations.html,sha256=yCm8DKvUDFS9EqS9x4PnpnFljAah2VlPH7d6mWo3LgM,1009 +ckan/templates/user/edit.html,sha256=kddM-mLw4d4VCm13cBza-2l2BcFRdeZHr4YP5W3cHzY,813 +ckan/templates/user/edit_base.html,sha256=d2WLnXoXvponLLODny28-NyC7dfIKmgUSB8XdWuzy3U,311 +ckan/templates/user/edit_user_form.html,sha256=YKwLK5dNyskuaryuI3Qi9tkIIkJj6RR4u4k_jUjC6WU,3478 +ckan/templates/user/followers.html,sha256=l0TvcqjhMYI4Fx-3UF6Uy9QFiJJBiIOHoWRsrA-ymt4,351 +ckan/templates/user/list.html,sha256=-PugqwTwrfP6LK2_EkLkpoO9i6msr9DS24NicPsgoqY,939 +ckan/templates/user/login.html,sha256=AEGZ2f5clYwb_gn6hIbmnuiARBy0ADbP24mNwe846Uc,1858 +ckan/templates/user/logout.html,sha256=KGht1zvqpL8rDWScFFq5Jm2iHUQD2_5DcddAsFVZ9_A,384 +ckan/templates/user/logout_first.html,sha256=M1jsq-iVhg9Di2euD2OvjTX7CftjhE9QqSNGGVy1BmU,1354 +ckan/templates/user/new.html,sha256=XIydTbhnJJRAspPhB0C1kRoL_3wf58yleBy0yUyYjmk,946 +ckan/templates/user/new_user_form.html,sha256=HQNxHgov5zZNUNNMzuMkAV6jA9fN7Sex2-ydusEIrCg,1489 +ckan/templates/user/perform_reset.html,sha256=H4G3xxu-lYo7lbYaWiR2djd9a7MaNANmg49JWT13pu0,2134 +ckan/templates/user/read.html,sha256=Mv4IHOeDOoWvvqXwcI0G9gLN1KmuDurwtakh14mCVmw,906 +ckan/templates/user/read_base.html,sha256=1lyPxWOxCV05OyhP5jREVrAbO8lytT13328tPLihUCY,3587 +ckan/templates/user/request_reset.html,sha256=prECngLLbVig3i1g4GC5HeRj6jW_4e3sx93rKKbOmAI,1494 +ckan/templates/user/snippets/followee_dropdown.html,sha256=qRZ0Uj9pp-yxjJhR8EYm0DiQ8iqSO0B9Y7D_iptvqlE,1940 +ckan/templates/user/snippets/followers.html,sha256=VU-SU90J_tKiWjW45Cqc70FH740Ue_HslUzUyHgdWBs,338 +ckan/templates/user/snippets/login_form.html,sha256=Ky7rMG369w-g38A-jnNoZ5_02Ws3-35uKWSUwgFNJW8,1038 +ckan/templates/user/snippets/recaptcha.html,sha256=cfIJOaA26LqHiEJSVziDEKR-o0MhM_9HaZm_COpmEbQ,942 +ckan/templates/user/snippets/user_search.html,sha256=KtQAYnTkTukHEIF_iSA9IwS_BK3WQHGTVbCeFutrBeA,696 +ckan/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/tests/__init__.pyc,, +ckan/tests/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/tests/config/__init__.pyc,, +ckan/tests/config/test_environment.py,sha256=aqX9IbANH1NMv9PZ7RbtvctUGkdMBygAgxTkoiopPcI,4247 +ckan/tests/config/test_environment.pyc,, +ckan/tests/config/test_middleware.py,sha256=M_7UN7-0vV-v0U1T1_c3iSDm9IuXO1jqxXaESig2guk,20916 +ckan/tests/config/test_middleware.pyc,, +ckan/tests/config/test_sessions.py,sha256=XFX58TX9a3UXJJBbnQ9p2USTHJdvCHrSJ1ERdUXLpKY,4675 +ckan/tests/config/test_sessions.pyc,, +ckan/tests/controllers/__init__.py,sha256=BV9JqxiGbkzTK_ABFBAc-sKdee_m3TpBrGCu8jcoQCk,2240 +ckan/tests/controllers/__init__.pyc,, +ckan/tests/controllers/test_admin.py,sha256=8f4U89OkZ8i45PpQ5n2LybxrkNwE77kAZQIqFsU-XFQ,15758 +ckan/tests/controllers/test_admin.pyc,, +ckan/tests/controllers/test_api.py,sha256=tuMFImlIPskLaEF6pj1Zyv9nF-q_0_XPVMMfyxTTArs,13488 +ckan/tests/controllers/test_api.pyc,, +ckan/tests/controllers/test_feed.py,sha256=G6AobdiZvYsWAP3aJhIjk53boDcbNdd33aGZp-QavVI,5069 +ckan/tests/controllers/test_feed.pyc,, +ckan/tests/controllers/test_group.py,sha256=-DX9kmROqjcC1bC_BLPPO9qREDOKYw6VSpez0TH6k0w,29765 +ckan/tests/controllers/test_group.pyc,, +ckan/tests/controllers/test_home.py,sha256=AvKlvlp9mGQ-l19ErJ-Mg3vO6Rj_n05_iV7gKjuWb7U,4621 +ckan/tests/controllers/test_home.pyc,, +ckan/tests/controllers/test_organization.py,sha256=8msinJelWoJ0EBiKmKGTA7qjvANUH4S_-r7EbHFTLgQ,23650 +ckan/tests/controllers/test_organization.pyc,, +ckan/tests/controllers/test_package.py,sha256=OtM22ikyqcxtXqYDvD_YwJnDvYNFRJAyG9rcN3Gxa2Y,67289 +ckan/tests/controllers/test_package.pyc,, +ckan/tests/controllers/test_tags.py,sha256=Rhp9jLrCWGkXjq0qbydRXJ3cn3vnxCH-0UP_oQPB-Z4,4609 +ckan/tests/controllers/test_tags.pyc,, +ckan/tests/controllers/test_template.py,sha256=Dzh1L6RsliY_K_cpMSGJ4O8rWCfdTF65Ju3-NF0ISBk,585 +ckan/tests/controllers/test_template.pyc,, +ckan/tests/controllers/test_user.py,sha256=-zSn1mhtvnmpp7cCwm7W0bexDH9BzVEb3P0pztdxxFM,25420 +ckan/tests/controllers/test_user.pyc,, +ckan/tests/controllers/test_util.py,sha256=Z0dy8LAwR4-hivHkozpLwrEhTvqwXoOvf0avCk3frRk,1291 +ckan/tests/controllers/test_util.pyc,, +ckan/tests/factories.py,sha256=L8xRXFfM3PfB-Rljzp01WAMLG1IKIyFIJt7eYPvq02A,13931 +ckan/tests/factories.pyc,, +ckan/tests/helpers.py,sha256=YpfUyEonuWZtF62stIrtIsh6tI3TK_BoVWqjhONhANM,23289 +ckan/tests/helpers.pyc,, +ckan/tests/i18n/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/tests/i18n/__init__.pyc,, +ckan/tests/i18n/test_check_po_files.py,sha256=kROTwDEPftwfPy8KK51pS4mDE-do6IvOQTLVBTWKeac,3725 +ckan/tests/i18n/test_check_po_files.pyc,, +ckan/tests/legacy/__init__.py,sha256=X6StTpEijiFDQEunGh4wqfn-5Iz-MdAhHlroLJC0um4,13155 +ckan/tests/legacy/__init__.pyc,, +ckan/tests/legacy/ckantestplugins.py,sha256=5mCIly2seHB28UYRQIb7oIvwns81DvxyVKbUxDsa8tM,5486 +ckan/tests/legacy/ckantestplugins.pyc,, +ckan/tests/legacy/functional/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/tests/legacy/functional/__init__.pyc,, +ckan/tests/legacy/functional/api/__init__.py,sha256=LZHieMqgkq5bvRdYiCsbbBth2xEzKccdCm7wei9H6n0,1388 +ckan/tests/legacy/functional/api/__init__.pyc,, +ckan/tests/legacy/functional/api/base.py,sha256=PKP3mEIecDzB1vpjpkewpd6LC8HabaeZAQEpsTzLaEk,11772 +ckan/tests/legacy/functional/api/base.pyc,, +ckan/tests/legacy/functional/api/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/tests/legacy/functional/api/model/__init__.pyc,, +ckan/tests/legacy/functional/api/model/test_group.py,sha256=_LKyhwmuwDiCAriFyDpOqeFAb6IwNSuuCYpVF1W58dI,7636 +ckan/tests/legacy/functional/api/model/test_group.pyc,, +ckan/tests/legacy/functional/api/model/test_package.py,sha256=ZSJgcOGPpbd1Gg_qxUaBDpSU3ZupwlwzfPJx41U01RE,24899 +ckan/tests/legacy/functional/api/model/test_package.pyc,, +ckan/tests/legacy/functional/api/model/test_ratings.py,sha256=ENL1YeUa9a4G_I38yYykLBpjUMMnxUKHc9vp7Vcpwow,3770 +ckan/tests/legacy/functional/api/model/test_ratings.pyc,, +ckan/tests/legacy/functional/api/model/test_revisions.py,sha256=2AYfoSXQIDJlJPJgfDGuKQsgMviZYm7bhzh0HlFZxd0,2273 +ckan/tests/legacy/functional/api/model/test_revisions.pyc,, +ckan/tests/legacy/functional/api/model/test_tag.py,sha256=mWFXsF_7Quzn3MkCWRfC3vZH1xY29IYgBIoLte53q_0,1916 +ckan/tests/legacy/functional/api/model/test_tag.pyc,, +ckan/tests/legacy/functional/api/model/test_vocabulary.py,sha256=u4qPniexs6xejibD4JvKmCjapZNWYhNi8wiiGeOr7Yo,46816 +ckan/tests/legacy/functional/api/model/test_vocabulary.pyc,, +ckan/tests/legacy/functional/api/test_activity.py,sha256=wlX_eo9f9SeICUFkT1N3b99U-qIXH0bFl_m79h0GRvk,91245 +ckan/tests/legacy/functional/api/test_activity.pyc,, +ckan/tests/legacy/functional/api/test_api.py,sha256=JDqFJQ_qPqg9U9tzO0DZ9aE3NJ3_HFlROJmfTqEpIKY,1802 +ckan/tests/legacy/functional/api/test_api.pyc,, +ckan/tests/legacy/functional/api/test_dashboard.py,sha256=FjCX8uWIWuzGqqnaagKYWdsiEKubCNRBqa6S7HYvMec,16195 +ckan/tests/legacy/functional/api/test_dashboard.pyc,, +ckan/tests/legacy/functional/api/test_email_notifications.py,sha256=pKZe9ZPEWVUPBOqbQoDoagjVGapqvGPvp7vCyRkWWyY,20281 +ckan/tests/legacy/functional/api/test_email_notifications.pyc,, +ckan/tests/legacy/functional/api/test_follow.py,sha256=SBazL-dfaXlJfTkaIdYqceB-Xjx0wFMhet4qGyQJfy8,60213 +ckan/tests/legacy/functional/api/test_follow.pyc,, +ckan/tests/legacy/functional/api/test_package_search.py,sha256=mUuKZV0F-rCsf4-pllmqrNFwvo5EoApvMW1Sq3S7bOY,20401 +ckan/tests/legacy/functional/api/test_package_search.pyc,, +ckan/tests/legacy/functional/api/test_resource.py,sha256=UVjyvXbLzgYxZXZ-PCpoz6XzFOxs78VkkX5T0J4wVPA,2711 +ckan/tests/legacy/functional/api/test_resource.pyc,, +ckan/tests/legacy/functional/api/test_resource_search.py,sha256=9KIPabgRBQlzfVyTt1eIXlP1ZyRh9EJ1c_8U_g9_E-U,3271 +ckan/tests/legacy/functional/api/test_resource_search.pyc,, +ckan/tests/legacy/functional/api/test_user.py,sha256=J_Gaq1x0Q-KmMpPIEt5URxWhuNIIlpqufpM9lFhKQ20,8096 +ckan/tests/legacy/functional/api/test_user.pyc,, +ckan/tests/legacy/functional/api/test_util.py,sha256=c80lNL4nKTuhqUxbwTPiGfz7kkgI2ie52_gJZs5rUt0,1315 +ckan/tests/legacy/functional/api/test_util.pyc,, +ckan/tests/legacy/functional/base.py,sha256=Q0twTGCnb2rlDFN4NxUGlpyG8WyOnyrxKBr5BCBRsPU,218 +ckan/tests/legacy/functional/base.pyc,, +ckan/tests/legacy/functional/test_activity.py,sha256=46Scq7E0eIOutHjtecTc-TRcxXmMCpA8dSM5h4R1mlI,10154 +ckan/tests/legacy/functional/test_activity.pyc,, +ckan/tests/legacy/functional/test_admin.py,sha256=3OV-kN6L8vQOqTyQb6fr_RHdThY0aHH1My-YRgrRzBs,916 +ckan/tests/legacy/functional/test_admin.pyc,, +ckan/tests/legacy/functional/test_error.py,sha256=I-5R5rRt6WqNPe7_6VLTU0GAjnxKDZwl5XQ_HaxEEbE,276 +ckan/tests/legacy/functional/test_error.pyc,, +ckan/tests/legacy/functional/test_group.py,sha256=hOnZlepLO-mnqj7PXvkhylqtb3-QNf0A_um2scPIhbw,6410 +ckan/tests/legacy/functional/test_group.pyc,, +ckan/tests/legacy/functional/test_package.py,sha256=dDBmgmWEqHWfDeCqEt2GDgHrh99BdxePViR9KS_NB4Y,31562 +ckan/tests/legacy/functional/test_package.pyc,, +ckan/tests/legacy/functional/test_pagination.py,sha256=VZcxfgePihyWBilicQRSoEWzJSLNuvE4042sbns0E38,6011 +ckan/tests/legacy/functional/test_pagination.pyc,, +ckan/tests/legacy/functional/test_preview_interface.py,sha256=U4J_yFHsguiYN4rVMbjrqXQMyG0RiXluhNnW-HHg8DM,3533 +ckan/tests/legacy/functional/test_preview_interface.pyc,, +ckan/tests/legacy/functional/test_revision.py,sha256=neIl5aab6Ie950P2KXMIdlot61VXfBNieDaRVKj_CWw,6004 +ckan/tests/legacy/functional/test_revision.pyc,, +ckan/tests/legacy/functional/test_tag.py,sha256=pAr2tepQMes1fLEXirsFeIpdtc6G3MiW7CG3Ul1r2Q4,2075 +ckan/tests/legacy/functional/test_tag.pyc,, +ckan/tests/legacy/functional/test_tracking.py,sha256=WWeufX2vgVQqMGbmmZbhKRm199cSnvk-SXfKdqNncwY,30309 +ckan/tests/legacy/functional/test_tracking.pyc,, +ckan/tests/legacy/functional/test_user.py,sha256=CqhERFVfLKJ-lRzhGt2gVcMhMjgtaDF41MVdU4WekZU,6737 +ckan/tests/legacy/functional/test_user.pyc,, +ckan/tests/legacy/html_check.py,sha256=ySB_-FKuIi9MaNRF2JMkajKqZWpScFg-4_n_hsIPSOQ,4732 +ckan/tests/legacy/html_check.pyc,, +ckan/tests/legacy/lib/__init__.py,sha256=SEWlSR75zGq-RCGnsfX_BYJipYEflC9INrYu6FWmD0Y,529 +ckan/tests/legacy/lib/__init__.pyc,, +ckan/tests/legacy/lib/test_alphabet_pagination.py,sha256=pjmE6i02ET63_loLhak-uLh48d5qu_FvFxHe8AqKMPg,4895 +ckan/tests/legacy/lib/test_alphabet_pagination.pyc,, +ckan/tests/legacy/lib/test_authenticator.py,sha256=6HOeRrfdO3Z3oAhVxt8wxVYIqAQ5DuzyVDyiA3kIxXg,2485 +ckan/tests/legacy/lib/test_authenticator.pyc,, +ckan/tests/legacy/lib/test_cli.py,sha256=vRdw7AJg-0322EmK1IrJ4WYlENEgFiM13hqgQNo3BG0,2106 +ckan/tests/legacy/lib/test_cli.pyc,, +ckan/tests/legacy/lib/test_dictization.py,sha256=XZ2sIC-c5bQsWKcP2z7DnQPEtshBd7-lB-GoMh-7u0E,30407 +ckan/tests/legacy/lib/test_dictization.pyc,, +ckan/tests/legacy/lib/test_dictization_schema.py,sha256=QY-8JKpvLQSn7laa-b7ylWqm6Cs04vvRah0KprnD5kk,9605 +ckan/tests/legacy/lib/test_dictization_schema.pyc,, +ckan/tests/legacy/lib/test_email_notifications.py,sha256=NvXe-eiQnySqwMdSHmR0WupU0PDglZq3bDaPeo-llww,2357 +ckan/tests/legacy/lib/test_email_notifications.pyc,, +ckan/tests/legacy/lib/test_hash.py,sha256=u2hLbohrJ52EN73zKPmDz7R1Kry1bEJBifZyBw_PXmQ,580 +ckan/tests/legacy/lib/test_hash.pyc,, +ckan/tests/legacy/lib/test_helpers.py,sha256=VIuBgA6hxrOpDGzL8KGfYWxK_nFIhKnRFjjpSLfnCCU,8070 +ckan/tests/legacy/lib/test_helpers.pyc,, +ckan/tests/legacy/lib/test_i18n.py,sha256=PJamTqmVhMK8JF5Jr6UEy9rXadHZj_0v5t7DQVio5xI,1287 +ckan/tests/legacy/lib/test_i18n.pyc,, +ckan/tests/legacy/lib/test_navl.py,sha256=1HTPkYavo68Eu16Wt2I5cJ9gjXldu4yHwO5D6yGVYhg,11963 +ckan/tests/legacy/lib/test_navl.pyc,, +ckan/tests/legacy/lib/test_resource_search.py,sha256=3c1xbceC2FqR773kKkA_44lLWuse6JhmVmSoucEy62M,7309 +ckan/tests/legacy/lib/test_resource_search.pyc,, +ckan/tests/legacy/lib/test_solr_package_search.py,sha256=1jt3pxVTEzz7k8PpS6rSISq_0xy34zvlXX5oYP6LCSQ,20978 +ckan/tests/legacy/lib/test_solr_package_search.pyc,, +ckan/tests/legacy/lib/test_solr_package_search_synchronous_update.py,sha256=08nE5_XoXYEDVWXnpEsZwhOZmUvITynfXFaSYidHDio,4014 +ckan/tests/legacy/lib/test_solr_package_search_synchronous_update.pyc,, +ckan/tests/legacy/lib/test_solr_schema_version.py,sha256=Nkoj7lasW5R8uo7q-at_XTpZyGPJmY0A4W3BY4gXyX4,1587 +ckan/tests/legacy/lib/test_solr_schema_version.pyc,, +ckan/tests/legacy/lib/test_solr_search_index.py,sha256=Oqgg-295oALpSHN_Zv-dLaPYFFTWhmQ0XB_nfvTq7Fk,2108 +ckan/tests/legacy/lib/test_solr_search_index.pyc,, +ckan/tests/legacy/lib/test_tag_search.py,sha256=-Rhazijui8DXsow_JpiSLAi6nMY2Yk9lqvTREi8PLZw,2914 +ckan/tests/legacy/lib/test_tag_search.pyc,, +ckan/tests/legacy/logic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/tests/legacy/logic/__init__.pyc,, +ckan/tests/legacy/logic/test_action.py,sha256=_r2qg07tzbElnyaOsqvsujB4BCUkFkjMSDZtKmL9BZc,49621 +ckan/tests/legacy/logic/test_action.pyc,, +ckan/tests/legacy/logic/test_auth.py,sha256=y50MMqLUCk6tiqarlUdS094c8oUB89Tn9wVinFA-Gr8,22144 +ckan/tests/legacy/logic/test_auth.pyc,, +ckan/tests/legacy/logic/test_init.py,sha256=SnE4pgxAlyeoEiRGeKiioMbqX4-oFX6V8Vcvf-Y8xDA,1959 +ckan/tests/legacy/logic/test_init.pyc,, +ckan/tests/legacy/logic/test_member.py,sha256=9-xejwgC6uffkAPZTG9nNHQH-gqW2AXXnHK9moKDfKs,9181 +ckan/tests/legacy/logic/test_member.pyc,, +ckan/tests/legacy/logic/test_tag.py,sha256=nAEFLtQFECeTGUni5pojFQ2gZhl5BMF2z3bLVSOdDn8,16009 +ckan/tests/legacy/logic/test_tag.pyc,, +ckan/tests/legacy/logic/test_tag_vocab.py,sha256=tEUOowRpIlApfa0exHUwI6OmAg4pj2gJtJ3jew4kBeE,4772 +ckan/tests/legacy/logic/test_tag_vocab.pyc,, +ckan/tests/legacy/logic/test_validators.py,sha256=xgAhzD37RvomqgFSu3ShuEOaItUIQUoYhaCEvC9T8sM,1142 +ckan/tests/legacy/logic/test_validators.pyc,, +ckan/tests/legacy/misc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/tests/legacy/misc/__init__.pyc,, +ckan/tests/legacy/misc/test_mock_mail_server.py,sha256=NO2njk_34e38N1Ti8pUBSNKjcWwsqylXec6AOc1ZLNE,1367 +ckan/tests/legacy/misc/test_mock_mail_server.pyc,, +ckan/tests/legacy/misc/test_sync.py,sha256=TO5kcHyu0p7XBJcjLWFMr_aGAz7v4iaQ8cDGJZAdVd0,2682 +ckan/tests/legacy/misc/test_sync.pyc,, +ckan/tests/legacy/mock_mail_server.py,sha256=lQlujGAh0d3qxTDfRw9y41QXBjJt0q0-Wt1claQT2lk,2626 +ckan/tests/legacy/mock_mail_server.pyc,, +ckan/tests/legacy/mock_plugin.py,sha256=kMt6mGt4O9NYHAQW4_qKJRKnUc-Hthk2m7jf3DfEUeE,1347 +ckan/tests/legacy/mock_plugin.pyc,, +ckan/tests/legacy/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/tests/legacy/models/__init__.pyc,, +ckan/tests/legacy/models/test_activity.py,sha256=LjRYrVYNVOL06z4bsHZtTqCr9azctbj1Ok8hLcMkD1o,617 +ckan/tests/legacy/models/test_activity.pyc,, +ckan/tests/legacy/models/test_extras.py,sha256=leEdM7L2hm6_S4p7EKH7UkHHfPAy6d3MZxgisxowXTo,2262 +ckan/tests/legacy/models/test_extras.pyc,, +ckan/tests/legacy/models/test_follower.py,sha256=gVMBfkjgrXZIWNj_OdpyIi70-r2YKjx5OSO39PlPo08,4502 +ckan/tests/legacy/models/test_follower.pyc,, +ckan/tests/legacy/models/test_group.py,sha256=kjbFu3Zw7sylKG5Qs2fAVthlt9rvQBBAVQpesHeCPXk,10730 +ckan/tests/legacy/models/test_group.pyc,, +ckan/tests/legacy/models/test_misc.py,sha256=qHPh_EKdi5Gtd4OUwglWuCY933YjlliEI1djc9-QKZc,2394 +ckan/tests/legacy/models/test_misc.pyc,, +ckan/tests/legacy/models/test_package.py,sha256=Ut2C3myHU6Y3NCPbHyYY6RjDhuqtX5MNMbPAU4K-gPA,14989 +ckan/tests/legacy/models/test_package.pyc,, +ckan/tests/legacy/models/test_package_relationships.py,sha256=IqmCRbBc_BMxYBOmbxf8LIctb62cICFK1XTFD4ohIvQ,13704 +ckan/tests/legacy/models/test_package_relationships.pyc,, +ckan/tests/legacy/models/test_purge_revision.py,sha256=NY45alSknrKnVwtFdJH7Hk412a5aN8IZeYRb1N9p4UY,2715 +ckan/tests/legacy/models/test_purge_revision.pyc,, +ckan/tests/legacy/models/test_resource.py,sha256=Pd-lI5vBzsNtRof12oaNtPN16JStB8rQHT-iv1YoPVY,6675 +ckan/tests/legacy/models/test_resource.pyc,, +ckan/tests/legacy/models/test_revision.py,sha256=ZigBzxYAYPa4cJc4Ng7i4T7QaJY14R-Hh_Efm69QfjE,1565 +ckan/tests/legacy/models/test_revision.pyc,, +ckan/tests/legacy/models/test_user.py,sha256=duZtW1tdGZkrnlAJygtADDAU4aZIZGzxh_LlT71ESu0,6407 +ckan/tests/legacy/models/test_user.pyc,, +ckan/tests/legacy/pylons_controller.py,sha256=pFuLc-OYVaVSKSgdB2JFTMrXYyx7E8XCQt-_xDMs5aI,2399 +ckan/tests/legacy/pylons_controller.pyc,, +ckan/tests/legacy/schema/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/tests/legacy/schema/__init__.pyc,, +ckan/tests/legacy/schema/test_schema.py,sha256=K5lCfHfsmALKN0JeuMTa57Qq-MZiDgeYW-fvAnPUrKk,6671 +ckan/tests/legacy/schema/test_schema.pyc,, +ckan/tests/legacy/test_coding_standards.py,sha256=IQKJGyHAXVquWy6cYhnIivE0J6rD9V17zBop8u0hojo,37116 +ckan/tests/legacy/test_coding_standards.pyc,, +ckan/tests/legacy/test_plugins.py,sha256=w7vjibdzDZt5KClA2qKGIFCsMFTDQ5apDmMoeC-_tuA,7561 +ckan/tests/legacy/test_plugins.pyc,, +ckan/tests/legacy/test_versions.py,sha256=74AfP2tysdkYb1okT5CAvL2SC58-Zq-5_RhxMgQpSKc,339 +ckan/tests/legacy/test_versions.pyc,, +ckan/tests/lib/__init__.py,sha256=M0ZgdYb8ru4rzA3kaWoig88nRBGbz9d5OChsZ-Wpuv8,677 +ckan/tests/lib/__init__.pyc,, +ckan/tests/lib/dictization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/tests/lib/dictization/__init__.pyc,, +ckan/tests/lib/dictization/test_model_dictize.py,sha256=uhOewtmDCmiezlL_l6Dff-66-32YS44MWowl_iijJM4,24247 +ckan/tests/lib/dictization/test_model_dictize.pyc,, +ckan/tests/lib/navl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/tests/lib/navl/__init__.pyc,, +ckan/tests/lib/navl/test_dictization_functions.py,sha256=_25rKna1BO5yD7mn6hVcON1rSKFKVOLwwTXu_GEiiOM,2558 +ckan/tests/lib/navl/test_dictization_functions.pyc,, +ckan/tests/lib/navl/test_validators.py,sha256=AWN5lHv17-auCXQ2ybZCgbLg7ZcLZp5vUJu0I_OaOv4,10076 +ckan/tests/lib/navl/test_validators.pyc,, +ckan/tests/lib/search/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/tests/lib/search/__init__.pyc,, +ckan/tests/lib/search/test_index.py,sha256=iTbU3PJo4zycAtudmV1y0E8dFuTCdrgkyP1H813rVso,10564 +ckan/tests/lib/search/test_index.pyc,, +ckan/tests/lib/test_app_globals.py,sha256=D2barSsgr9Rz2hgPtcO_F9SGcgV5CiJ133iewUvHutQ,702 +ckan/tests/lib/test_app_globals.pyc,, +ckan/tests/lib/test_auth_tkt.py,sha256=gy0ljZDq2iNsEYMqtqC4TczSowtnvnzBJRVRkoftqMs,5772 +ckan/tests/lib/test_auth_tkt.pyc,, +ckan/tests/lib/test_base.py,sha256=sKykBBjMl3lzVnNgBpmeloFYdbdqpUdYW1kxGkosLkw,16860 +ckan/tests/lib/test_base.pyc,, +ckan/tests/lib/test_cli.py,sha256=w8muOM615H4CR1Ty-s3VRnDvq3iHKypvAOpvAKgrUfE,11228 +ckan/tests/lib/test_cli.pyc,, +ckan/tests/lib/test_config_tool.py,sha256=tJU2nnLV0GLqXE_V-J4U9TTyh7ccqSqWIAFa4PDsgfk,4978 +ckan/tests/lib/test_config_tool.pyc,, +ckan/tests/lib/test_datapreview.py,sha256=NFKLA367GmSq0NPHs4bRizg0yOx_OpEYHRGfOQ_iSpg,9554 +ckan/tests/lib/test_datapreview.pyc,, +ckan/tests/lib/test_helpers.py,sha256=YaUv5E1r2XUvD8arHZgpwP6fzWqi3CLVb_-KrfO0i8I,26812 +ckan/tests/lib/test_helpers.pyc,, +ckan/tests/lib/test_i18n.py,sha256=2Yh9BNkjtiqSBDdEEckKUTzP_cRqa3pnqBB3be1sKvA,5506 +ckan/tests/lib/test_i18n.pyc,, +ckan/tests/lib/test_io.py,sha256=QnWu3KBKrs8gjeiCrh9ml3UwnmrMvEgdogv65RacC64,1241 +ckan/tests/lib/test_io.pyc,, +ckan/tests/lib/test_jobs.py,sha256=Ia68o-Vok_0jRjHd-7_uVDrnk6rGG0kqTJ6GNtwHRLU,8767 +ckan/tests/lib/test_jobs.pyc,, +ckan/tests/lib/test_mailer.py,sha256=kKUBl2XFrOM0Dl-AkMazsHUSpn3emj5lNzMuQYCmB5w,8029 +ckan/tests/lib/test_mailer.pyc,, +ckan/tests/lib/test_munge.py,sha256=aB4Hv1t0fPY-mKxS7yxnQdZvhvWJh6C2yDzL2qN9k00,5494 +ckan/tests/lib/test_munge.pyc,, +ckan/tests/lib/test_navl.py,sha256=FU8Rr4e4LVg0Fot4Jt6v4FZBQHpH1ysUvT3XQJacSYw,935 +ckan/tests/lib/test_navl.pyc,, +ckan/tests/logic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckan/tests/logic/__init__.pyc,, +ckan/tests/logic/action/__init__.py,sha256=IFCvskqsS2teN0MzD8QZqRzb9k43iXpL3qZwYDTO_WQ,1531 +ckan/tests/logic/action/__init__.pyc,, +ckan/tests/logic/action/test_create.py,sha256=mQ8Qu4EbpVC7iSplsk7uiYwVkgJFS1OMfH_V6A_4FEQ,40220 +ckan/tests/logic/action/test_create.pyc,, +ckan/tests/logic/action/test_delete.py,sha256=aaraOqnfpZWlA5Us-5-ml2aU8ylIeKwHn2pcHpOfYMk,22555 +ckan/tests/logic/action/test_delete.pyc,, +ckan/tests/logic/action/test_get.py,sha256=TJ2oz7MrVm28d8_DbsNT0tDrLmkwPSFKYaShr6spfRg,83913 +ckan/tests/logic/action/test_get.pyc,, +ckan/tests/logic/action/test_patch.py,sha256=lrdp0Gy_HmIB7oLH_SnwQehle-eF8qvi3_CZUp0s39M,4164 +ckan/tests/logic/action/test_patch.pyc,, +ckan/tests/logic/action/test_update.py,sha256=9MkFjXXomkkKemNLaiNMnvSnry8GSD1pFcH2l7MoCYY,59419 +ckan/tests/logic/action/test_update.pyc,, +ckan/tests/logic/auth/__init__.py,sha256=fQojfIDvXrRfhFBdqDVgQUFKD1BAeCOZWJQ-FaneoNQ,604 +ckan/tests/logic/auth/__init__.pyc,, +ckan/tests/logic/auth/test_create.py,sha256=9yStOcdeNP8K9E3yhQSe6ypIuNHcIPyeri36WZp8_CI,13398 +ckan/tests/logic/auth/test_create.pyc,, +ckan/tests/logic/auth/test_delete.py,sha256=Bi4vnupQ7xnux8WlS8gnuVwT-Ax7ew2mLnchj1y_McE,4533 +ckan/tests/logic/auth/test_delete.pyc,, +ckan/tests/logic/auth/test_get.py,sha256=I6bRgPXXrSuKm92QjWr_PLtn0gZBS9SBieHogE35BhY,6361 +ckan/tests/logic/auth/test_get.pyc,, +ckan/tests/logic/auth/test_init.py,sha256=uqhg-N3i4hEFwcWHMqwjTZRnWtfMcxXh948W2IwfANk,5006 +ckan/tests/logic/auth/test_init.pyc,, +ckan/tests/logic/auth/test_update.py,sha256=7iqRre3VgVkCEm3UpJqcoLzFA4QCaprCHY2whPwvKis,9793 +ckan/tests/logic/auth/test_update.pyc,, +ckan/tests/logic/test_conversion.py,sha256=zACdfgVMAGaw2OgN46ji5hBHM_Y6gvMKS-D4wTJOAeM,4171 +ckan/tests/logic/test_conversion.pyc,, +ckan/tests/logic/test_converters.py,sha256=NyufN-9XzvzUSi_k4aRj8a9ZJ6A7Z-kzzsA565fFg6k,2434 +ckan/tests/logic/test_converters.pyc,, +ckan/tests/logic/test_schema.py,sha256=Y_7XZq3Eei0LAdkpc9kVK_8IlECaFMqfHG3hqP4yAoc,547 +ckan/tests/logic/test_schema.pyc,, +ckan/tests/logic/test_validators.py,sha256=6aSIFIriYtB0mtk5YaDe4QkOjwOAXSYwFovfEBvY7KA,22174 +ckan/tests/logic/test_validators.pyc,, +ckan/tests/migration/__init__.py,sha256=MhUQJoByNWt5ocf3aO_BRlIbGn3cdzkZsW6te4wksf0,200 +ckan/tests/migration/__init__.pyc,, +ckan/tests/model/__init__.py,sha256=2SjjQeQz4nFjnEjUM3pGsZxlBeGNQp2IffQUP_eg0dw,272 +ckan/tests/model/__init__.pyc,, +ckan/tests/model/test_license.py,sha256=v3rfVp-GBkRFg2LECo8vW459atZO085ELSwSTCi4lCE,3716 +ckan/tests/model/test_license.pyc,, +ckan/tests/model/test_resource.py,sha256=miF-Ro9s38qceGoUfNVStUZNKCZsTjiOBcy9DYqq5SY,2564 +ckan/tests/model/test_resource.pyc,, +ckan/tests/model/test_resource_view.py,sha256=IxEGO-anDDF0tiBu3_we2AnB_asU9lo05TkT52FmJNw,2252 +ckan/tests/model/test_resource_view.pyc,, +ckan/tests/model/test_system_info.py,sha256=ulzsr4RxpWBGEoQJNs7inpCSfcxi3kZseOFHqmW65fk,1938 +ckan/tests/model/test_system_info.pyc,, +ckan/tests/model/test_user.py,sha256=ifoSkdPb60W0M536DFk-F33mdpUwTj_-X9WmSEXxnfA,5331 +ckan/tests/model/test_user.pyc,, +ckan/tests/plugins/__init__.py,sha256=juwCUouXv5AP1jmDcO3UbQsv5R7um2veDDeNfZDe-sI,1158 +ckan/tests/plugins/__init__.pyc,, +ckan/tests/plugins/test_toolkit.py,sha256=OUr2YiunPsDPnj34cIjkNuxHB407AWHdmkPpxyEuPS0,9620 +ckan/tests/plugins/test_toolkit.pyc,, +ckan/tests/test_authz.py,sha256=5OyR35EXeZGeX_e7zUdmsq-5M-7n1ULXx_mwIZkKXqo,2151 +ckan/tests/test_authz.pyc,, +ckan/tests/test_coding_standards.py,sha256=koBY-XNazVbQDH4Uyp2AdnbkRk2e4KpQyAdI2gdfVXE,31244 +ckan/tests/test_coding_standards.pyc,, +ckan/tests/test_common.py,sha256=eIWQgm8KORW83rplZOJV3pkm-EF8UUIPYmEkiE5qUIE,7233 +ckan/tests/test_common.pyc,, +ckan/tests/test_factories.py,sha256=ub2SpENRenF0GDSHPZQfDu4f2BCtaLDaBMTliSC0oaQ,2138 +ckan/tests/test_factories.pyc,, +ckan/tests/test_none_root.py,sha256=YceLSWkl_68b1ZpXFW4UAd8ZwakLfW2r1QSEme4WGrw,553 +ckan/tests/test_none_root.pyc,, +ckan/tests/test_robots_txt.py,sha256=_Ac1PbErKp0qhPqNAQQ1zQRFDrA2zxBpOqB6LQuSfYk,400 +ckan/tests/test_robots_txt.pyc,, +ckan/views/__init__.py,sha256=MXZ8TL04Wl5MtBQcAiFrav4Oc5i_WSDVXMuS7aUSSGA,7161 +ckan/views/__init__.pyc,, +ckan/views/admin.py,sha256=j8nAFMPHc0GCoMjmoDxmJh4g4lwaJgIprHPyx7THv10,7680 +ckan/views/admin.pyc,, +ckan/views/api.py,sha256=T43b2Hz3NUUMF4Kv6F9rPkM-eerod1tcJbYBbZeqHRo,18439 +ckan/views/api.pyc,, +ckan/views/dashboard.py,sha256=U78DVCV5G2aFBXtmlyRKLmbQW9nL_Zocp2JKEosJ21o,4855 +ckan/views/dashboard.pyc,, +ckan/views/feed.py,sha256=zXmF1salYBdZ1F5V7Ukf1hmUzCuXTmEIrQ_J4oyt_24,19409 +ckan/views/feed.pyc,, +ckan/views/home.py,sha256=26n-emo15VcuhiARPR3zfd3rmPQt_exvz0RfXhHgcYI,2267 +ckan/views/home.pyc,, +ckan/views/user.py,sha256=ke_Q3t4N-bEIkDDTHxJcXi2ctM2dIf-erEV01Aq98sU,24819 +ckan/views/user.pyc,, +ckan/websetup.py,sha256=wyLjeo-0uZgOiSXq34a-6C082oAOSSheYi66Wz-A-MQ,431 +ckan/websetup.pyc,, +ckanext/datapusher/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/datapusher/__init__.pyc,, +ckanext/datapusher/cli.py,sha256=o94jMyG7eLzFGIPEZKQp6yikR65HHSZaKLEqAEYwV9Q,4058 +ckanext/datapusher/cli.pyc,, +ckanext/datapusher/helpers.py,sha256=JGbQc9YK3K1KlIgHVfwg2_dvslR5F7cz7gkqpX3ONig,702 +ckanext/datapusher/helpers.pyc,, +ckanext/datapusher/interfaces.py,sha256=TlY6Pkvt8O-dAdYsjod4rEwj1APkPL-cMWMhAYTaNHk,2017 +ckanext/datapusher/interfaces.pyc,, +ckanext/datapusher/logic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/datapusher/logic/__init__.pyc,, +ckanext/datapusher/logic/action.py,sha256=HYD8tYQUIDXyc62bHu5X8A_rexOBhHObqeEJ-xmMPIE,11713 +ckanext/datapusher/logic/action.pyc,, +ckanext/datapusher/logic/auth.py,sha256=XhaLHEbQC0of2HvlnW2eiK0hqdcM1OWgzoHuk9dli2E,255 +ckanext/datapusher/logic/auth.pyc,, +ckanext/datapusher/logic/schema.py,sha256=Y2WAZ5WQ5cGNJ1ZCSTShuwvZoVf3z5Nar9xxCTjOe8w,942 +ckanext/datapusher/logic/schema.pyc,, +ckanext/datapusher/plugin.py,sha256=kFqmrcCOy_rlVpiffIyijSjfMlOGs6y0_liOWKtsE3g,6221 +ckanext/datapusher/plugin.pyc,, +ckanext/datapusher/templates/datapusher/resource_data.html,sha256=UHM8vSa1_QvYZKAuzAqOEN5QJIMxMcHzTcI_co4XjsQ,3439 +ckanext/datapusher/templates/package/resource_edit_base.html,sha256=Fk7_Q4MqwUt_y57DAV1rXPKnaxJzqkd2XJSr4Mew-Ok,172 +ckanext/datapusher/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/datapusher/tests/__init__.pyc,, +ckanext/datapusher/tests/test.py,sha256=2pe0q1QK0-TfmiJkrXdG41JJ82iLw9iQFf-S8kWCoRc,9211 +ckanext/datapusher/tests/test.pyc,, +ckanext/datapusher/tests/test_action.py,sha256=RMo7v3wfKXDfn5U12r16L_LcQ2xHWA16MkuOvUbwKPI,11222 +ckanext/datapusher/tests/test_action.pyc,, +ckanext/datapusher/tests/test_default_views.py,sha256=CaclGUN90_mUGOjM11-RQ2R53_cAT7I1akeja-kC5X4,4055 +ckanext/datapusher/tests/test_default_views.pyc,, +ckanext/datapusher/tests/test_interfaces.py,sha256=wFQvHna8SP5uvPhVFdpDVXy5yAz6KkggSdy4coDmt_w,4775 +ckanext/datapusher/tests/test_interfaces.pyc,, +ckanext/datastore/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/datastore/__init__.pyc,, +ckanext/datastore/backend/__init__.py,sha256=GfSwAAXWumwhsKXnR9L4IO1S1-Ck_kSmUXGHYH5oP5c,6579 +ckanext/datastore/backend/__init__.pyc,, +ckanext/datastore/backend/postgres.py,sha256=-ENgZ_l7Op1RKUgHbi9ctBLGFCn7wVnV-UGTp5kxEXw,68792 +ckanext/datastore/backend/postgres.pyc,, +ckanext/datastore/commands.py,sha256=5oV7DiytbNVhrPEpLdFCESYDUIZ6G7oEEiEwALFdEIg,2889 +ckanext/datastore/commands.pyc,, +ckanext/datastore/controller.py,sha256=z-O2ewCFzZe1ZTSiwkvYSGaZFxPlFDXWaniasLG_Oo0,5053 +ckanext/datastore/controller.pyc,, +ckanext/datastore/helpers.py,sha256=fq1QY_7f2FaXo8SY2Xl7VGC11xGGfrAfRZS9oaGgj5A,4504 +ckanext/datastore/helpers.pyc,, +ckanext/datastore/interfaces.py,sha256=SvKr3X-1IVDi40Hf-mO_AzllI3kTdudySAOstz8b764,7032 +ckanext/datastore/interfaces.pyc,, +ckanext/datastore/logic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/datastore/logic/__init__.pyc,, +ckanext/datastore/logic/action.py,sha256=V5_5J0NQUkVdFvkT-Yd2xiBvzg2WfrV6XWayzYKBn54,23144 +ckanext/datastore/logic/action.pyc,, +ckanext/datastore/logic/auth.py,sha256=KCeFI0pMYOkDHPM0yOKgNVFckH0Imp3u_CGZkI0VOPU,2415 +ckanext/datastore/logic/auth.pyc,, +ckanext/datastore/logic/schema.py,sha256=T2necEBC47H8XDhjNKLvSCFyqJApXBX4WhYG2N6l-2Q,6279 +ckanext/datastore/logic/schema.pyc,, +ckanext/datastore/plugin.py,sha256=BwMycFd41LlBNx4d5d0PKbUTvXsNc0BSqEGxgouNkBo,8864 +ckanext/datastore/plugin.pyc,, +ckanext/datastore/set_permissions.sql,sha256=UtXetjcx-LYwXyzuWmNettPv_EDq_4V2uoetCY97W_o,4171 +ckanext/datastore/templates/ajax_snippets/api_info.html,sha256=517tufAFQUEfMTeHihU6B4YCy46SujnvuK_sGctb30Q,6154 +ckanext/datastore/templates/datastore/dictionary.html,sha256=4Sjz4OlfeC5X9zwlJEIkQVXme8_3JsVvitrg7vKNFKk,788 +ckanext/datastore/templates/datastore/snippets/dictionary_form.html,sha256=EOZvm_DLUhbjeNlTpzNN7-lkKLzaviAmSNXy0skIseo,985 +ckanext/datastore/templates/package/resource_edit_base.html,sha256=iV7ZyWHe7NzD7JQQxfTJ6XqidXecmFNhzvckjHTzdzc,232 +ckanext/datastore/templates/package/resource_read.html,sha256=CyiqXqhdOLJn3Ct_fHv6mpR6mpxz8RzFanVwwRPD81w,1217 +ckanext/datastore/templates/package/snippets/data_api_button.html,sha256=zcYLkb55jF-Vi9Xb4GLC7VNgsjXEwOFJUDaEWgVDlM8,494 +ckanext/datastore/templates/package/snippets/dictionary_table.html,sha256=DugdpnO4X82uS2Hpnc6yOq07R6K19-h71nkaOaS4KBE,221 +ckanext/datastore/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/datastore/tests/__init__.pyc,, +ckanext/datastore/tests/helpers.py,sha256=pCIAiC5THserDUQcZpgIq1-0971gc6bIY4CHvePF1Aw,2685 +ckanext/datastore/tests/helpers.pyc,, +ckanext/datastore/tests/sample_datastore_plugin.py,sha256=mgy-zaVMpDwfytxVshsNWNsGRnJoDQb7VAjL1z-uwPE,1648 +ckanext/datastore/tests/sample_datastore_plugin.pyc,, +ckanext/datastore/tests/test_chained_action.py,sha256=vu8cWQ6qkX6kfc6jRoGWeraj6Ry_l8qX5sP56y859Gk,2188 +ckanext/datastore/tests/test_chained_action.pyc,, +ckanext/datastore/tests/test_chained_auth_functions.py,sha256=MEDXNIw8vuy5qLzsfvhA2-Zgs0NK74Hy8V_Q5S8ObPE,2343 +ckanext/datastore/tests/test_chained_auth_functions.pyc,, +ckanext/datastore/tests/test_configure.py,sha256=iREF6dwE3pV1UkkGQ_vH3r9qi3QmXXZB6y6cHF49BdE,3450 +ckanext/datastore/tests/test_configure.pyc,, +ckanext/datastore/tests/test_create.py,sha256=muKpw04aIpuglVKQ3w1aC_f7zTMvNTzz9TzpwekqF48,44207 +ckanext/datastore/tests/test_create.pyc,, +ckanext/datastore/tests/test_db.py,sha256=O9l3Q8QMHCXexxs0Xw1s_8RJap0evhswx5Z6PP-8ho0,8138 +ckanext/datastore/tests/test_db.pyc,, +ckanext/datastore/tests/test_delete.py,sha256=9WM86HF7NSNNJl6coE-UfbX8GBDATJXBoLaE9HtWFrY,11770 +ckanext/datastore/tests/test_delete.pyc,, +ckanext/datastore/tests/test_disable.py,sha256=dYFCvofqODBPsqyGwuuOjA3qctdA8omgAGolANVfurs,648 +ckanext/datastore/tests/test_disable.pyc,, +ckanext/datastore/tests/test_dump.py,sha256=ijlNlEqV4hCQ6JGpB8rBjPAJz5J0ysryr1YhEvVCgRY,6976 +ckanext/datastore/tests/test_dump.pyc,, +ckanext/datastore/tests/test_helpers.py,sha256=IUGb8aEWgRJMW6PfJrUhHRI8eDQaJcpqSgn-6pwQgpc,4541 +ckanext/datastore/tests/test_helpers.pyc,, +ckanext/datastore/tests/test_info.py,sha256=JevTmPrY4bXh7yzWU9hoMY5rGiTXuVlN-IpymCMK09o,3025 +ckanext/datastore/tests/test_info.pyc,, +ckanext/datastore/tests/test_interface.py,sha256=o7W0lAqOKnteg9By0xGdoOEi2Bgyu1o-HxJURjZ45CU,5860 +ckanext/datastore/tests/test_interface.pyc,, +ckanext/datastore/tests/test_plugin.py,sha256=ZorJh66l2YgOwYr08IJaTZVFAagrQ4Jcn2YXF3bMbrI,7459 +ckanext/datastore/tests/test_plugin.pyc,, +ckanext/datastore/tests/test_search.py,sha256=9h-LeHBo_QnxkAcHmVg2s1Z1ZBRaI919Pr5JfGrbyuU,51726 +ckanext/datastore/tests/test_search.pyc,, +ckanext/datastore/tests/test_unit.py,sha256=VNQCs_7HUE1qRD1msP-bTle0rj9SmWsKAibgaNWdGI0,1742 +ckanext/datastore/tests/test_unit.pyc,, +ckanext/datastore/tests/test_upsert.py,sha256=i1bCcCGrhorSDoBke9AYBiDkYoAmXbk9Jge6oseDdjA,22172 +ckanext/datastore/tests/test_upsert.pyc,, +ckanext/datastore/writer.py,sha256=ekPRQnHrVWT3XbGd6kmGlQFpO8GsGijYxbUP7X3Bpjo,6015 +ckanext/datastore/writer.pyc,, +ckanext/datatablesview/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/datatablesview/__init__.pyc,, +ckanext/datatablesview/controller.py,sha256=74p2iy2eh70BII0Jtgrj5Na5t61owulKiuW8Jl61gB0,3142 +ckanext/datatablesview/controller.pyc,, +ckanext/datatablesview/plugin.py,sha256=04gQkNvTTTyD1XgsketGxgDpvbG1x0dqZ_gN5GtqktY,1910 +ckanext/datatablesview/plugin.pyc,, +ckanext/datatablesview/public/datatablesview.js,sha256=JTvtj1XM-1MS53H7Q6g8akOaxpKg1ESgsJOWpu-Yrwg,148 +ckanext/datatablesview/public/datatablesview.min.js,sha256=FVmHkqGbNyVlxywMf9Ic9yRGMd5YkW5tUnPANEzmBp0,117 +ckanext/datatablesview/public/resource.config,sha256=U8WlfWGkUwfIi3vSFl1arQwku0wCfzfTNUmnw2bQMqk,1048 +ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/css/bootstrap-theme.css,sha256=xOpS-e_dER8z72w-qryCieOGysQI8cELAVt3MHG0phY,26132 +ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/css/bootstrap-theme.css.map,sha256=cZQbJTuJQjdM1XuKhdRzSJ8hMRQ4up491P76Iq2_D1M,47706 +ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/css/bootstrap.css,sha256=fmMNkMcjSw3xcp9iuPnku_ryk9kaWgrEbfJfKmdZ45o,146010 +ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/css/bootstrap.css.map,sha256=nNhKKlFiyBaou715z53AYFvA6obkzSurQ67gabuD0mY,389287 +ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/fonts/glyphicons-halflings-regular.eot,sha256=E2NNqH2eI_jD7ZEIzhck0YOjmtBy5z4bPYy_ZG0tBAc,20127 +ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/fonts/glyphicons-halflings-regular.svg,sha256=QvYGWdJlwaPDD5-kKry7Vr1KU69Ng9MW1t16NpA8Q-U,108738 +ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/fonts/glyphicons-halflings-regular.ttf,sha256=45UEQJN1fYKvyxOJV9BqHqk2G9zwtELQahioBRr1dFY,45404 +ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/fonts/glyphicons-halflings-regular.woff,sha256=omOU9-3hAMoRjv8u2ghZYnWpg5uVnCJuFUOVV6WoB0I,23424 +ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/fonts/glyphicons-halflings-regular.woff2,sha256=_hhdEaSWdokNR7t4MxKgzaWkTEA5IUCU55V7TAQO8Rw,18028 +ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/js/bootstrap.js,sha256=Cr6N6zNN4bp0OwTQOZ6Z66M2r-2dpy_EwKMCyZ-SOMg,69707 +ckanext/datatablesview/public/vendor/Bootstrap-3.3.7/js/npm.js,sha256=x6qCoap9RSJKONkm0q2v9_5K71vNr6Kke9rAV_RCLC0,484 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/buttons.bootstrap.css,sha256=O8hQxrprjvzDaH6yF3SM26S_GyM5ts0kucgukEbkqso,3425 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/buttons.dataTables.css,sha256=9LToFQfPvsxOAbaciksFwC-sJ0eEDYkEDi7DDyK2zUE,11169 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/buttons.foundation.css,sha256=MqZXvz3AeS2Kf85pHC217V2086w3c9hff-Mk-xZXGNM,4089 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/buttons.jqueryui.css,sha256=_8eotTSf3iGK0ZZaIfOIDbvLEuk6JQutjPGnHIh0UNA,5221 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/buttons.semanticui.css,sha256=674vbgdQ5CONjIl6NnPBNWMUfVkJvyO0b2F0LtRFty4,3576 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/common.scss,sha256=gWSJOuUS90pMpVkwC9zppLhgl5w6A-SlJ69goIbEoYI,431 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/css/mixins.scss,sha256=IJHZJZ-_tHq9mupdGFB00PlCt69K8J99ZGhhRC4OYCk,3799 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/js/buttons.bootstrap.js,sha256=ERiLI8VWvy_01eFEza1n-qQX6zw27sbLzXshVm2c-sE,1400 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/js/buttons.colVis.js,sha256=9160Y8TN0mg8jPecP32pgS0o-IkarN6pJT-K4sMxAOw,5156 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/js/buttons.foundation.js,sha256=nOD3PDD7c-mgbRxqTTqaNjBwSVZjYDS00UlBMsUa2K4,1863 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/js/buttons.jqueryui.js,sha256=jWj3v-QihxyM-ds9Pm6dw6C_4VsjBp5TSYP5waVxpsY,1424 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/js/buttons.semanticui.js,sha256=Ok-4hSkRv8k6HghaqB--RocJPEh8JhRzGgONZsPjtOM,1178 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/js/dataTables.buttons.js,sha256=Hk_qncGNQKCmNqmaFLvf8W6OxjX10cYcfVLCnw5BnVo,40956 +ckanext/datatablesview/public/vendor/Buttons-1.3.1/swf/flashExport.swf,sha256=6kIqxERVuIHKN-9zn28UOS0XNkZBnXj63Zv26w4qd0c,64573 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/css/dataTables.bootstrap.css,sha256=I9TOaqmifat8pWeF6SutmajUX3mfSpUgRnCjCBX2YxY,4823 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/css/dataTables.foundation.css,sha256=xUcsXoi0XVRWxF9elX4gF8PHxx5wV24L92iteCON6ug,3015 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/css/dataTables.jqueryui.css,sha256=Qqu7okRW4E0cvBxRSujqETU0c6N3fl0mB3oNXkd_B-E,16531 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/css/dataTables.semanticui.css,sha256=0wZ0R8UYdVtjtRKHxu-Xy5Jp206YCqeJdqZOdajtYQw,2937 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/css/jquery.dataTables.css,sha256=aFhojHUpG0tEx0zrp4tytAjXVErJ_6ySHaButv8C-eo,16026 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/css/jquery.dataTables_themeroller.css,sha256=_I46pnUiYUyilMyUMv08B9tEUgxxuWJJCqoyl6-Cz-I,14229 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/images/sort_asc.png,sha256=WVcEw_PPTLZcfZyFCKmedIDhUAlUc_rtMaB8IbEzibg,160 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/images/sort_asc_disabled.png,sha256=pluPT4TWQnqBw2AoL8U5TVG_mdraXxWeaqD848OWglw,148 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/images/sort_both.png,sha256=PgFsI65RQXOCtkCuLRnrSAR1MsN61TiUvRhVhlWcz_s,201 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/images/sort_desc.png,sha256=0I7Q4h8YfdMJAw1GUiTagIURmhWhfWFroOR3u1DG8Q0,158 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/images/sort_desc_disabled.png,sha256=bA8MGyHvaAcFevyN3BqSXR29IcsR6ScOyE_0rEDZo_o,146 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/js/dataTables.bootstrap.js,sha256=tJIoHA64cNe60LQ0mux9ILw-9cLD-RobM7arU7vNlJk,4559 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/js/dataTables.foundation.js,sha256=QpIM9XOHotY-SfWSxDhCRy9gLQq724DweRgqqBP3DwI,4339 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/js/dataTables.jqueryui.js,sha256=zPRTON__4hEzIZ8HidE-2j1j8stiW9RB8wJkomLDzEI,4486 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/js/dataTables.semanticui.js,sha256=cpyMpAbHWuyD_cRMYBp5pdFTNThWVU2U1UwcMjF_Taw,5091 +ckanext/datatablesview/public/vendor/DataTables-1.10.15/js/jquery.dataTables.js,sha256=E-_kL-CHUqkr0DmPPZ7ps65UqND-U_ZGCke2LM_XCLs,449307 +ckanext/datatablesview/public/vendor/FixedColumns-3.2.2/css/fixedColumns.bootstrap.css,sha256=fdHoXd_oNVuenK1Sy0q29LfZT8VRsw7zwAGAYErjTfQ,1437 +ckanext/datatablesview/public/vendor/FixedColumns-3.2.2/css/fixedColumns.dataTables.css,sha256=rrrHGibze9sxJdpm6-7I-MT6XdwofgFyZJtKRCC_9OM,367 +ckanext/datatablesview/public/vendor/FixedColumns-3.2.2/css/fixedColumns.foundation.css,sha256=gKC4NeugcIsCMc0GztYbRaMe3bm2ExkWKJ-EMZQaiT8,561 +ckanext/datatablesview/public/vendor/FixedColumns-3.2.2/css/fixedColumns.jqueryui.css,sha256=eqrmHbqsHwV-4-4RQ6RCCTKK1tbFcRVsBJfUF4037jc,217 +ckanext/datatablesview/public/vendor/FixedColumns-3.2.2/js/dataTables.fixedColumns.js,sha256=DsuWda4M-x8gCTAPrxZBakM7I0mQISldALeQJZ26_6Y,45771 +ckanext/datatablesview/public/vendor/FixedHeader-3.1.2/css/fixedHeader.bootstrap.css,sha256=P2N4TcjjSJX96no_xAt4CkysTuuvzMj4LVMjVrxwn2M,380 +ckanext/datatablesview/public/vendor/FixedHeader-3.1.2/css/fixedHeader.dataTables.css,sha256=vfoBa-bqvHsGpGGk3oFLPD7_ePgbXUiLqsJykrxbyiY,318 +ckanext/datatablesview/public/vendor/FixedHeader-3.1.2/css/fixedHeader.foundation.css,sha256=P2N4TcjjSJX96no_xAt4CkysTuuvzMj4LVMjVrxwn2M,380 +ckanext/datatablesview/public/vendor/FixedHeader-3.1.2/css/fixedHeader.jqueryui.css,sha256=rJgmDyvAhQkTnpI_uoHdO6lief0-ACzYwNtNvzkD-qI,250 +ckanext/datatablesview/public/vendor/FixedHeader-3.1.2/js/dataTables.fixedHeader.js,sha256=9c10Hp8BL6JBBsZ8BU2njRY-t_i1adcWNgHWOcp9eXo,16546 +ckanext/datatablesview/public/vendor/KeyTable-2.2.1/css/keyTable.bootstrap.css,sha256=iPC-gNWWWFGnTC1x6U5XkWN6b5s_MjZFr-cARq2TqFc,109 +ckanext/datatablesview/public/vendor/KeyTable-2.2.1/css/keyTable.dataTables.css,sha256=YbURVFWHYcHH9RrajfyY3Z_LNOQO8lvqdtmwxTJ60RY,109 +ckanext/datatablesview/public/vendor/KeyTable-2.2.1/css/keyTable.foundation.css,sha256=IWbJenY0CVHKEIBKtP2dQ7Ue9EIh3KYS9zXOcTee03k,109 +ckanext/datatablesview/public/vendor/KeyTable-2.2.1/css/keyTable.jqueryui.css,sha256=YbURVFWHYcHH9RrajfyY3Z_LNOQO8lvqdtmwxTJ60RY,109 +ckanext/datatablesview/public/vendor/KeyTable-2.2.1/css/keyTable.semanticui.css,sha256=wK7sI9e86GoWM5YwISpKakX8t0Q5CH8s5DhyO9Vt1Bc,106 +ckanext/datatablesview/public/vendor/KeyTable-2.2.1/js/dataTables.keyTable.js,sha256=EZm35oIwthlFdaLHcRJzMac8OJjj-I_kzD47ws9dwt0,22569 +ckanext/datatablesview/public/vendor/Responsive-2.1.1/css/responsive.bootstrap.css,sha256=3PPpfWpTvFQYKk9wgmnFqHPPNrUZRbuTuRep9aiJIZ0,4713 +ckanext/datatablesview/public/vendor/Responsive-2.1.1/css/responsive.dataTables.css,sha256=QOC16ffmC7R5r-z7CkDkodJifjJdDr8X6nUXEXxYqM0,4642 +ckanext/datatablesview/public/vendor/Responsive-2.1.1/css/responsive.foundation.css,sha256=Ik7oDm56P0MwOtWNis9ssacn9wPHOtig2zCsFx-jWn0,4702 +ckanext/datatablesview/public/vendor/Responsive-2.1.1/css/responsive.jqueryui.css,sha256=QOC16ffmC7R5r-z7CkDkodJifjJdDr8X6nUXEXxYqM0,4642 +ckanext/datatablesview/public/vendor/Responsive-2.1.1/js/dataTables.responsive.js,sha256=zNYQJWGPqwpJ4utT2qjZZ-ScddKmkp-umV9IfMesdgI,34989 +ckanext/datatablesview/public/vendor/Responsive-2.1.1/js/responsive.bootstrap.js,sha256=YpUtbqVeuxXA6LWjm1HpWtBsBb0hGKUp567-ugOAtPc,1999 +ckanext/datatablesview/public/vendor/Responsive-2.1.1/js/responsive.foundation.js,sha256=P-GXa2Zxbaw1WQqC-SowuL6YOo2MPEGqArsjmtrF_-8,1491 +ckanext/datatablesview/public/vendor/Responsive-2.1.1/js/responsive.jqueryui.js,sha256=6YsTyJMfi3uzUPIPvmUSNLbauhEwx0dMVJq4Kaaa3oI,1399 +ckanext/datatablesview/public/vendor/Select-1.2.2/css/select.bootstrap.css,sha256=B9f-8CYXwVNhnlM4if5vqdEePEnCYZuoelfwUlv5n60,4623 +ckanext/datatablesview/public/vendor/Select-1.2.2/css/select.dataTables.css,sha256=WhLMa3LB5BL2zNx2ovRiemjefIuKba_NANT4E9eR7yI,4370 +ckanext/datatablesview/public/vendor/Select-1.2.2/css/select.foundation.css,sha256=eT-4zjqKFZnxAS4DT2w-45zKyreq1b0RXlRuJkY0cYY,4710 +ckanext/datatablesview/public/vendor/Select-1.2.2/css/select.jqueryui.css,sha256=WhLMa3LB5BL2zNx2ovRiemjefIuKba_NANT4E9eR7yI,4370 +ckanext/datatablesview/public/vendor/Select-1.2.2/css/select.semanticui.css,sha256=32gjAit4VpoDzPE-LFPCMAoLGTpNbI0BhuQVycYGxeQ,4732 +ckanext/datatablesview/public/vendor/Select-1.2.2/js/dataTables.select.js,sha256=skqdJgUbqPv-QWDmZw-4QZEOAFyQldziWnoc2QpSyOA,31010 +ckanext/datatablesview/public/vendor/datatables.css,sha256=UpzQyrbBWeh9COljeLndAJqkNwVsgzxG4BUzMYQeVXI,166312 +ckanext/datatablesview/public/vendor/datatables.js,sha256=Y_hgxf5MC7ZYXpe88d3Kk9nfMaFAtTcF05NGu2WHDVo,1473101 +ckanext/datatablesview/templates/datatables/datatables_form.html,sha256=XcVL0LpjhHJKz5ydPXN3cyu_opW8HeFx0lT_IdGNujw,1068 +ckanext/datatablesview/templates/datatables/datatables_view.html,sha256=tc4Ok4MLwB7VYcBdP0uP7UsXrxvBPNv_4kHX9udFGWk,1476 +ckanext/example_flask_iblueprint/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_flask_iblueprint/__init__.pyc,, +ckanext/example_flask_iblueprint/plugin.py,sha256=kQYEHixZq4k6e0HXdaFKE3MnJKtG7rWcgCMOC_YiK-w,2934 +ckanext/example_flask_iblueprint/plugin.pyc,, +ckanext/example_flask_iblueprint/templates/about.html,sha256=zGA7HGADiDwWON3cbTXSKquD8DXOiYdJXq9KugTReU8,183 +ckanext/example_flask_iblueprint/templates/about_base.html,sha256=QfGAm_S_4EXHBjaZA2iC-oAaNGPaGxydTkjPqI2KbDE,107 +ckanext/example_flask_iblueprint/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_flask_iblueprint/tests/__init__.pyc,, +ckanext/example_flask_iblueprint/tests/test_routes.py,sha256=u1rJ5u7gxZQXgZLsZY-AKpvYerVh88LTKfppuzR28ok,2240 +ckanext/example_flask_iblueprint/tests/test_routes.pyc,, +ckanext/example_flask_streaming/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_flask_streaming/__init__.pyc,, +ckanext/example_flask_streaming/plugin.py,sha256=tUyAYvClNFxF0cvyB64ctdBYdv9OK4xL36D9JtMjP24,2956 +ckanext/example_flask_streaming/plugin.pyc,, +ckanext/example_flask_streaming/templates/stream.html,sha256=rG6Whc_esJxRj8MfhZ6GPZ5pZnzpyfyOHhEAtzR_SFs,232 +ckanext/example_flask_streaming/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_flask_streaming/tests/__init__.pyc,, +ckanext/example_flask_streaming/tests/test_streaming_responses.py,sha256=D2kvxxjBobfGwVpVl1dHKQY1uucxTkONv_PAoPU2-aM,2415 +ckanext/example_flask_streaming/tests/test_streaming_responses.pyc,, +ckanext/example_iauthfunctions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_iauthfunctions/__init__.pyc,, +ckanext/example_iauthfunctions/plugin_v1.py,sha256=IYXvL2wmlBSRv-YRfsvRhGMuOx20fYIML0X49j0GovI,121 +ckanext/example_iauthfunctions/plugin_v1.pyc,, +ckanext/example_iauthfunctions/plugin_v2.py,sha256=GD-ro0u5mug--Z77qu3KXSC2OGsxJOukWu-Ix84xnRs,360 +ckanext/example_iauthfunctions/plugin_v2.pyc,, +ckanext/example_iauthfunctions/plugin_v3.py,sha256=2ZlXu6BSGWlAJ62-3WDwjbPdQ8OfkYWndSsfFTYZ7jo,1237 +ckanext/example_iauthfunctions/plugin_v3.pyc,, +ckanext/example_iauthfunctions/plugin_v4.py,sha256=arUfk5ic7YmkOYXkkqXl_wwBby1K30kT8Of-fatmFFk,1772 +ckanext/example_iauthfunctions/plugin_v4.pyc,, +ckanext/example_iauthfunctions/plugin_v5_custom_config_setting.py,sha256=JNRkSq7Wkhbd4XnRrhZdJEESdyxskXukqV8j0miJ5zA,933 +ckanext/example_iauthfunctions/plugin_v5_custom_config_setting.pyc,, +ckanext/example_iauthfunctions/plugin_v6_parent_auth_functions.py,sha256=fWl-ZBsHYIOVeADQ0fkGnLVgK4aP5WOpiHgSaGzUk9g,449 +ckanext/example_iauthfunctions/plugin_v6_parent_auth_functions.pyc,, +ckanext/example_iconfigurer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_iconfigurer/__init__.pyc,, +ckanext/example_iconfigurer/controller.py,sha256=EirqgvVFpsDFo2Okh72eO1wshqekDafDI--dFiFF8QY,742 +ckanext/example_iconfigurer/controller.pyc,, +ckanext/example_iconfigurer/plugin.py,sha256=UgIGRjWOYFnnOIoY5cK8nYAg4OWhzwFChcs9QIG6Zhk,2481 +ckanext/example_iconfigurer/plugin.pyc,, +ckanext/example_iconfigurer/plugin_v1.py,sha256=5qVgdYnGrGVBa03QHwPQJwlyRs7Qzir9GBPsb-IFgXY,888 +ckanext/example_iconfigurer/plugin_v1.pyc,, +ckanext/example_iconfigurer/plugin_v2.py,sha256=YFbJaeYdnJfDS4NQL-6LGZYer7h7x0nnMgjO5PShP6k,1029 +ckanext/example_iconfigurer/plugin_v2.pyc,, +ckanext/example_iconfigurer/templates/admin/config.html,sha256=oU6VswbAE36HJv9SA9HXkoRfZdWuO93FG1dfFpzdHJg,860 +ckanext/example_iconfigurer/templates/admin/myext_config.html,sha256=ghe89pfS8uo9wSaqnCAMlYO14URnR5hC97YjGtlLmZ4,107 +ckanext/example_iconfigurer/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_iconfigurer/tests/__init__.pyc,, +ckanext/example_iconfigurer/tests/test_example_iconfigurer.py,sha256=IC-RrO1lT1bLBtbGMsky2oebLaz2_pExdhdMqDauARk,3495 +ckanext/example_iconfigurer/tests/test_example_iconfigurer.pyc,, +ckanext/example_iconfigurer/tests/test_iconfigurer_toolkit.py,sha256=lwIQPa0XCnxwnrdjsdCoN-D5_6PvYPBT_20yD7ML33Y,3556 +ckanext/example_iconfigurer/tests/test_iconfigurer_toolkit.pyc,, +ckanext/example_iconfigurer/tests/test_iconfigurer_update_config.py,sha256=Ao4BWp15vR0KWt8SSNAx3eLe86tFn-GFdrfpYiHESzk,5122 +ckanext/example_iconfigurer/tests/test_iconfigurer_update_config.pyc,, +ckanext/example_idatasetform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_idatasetform/__init__.pyc,, +ckanext/example_idatasetform/plugin.py,sha256=6yR4bYnL6JZqoq4C94DtDz4OYjxFTR9zWk8PTanRUsA,6964 +ckanext/example_idatasetform/plugin.pyc,, +ckanext/example_idatasetform/plugin_v1.py,sha256=gEpD7Y4Uijgnmxqd4H28YjKZ4oRMEtqibpQ4L3VfgF4,1574 +ckanext/example_idatasetform/plugin_v1.pyc,, +ckanext/example_idatasetform/plugin_v2.py,sha256=beZnYQjAu2jMJN4rCNSST1vgOZC1Gd6IfQ9s0rwMAtw,1856 +ckanext/example_idatasetform/plugin_v2.pyc,, +ckanext/example_idatasetform/plugin_v3.py,sha256=L5UG6sFONUNYiHRvOzoaoSpgT4WieoYyAic4yznHgyo,1508 +ckanext/example_idatasetform/plugin_v3.pyc,, +ckanext/example_idatasetform/plugin_v4.py,sha256=OfL3IyDwaWIf1K5bfidwZJzMEbklrDXQAn3YSSA_Z9M,3156 +ckanext/example_idatasetform/plugin_v4.pyc,, +ckanext/example_idatasetform/templates/package/read.html,sha256=seyD5n2To9MzkC7PyFGrECMEfNMrBuFSB-SlLr3jOO8,358 +ckanext/example_idatasetform/templates/package/search.html,sha256=N-r_w9LL8kBN8gXZ9Z9VwmDz0BGXpnvUI4GV1ybCJvQ,894 +ckanext/example_idatasetform/templates/package/snippets/additional_info.html,sha256=nl9Gf8wdv5R9FSuJXpl9ha9hjpB0Oa5_epNlQtnrCcs,257 +ckanext/example_idatasetform/templates/package/snippets/package_basic_fields.html,sha256=_Z0VnBcvre2ZIggq2QeHnQs_SDav59V0JBDY2gkV62g,265 +ckanext/example_idatasetform/templates/package/snippets/package_metadata_fields.html,sha256=kNj1Bqmn7IshSm7xkn_CKDWreJncSwJtwYyt8HX5vok,749 +ckanext/example_idatasetform/templates/package/snippets/resource_form.html,sha256=qnII7yo-WvcCon_N-DKUPNO125Q4LAhe1ODBprQ9uoU,314 +ckanext/example_idatasetform/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_idatasetform/tests/__init__.pyc,, +ckanext/example_idatasetform/tests/test_controllers.py,sha256=_CrUXwRsrIWg5TKzzL6eu3ItzAd3QPqx34wUiCyAQ3M,2425 +ckanext/example_idatasetform/tests/test_controllers.pyc,, +ckanext/example_idatasetform/tests/test_example_idatasetform.py,sha256=76CwXQjH_IwgVQxofFZK8snSGhtUfYLZr9YFK7ahtTw,8930 +ckanext/example_idatasetform/tests/test_example_idatasetform.pyc,, +ckanext/example_idatastorebackend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_idatastorebackend/__init__.pyc,, +ckanext/example_idatastorebackend/example_sqlite.py,sha256=hOe8pSzntVEz6xCQC271rWBMZwD19SGcDHMxA1OVLs0,3582 +ckanext/example_idatastorebackend/example_sqlite.pyc,, +ckanext/example_idatastorebackend/plugin.py,sha256=IgrabpbJrSC0oG93oZhj1VIsq-55S3iLzfuwqzyl0oc,423 +ckanext/example_idatastorebackend/plugin.pyc,, +ckanext/example_idatastorebackend/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_idatastorebackend/test/__init__.pyc,, +ckanext/example_idatastorebackend/test/test_plugin.py,sha256=3ErYoTRqJ7ufazOfRyDZEmlQLNK1GwswBY-VBcK3z78,4379 +ckanext/example_idatastorebackend/test/test_plugin.pyc,, +ckanext/example_igroupform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_igroupform/__init__.pyc,, +ckanext/example_igroupform/plugin.py,sha256=gTyeOX1-lT9TFquQHwFYW-3TiEMuosAiDLw6lYWSt2k,2320 +ckanext/example_igroupform/plugin.pyc,, +ckanext/example_igroupform/templates/example_igroup_form/group_form.html,sha256=-J0u1XaJ-YuwCBjUX5fR_WCQL6Ftmn3k1YM6Z8kqb3k,189 +ckanext/example_igroupform/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_igroupform/tests/__init__.pyc,, +ckanext/example_igroupform/tests/test_controllers.py,sha256=oYdMc3s7pM4r09Gn2uveOpk2CS0uuWgUMKXabCFW4v4,11258 +ckanext/example_igroupform/tests/test_controllers.pyc,, +ckanext/example_ipermissionlabels/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_ipermissionlabels/__init__.pyc,, +ckanext/example_ipermissionlabels/plugin.py,sha256=fbxgcBn0zLHmCGayv-ftT5xJag0jL8KRb9e7Ai_bc58,1542 +ckanext/example_ipermissionlabels/plugin.pyc,, +ckanext/example_ipermissionlabels/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_ipermissionlabels/tests/__init__.pyc,, +ckanext/example_ipermissionlabels/tests/test_example_ipermissionlabels.py,sha256=heAs5qlAoLbliICcUByR8F96WG3syTstfWlWq58f4_I,4127 +ckanext/example_ipermissionlabels/tests/test_example_ipermissionlabels.pyc,, +ckanext/example_iresourcecontroller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_iresourcecontroller/__init__.pyc,, +ckanext/example_iresourcecontroller/plugin.py,sha256=R819FJpJXH2IIkE-x8sEQmyrBY11ZMAqk3vj7VtlBDU,934 +ckanext/example_iresourcecontroller/plugin.pyc,, +ckanext/example_itemplatehelpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_itemplatehelpers/__init__.pyc,, +ckanext/example_itemplatehelpers/plugin.py,sha256=rXWKYTUFA1pVFxAzhla1jK7cm1Il7yFEhmO9ID7doXo,969 +ckanext/example_itemplatehelpers/plugin.pyc,, +ckanext/example_itemplatehelpers/templates/home/index.html,sha256=JHqWExUjwI-gNA63V2mC5gtiwWlOoFSd5dTOfkhFCpQ,108 +ckanext/example_itranslation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_itranslation/__init__.pyc,, +ckanext/example_itranslation/i18n/ckanext-example_itranslation.pot,sha256=auCc_WyOKUMaZszNoUT7C6x87PRcDd-3B6GWYDaDMEQ,683 +ckanext/example_itranslation/i18n/en/LC_MESSAGES/ckanext-example_itranslation.mo,sha256=n5ZXj7nfuRcMSDr3rkYoFnH2UpnS0jWQ8KPL56acHdI,542 +ckanext/example_itranslation/i18n/en/LC_MESSAGES/ckanext-example_itranslation.po,sha256=V6HgKNLEsZ-0mZX1O1qMcufvPw0uH4aOxnlA6Fs7MUI,748 +ckanext/example_itranslation/i18n/en/LC_MESSAGES/ckanext-example_translation.po,sha256=V6HgKNLEsZ-0mZX1O1qMcufvPw0uH4aOxnlA6Fs7MUI,748 +ckanext/example_itranslation/i18n/fr/LC_MESSAGES/ckanext-example_itranslation.mo,sha256=wRH_bNMjmkGxhtQGksZzAnhCG0Ckr-uFhVLOFHGnzjw,559 +ckanext/example_itranslation/i18n/fr/LC_MESSAGES/ckanext-example_itranslation.po,sha256=N9yDF8SNHN6FxeWogK8D6zxerOTK_qsPBsoScAzMSR0,621 +ckanext/example_itranslation/plugin.py,sha256=jRFt0nH9IEhTB-77u9YpPXmLWUAk6iOy9rHquE8EYWY,392 +ckanext/example_itranslation/plugin.pyc,, +ckanext/example_itranslation/plugin_v1.py,sha256=i1LyEkQG54RLmrYJXeWY_OQm9oeBtf191Ua9Mk2YqSQ,279 +ckanext/example_itranslation/plugin_v1.pyc,, +ckanext/example_itranslation/templates/home/index.html,sha256=yWzt4EpScDxM81fHFEC11j9I4BmRW527tJHuEHvkOjs,119 +ckanext/example_itranslation/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_itranslation/tests/__init__.pyc,, +ckanext/example_itranslation/tests/test_plugin.py,sha256=UTF5CZuqrFq0QPatAw1N1FD56IKSscSGuZ55U-N7F-o,2560 +ckanext/example_itranslation/tests/test_plugin.pyc,, +ckanext/example_iuploader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_iuploader/__init__.pyc,, +ckanext/example_iuploader/plugin.py,sha256=rvbE6Tuzq_syN3ldgHU6yob0iFGVv4J4dY1Qjfu9Dz4,600 +ckanext/example_iuploader/plugin.pyc,, +ckanext/example_iuploader/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_iuploader/test/__init__.pyc,, +ckanext/example_iuploader/test/test_plugin.py,sha256=zs7QCARIrBHtB765Z2-uoBDCKiRkf9zqua6GOIoCnkw,4447 +ckanext/example_iuploader/test/test_plugin.pyc,, +ckanext/example_ivalidators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_ivalidators/__init__.pyc,, +ckanext/example_ivalidators/plugin.py,sha256=1QNBQsSZLxMkWXARWsKxW3FjnmabkESr4OAhkxw3m_8,763 +ckanext/example_ivalidators/plugin.pyc,, +ckanext/example_ivalidators/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_ivalidators/tests/__init__.pyc,, +ckanext/example_ivalidators/tests/test_ivalidators.py,sha256=FddSKEx_qwLrZ6lDjZNRQsBe4N9E_Fzn2eh728MhW0Q,1106 +ckanext/example_ivalidators/tests/test_ivalidators.pyc,, +ckanext/example_theme_docs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/__init__.pyc,, +ckanext/example_theme_docs/custom_config_setting/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/custom_config_setting/__init__.pyc,, +ckanext/example_theme_docs/custom_config_setting/plugin.py,sha256=OPCPcNONo9Ku3wIImh8172i73LGT9OGVSDGWIJHL9io,2035 +ckanext/example_theme_docs/custom_config_setting/plugin.pyc,, +ckanext/example_theme_docs/custom_emails/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/custom_emails/__init__.pyc,, +ckanext/example_theme_docs/custom_emails/plugin.py,sha256=hRFa-3LcG0PMvHf2xaQe777EtaHt7WEyrxV-Lzd06tY,484 +ckanext/example_theme_docs/custom_emails/plugin.pyc,, +ckanext/example_theme_docs/custom_emails/tests.py,sha256=74fShIe3LXJF7hKwUas4yHhOxlLbrIB__yzePpjfc4M,3817 +ckanext/example_theme_docs/custom_emails/tests.pyc,, +ckanext/example_theme_docs/v01_empty_extension/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v01_empty_extension/__init__.pyc,, +ckanext/example_theme_docs/v01_empty_extension/plugin.py,sha256=58LmEwuz2rmHDC_rGUQcLzY-4xhnTMicVa_Q34A6AqY,153 +ckanext/example_theme_docs/v01_empty_extension/plugin.pyc,, +ckanext/example_theme_docs/v02_empty_template/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v02_empty_template/__init__.pyc,, +ckanext/example_theme_docs/v02_empty_template/plugin.py,sha256=jzzujrTyA9xlWvTtrzdphc66G64Wv1snEVrzuQrcDn0,635 +ckanext/example_theme_docs/v02_empty_template/plugin.pyc,, +ckanext/example_theme_docs/v03_jinja/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v03_jinja/__init__.pyc,, +ckanext/example_theme_docs/v03_jinja/plugin.py,sha256=jzzujrTyA9xlWvTtrzdphc66G64Wv1snEVrzuQrcDn0,635 +ckanext/example_theme_docs/v03_jinja/plugin.pyc,, +ckanext/example_theme_docs/v04_ckan_extends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v04_ckan_extends/__init__.pyc,, +ckanext/example_theme_docs/v04_ckan_extends/plugin.py,sha256=jzzujrTyA9xlWvTtrzdphc66G64Wv1snEVrzuQrcDn0,635 +ckanext/example_theme_docs/v04_ckan_extends/plugin.pyc,, +ckanext/example_theme_docs/v05_block/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v05_block/__init__.pyc,, +ckanext/example_theme_docs/v05_block/plugin.py,sha256=jzzujrTyA9xlWvTtrzdphc66G64Wv1snEVrzuQrcDn0,635 +ckanext/example_theme_docs/v05_block/plugin.pyc,, +ckanext/example_theme_docs/v06_super/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v06_super/__init__.pyc,, +ckanext/example_theme_docs/v06_super/plugin.py,sha256=jzzujrTyA9xlWvTtrzdphc66G64Wv1snEVrzuQrcDn0,635 +ckanext/example_theme_docs/v06_super/plugin.pyc,, +ckanext/example_theme_docs/v07_helper_function/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v07_helper_function/__init__.pyc,, +ckanext/example_theme_docs/v07_helper_function/plugin.py,sha256=jzzujrTyA9xlWvTtrzdphc66G64Wv1snEVrzuQrcDn0,635 +ckanext/example_theme_docs/v07_helper_function/plugin.pyc,, +ckanext/example_theme_docs/v08_custom_helper_function/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v08_custom_helper_function/__init__.pyc,, +ckanext/example_theme_docs/v08_custom_helper_function/plugin.py,sha256=qTljRe5voITntzJuYOAJ1vvWKaW1W9g1QIidMez5QH4,1380 +ckanext/example_theme_docs/v08_custom_helper_function/plugin.pyc,, +ckanext/example_theme_docs/v09_snippet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v09_snippet/__init__.pyc,, +ckanext/example_theme_docs/v09_snippet/plugin.py,sha256=qTljRe5voITntzJuYOAJ1vvWKaW1W9g1QIidMez5QH4,1380 +ckanext/example_theme_docs/v09_snippet/plugin.pyc,, +ckanext/example_theme_docs/v10_custom_snippet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v10_custom_snippet/__init__.pyc,, +ckanext/example_theme_docs/v10_custom_snippet/plugin.py,sha256=qTljRe5voITntzJuYOAJ1vvWKaW1W9g1QIidMez5QH4,1380 +ckanext/example_theme_docs/v10_custom_snippet/plugin.pyc,, +ckanext/example_theme_docs/v11_HTML_and_CSS/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v11_HTML_and_CSS/__init__.pyc,, +ckanext/example_theme_docs/v11_HTML_and_CSS/plugin.py,sha256=qTljRe5voITntzJuYOAJ1vvWKaW1W9g1QIidMez5QH4,1380 +ckanext/example_theme_docs/v11_HTML_and_CSS/plugin.pyc,, +ckanext/example_theme_docs/v12_extra_public_dir/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v12_extra_public_dir/__init__.pyc,, +ckanext/example_theme_docs/v12_extra_public_dir/plugin.py,sha256=sKI9cmMMFaOfOyiRgUCAHr-54XNu5RUp3ZdTOOjPXPs,1567 +ckanext/example_theme_docs/v12_extra_public_dir/plugin.pyc,, +ckanext/example_theme_docs/v13_custom_css/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v13_custom_css/__init__.pyc,, +ckanext/example_theme_docs/v13_custom_css/plugin.py,sha256=sKI9cmMMFaOfOyiRgUCAHr-54XNu5RUp3ZdTOOjPXPs,1567 +ckanext/example_theme_docs/v13_custom_css/plugin.pyc,, +ckanext/example_theme_docs/v14_more_custom_css/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v14_more_custom_css/__init__.pyc,, +ckanext/example_theme_docs/v14_more_custom_css/plugin.py,sha256=sKI9cmMMFaOfOyiRgUCAHr-54XNu5RUp3ZdTOOjPXPs,1567 +ckanext/example_theme_docs/v14_more_custom_css/plugin.pyc,, +ckanext/example_theme_docs/v15_fanstatic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v15_fanstatic/__init__.pyc,, +ckanext/example_theme_docs/v15_fanstatic/plugin.py,sha256=RVvYUFD7zcZcRx5eo27TIeZlI0rwpQe83eCEjciDs4s,1928 +ckanext/example_theme_docs/v15_fanstatic/plugin.pyc,, +ckanext/example_theme_docs/v16_initialize_a_javascript_module/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v16_initialize_a_javascript_module/__init__.pyc,, +ckanext/example_theme_docs/v16_initialize_a_javascript_module/plugin.py,sha256=fDE9Ye9GXePK_8kOFi33dief0dgscyjXQ8JqbwGQqzw,385 +ckanext/example_theme_docs/v16_initialize_a_javascript_module/plugin.pyc,, +ckanext/example_theme_docs/v17_popover/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v17_popover/__init__.pyc,, +ckanext/example_theme_docs/v17_popover/plugin.py,sha256=fDE9Ye9GXePK_8kOFi33dief0dgscyjXQ8JqbwGQqzw,385 +ckanext/example_theme_docs/v17_popover/plugin.pyc,, +ckanext/example_theme_docs/v18_snippet_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v18_snippet_api/__init__.pyc,, +ckanext/example_theme_docs/v18_snippet_api/plugin.py,sha256=fDE9Ye9GXePK_8kOFi33dief0dgscyjXQ8JqbwGQqzw,385 +ckanext/example_theme_docs/v18_snippet_api/plugin.pyc,, +ckanext/example_theme_docs/v19_01_error/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v19_01_error/__init__.pyc,, +ckanext/example_theme_docs/v19_01_error/plugin.py,sha256=fDE9Ye9GXePK_8kOFi33dief0dgscyjXQ8JqbwGQqzw,385 +ckanext/example_theme_docs/v19_01_error/plugin.pyc,, +ckanext/example_theme_docs/v19_02_error_handling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v19_02_error_handling/__init__.pyc,, +ckanext/example_theme_docs/v19_02_error_handling/plugin.py,sha256=fDE9Ye9GXePK_8kOFi33dief0dgscyjXQ8JqbwGQqzw,385 +ckanext/example_theme_docs/v19_02_error_handling/plugin.pyc,, +ckanext/example_theme_docs/v20_pubsub/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v20_pubsub/__init__.pyc,, +ckanext/example_theme_docs/v20_pubsub/plugin.py,sha256=fDE9Ye9GXePK_8kOFi33dief0dgscyjXQ8JqbwGQqzw,385 +ckanext/example_theme_docs/v20_pubsub/plugin.pyc,, +ckanext/example_theme_docs/v21_custom_jquery_plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/example_theme_docs/v21_custom_jquery_plugin/__init__.pyc,, +ckanext/example_theme_docs/v21_custom_jquery_plugin/plugin.py,sha256=fDE9Ye9GXePK_8kOFi33dief0dgscyjXQ8JqbwGQqzw,385 +ckanext/example_theme_docs/v21_custom_jquery_plugin/plugin.pyc,, +ckanext/imageview/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/imageview/__init__.pyc,, +ckanext/imageview/plugin.py,sha256=0Ugz_1ne-G4ZTieBT6hDiRUgDkaj8p-_xMw_XckYJqo,1318 +ckanext/imageview/plugin.pyc,, +ckanext/imageview/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/imageview/tests/__init__.pyc,, +ckanext/imageview/tests/test_view.py,sha256=bSIMxPxYqq_IVPg2vKqGHQDKvTQxnCqiOaeGzl8XC5c,929 +ckanext/imageview/tests/test_view.pyc,, +ckanext/imageview/theme/templates/image_form.html,sha256=xPp28RFmxj-GfzHJGlTtT2mcvax0AORrV0pQ22Gy3c8,285 +ckanext/imageview/theme/templates/image_view.html,sha256=5iSyHlZbMlQlign-t8p-FK3a3NuRsV9_bqAEsFK89Yg,126 +ckanext/multilingual/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/multilingual/__init__.pyc,, +ckanext/multilingual/plugin.py,sha256=fLnD-zCOneejy8AYpJHEcqIYYL4eKMdipupQ5Q5QfmQ,16113 +ckanext/multilingual/plugin.pyc,, +ckanext/reclineview/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/reclineview/__init__.pyc,, +ckanext/reclineview/plugin.py,sha256=yoUTGAxpZU_Yg-UhGJNlnFsXZtg_Upc1BVgxOhYfK6c,9201 +ckanext/reclineview/plugin.pyc,, +ckanext/reclineview/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/reclineview/tests/__init__.pyc,, +ckanext/reclineview/tests/test_view.py,sha256=qVsv_EP0VoMUyDs2PgXeJRNoNFoi0c9HcNu9vNt7f68,6003 +ckanext/reclineview/tests/test_view.pyc,, +ckanext/reclineview/theme/public/css/recline.css,sha256=V2UQpSFOr5S9Lhfzh26huIKPaJ7Vxb5GTfjohaaLrj4,9348 +ckanext/reclineview/theme/public/css/recline.min.css,sha256=ILmTtltkLyzBlDQ2SlFEdOm1fTiFkYfMGe3NdRSyS0A,7363 +ckanext/reclineview/theme/public/img/ajaxload-circle.gif,sha256=TcFP5d9o0q6JniN_r5Jk1t8CYF3WVTaMuFbNbOdcdXM,4176 +ckanext/reclineview/theme/public/recline_view.js,sha256=MCvc8zQx2NRFyp27_5UuANcmpWQGoHTaKbOluYjoL2Q,8198 +ckanext/reclineview/theme/public/recline_view.min.js,sha256=OS89CcYL2_0pAUjpF13ZAo7EAsXv9cce-DNVP50OTag,5625 +ckanext/reclineview/theme/public/resource.config,sha256=OwxnWEoackdDpU99hlW4aeT7YrTB7-bpe3jSZozYVuI,1469 +ckanext/reclineview/theme/public/vendor/backbone/1.0.0/backbone.js,sha256=Lrus3IOT2sTOHUz7uOsZV7p714Efp77WfJSkoRk6ePM,59498 +ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/css/bootstrap-theme.css,sha256=did6yQnyomrwO1H3_azVybkTlMV8f7drEzTUughLZ9U,21368 +ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/css/bootstrap.css,sha256=0I8pG8uDB5sTMwlPTAIWQbMxgpFbXnSui93117T9Rpg,132546 +ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/fonts/glyphicons-halflings-regular.eot,sha256=9JXzTk8XfPARWvmVu7_rP8q8iFAodudvxRpKtDm8hDE,20335 +ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/fonts/glyphicons-halflings-regular.svg,sha256=0WjVCojHMLTmgw3A2iorUdrkZYp32WGZQ8J7js_BnRo,62927 +ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/fonts/glyphicons-halflings-regular.ttf,sha256=vRjv0-_XD-yK0JYRogzb-ZRAssHUAIXCm-A2-JHWU1g,41280 +ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/fonts/glyphicons-halflings-regular.woff,sha256=_Jadwcb_Uxq882gIncuvV3UTOwYm_1a1IwGgWfwPnh4,23320 +ckanext/reclineview/theme/public/vendor/bootstrap/3.2.0/js/bootstrap.js,sha256=eXDzGQfZG_Dxnv6K7-501vCi2McrL48gpeKX08QUp48,60681 +ckanext/reclineview/theme/public/vendor/ckan.js/ckan.js,sha256=Ihq9SiLblv8Nx5zDly3uoSux8rz37w-_qPcBCcwe254,6986 +ckanext/reclineview/theme/public/vendor/flot/excanvas.js,sha256=OHPYC42T-UItVaEMqbckEd5l9T2XBdkCAlRTiYtUkYA,41943 +ckanext/reclineview/theme/public/vendor/flot/excanvas.min.js,sha256=YgHbvZvrlNiiQaDevyzC5u_AfsafTR8RaYTiG0We5Wk,19314 +ckanext/reclineview/theme/public/vendor/flot/jquery.flot.js,sha256=Buc_Aq0rp0po9B9asnLeDsjyMOYxo7hHCvTqRVpipJ8,119052 +ckanext/reclineview/theme/public/vendor/flot/jquery.flot.time.js,sha256=85ddZgnesEPBidx58QQeSKRKCl_h77LoMRQYq2MhYNY,11729 +ckanext/reclineview/theme/public/vendor/flotr2/flotr2.js,sha256=XL2cg9onvGwVeUOMzYg7Vtmmqw55GAn0GaAolG_JDgI,199723 +ckanext/reclineview/theme/public/vendor/flotr2/flotr2.min.js,sha256=38HKBIQVii-SwdUlGI_M8PpUQIkK4xSBDN6yF4RfZ4M,116019 +ckanext/reclineview/theme/public/vendor/jquery/1.7.1/jquery.js,sha256=n8wkEJNAWUaIUDnfQoz6fwBRofK9vMWjE6F3qeNfiAY,248235 +ckanext/reclineview/theme/public/vendor/jquery/1.7.1/jquery.min.js,sha256=iBcUE_x23aI6syuqF7EeT_-JFBxjPs5zeFJEXxumwb0,93868 +ckanext/reclineview/theme/public/vendor/json/json2.js,sha256=1UOI69ewpH08MyL2J1Ao4cNpfQVAk1aslfD4RfbaLmY,17530 +ckanext/reclineview/theme/public/vendor/json/json2.min.js,sha256=3QoMg35QSA_zQLMeqbMTi1up0GgvSTS3FiXgezpdkJ8,3449 +ckanext/reclineview/theme/public/vendor/leaflet.markercluster/MarkerCluster.Default.css,sha256=YSWCMtmNZNwqex4CEw1nQhvFub2lmU7vcCKP-XVwwXA,1287 +ckanext/reclineview/theme/public/vendor/leaflet.markercluster/MarkerCluster.css,sha256=Fvquvz2fsjbSkYnNwV1vmiV-vwrGSH8PAAunmlOmSys,366 +ckanext/reclineview/theme/public/vendor/leaflet.markercluster/leaflet.markercluster-src.js,sha256=D4p2Xv-SEPUrontcwIq_FuXlugOT5MfQPd1Keq3Kwcc,60524 +ckanext/reclineview/theme/public/vendor/leaflet.markercluster/leaflet.markercluster.js,sha256=pLyXQhrUrqzgxi537mZCstHxaliRrKXw4uzxG6WQvjM,28784 +ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/images/layers-2x.png,sha256=fDW_tRAiuIKE3cGejl2ECaTHyB1WTwvIaNjhT3XK3vA,1585 +ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/images/layers.png,sha256=4Q5V2r1RKGY1lUnFRtcmwge-7tOwtbAyUzZIBjAMxZw,913 +ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/images/marker-icon-2x.png,sha256=VfDJxHH5bd68zhleKI3sAWXOW-jetd_bDFGoG2J-CjA,4032 +ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/images/marker-icon.png,sha256=kV6Dpvx5jFmeXJ4_dZ1rwGXWUVEBms0EENH0cxvKr3I,1747 +ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/images/marker-shadow.png,sha256=pydFIZRmlF7SZ7eCWTWDoB1rtsVKO-p1jvqU9hQdlMs,681 +ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/leaflet-src.js,sha256=x1atEEGVVUoT4E3FQ2ZU7alopQsPtzMJl7sF28b9sWg,215410 +ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/leaflet.css,sha256=ymZGho-WjeQQ2jvjHInYJd0h20DI6_AE0fYq-BGYXqY,10183 +ckanext/reclineview/theme/public/vendor/leaflet/0.7.7/leaflet.js,sha256=aReBHzIjoMzKrp0H4XnxXIm0mwuNG_F-00pKDiFuLxI,125709 +ckanext/reclineview/theme/public/vendor/moment/2.0.0/moment.js,sha256=izpyeDkrZX5XuzceX635oH9Z21DbvI-q3sWrFveY5Mc,43905 +ckanext/reclineview/theme/public/vendor/mustache/0.5.0-dev/mustache.js,sha256=A9bfZFBW71tPzgGWpGrwDJCicHS1oApzkO2J2aTVi3Q,14513 +ckanext/reclineview/theme/public/vendor/mustache/0.5.0-dev/mustache.min.js,sha256=U1lia3ELviWknhV6xBNwhG0LbT3s1jj61FOkYaU6DWA,7633 +ckanext/reclineview/theme/public/vendor/recline/flot.css,sha256=siqm9FWlaT8jYMzv0toMq7UC-I4hodMmGKPHo-pw7A4,391 +ckanext/reclineview/theme/public/vendor/recline/map.css,sha256=8coNfkFrpT9iIGNMCLFO4w1qQLpjFAWOEFHwThdyVGI,482 +ckanext/reclineview/theme/public/vendor/recline/recline.css,sha256=_gBooqNPDXgrWfNrXBDgZtZ7LGwxv3U3fS1NEFWpCvg,18942 +ckanext/reclineview/theme/public/vendor/recline/recline.dataset.js,sha256=P4IMXDvy2DgRLsxsEGD9NifxQONMfCBTqsKZ_jgRMmU,27239 +ckanext/reclineview/theme/public/vendor/recline/recline.js,sha256=KvQBrwrZ3duVFIVySKZZGSAQd1kMCqjxURScIaKkRQA,140019 +ckanext/reclineview/theme/public/vendor/recline/slickgrid.css,sha256=-z59z-pHHcBDoVukOKqrpRBP2pRTsvGlgvNcfRhq1XU,4482 +ckanext/reclineview/theme/public/vendor/showdown/20120615/showdown.js,sha256=NcqqADp69pNxPmUTWoXpP2Qxz_fDuCHQHh1LE1Q8NE8,35163 +ckanext/reclineview/theme/public/vendor/showdown/20120615/showdown.min.js,sha256=n2ZfshVJvCq6TH5ZiZSy3nH6IwscUiVx94h9VUrXpVw,12533 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/MIT-LICENSE.txt,sha256=vqv7ER83IA6SHmVbEHxvKQR5C3YPKhl2z4X7cY_IYus,1096 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/README.md,sha256=sYu02HT_gv1--EFVmvR9hNw9wTT0HSmk3RwSB5GTq4g,1177 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/controls/slick.columnpicker.css,sha256=UiRlEe6TzhciypR_WO7_ZgPcWrkU0Pzqq0bCHpyKS5U,529 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/controls/slick.columnpicker.js,sha256=shpeTt8ON9lcNqkMz-KX0sLgNvJuedaIgzySqdjlGMU,4522 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/controls/slick.pager.css,sha256=rttVoH7vsXyPVyg-uSoG7p0LBfY5It4YsA7enNVSCCY,728 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/controls/slick.pager.js,sha256=ZF5nVomllj5kvs6Z-eGmIka9E0E2JsOp_7q0t2-gYcs,5023 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png,sha256=rmWnriLEwjEVlI_etcBckTfb0Tyi1CazxMPEGDRR5BA,86 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png,sha256=T7vZ_efwD5tu8VDGvYAN1kaf0H18Q66EenIG5PieDBQ,74 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png,sha256=eWkLi3BlPYqtdjcao2Qe3f92rTVuRxfyQVx_05y6owk,111 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png,sha256=lSfevwhe5BAbpkMyRzFSK7TOfMvIeGzh1iDy-rlYFhI,90 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png,sha256=RKb60MJ3VsbQtlbdBlyijT6SFrUvvWq3chUiYEKX_RQ,102 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png,sha256=NSIIuQtDmQ3QMplH5vNLrksn3AEcfNvh6J5xDvNGtsY,102 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png,sha256=q9VbFUrWMPt2mPbOwAHkwibwjNeJnuoPoyf8Ny4h5Vo,115 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png,sha256=pBpcpEVLdhffW3vFKvn51XuqFsJMfc_qbVSwRls4FxU,86 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-icons_222222_256x240.png,sha256=WxTFLRlP1BEBaCU4kFcyrl6XY1dXviAqcMkUyXtcx08,3687 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-icons_2e83ff_256x240.png,sha256=V_VNX0tirJl5Jas8jRRAuOhx1_xntVPM1gCAkIvYmu4,3687 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-icons_454545_256x240.png,sha256=c8MSxYv7D-mo5xMuvuZulZCpilAWoEbDqsvuFzatlrg,3687 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-icons_888888_256x240.png,sha256=jvY1dbase4dRuxooM-M2CoafAv4DNHQHwmnNMXg7dTY,3687 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/images/ui-icons_cd0a0a_256x240.png,sha256=nyOt25eVDaw8bkOVglIw7WIXU_cNSmWlM_fzhcyBxy0,3687 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/css/smoothness/jquery-ui-1.8.16.custom.css,sha256=HKURk0bjEhw8pSawLaBpZZAeafqN8UlXNLjK9JgLevs,25635 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/actions.gif,sha256=D8Nct4Hxzzrn_OIXiCCszk4jEtbDvQhKkAEg-HzgukQ,170 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/ajax-loader-small.gif,sha256=9uz_YX7Cun9Vnm9TXK2bcKP5ESBzdTXatNRUimyDV2w,1849 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/arrow_redo.png,sha256=T4arZb3pQUuI331CIQ-WAf5cPrCvmyyYSjrp4N7RMbM,550 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/arrow_right_peppermint.png,sha256=p8cDIKLwCqieqtVbkr34XgO4ZlrKuXRuD0W3hUda9sc,126 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/arrow_right_spearmint.png,sha256=WXL7dHiZnlCP4YRBGiuK1jtc4qSXWUZKxGtTA9prN-Q,126 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/arrow_undo.png,sha256=QSXplBjMGhFGsmsIyeR17ZssxQygVuUurXArYTdan1Q,555 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/bullet_blue.png,sha256=T-RxwwhAZl3qkRCQcF6bGYw4WrbbzmuqMjEy77FflCw,231 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/bullet_star.png,sha256=h3N5XnzKHKqQ6uFjLjGQ_yTOgUCYRRsOccfk-NNk8nE,273 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/bullet_toggle_minus.png,sha256=ViFVH26a_OZyfVxWtjIB6syTSD-iCGiukNYIA7hyvnQ,154 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/bullet_toggle_plus.png,sha256=v75A2SqCbUl65PwRN0LRQwIe3lLheTlsqwgswgrofQI,156 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/calendar.gif,sha256=q-Z8hknKgIM90QfA_Yhr7pYFP_z-SfqAFlWcmD9BSg8,1035 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/collapse.gif,sha256=MyP5gR8eSLuf9gcBivutfeQobC8cRbR-2RHKwmRcH-o,846 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/comment_yellow.gif,sha256=-OP6HChnjFZK_fRX0dgfkVsbuEhimMCq4v080rO_oKs,257 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/down.gif,sha256=q8WFg_gE6WhoeiSIK4_ctlqQzvR1_hB7YWE6p5OBFi8,59 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/drag-handle.png,sha256=n8MkfeAS4U6NANAWysIsy85drVKZnzwNkKQXUVwassk,98 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/editor-helper-bg.gif,sha256=wcK-il-lyA37ACXU-oPyK3dXLO769xL2LhGKZjlb4r0,1164 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/expand.gif,sha256=Y00F-D6szBAt6IhV7-JT1EM89g02Y_dnfta5lMq4NYI,851 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/header-bg.gif,sha256=4UEN2wVeC6M6H_AObzcQyYubZXe7UD8QFcfCaGeCN-A,872 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/header-columns-bg.gif,sha256=Xw82RUqYSpkNGEKVks1-0CeLt6yqMXb4UC0bvp3g7j0,836 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/header-columns-over-bg.gif,sha256=9MCfFz_paCEunw6vVlelFqmYuHj0uYAGUSpVcQId3Ho,823 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/help.png,sha256=OKJ1jMF9IgTMe-TGU3R-Kf-bQCUp89t-JpK144v8498,328 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/info.gif,sha256=YbHWd6h5zrTUtIVn4x0VMz_JRNAl9n0XAI5HcFZtUFc,80 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/listview.gif,sha256=FgqWew2rbxOMakO8Xnv4saMzCyq86Vy0OaOKf1XOSZ4,2380 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/pencil.gif,sha256=CsRU4xSOUs-oDce4pyy7mXKh5p0-214QQ6OM8T4xyNA,914 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/row-over-bg.gif,sha256=RpLS_M4Pgh_XCftdCzMz4HRKbHHTZzeYGBy7BXvBi2I,823 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/sort-asc.gif,sha256=oyCuk-hGo5GOQJGbp-eO-52iCKPTcI81XaOXm90J07M,830 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/sort-asc.png,sha256=yQfeBXgZmV4MvvwwxseHCrl577eWqeZWZeOJ4zWwa-g,104 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/sort-desc.gif,sha256=3Utsn2iM-NKeAb-UpdEMbV0oX3PcLA4SS6IFEnUz4WU,833 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/sort-desc.png,sha256=BswbGoH2XzVv4IYQkGEtUxE5XYiILIZrAIbiY7HtmSY,106 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/stripes.png,sha256=4vHkGIUx6nKfY_2MvPV67V11ZK8caZkB6WbfiP0tLQ4,94 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/tag_red.png,sha256=wwRB6e9lBL9Oy1pGojNdDaFWvevKVDAWZUBc_X_Ml8M,529 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/tick.png,sha256=eaOl41DuLotc6ULIlQ7ThADClKVx5RjT8Wsxwyy37jA,465 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/user_identity.gif,sha256=RPDOLnV7zg_hM2P2Cli-PcFTLqle3ABXBbbwGWxixgY,905 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/images/user_identity_plus.gif,sha256=6gNOx64wEeQcGmcyOau6Hzu2kl2AysfS-JS91m3uoQM,546 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery-1.7.min.js,sha256=_05Jde9AMAT4_o5ZAI23rUf1SxDYTHLrkOco0eyRV84,94020 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery-ui-1.8.16.custom.js,sha256=8vtFEuIQ5GY7-BSLNXUVkT5-3CkJkBRotdy-EsvwrYs,159838 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery.event.drag-2.2.js,sha256=yGrghELeXGHSXqJ6WNt9LN42Vm0JRU6bIoVQhbn_Tk8,12832 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery.event.drop-2.2.js,sha256=R6P3ehPYyyR21Bh0OnOZS8rP13AxE_YcfnVHJ0W8i8s,9420 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.autotooltips.js,sha256=zIsmzaLb3FhagCG84zAScQUv4yKgqnMJ1ynv6nt6te0,2567 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellcopymanager.js,sha256=OSdajrluv7XyKNS12nJ6alcyNgagorm0ATqx7bvi4To,2301 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellrangedecorator.js,sha256=6PQrefZ-o_KXMHGrQu1LZpoC_zwrmt_keiykMYOTXng,1570 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellrangeselector.js,sha256=HYT58V13rJ1M41xPMxPbLCTkFkWJQVblq3y7EIa_r_g,2739 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellselectionmodel.js,sha256=CD7G-3V63m72sJCjyOidUWBDsC3ubQDJXsr3G4oDfoM,4564 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.checkboxselectcolumn.js,sha256=n_P71kYWfImeSAywWusskTKBIQpeIW9s-DyIdKRXqLc,4456 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headerbuttons.css,sha256=wbmtS7c2b1aTelR5DbgRRWcF8TtEGsJLOmMtzpWIGsE,903 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headerbuttons.js,sha256=7pq9aV88FCa-Wl-vRU9QW275DfprqSwnMq5_NeGEIzU,5126 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headermenu.css,sha256=-cVU10fvj8PzpmxbW--1_12XDOpiQF5ta8YfREZdMTY,1036 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headermenu.js,sha256=H1bEvnwsq8e-wp3csQ4N1b-1ICzMijg00nWHInaKugs,7544 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.rowmovemanager.js,sha256=wrEAMabWKXiUAbs6MX74vUa4oP5IQlKbj-Da-6y-09s,3654 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.rowselectionmodel.js,sha256=95bb4SHKrU5GwOr78zYvzeggIIW7WQgVVyPBxDy5GWU,4948 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick-default-theme.css,sha256=jLctqTWQVq39egLLKFBJPpFlRFXa5r_9gjyFLq_ltQY,2261 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.core.js,sha256=A74SUXyU-8RsPIBPXEfB7hkpc-R_aKtZ-gA1VXKPFSM,12990 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.dataview.js,sha256=gEu_r0dSZImHABvwWQpjoEe3M1WumK_6fvVbtafM3iA,33220 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.editors.js,sha256=QIxbJWZIHL9ES3fkb2N_L-_O2zEw12XCZvpWnZPOBdQ,12379 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.formatters.js,sha256=ZDEbeyt6aIi7kEHRnf7vsYyAuzpUpDwIOFfybkTFSOI,1572 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.grid.css,sha256=yLkBh0Xk4BNBWde5NbnQB_Dx3bO89t1Jbm7tInDLvzE,2872 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.grid.js,sha256=77ukobS0Ianm0sIAg6A-2JPl93nIfAqsYwtd2ha8Z3M,108096 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.groupitemmetadataprovider.js,sha256=IY2KvvkkIDVvs8_MfZj4ZEWm57D8_SvGEtMPZR3BLhQ,4659 +ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.remotemodel.js,sha256=9GM9-NY5qIdDSeDA2U9GR8qcHg3kEa7r5sIKiaMw-GM,4276 +ckanext/reclineview/theme/public/vendor/timeline/LICENSE,sha256=LgdH2aOc_2p1lB_SKEwQ1vu53W_MgdSIWMzs6Xv44hg,16430 +ckanext/reclineview/theme/public/vendor/timeline/README,sha256=i9q0MdmCRKGb2eYzhjyIB1bnFl1_SGanfDL9DX10RSg,24 +ckanext/reclineview/theme/public/vendor/timeline/css/loading.gif,sha256=nFAsxHpsvCPKPAGsxKJKD0MUSf1QYqtaQRUshJlmVZM,6909 +ckanext/reclineview/theme/public/vendor/timeline/css/timeline.css,sha256=N4KZHQu5X37XoasFH-D9a-8N-YgzVjWcl-wxjvyQiog,71281 +ckanext/reclineview/theme/public/vendor/timeline/css/timeline.png,sha256=kMh0Ezbho-fJiSRHdUHAbR3sS8JOQTsA4gdAsB4Riuo,14048 +ckanext/reclineview/theme/public/vendor/timeline/css/timeline@2x.png,sha256=GGErnYyo2KpvuBMpwdcYNkvmbPYtdRup0LaTdcjQf_A,36430 +ckanext/reclineview/theme/public/vendor/timeline/js/timeline.js,sha256=VxBgqxPglCHdbDiY6X12ed_gHhdxqAdacg-lYFbQwHA,305847 +ckanext/reclineview/theme/public/vendor/underscore.deferred/0.4.0/underscore.deferred.js,sha256=1sPQM1MDCqg8qxBEqshaALfg4W0Xqvn2EfM_aieYhxI,13034 +ckanext/reclineview/theme/public/vendor/underscore.deferred/0.4.0/underscore.deferred.min.js,sha256=bp0LUKoVeU69hLtHKIkbl4DRpQxv_IlYHEllCJc3CeQ,5832 +ckanext/reclineview/theme/public/vendor/underscore/1.4.4/underscore.js,sha256=Ah_oWEWKIflX254AMEUx0p8OEO2yTcxFJdF_Nm6B1M0,41426 +ckanext/reclineview/theme/public/widget.recordcount.js,sha256=XMZ1TLfq4dVW4BLIPUkizVA-mnQLXH7h_cBi7vpny1Q,701 +ckanext/reclineview/theme/public/widget.recordcount.min.js,sha256=NcDzmTbNjZBgp-vhFbY1SwSw6i-QTz6Y0XN5JPxIcbc,577 +ckanext/reclineview/theme/templates/recline_graph_form.html,sha256=5GvVcsjHeobMJ39JSBtvBMddIIWQPJ7Qil5F5MmARmg,730 +ckanext/reclineview/theme/templates/recline_map_form.html,sha256=AOxG6e8-zVukYtZYr87lb5ZGk5TfSL4Otut1C124njU,1223 +ckanext/reclineview/theme/templates/recline_view.html,sha256=ndPrHyhVEw3QJDUsFIXMt8FE4z4JNTr7QMxCrGY-MHU,775 +ckanext/resourceproxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/resourceproxy/__init__.pyc,, +ckanext/resourceproxy/controller.py,sha256=sJhTJlQzHuJuffz6B2FjeFdU_zsnIHb7PAgk3TOmx6g,3486 +ckanext/resourceproxy/controller.pyc,, +ckanext/resourceproxy/plugin.py,sha256=7Ncf25OTDXBGaCKakwYEZJbm36CBsvf3UrUicyo6edg,2549 +ckanext/resourceproxy/plugin.pyc,, +ckanext/resourceproxy/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/resourceproxy/tests/__init__.pyc,, +ckanext/resourceproxy/tests/test_proxy.py,sha256=LMLQqxdpNxFeFWQ9aGYOSociKprZJtePS_UjmREPx5E,5794 +ckanext/resourceproxy/tests/test_proxy.pyc,, +ckanext/stats/controller.py,sha256=BXwisMgks_lDOXpzEqAXqK5JlLiAn8FBxuKz56EeYc0,1982 +ckanext/stats/controller.pyc,, +ckanext/stats/plugin.py,sha256=6IUpE32bfonfFiAaStA0yaT4OaMBUN5x7QiUnAjfwIE,939 +ckanext/stats/plugin.pyc,, +ckanext/stats/public/.gitignore,sha256=b_E-Djjo16zhC3AvLZX7gekmfRGyieSVwMoWHlkuVKc,21 +ckanext/stats/public/__init__.py,sha256=7jFPzUbP-JA2Kg63ZeSxsP-EPoS_oaBdGSv5WrIWmvU,219 +ckanext/stats/public/__init__.pyc,, +ckanext/stats/public/ckanext/__init__.py,sha256=7jFPzUbP-JA2Kg63ZeSxsP-EPoS_oaBdGSv5WrIWmvU,219 +ckanext/stats/public/ckanext/__init__.pyc,, +ckanext/stats/public/ckanext/stats/__init__.py,sha256=7jFPzUbP-JA2Kg63ZeSxsP-EPoS_oaBdGSv5WrIWmvU,219 +ckanext/stats/public/ckanext/stats/__init__.pyc,, +ckanext/stats/public/ckanext/stats/css/stats.css,sha256=8uEtFAE7Y8y153UDRdl2Y8y7roHBXtEGgweHzvVyWTQ,221 +ckanext/stats/public/ckanext/stats/css/stats.min.css,sha256=9RHW-6h87-74btv-jShfgcYkPx2KfyKEXzEnrXVZoXM,175 +ckanext/stats/public/ckanext/stats/javascript/modules/plot.js,sha256=Muzy9SIirvky3MONvGCtJ8nUsP28b21FU1DuTpsf3OQ,6308 +ckanext/stats/public/ckanext/stats/javascript/modules/plot.min.js,sha256=Vjut4BaiZoMj-xQeg4leUIGDQI4fNeu4K-pdbbKMsqk,1621 +ckanext/stats/public/ckanext/stats/javascript/modules/stats-nav.js,sha256=WjsVs-B_PneX98bW_e14WX6C7T1Xo3B-KH2LeP4beCU,1036 +ckanext/stats/public/ckanext/stats/javascript/modules/stats-nav.min.js,sha256=TIpsr16iY5pSNkF_vwexQzMt4TVxnpQm7IdUdQzW_AY,363 +ckanext/stats/public/ckanext/stats/resource.config,sha256=5uFVjJWQCvdxeqUFc-PXlGWiiBY2Ae7TS3JR7v1Hi6I,201 +ckanext/stats/public/ckanext/stats/test/fixtures/table.html,sha256=pvQsK1JyFe_LayUOYnyiRHrpn0BSao_m6zZfcxzOMpg,699 +ckanext/stats/public/ckanext/stats/test/index.html,sha256=RLmgdrjHMjRh4nKXI9CcBqs2wjv1e4w0L44xzs6WZXE,2199 +ckanext/stats/public/ckanext/stats/test/spec/modules/plot.spec.js,sha256=lXEy055vOnAMeIWfbVx_GHyXO5vqrbn94wTWK6FgrS8,4014 +ckanext/stats/public/ckanext/stats/test/spec/modules/plot.spec.min.js,sha256=4mhkzwg4vMqU3UdxdPsr6ULJCh9Xn1rThzC18zv1KPQ,3161 +ckanext/stats/public/ckanext/stats/test/spec/modules/stats-nav.spec.js,sha256=t0_udHJCEQIGaqaMquvuROrmdMClQWBasqeZFg1TA08,1294 +ckanext/stats/public/ckanext/stats/test/spec/modules/stats-nav.spec.min.js,sha256=pzefEXdSP9LACe-q2Hx5NR4dmyf7y4UNdn-vx-NfmYc,994 +ckanext/stats/public/ckanext/stats/vendor/excanvas.js,sha256=6TbgbRokjoEtwG0-26w8BRD7pTj9DDdzej_icKz2BLw,41784 +ckanext/stats/public/ckanext/stats/vendor/jquery.flot.js,sha256=_FLyyIbAtKdPPkunG3XS8AF2x94UAMdDmbrzR-RjlWQ,106797 +ckanext/stats/stats.py,sha256=6G1GuWrX3krM4ArAat4K4cgxaldCNR5OM8gHU8so-iE,17524 +ckanext/stats/stats.pyc,, +ckanext/stats/templates/ckanext/stats/index.html,sha256=ChUoSmKxyQwO0eNj6smHKEWV2N92qLbBaCF9eJdKQJI,7628 +ckanext/stats/tests/__init__.py,sha256=i00kZNPmTT2abOUtE2pQ4TUjlAuPRDXYuTVqhUq0RF4,403 +ckanext/stats/tests/__init__.pyc,, +ckanext/stats/tests/test_stats_lib.py,sha256=rJveS5VXuFjGN56wq7hj7tDwwCzMJkQ0RhBMaFw5bkw,6281 +ckanext/stats/tests/test_stats_lib.pyc,, +ckanext/stats/tests/test_stats_plugin.py,sha256=nP9tBKU_1l_NkWa_iAX_w-4MzNUK6Hjr5DE2YWsh8Ms,430 +ckanext/stats/tests/test_stats_plugin.pyc,, +ckanext/test_tag_vocab_plugin.py,sha256=sJ2EKFk0lZbSJC3nyxakC1dsV1k7LvgX4orzDw7ya-w,2085 +ckanext/test_tag_vocab_plugin.pyc,, +ckanext/textview/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/textview/__init__.pyc,, +ckanext/textview/plugin.py,sha256=DD5oeNGD5-cKb2_vfyUMH0XHsb9U0nGur4BZFXlIxdg,3385 +ckanext/textview/plugin.pyc,, +ckanext/textview/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/textview/tests/__init__.pyc,, +ckanext/textview/tests/test_view.py,sha256=DrAlXFtyaAHwng23wSb-Qk-4CfuOyCGINBa71iNNMks,4031 +ckanext/textview/tests/test_view.pyc,, +ckanext/textview/theme/public/LICENSE,sha256=X74KkubaFsdaO5BV1vZKKTePlnZYZljmuDzVwpehg5M,1549 +ckanext/textview/theme/public/css/text.css,sha256=IL3ZbCrpAntIzJ2es3ufO8Ea465DKYXNjRSpWB3r_QM,224 +ckanext/textview/theme/public/css/text.min.css,sha256=tp0lxwl-YUgTRap6YFbBvKkElhcb8QfbimxVZKoN7uE,170 +ckanext/textview/theme/public/resource.config,sha256=faVkqp_0CVva0Alr1A68OoFrgoP5ELN3VMA_OcFTDXk,132 +ckanext/textview/theme/public/styles/default.css,sha256=rgq8O9Wjv94ygUoCQMapt6GAzgQp1zPAvc3nXEj7IcI,2141 +ckanext/textview/theme/public/styles/default.min.css,sha256=nHSe-ps5Km79AglxEN-G6NccV8zQvP-Zb4q7eY3Zg8Q,1862 +ckanext/textview/theme/public/styles/github.css,sha256=yOKZQeJoMoZp05wtNejLgD2F431kV2v6sp8qDkpzoLU,1585 +ckanext/textview/theme/public/styles/github.min.css,sha256=O-a9ajSJUJEsNRoULcZzev1SmAuZCFZCMSquR-cC0MQ,1291 +ckanext/textview/theme/public/text_view.js,sha256=XU7UxLn3_VhGCdsiWvwhOi72FuCekj7rtM7C_Y_6hwo,2371 +ckanext/textview/theme/public/text_view.min.js,sha256=-Lz0cWShGZLfyb06_omI-2tcIGVVseBPt6FlfljWhFE,1600 +ckanext/textview/theme/public/vendor/highlight.pack.js,sha256=PTB4ijs0axiPEHkLlbSoAlXvX93WERxO660X9TfPE3Y,8153 +ckanext/textview/theme/templates/text_form.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/textview/theme/templates/text_view.html,sha256=_v7gUOr38tHToNtaBJDKOjROb7_Wt8_m2h6qX7h5FDg,530 +ckanext/webpageview/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/webpageview/__init__.pyc,, +ckanext/webpageview/plugin.py,sha256=rKhwHy50gHvhmezRWyvZaJ9GslvzBwk6MRMO6pmIcks,1234 +ckanext/webpageview/plugin.pyc,, +ckanext/webpageview/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ckanext/webpageview/tests/__init__.pyc,, +ckanext/webpageview/tests/test_view.py,sha256=55fYrEj3xFk_wu8Ua8QRMggrcP81T74KI0JXPMyF9Ww,1286 +ckanext/webpageview/tests/test_view.pyc,, +ckanext/webpageview/theme/templates/webpage_form.html,sha256=FoyN0Y1sQz3sXgzrkYF8q4MVSZlLxX5K494XNKPckaw,274 +ckanext/webpageview/theme/templates/webpage_view.html,sha256=r1jAMBgo9MQx2c4xIwy0DlPY__SlpXokqnVWWtJ1QQQ,198 diff --git a/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/WHEEL b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/WHEEL new file mode 100644 index 00000000..db240751 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.33.4) +Root-Is-Purelib: true +Tag: cp27-none-any + diff --git a/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/entry_points.txt b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/entry_points.txt new file mode 100644 index 00000000..7ceaac62 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/entry_points.txt @@ -0,0 +1,160 @@ +[babel.extractors] +ckan = ckan.lib.extract:extract_ckan + +[ckan.forms] +group = ckan.forms.group:get_group_fieldset +package = ckan.forms.package:get_standard_fieldset +package_group = ckan.forms.group:get_package_group_fieldset +standard = ckan.forms.package:get_standard_fieldset + +[ckan.plugins] +datapusher = ckanext.datapusher.plugin:DatapusherPlugin +datastore = ckanext.datastore.plugin:DatastorePlugin +datatables_view = ckanext.datatablesview.plugin:DataTablesView +example_flask_iblueprint = ckanext.example_flask_iblueprint.plugin:ExampleFlaskIBlueprintPlugin +example_flask_streaming = ckanext.example_flask_streaming.plugin:ExampleFlaskStreamingPlugin +example_iauthfunctions_v1 = ckanext.example_iauthfunctions.plugin_v1:ExampleIAuthFunctionsPlugin +example_iauthfunctions_v2 = ckanext.example_iauthfunctions.plugin_v2:ExampleIAuthFunctionsPlugin +example_iauthfunctions_v3 = ckanext.example_iauthfunctions.plugin_v3:ExampleIAuthFunctionsPlugin +example_iauthfunctions_v4 = ckanext.example_iauthfunctions.plugin_v4:ExampleIAuthFunctionsPlugin +example_iauthfunctions_v5_custom_config_setting = ckanext.example_iauthfunctions.plugin_v5_custom_config_setting:ExampleIAuthFunctionsPlugin +example_iauthfunctions_v6_parent_auth_functions = ckanext.example_iauthfunctions.plugin_v6_parent_auth_functions:ExampleIAuthFunctionsPlugin +example_iconfigurer = ckanext.example_iconfigurer.plugin:ExampleIConfigurerPlugin +example_iconfigurer_v1 = ckanext.example_iconfigurer.plugin_v1:ExampleIConfigurerPlugin +example_iconfigurer_v2 = ckanext.example_iconfigurer.plugin_v2:ExampleIConfigurerPlugin +example_idatasetform = ckanext.example_idatasetform.plugin:ExampleIDatasetFormPlugin +example_idatasetform_v1 = ckanext.example_idatasetform.plugin_v1:ExampleIDatasetFormPlugin +example_idatasetform_v2 = ckanext.example_idatasetform.plugin_v2:ExampleIDatasetFormPlugin +example_idatasetform_v3 = ckanext.example_idatasetform.plugin_v3:ExampleIDatasetFormPlugin +example_idatasetform_v4 = ckanext.example_idatasetform.plugin_v4:ExampleIDatasetFormPlugin +example_idatastorebackend = ckanext.example_idatastorebackend.plugin:ExampleIDatastoreBackendPlugin +example_igroupform = ckanext.example_igroupform.plugin:ExampleIGroupFormPlugin +example_igroupform_default_group_type = ckanext.example_igroupform.plugin:ExampleIGroupFormPlugin_DefaultGroupType +example_igroupform_organization = ckanext.example_igroupform.plugin:ExampleIGroupFormOrganizationPlugin +example_ipermissionlabels = ckanext.example_ipermissionlabels.plugin:ExampleIPermissionLabelsPlugin +example_iresourcecontroller = ckanext.example_iresourcecontroller.plugin:ExampleIResourceControllerPlugin +example_itemplatehelpers = ckanext.example_itemplatehelpers.plugin:ExampleITemplateHelpersPlugin +example_itranslation = ckanext.example_itranslation.plugin:ExampleITranslationPlugin +example_iuploader = ckanext.example_iuploader.plugin:ExampleIUploader +example_ivalidators = ckanext.example_ivalidators.plugin:ExampleIValidatorsPlugin +example_theme_custom_config_setting = ckanext.example_theme_docs.custom_config_setting.plugin:ExampleThemePlugin +example_theme_custom_emails = ckanext.example_theme_docs.custom_emails.plugin:ExampleCustomEmailsPlugin +example_theme_v01_empty_extension = ckanext.example_theme_docs.v01_empty_extension.plugin:ExampleThemePlugin +example_theme_v02_empty_template = ckanext.example_theme_docs.v02_empty_template.plugin:ExampleThemePlugin +example_theme_v03_jinja = ckanext.example_theme_docs.v03_jinja.plugin:ExampleThemePlugin +example_theme_v04_ckan_extends = ckanext.example_theme_docs.v04_ckan_extends.plugin:ExampleThemePlugin +example_theme_v05_block = ckanext.example_theme_docs.v05_block.plugin:ExampleThemePlugin +example_theme_v06_super = ckanext.example_theme_docs.v06_super.plugin:ExampleThemePlugin +example_theme_v07_helper_function = ckanext.example_theme_docs.v07_helper_function.plugin:ExampleThemePlugin +example_theme_v08_custom_helper_function = ckanext.example_theme_docs.v08_custom_helper_function.plugin:ExampleThemePlugin +example_theme_v09_snippet = ckanext.example_theme_docs.v09_snippet.plugin:ExampleThemePlugin +example_theme_v10_custom_snippet = ckanext.example_theme_docs.v10_custom_snippet.plugin:ExampleThemePlugin +example_theme_v11_HTML_and_CSS = ckanext.example_theme_docs.v11_HTML_and_CSS.plugin:ExampleThemePlugin +example_theme_v12_extra_public_dir = ckanext.example_theme_docs.v12_extra_public_dir.plugin:ExampleThemePlugin +example_theme_v13_custom_css = ckanext.example_theme_docs.v13_custom_css.plugin:ExampleThemePlugin +example_theme_v14_more_custom_css = ckanext.example_theme_docs.v14_more_custom_css.plugin:ExampleThemePlugin +example_theme_v15_fanstatic = ckanext.example_theme_docs.v15_fanstatic.plugin:ExampleThemePlugin +example_theme_v16_initialize_a_javascript_module = ckanext.example_theme_docs.v16_initialize_a_javascript_module.plugin:ExampleThemePlugin +example_theme_v17_popover = ckanext.example_theme_docs.v17_popover.plugin:ExampleThemePlugin +example_theme_v18_snippet_api = ckanext.example_theme_docs.v18_snippet_api.plugin:ExampleThemePlugin +example_theme_v19_01_error = ckanext.example_theme_docs.v19_01_error.plugin:ExampleThemePlugin +example_theme_v19_02_error_handling = ckanext.example_theme_docs.v19_02_error_handling.plugin:ExampleThemePlugin +example_theme_v20_pubsub = ckanext.example_theme_docs.v20_pubsub.plugin:ExampleThemePlugin +example_theme_v21_custom_jquery_plugin = ckanext.example_theme_docs.v21_custom_jquery_plugin.plugin:ExampleThemePlugin +image_view = ckanext.imageview.plugin:ImageView +multilingual_dataset = ckanext.multilingual.plugin:MultilingualDataset +multilingual_group = ckanext.multilingual.plugin:MultilingualGroup +multilingual_resource = ckanext.multilingual.plugin:MultilingualResource +multilingual_tag = ckanext.multilingual.plugin:MultilingualTag +organizations = ckanext.organizations.forms:OrganizationForm +organizations_dataset = ckanext.organizations.forms:OrganizationDatasetForm +publisher_dataset_form = ckanext.publisher_form.forms:PublisherDatasetForm +publisher_form = ckanext.publisher_form.forms:PublisherForm +recline_graph = ckanext.reclineview.plugin:ReclineGraphView +recline_graph_view = ckanext.reclineview.plugin:ReclineGraphView +recline_grid = ckanext.reclineview.plugin:ReclineGridView +recline_grid_view = ckanext.reclineview.plugin:ReclineGridView +recline_map = ckanext.reclineview.plugin:ReclineMapView +recline_map_view = ckanext.reclineview.plugin:ReclineMapView +recline_preview = ckanext.reclineview.plugin:ReclineView +recline_view = ckanext.reclineview.plugin:ReclineView +resource_proxy = ckanext.resourceproxy.plugin:ResourceProxy +stats = ckanext.stats.plugin:StatsPlugin +synchronous_search = ckan.lib.search:SynchronousSearchPlugin +test_tag_vocab_plugin = ckanext.test_tag_vocab_plugin:MockVocabTagsPlugin +text_preview = ckanext.textview.plugin:TextView +text_view = ckanext.textview.plugin:TextView +webpage_view = ckanext.webpageview.plugin:WebPageView + +[ckan.search] +solr = ckan.lib.search.solr_backend:SolrSearchBackend +sql = ckan.lib.search.sql:SqlSearchBackend + +[ckan.system_plugins] +domain_object_mods = ckan.model.modification:DomainObjectModificationExtension + +[ckan.test_plugins] +action_plugin = tests.legacy.ckantestplugins:ActionPlugin +auth_plugin = tests.legacy.ckantestplugins:AuthPlugin +authorizer_plugin = tests.legacy.ckantestplugins:AuthorizerPlugin +example_data_store_search_sql_plugin = ckanext.datastore.tests.test_chained_auth_functions:ExampleDataStoreSearchSQLPlugin +example_datastore_deleted_with_count_plugin = ckanext.datastore.tests.test_chained_action:ExampleDataStoreDeletedWithCountPlugin +example_external_provider_plugin = ckanext.datastore.tests.test_chained_auth_functions:ExampleExternalProviderPlugin +mapper_plugin = tests.legacy.ckantestplugins:MapperPlugin +mapper_plugin2 = tests.legacy.ckantestplugins:MapperPlugin2 +routes_plugin = tests.legacy.ckantestplugins:RoutesPlugin +sample_datastore_plugin = ckanext.datastore.tests.sample_datastore_plugin:SampleDataStorePlugin +session_plugin = tests.legacy.ckantestplugins:SessionPlugin +test_datapusher_plugin = ckanext.datapusher.tests.test_interfaces:FakeDataPusherPlugin +test_datastore_view = ckan.tests.lib.test_datapreview:MockDatastoreBasedResourceView +test_feed_plugin = ckan.tests.controllers.test_feed:MockFeedPlugin +test_flash_plugin = ckan.tests.config.test_sessions:FlashMessagePlugin +test_group_plugin = tests.legacy.ckantestplugins:MockGroupControllerPlugin +test_helpers_plugin = ckan.tests.lib.test_helpers:TestHelpersPlugin +test_js_translations_plugin = ckan.tests.lib.test_i18n:TestJSTranslationsPlugin +test_json_resource_preview = tests.legacy.ckantestplugins:JsonMockResourcePreviewExtension +test_observer_plugin = tests.legacy.ckantestplugins:PluginObserverPlugin +test_package_controller_plugin = tests.legacy.ckantestplugins:MockPackageControllerPlugin +test_resource_preview = tests.legacy.ckantestplugins:MockResourcePreviewExtension +test_routing_plugin = ckan.tests.config.test_middleware:MockRoutingPlugin + +[console_scripts] +ckan-admin = bin.ckan_admin:Command + +[nose.plugins.0.10] +main = ckan.ckan_nose_plugin:CkanNose + +[paste.app_factory] +main = ckan.config.middleware:make_app + +[paste.app_install] +main = ckan.config.install:CKANInstaller + +[paste.paster_command] +check-po-files = ckan.i18n.check_po_files:CheckPoFiles +color = ckan.lib.cli:CreateColorSchemeCommand +config-tool = ckan.lib.cli:ConfigToolCommand +create-test-data = ckan.lib.cli:CreateTestDataCommand +datapusher = ckanext.datapusher.cli:DatapusherCommand +dataset = ckan.lib.cli:DatasetCmd +datastore = ckanext.datastore.commands:datastore_group +db = ckan.lib.cli:ManageDb +front-end-build = ckan.lib.cli:FrontEndBuildCommand +jobs = ckan.lib.cli:JobsCommand +less = ckan.lib.cli:LessCommand +minify = ckan.lib.cli:MinifyCommand +notify = ckan.lib.cli:Notification +plugin-info = ckan.lib.cli:PluginInfo +profile = ckan.lib.cli:Profile +ratings = ckan.lib.cli:Ratings +rdf-export = ckan.lib.cli:RDFExport +search-index = ckan.lib.cli:SearchIndexCommand +sysadmin = ckan.lib.cli:Sysadmin +tracking = ckan.lib.cli:Tracking +trans = ckan.lib.cli:TranslationsCommand +user = ckan.lib.cli:UserCmd +views = ckan.lib.cli:ViewsCommand + +[paste.paster_create_template] +ckanext = ckan.pastertemplates:CkanextTemplate + diff --git a/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/namespace_packages.txt b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/namespace_packages.txt new file mode 100644 index 00000000..d25d4034 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/namespace_packages.txt @@ -0,0 +1,2 @@ +ckanext +ckanext.stats diff --git a/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/top_level.txt b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/top_level.txt new file mode 100644 index 00000000..43bab525 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan-2.8.3.dist-info/top_level.txt @@ -0,0 +1,2 @@ +ckan +ckanext diff --git a/venv/lib/python2.7/site-packages/ckan/__init__.py b/venv/lib/python2.7/site-packages/ckan/__init__.py new file mode 100644 index 00000000..64d0bccd --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/__init__.py @@ -0,0 +1,22 @@ +# encoding: utf-8 + +__version__ = '2.8.3' + +__description__ = 'CKAN Software' +__long_description__ = \ +''' +CKAN is the world's leading Open Source data portal platform. + +It powers dozens of Open Data portals around the world, including +data.gov, open.canada.ca and europeandataportal.eu but also regional, +research and community organizations. + +It makes easy to publish, share and find data online and is fully +customizable via extensions and plugins. + +Check https://ckan.org to know more. +''' +__license__ = 'AGPL' + +# The packaging system relies on this import, please do not remove it +import sys; sys.path.insert(0, __path__[0]) diff --git a/venv/lib/python2.7/site-packages/ckan/authz.py b/venv/lib/python2.7/site-packages/ckan/authz.py new file mode 100644 index 00000000..73aa0e6f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/authz.py @@ -0,0 +1,473 @@ +# encoding: utf-8 + +import functools +import sys +import re + +from collections import defaultdict +from logging import getLogger + +from ckan.common import config +from paste.deploy.converters import asbool + +import ckan.plugins as p +import ckan.model as model +from ckan.common import OrderedDict, _, c + +import ckan.lib.maintain as maintain + +log = getLogger(__name__) + + +class AuthFunctions: + ''' This is a private cache used by get_auth_function() and should never be + accessed directly we will create an instance of it and then remove it.''' + _functions = {} + + def clear(self): + ''' clear any stored auth functions. ''' + self._functions.clear() + + def keys(self): + ''' Return a list of known auth functions.''' + if not self._functions: + self._build() + return self._functions.keys() + + def get(self, function): + ''' Return the requested auth function. ''' + if not self._functions: + self._build() + return self._functions.get(function) + + @staticmethod + def _is_chained_auth_function(func): + ''' + Helper function to check if a function is a chained auth function, i.e. + it has been decorated with the chain auth function decorator. + ''' + return getattr(func, 'chained_auth_function', False) + + def _build(self): + ''' Gather the auth functions. + + First get the default ones in the ckan/logic/auth directory Rather than + writing them out in full will use __import__ to load anything from + ckan.auth that looks like it might be an authorisation function''' + + module_root = 'ckan.logic.auth' + + for auth_module_name in ['get', 'create', 'update', 'delete', 'patch']: + module_path = '%s.%s' % (module_root, auth_module_name,) + try: + module = __import__(module_path) + except ImportError: + log.debug('No auth module for action "%s"' % auth_module_name) + continue + + for part in module_path.split('.')[1:]: + module = getattr(module, part) + + for key, v in module.__dict__.items(): + if not key.startswith('_'): + # Whitelist all auth functions defined in + # logic/auth/get.py as not requiring an authorized user, + # as well as ensuring that the rest do. In both cases, do + # nothing if a decorator has already been used to define + # the behaviour + if not hasattr(v, 'auth_allow_anonymous_access'): + if auth_module_name == 'get': + v.auth_allow_anonymous_access = True + else: + v.auth_allow_anonymous_access = False + self._functions[key] = v + + # Then overwrite them with any specific ones in the plugins: + resolved_auth_function_plugins = {} + fetched_auth_functions = {} + chained_auth_functions = defaultdict(list) + for plugin in p.PluginImplementations(p.IAuthFunctions): + for name, auth_function in plugin.get_auth_functions().items(): + if self._is_chained_auth_function(auth_function): + chained_auth_functions[name].append(auth_function) + elif name in resolved_auth_function_plugins: + raise Exception( + 'The auth function %r is already implemented in %r' % ( + name, + resolved_auth_function_plugins[name] + ) + ) + else: + resolved_auth_function_plugins[name] = plugin.name + fetched_auth_functions[name] = auth_function + + for name, func_list in chained_auth_functions.iteritems(): + if (name not in fetched_auth_functions and + name not in self._functions): + raise Exception('The auth %r is not found for chained auth' % ( + name)) + # create the chain of functions in the correct order + for func in reversed(func_list): + if name in fetched_auth_functions: + prev_func = fetched_auth_functions[name] + else: + # fallback to chaining off the builtin auth function + prev_func = self._functions[name] + fetched_auth_functions[name] = ( + functools.partial(func, prev_func)) + + # Use the updated ones in preference to the originals. + self._functions.update(fetched_auth_functions) + +_AuthFunctions = AuthFunctions() +#remove the class +del AuthFunctions + + +def clear_auth_functions_cache(): + _AuthFunctions.clear() + + +def auth_functions_list(): + '''Returns a list of the names of the auth functions available. Currently + this is to allow the Auth Audit to know if an auth function is available + for a given action.''' + return _AuthFunctions.keys() + + +def is_sysadmin(username): + ''' Returns True is username is a sysadmin ''' + user = _get_user(username) + return user and user.sysadmin + + +def _get_user(username): + ''' Try to get the user from c, if possible, and fallback to using the DB ''' + if not username: + return None + # See if we can get the user without touching the DB + try: + if c.userobj and c.userobj.name == username: + return c.userobj + except AttributeError: + # c.userobj not set + pass + except TypeError: + # c is not available + pass + # Get user from the DB + return model.User.get(username) + + +def get_group_or_org_admin_ids(group_id): + if not group_id: + return [] + group_id = model.Group.get(group_id).id + q = model.Session.query(model.Member) \ + .filter(model.Member.group_id == group_id) \ + .filter(model.Member.table_name == 'user') \ + .filter(model.Member.state == 'active') \ + .filter(model.Member.capacity == 'admin') + return [a.table_id for a in q.all()] + + +def is_authorized_boolean(action, context, data_dict=None): + ''' runs the auth function but just returns True if allowed else False + ''' + outcome = is_authorized(action, context, data_dict=data_dict) + return outcome.get('success', False) + + +def is_authorized(action, context, data_dict=None): + if context.get('ignore_auth'): + return {'success': True} + + auth_function = _AuthFunctions.get(action) + if auth_function: + username = context.get('user') + user = _get_user(username) + + if user: + # deleted users are always unauthorized + if user.is_deleted(): + return {'success': False} + # sysadmins can do anything unless the auth_sysadmins_check + # decorator was used in which case they are treated like all other + # users. + elif user.sysadmin: + if not getattr(auth_function, 'auth_sysadmins_check', False): + return {'success': True} + + # If the auth function is flagged as not allowing anonymous access, + # and an existing user object is not provided in the context, deny + # access straight away + if not getattr(auth_function, 'auth_allow_anonymous_access', False) \ + and not context.get('auth_user_obj'): + return { + 'success': False, + 'msg': 'Action {0} requires an authenticated user'.format( + action) + } + + return auth_function(context, data_dict) + else: + raise ValueError(_('Authorization function not found: %s' % action)) + + +# these are the permissions that roles have +ROLE_PERMISSIONS = OrderedDict([ + ('admin', ['admin']), + ('editor', ['read', 'delete_dataset', 'create_dataset', 'update_dataset', 'manage_group']), + ('member', ['read', 'manage_group']), +]) + + +def _trans_role_admin(): + return _('Admin') + + +def _trans_role_editor(): + return _('Editor') + + +def _trans_role_member(): + return _('Member') + + +def trans_role(role): + module = sys.modules[__name__] + return getattr(module, '_trans_role_%s' % role)() + + +def roles_list(): + ''' returns list of roles for forms ''' + roles = [] + for role in ROLE_PERMISSIONS: + roles.append(dict(text=trans_role(role), value=role)) + return roles + + +def roles_trans(): + ''' return dict of roles with translation ''' + roles = {} + for role in ROLE_PERMISSIONS: + roles[role] = trans_role(role) + return roles + + +def get_roles_with_permission(permission): + ''' returns the roles with the permission requested ''' + roles = [] + for role in ROLE_PERMISSIONS: + permissions = ROLE_PERMISSIONS[role] + if permission in permissions or 'admin' in permissions: + roles.append(role) + return roles + + +def has_user_permission_for_group_or_org(group_id, user_name, permission): + ''' Check if the user has the given permissions for the group, allowing for + sysadmin rights and permission cascading down a group hierarchy. + + ''' + if not group_id: + return False + group = model.Group.get(group_id) + if not group: + return False + group_id = group.id + + # Sys admins can do anything + if is_sysadmin(user_name): + return True + + user_id = get_user_id_for_username(user_name, allow_none=True) + if not user_id: + return False + if _has_user_permission_for_groups(user_id, permission, [group_id]): + return True + # Handle when permissions cascade. Check the user's roles on groups higher + # in the group hierarchy for permission. + for capacity in check_config_permission('roles_that_cascade_to_sub_groups'): + parent_groups = group.get_parent_group_hierarchy(type=group.type) + group_ids = [group_.id for group_ in parent_groups] + if _has_user_permission_for_groups(user_id, permission, group_ids, + capacity=capacity): + return True + return False + + +def _has_user_permission_for_groups(user_id, permission, group_ids, + capacity=None): + ''' Check if the user has the given permissions for the particular + group (ignoring permissions cascading in a group hierarchy). + Can also be filtered by a particular capacity. + ''' + if not group_ids: + return False + # get any roles the user has for the group + q = model.Session.query(model.Member) \ + .filter(model.Member.group_id.in_(group_ids)) \ + .filter(model.Member.table_name == 'user') \ + .filter(model.Member.state == 'active') \ + .filter(model.Member.table_id == user_id) + if capacity: + q = q.filter(model.Member.capacity == capacity) + # see if any role has the required permission + # admin permission allows anything for the group + for row in q.all(): + perms = ROLE_PERMISSIONS.get(row.capacity, []) + if 'admin' in perms or permission in perms: + return True + return False + + +def users_role_for_group_or_org(group_id, user_name): + ''' Returns the user's role for the group. (Ignores privileges that cascade + in a group hierarchy.) + + ''' + if not group_id: + return None + group_id = model.Group.get(group_id).id + + user_id = get_user_id_for_username(user_name, allow_none=True) + if not user_id: + return None + # get any roles the user has for the group + q = model.Session.query(model.Member) \ + .filter(model.Member.group_id == group_id) \ + .filter(model.Member.table_name == 'user') \ + .filter(model.Member.state == 'active') \ + .filter(model.Member.table_id == user_id) + # return the first role we find + for row in q.all(): + return row.capacity + return None + + +def has_user_permission_for_some_org(user_name, permission): + ''' Check if the user has the given permission for any organization. ''' + user_id = get_user_id_for_username(user_name, allow_none=True) + if not user_id: + return False + roles = get_roles_with_permission(permission) + + if not roles: + return False + # get any groups the user has with the needed role + q = model.Session.query(model.Member) \ + .filter(model.Member.table_name == 'user') \ + .filter(model.Member.state == 'active') \ + .filter(model.Member.capacity.in_(roles)) \ + .filter(model.Member.table_id == user_id) + group_ids = [] + for row in q.all(): + group_ids.append(row.group_id) + # if not in any groups has no permissions + if not group_ids: + return False + + # see if any of the groups are orgs + q = model.Session.query(model.Group) \ + .filter(model.Group.is_organization == True) \ + .filter(model.Group.state == 'active') \ + .filter(model.Group.id.in_(group_ids)) + + return bool(q.count()) + + +def get_user_id_for_username(user_name, allow_none=False): + ''' Helper function to get user id ''' + # first check if we have the user object already and get from there + try: + if c.userobj and c.userobj.name == user_name: + return c.userobj.id + except TypeError: + # c is not available + pass + user = model.User.get(user_name) + if user: + return user.id + if allow_none: + return None + raise Exception('Not logged in user') + + +CONFIG_PERMISSIONS_DEFAULTS = { + # permission and default + # these are prefixed with ckan.auth. in config to override + 'anon_create_dataset': False, + 'create_dataset_if_not_in_organization': True, + 'create_unowned_dataset': True, + 'user_create_groups': True, + 'user_create_organizations': True, + 'user_delete_groups': True, + 'user_delete_organizations': True, + 'create_user_via_api': False, + 'create_user_via_web': True, + 'roles_that_cascade_to_sub_groups': 'admin', +} + + +def check_config_permission(permission): + '''Returns the configuration value for the provided permission + + Permission is a string indentifying the auth permission (eg + `anon_create_dataset`), optionally prefixed with `ckan.auth.`. + + The possible values for `permission` are the keys of + CONFIG_PERMISSIONS_DEFAULTS. These can be overriden in the config file + by prefixing them with `ckan.auth.`. + + Returns the permission value, generally True or False, except on + `roles_that_cascade_to_sub_groups` which is a list of strings. + + ''' + + key = permission.replace('ckan.auth.', '') + + if key not in CONFIG_PERMISSIONS_DEFAULTS: + return False + + default_value = CONFIG_PERMISSIONS_DEFAULTS.get(key) + + config_key = 'ckan.auth.' + key + + value = config.get(config_key, default_value) + + if key == 'roles_that_cascade_to_sub_groups': + # This permission is set as a list of strings (space separated) + value = value.split() if value else [] + else: + value = asbool(value) + + return value + + +@maintain.deprecated('Use auth_is_loggedin_user instead') +def auth_is_registered_user(): + ''' + This function is deprecated, please use the auth_is_loggedin_user instead + ''' + return auth_is_loggedin_user() + +def auth_is_loggedin_user(): + ''' Do we have a logged in user ''' + try: + context_user = c.user + except TypeError: + context_user = None + return bool(context_user) + +def auth_is_anon_user(context): + ''' Is this an anonymous user? + eg Not logged in if a web request and not user defined in context + if logic functions called directly + + See ckan/lib/base.py:232 for pylons context object logic + ''' + context_user = context.get('user') + is_anon_user = not bool(context_user) + + return is_anon_user diff --git a/venv/lib/python2.7/site-packages/ckan/ckan_nose_plugin.py b/venv/lib/python2.7/site-packages/ckan/ckan_nose_plugin.py new file mode 100644 index 00000000..d45120fb --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/ckan_nose_plugin.py @@ -0,0 +1,133 @@ +# encoding: utf-8 + +from nose.plugins import Plugin +from inspect import isclass +import hashlib +import os +import sys +import re +import pkg_resources +from paste.deploy import loadapp +from ckan.common import config +import unittest +import time + +class CkanNose(Plugin): + settings = None + + def startContext(self, ctx): + # import needs to be here or setup happens too early + import ckan.model as model + + if 'legacy' not in repr(ctx): + # We don't want to do the stuff below for new-style tests. + if not CkanNose.settings.reset_database: + model.repo.tables_created_and_initialised = True + return + + if isclass(ctx): + if hasattr(ctx, "no_db") and ctx.no_db: + return + if (not CkanNose.settings.reset_database + and not CkanNose.settings.ckan_migration): + model.Session.close_all() + model.repo.tables_created_and_initialised = True + model.repo.rebuild_db() + self.is_first_test = False + elif self.is_first_test or CkanNose.settings.ckan_migration: + model.Session.close_all() + model.repo.clean_db() + self.is_first_test = False + if CkanNose.settings.ckan_migration: + model.Session.close_all() + model.repo.upgrade_db() + + ## This is to make sure the configuration is run again. + ## Plugins use configure to make their own tables and they + ## may need to be recreated to make tests work. + from ckan.plugins import PluginImplementations + from ckan.plugins.interfaces import IConfigurable + for plugin in PluginImplementations(IConfigurable): + plugin.configure(config) + + # init_db is run at the start of every class because + # when you use an in-memory sqlite db, it appears that + # the db is destroyed after every test when you Session.Remove(). + model.repo.init_db() + + def options(self, parser, env): + parser.add_option( + '--ckan', + action='store_true', + dest='is_ckan', + help='Always set this when testing CKAN.') + parser.add_option( + '--ckan-migration', + action='store_true', + dest='ckan_migration', + help='set this when wanting to test migrations') + parser.add_option( + '--docstrings', + action='store_true', + dest='docstrings', + help='set this to display test docstrings instead of module names') + parser.add_option( + '--segments', + dest='segments', + help='A string containing a hex digits that represent which of' + 'the 16 test segments to run. i.e 15af will run segments 1,5,a,f') + parser.add_option( + '--reset-db', + action='store_true', + dest='reset_database', + help='drop database and reinitialize before tests are run') + + def wantClass(self, cls): + if self.segments and str(hashlib.md5( + cls.__name__).hexdigest())[0] not in self.segments: + return False + + def wantFunction(self, fn): + if self.segments and hashlib.md5( + fn.__name__).hexdigest()[0] not in self.segments: + return False + + def finalize(self, report): + if self.segments: + print('Segments: %s' % self.segments) + + def configure(self, settings, config): + CkanNose.settings = settings + if settings.is_ckan: + self.enabled = True + self.is_first_test = True + self.segments = settings.segments + + def describeTest(self, test): + if not CkanNose.settings.docstrings: + # display module name instead of docstring + return False + + def startTest(self, test): + """ + startTest: start timing. + """ +## self._started = time.time() + + def stopTest(self, test): + """ + stopTest: stop timing, canonicalize the test name, and save + the run time. + """ +## runtime = time.time() - self._started +## +## # CTB: HACK! +## f = open('times.txt', 'a') +## +## testname = str(test) +## #if ' ' in testname: +## # testname = testname.split()[1] +## +## f.write('%s,%s\n' % (testname, str(runtime))) +## +## f.close() diff --git a/venv/lib/python2.7/site-packages/ckan/common.py b/venv/lib/python2.7/site-packages/ckan/common.py new file mode 100644 index 00000000..7341fa25 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/common.py @@ -0,0 +1,208 @@ +# encoding: utf-8 + +# This file contains commonly used parts of external libraries. The idea is +# to help in removing helpers from being used as a dependency by many files +# but at the same time making it easy to change for example the json lib +# used. +# +# NOTE: This file is specificaly created for +# from ckan.common import x, y, z to be allowed + +from collections import MutableMapping + +import flask +import pylons + +from werkzeug.local import Local, LocalProxy + +from flask_babel import (gettext as flask_ugettext, + ngettext as flask_ungettext) +from pylons.i18n import (ugettext as pylons_ugettext, + ungettext as pylons_ungettext) + +from pylons import response + +import simplejson as json + +try: + from collections import OrderedDict # from python 2.7 +except ImportError: + from sqlalchemy.util import OrderedDict + + +def is_flask_request(): + u''' + A centralized way to determine whether we are in the context of a + request being served by Flask or Pylons + ''' + try: + pylons.request.environ + pylons_request_available = True + except TypeError: + pylons_request_available = False + + return (flask.request and + (flask.request.environ.get(u'ckan.app') == u'flask_app' or + not pylons_request_available)) + + +def streaming_response( + data, mimetype=u'application/octet-stream', with_context=False): + iter_data = iter(data) + if is_flask_request(): + # Removal of context variables for pylon's app is prevented + # inside `pylons_app.py`. It would be better to decide on the fly + # whether we need to preserve context, but it won't affect performance + # in any visible way and we are going to get rid of pylons anyway. + # Flask allows to do this in easy way. + if with_context: + iter_data = flask.stream_with_context(iter_data) + resp = flask.Response(iter_data, mimetype=mimetype) + else: + response.app_iter = iter_data + resp = response.headers['Content-type'] = mimetype + return resp + + +def ugettext(*args, **kwargs): + if is_flask_request(): + return flask_ugettext(*args, **kwargs) + else: + return pylons_ugettext(*args, **kwargs) + + +_ = ugettext + + +def ungettext(*args, **kwargs): + if is_flask_request(): + return flask_ungettext(*args, **kwargs) + else: + return pylons_ungettext(*args, **kwargs) + + +class CKANConfig(MutableMapping): + u'''Main CKAN configuration object + + This is a dict-like object that also proxies any changes to the + Flask and Pylons configuration objects. + + The actual `config` instance in this module is initialized in the + `load_environment` method with the values of the ini file or env vars. + + ''' + + def __init__(self, *args, **kwargs): + self.store = dict() + self.update(dict(*args, **kwargs)) + + def __getitem__(self, key): + return self.store[key] + + def __iter__(self): + return iter(self.store) + + def __len__(self): + return len(self.store) + + def __repr__(self): + return self.store.__repr__() + + def copy(self): + return self.store.copy() + + def clear(self): + self.store.clear() + + try: + flask.current_app.config.clear() + except RuntimeError: + pass + try: + pylons.config.clear() + # Pylons set this default itself + pylons.config[u'lang'] = None + except TypeError: + pass + + def __setitem__(self, key, value): + self.store[key] = value + try: + flask.current_app.config[key] = value + except RuntimeError: + pass + try: + pylons.config[key] = value + except TypeError: + pass + + def __delitem__(self, key): + del self.store[key] + try: + del flask.current_app.config[key] + except RuntimeError: + pass + try: + del pylons.config[key] + except TypeError: + pass + + +def _get_request(): + if is_flask_request(): + return flask.request + else: + return pylons.request + + +class CKANRequest(LocalProxy): + u'''Common request object + + This is just a wrapper around LocalProxy so we can handle some special + cases for backwards compatibility. + + LocalProxy will forward to Flask or Pylons own request objects depending + on the output of `_get_request` (which essentially calls + `is_flask_request`) and at the same time provide all objects methods to be + able to interact with them transparently. + ''' + + @property + def params(self): + u''' Special case as Pylons' request.params is used all over the place. + All new code meant to be run just in Flask (eg views) should always + use request.args + ''' + try: + return super(CKANRequest, self).params + except AttributeError: + return self.args + + +def _get_c(): + if is_flask_request(): + return flask.g + else: + return pylons.c + + +def _get_session(): + if is_flask_request(): + return flask.session + else: + return pylons.session + + +local = Local() + +# This a proxy to the bounded config object +local(u'config') + +# Thread-local safe objects +config = local.config = CKANConfig() + +# Proxies to already thread-local safe objects +request = CKANRequest(_get_request) +# Provide a `c` alias for `g` for backwards compatibility +g = c = LocalProxy(_get_c) +session = LocalProxy(_get_session) diff --git a/venv/lib/python2.7/site-packages/ckan/config/__init__.py b/venv/lib/python2.7/site-packages/ckan/config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/venv/lib/python2.7/site-packages/ckan/config/deployment.ini_tmpl b/venv/lib/python2.7/site-packages/ckan/config/deployment.ini_tmpl new file mode 100644 index 00000000..aaa3bf26 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/config/deployment.ini_tmpl @@ -0,0 +1,228 @@ +# +# CKAN - Pylons configuration +# +# These are some of the configuration options available for your CKAN +# instance. Check the documentation in 'doc/configuration.rst' or at the +# following URL for a description of what they do and the full list of +# available options: +# +# http://docs.ckan.org/en/latest/maintaining/configuration.html +# +# The %(here)s variable will be replaced with the parent directory of this file +# + +[DEFAULT] + +# WARNING: *THIS SETTING MUST BE SET TO FALSE ON A PRODUCTION ENVIRONMENT* +debug = false + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 5000 + +[app:main] +use = egg:ckan +full_stack = true +cache_dir = /tmp/%(ckan.site_id)s/ +beaker.session.key = ckan + +# This is the secret token that the beaker library uses to hash the cookie sent +# to the client. `paster make-config` generates a unique value for this each +# time it generates a config file. +beaker.session.secret = ${app_instance_secret} + +# `paster make-config` generates a unique value for this each time it generates +# a config file. +app_instance_uuid = ${app_instance_uuid} + +# repoze.who config +who.config_file = %(here)s/who.ini +who.log_level = warning +who.log_file = %(cache_dir)s/who_log.ini +# Session timeout (user logged out after period of inactivity, in seconds). +# Inactive by default, so the session doesn't expire. +# who.timeout = 86400 + +## Database Settings +sqlalchemy.url = postgresql://ckan_default:pass@localhost/ckan_default + +#ckan.datastore.write_url = postgresql://ckan_default:pass@localhost/datastore_default +#ckan.datastore.read_url = postgresql://datastore_default:pass@localhost/datastore_default + +# PostgreSQL' full-text search parameters +ckan.datastore.default_fts_lang = english +ckan.datastore.default_fts_index_method = gist + + +## Site Settings + +ckan.site_url = +#ckan.use_pylons_response_cleanup_middleware = true + +## Authorization Settings + +ckan.auth.anon_create_dataset = false +ckan.auth.create_unowned_dataset = false +ckan.auth.create_dataset_if_not_in_organization = false +ckan.auth.user_create_groups = false +ckan.auth.user_create_organizations = false +ckan.auth.user_delete_groups = true +ckan.auth.user_delete_organizations = true +ckan.auth.create_user_via_api = false +ckan.auth.create_user_via_web = true +ckan.auth.roles_that_cascade_to_sub_groups = admin + + +## Search Settings + +ckan.site_id = default +#solr_url = http://127.0.0.1:8983/solr + + +## Redis Settings + +# URL to your Redis instance, including the database to be used. +#ckan.redis.url = redis://localhost:6379/0 + + +## CORS Settings + +# If cors.origin_allow_all is true, all origins are allowed. +# If false, the cors.origin_whitelist is used. +# ckan.cors.origin_allow_all = true +# cors.origin_whitelist is a space separated list of allowed domains. +# ckan.cors.origin_whitelist = http://example1.com http://example2.com + + +## Plugins Settings + +# Note: Add ``datastore`` to enable the CKAN DataStore +# Add ``datapusher`` to enable DataPusher +# Add ``resource_proxy`` to enable resorce proxying and get around the +# same origin policy +ckan.plugins = stats text_view image_view recline_view + +# Define which views should be created by default +# (plugins must be loaded in ckan.plugins) +ckan.views.default_views = image_view text_view recline_view + +# Customize which text formats the text_view plugin will show +#ckan.preview.json_formats = json +#ckan.preview.xml_formats = xml rdf rdf+xml owl+xml atom rss +#ckan.preview.text_formats = text plain text/plain + +# Customize which image formats the image_view plugin will show +#ckan.preview.image_formats = png jpeg jpg gif + +## Front-End Settings + +# Uncomment following configuration to enable using of Bootstrap 2 +#ckan.base_public_folder = public-bs2 +#ckan.base_templates_folder = templates-bs2 + +ckan.site_title = CKAN +ckan.site_logo = /base/images/ckan-logo.png +ckan.site_description = +ckan.favicon = /base/images/ckan.ico +ckan.gravatar_default = identicon +ckan.preview.direct = png jpg gif +ckan.preview.loadable = html htm rdf+xml owl+xml xml n3 n-triples turtle plain atom csv tsv rss txt json +ckan.display_timezone = server + +# package_hide_extras = for_search_index_only +#package_edit_return_url = http://another.frontend/dataset/ +#package_new_return_url = http://another.frontend/dataset/ +#ckan.recaptcha.publickey = +#ckan.recaptcha.privatekey = +#licenses_group_url = http://licenses.opendefinition.org/licenses/groups/ckan.json +# ckan.template_footer_end = + + +## Internationalisation Settings +ckan.locale_default = en +ckan.locale_order = en pt_BR ja it cs_CZ ca es fr el sv sr sr@latin no sk fi ru de pl nl bg ko_KR hu sa sl lv +ckan.locales_offered = +ckan.locales_filtered_out = en_GB + +## Feeds Settings + +ckan.feeds.authority_name = +ckan.feeds.date = +ckan.feeds.author_name = +ckan.feeds.author_link = + +## Storage Settings + +#ckan.storage_path = /var/lib/ckan +#ckan.max_resource_size = 10 +#ckan.max_image_size = 2 + +## Datapusher settings + +# Make sure you have set up the DataStore + +#ckan.datapusher.formats = csv xls xlsx tsv application/csv application/vnd.ms-excel application/vnd.openxmlformats-officedocument.spreadsheetml.sheet +#ckan.datapusher.url = http://127.0.0.1:8800/ +#ckan.datapusher.assume_task_stale_after = 3600 + +# Resource Proxy settings +# Preview size limit, default: 1MB +#ckan.resource_proxy.max_file_size = 1048576 +# Size of chunks to read/write. +#ckan.resource_proxy.chunk_size = 4096 + +## Activity Streams Settings + +#ckan.activity_streams_enabled = true +#ckan.activity_list_limit = 31 +#ckan.activity_streams_email_notifications = true +#ckan.email_notifications_since = 2 days +ckan.hide_activity_from_users = %(ckan.site_id)s + + +## Email settings + +#email_to = errors@example.com +#error_email_from = ckan-errors@example.com +#smtp.server = localhost +#smtp.starttls = False +#smtp.user = username@example.com +#smtp.password = your_password +#smtp.mail_from = + + +## Logging configuration +[loggers] +keys = root, ckan, ckanext + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console + +[logger_ckan] +level = INFO +handlers = console +qualname = ckan +propagate = 0 + +[logger_ckanext] +level = DEBUG +handlers = console +qualname = ckanext +propagate = 0 + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s diff --git a/venv/lib/python2.7/site-packages/ckan/config/environment.py b/venv/lib/python2.7/site-packages/ckan/config/environment.py new file mode 100644 index 00000000..b13c290e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/config/environment.py @@ -0,0 +1,318 @@ +# encoding: utf-8 + +'''CKAN environment configuration''' +import json +import os +import logging +import warnings +from urlparse import urlparse +import pytz + +import sqlalchemy +from pylons import config as pylons_config +import formencode + +import ckan.config.routing as routing +import ckan.model as model +import ckan.plugins as p +import ckan.lib.helpers as helpers +import ckan.lib.app_globals as app_globals +from ckan.lib.redis import is_redis_available +import ckan.lib.render as render +import ckan.lib.search as search +import ckan.logic as logic +import ckan.authz as authz +import ckan.lib.jinja_extensions as jinja_extensions +from ckan.lib.i18n import build_js_translations + +from ckan.common import _, ungettext, config +from ckan.exceptions import CkanConfigurationException + +log = logging.getLogger(__name__) + + +# Suppress benign warning 'Unbuilt egg for setuptools' +warnings.simplefilter('ignore', UserWarning) + + +def load_environment(global_conf, app_conf): + """ + Configure the Pylons environment via the ``pylons.config`` object. This + code should only need to be run once. + """ + # this must be run at a time when the env is semi-setup, thus inlined here. + # Required by the deliverance plugin and iATI + from pylons.wsgiapp import PylonsApp + import pkg_resources + find_controller_generic = getattr( + PylonsApp.find_controller, + '_old_find_controller', + PylonsApp.find_controller) + + # This is from pylons 1.0 source, will monkey-patch into 0.9.7 + def find_controller(self, controller): + if controller in self.controller_classes: + return self.controller_classes[controller] + # Check to see if its a dotted name + if '.' in controller or ':' in controller: + ep = pkg_resources.EntryPoint.parse('x={0}'.format(controller)) + + if hasattr(ep, 'resolve'): + # setuptools >= 10.2 + mycontroller = ep.resolve() + else: + # setuptools >= 11.3 + mycontroller = ep.load(False) + + self.controller_classes[controller] = mycontroller + return mycontroller + return find_controller_generic(self, controller) + find_controller._old_find_controller = find_controller_generic + PylonsApp.find_controller = find_controller + + os.environ['CKAN_CONFIG'] = global_conf['__file__'] + + # Pylons paths + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + valid_base_public_folder_names = ['public', 'public-bs2'] + static_files = app_conf.get('ckan.base_public_folder', 'public') + app_conf['ckan.base_public_folder'] = static_files + + if static_files not in valid_base_public_folder_names: + raise CkanConfigurationException( + 'You provided an invalid value for ckan.base_public_folder. ' + 'Possible values are: "public" and "public-bs2".' + ) + + log.info('Loading static files from %s' % static_files) + paths = dict(root=root, + controllers=os.path.join(root, 'controllers'), + static_files=os.path.join(root, static_files), + templates=[]) + + # Initialize main CKAN config object + config.update(global_conf) + config.update(app_conf) + + # Initialize Pylons own config object + pylons_config.init_app(global_conf, app_conf, package='ckan', paths=paths) + + # Update the main CKAN config object with the Pylons specific stuff, as it + # quite hard to keep them separated. This should be removed once Pylons + # support is dropped + config.update(pylons_config) + + # Setup the SQLAlchemy database engine + # Suppress a couple of sqlalchemy warnings + msgs = ['^Unicode type received non-unicode bind param value', + "^Did not recognize type 'BIGINT' of column 'size'", + "^Did not recognize type 'tsvector' of column 'search_vector'" + ] + for msg in msgs: + warnings.filterwarnings('ignore', msg, sqlalchemy.exc.SAWarning) + + # load all CKAN plugins + p.load_all() + + # Check Redis availability + if not is_redis_available(): + log.critical('Could not connect to Redis.') + + app_globals.reset() + + # issue #3260: remove idle transaction + # Session that was used for getting all config params nor committed, + # neither removed and we have idle connection as result + model.Session.commit() + + # Build JavaScript translations. Must be done after plugins have + # been loaded. + build_js_translations() + + +# A mapping of config settings that can be overridden by env vars. +# Note: Do not remove the following lines, they are used in the docs +# Start CONFIG_FROM_ENV_VARS +CONFIG_FROM_ENV_VARS = { + 'sqlalchemy.url': 'CKAN_SQLALCHEMY_URL', + 'ckan.datastore.write_url': 'CKAN_DATASTORE_WRITE_URL', + 'ckan.datastore.read_url': 'CKAN_DATASTORE_READ_URL', + 'ckan.redis.url': 'CKAN_REDIS_URL', + 'solr_url': 'CKAN_SOLR_URL', + 'solr_user': 'CKAN_SOLR_USER', + 'solr_password': 'CKAN_SOLR_PASSWORD', + 'ckan.site_id': 'CKAN_SITE_ID', + 'ckan.site_url': 'CKAN_SITE_URL', + 'ckan.storage_path': 'CKAN_STORAGE_PATH', + 'ckan.datapusher.url': 'CKAN_DATAPUSHER_URL', + 'smtp.server': 'CKAN_SMTP_SERVER', + 'smtp.starttls': 'CKAN_SMTP_STARTTLS', + 'smtp.user': 'CKAN_SMTP_USER', + 'smtp.password': 'CKAN_SMTP_PASSWORD', + 'smtp.mail_from': 'CKAN_SMTP_MAIL_FROM', + 'ckan.max_resource_size': 'CKAN_MAX_UPLOAD_SIZE_MB' +} +# End CONFIG_FROM_ENV_VARS + + +def update_config(): + ''' This code needs to be run when the config is changed to take those + changes into account. It is called whenever a plugin is loaded as the + plugin might have changed the config values (for instance it might + change ckan.site_url) ''' + + for plugin in p.PluginImplementations(p.IConfigurer): + # must do update in place as this does not work: + # config = plugin.update_config(config) + plugin.update_config(config) + + # Set whitelisted env vars on config object + # This is set up before globals are initialized + + ckan_db = os.environ.get('CKAN_DB', None) + if ckan_db: + msg = 'Setting CKAN_DB as an env var is deprecated and will be' \ + ' removed in a future release. Use CKAN_SQLALCHEMY_URL instead.' + log.warn(msg) + config['sqlalchemy.url'] = ckan_db + + for option in CONFIG_FROM_ENV_VARS: + from_env = os.environ.get(CONFIG_FROM_ENV_VARS[option], None) + if from_env: + config[option] = from_env + + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + site_url = config.get('ckan.site_url', '') + if not site_url: + raise RuntimeError( + 'ckan.site_url is not configured and it must have a value.' + ' Please amend your .ini file.') + if not site_url.lower().startswith('http'): + raise RuntimeError( + 'ckan.site_url should be a full URL, including the schema ' + '(http or https)') + + display_timezone = config.get('ckan.display_timezone', '') + if (display_timezone and + display_timezone != 'server' and + display_timezone not in pytz.all_timezones): + raise CkanConfigurationException( + "ckan.display_timezone is not 'server' or a valid timezone" + ) + + # Remove backslash from site_url if present + config['ckan.site_url'] = config['ckan.site_url'].rstrip('/') + + ckan_host = config['ckan.host'] = urlparse(site_url).netloc + if config.get('ckan.site_id') is None: + if ':' in ckan_host: + ckan_host, port = ckan_host.split(':') + assert ckan_host, 'You need to configure ckan.site_url or ' \ + 'ckan.site_id for SOLR search-index rebuild to work.' + config['ckan.site_id'] = ckan_host + + # ensure that a favicon has been set + favicon = config.get('ckan.favicon', '/base/images/ckan.ico') + config['ckan.favicon'] = favicon + + # Init SOLR settings and check if the schema is compatible + # from ckan.lib.search import SolrSettings, check_solr_schema_version + + # lib.search is imported here as we need the config enabled and parsed + search.SolrSettings.init(config.get('solr_url'), + config.get('solr_user'), + config.get('solr_password')) + search.check_solr_schema_version() + + routes_map = routing.make_map() + config['routes.map'] = routes_map + # The RoutesMiddleware needs its mapper updating if it exists + if 'routes.middleware' in config: + config['routes.middleware'].mapper = routes_map + # routes.named_routes is a CKAN thing + config['routes.named_routes'] = routing.named_routes + config['pylons.app_globals'] = app_globals.app_globals + + # initialise the globals + app_globals.app_globals._init() + + helpers.load_plugin_helpers() + config['pylons.h'] = helpers.helper_functions + + # Templates and CSS loading from configuration + valid_base_templates_folder_names = ['templates', 'templates-bs2'] + templates = config.get('ckan.base_templates_folder', 'templates') + config['ckan.base_templates_folder'] = templates + + if templates not in valid_base_templates_folder_names: + raise CkanConfigurationException( + 'You provided an invalid value for ckan.base_templates_folder. ' + 'Possible values are: "templates" and "templates-bs2".' + ) + + jinja2_templates_path = os.path.join(root, templates) + log.info('Loading templates from %s' % jinja2_templates_path) + template_paths = [jinja2_templates_path] + + extra_template_paths = config.get('extra_template_paths', '') + if extra_template_paths: + # must be first for them to override defaults + template_paths = extra_template_paths.split(',') + template_paths + config['computed_template_paths'] = template_paths + + # Set the default language for validation messages from formencode + # to what is set as the default locale in the config + default_lang = config.get('ckan.locale_default', 'en') + formencode.api.set_stdtranslation(domain="FormEncode", + languages=[default_lang]) + + # Markdown ignores the logger config, so to get rid of excessive + # markdown debug messages in the log, set it to the level of the + # root logger. + logging.getLogger("MARKDOWN").setLevel(logging.getLogger().level) + + # Create Jinja2 environment + env = jinja_extensions.Environment( + **jinja_extensions.get_jinja_env_options()) + env.install_gettext_callables(_, ungettext, newstyle=True) + # custom filters + env.filters['empty_and_escape'] = jinja_extensions.empty_and_escape + config['pylons.app_globals'].jinja_env = env + + # CONFIGURATION OPTIONS HERE (note: all config options will override + # any Pylons config options) + + # Initialize SQLAlchemy + engine = sqlalchemy.engine_from_config(config, client_encoding='utf8') + model.init_model(engine) + + for plugin in p.PluginImplementations(p.IConfigurable): + plugin.configure(config) + + # reset the template cache - we do this here so that when we load the + # environment it is clean + render.reset_template_info_cache() + + # clear other caches + logic.clear_actions_cache() + logic.clear_validators_cache() + authz.clear_auth_functions_cache() + + # Here we create the site user if they are not already in the database + try: + logic.get_action('get_site_user')({'ignore_auth': True}, None) + except (sqlalchemy.exc.ProgrammingError, sqlalchemy.exc.OperationalError): + # (ProgrammingError for Postgres, OperationalError for SQLite) + # The database is not initialised. This is a bit dirty. This occurs + # when running tests. + pass + except sqlalchemy.exc.InternalError: + # The database is not initialised. Travis hits this + pass + + # Close current session and open database connections to ensure a clean + # clean environment even if an error occurs later on + model.Session.remove() + model.Session.bind.dispose() diff --git a/venv/lib/python2.7/site-packages/ckan/config/install.py b/venv/lib/python2.7/site-packages/ckan/config/install.py new file mode 100644 index 00000000..d6cb33c9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/config/install.py @@ -0,0 +1,22 @@ +# encoding: utf-8 + +import re + +from pylons.util import PylonsInstaller + +import ckan + + +class CKANInstaller(PylonsInstaller): + + def config_content(self, command, vars): + ckan_version = ckan.__version__ + ckan_base_version = re.sub('[^0-9\.]', '', ckan_version) + if ckan_base_version == ckan_version: + ckan_doc_version = 'ckan-{0}'.format(ckan_version) + else: + ckan_doc_version = 'latest' + + vars.setdefault('doc_version', ckan_doc_version) + + return super(CKANInstaller, self).config_content(command, vars) diff --git a/venv/lib/python2.7/site-packages/ckan/config/middleware/__init__.py b/venv/lib/python2.7/site-packages/ckan/config/middleware/__init__.py new file mode 100644 index 00000000..082372d7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/config/middleware/__init__.py @@ -0,0 +1,210 @@ +# encoding: utf-8 + +"""WSGI app initialization""" +import urllib +import urlparse +import urllib + +import webob +from routes import request_config as routes_request_config + +from ckan.lib.i18n import get_locales_from_config +from ckan.config.environment import load_environment +from ckan.config.middleware.flask_app import make_flask_stack +from ckan.config.middleware.pylons_app import make_pylons_stack +from ckan.common import config +from ckan.lib.i18n import get_locales_from_config + +import logging +log = logging.getLogger(__name__) + +# This monkey-patches the webob request object because of the way it messes +# with the WSGI environ. + +# Start of webob.requests.BaseRequest monkey patch +original_charset__set = webob.request.BaseRequest._charset__set + + +def custom_charset__set(self, charset): + original_charset__set(self, charset) + if self.environ.get('CONTENT_TYPE', '').startswith(';'): + self.environ['CONTENT_TYPE'] = '' + + +webob.request.BaseRequest._charset__set = custom_charset__set + +webob.request.BaseRequest.charset = property( + webob.request.BaseRequest._charset__get, + custom_charset__set, + webob.request.BaseRequest._charset__del, + webob.request.BaseRequest._charset__get.__doc__) + +# End of webob.requests.BaseRequest monkey patch + +# This is a test Flask request context to be used internally. +# Do not use it! +_internal_test_request_context = None + + +def make_app(conf, full_stack=True, static_files=True, **app_conf): + ''' + Initialise both the pylons and flask apps, and wrap them in dispatcher + middleware. + ''' + + load_environment(conf, app_conf) + + pylons_app = make_pylons_stack(conf, full_stack, static_files, + **app_conf) + flask_app = make_flask_stack(conf, **app_conf) + + app = AskAppDispatcherMiddleware({'pylons_app': pylons_app, + 'flask_app': flask_app}) + + # Set this internal test request context with the configured environment so + # it can be used when calling url_for from tests + global _internal_test_request_context + _internal_test_request_context = flask_app._wsgi_app.test_request_context() + + return app + + +class AskAppDispatcherMiddleware(object): + + ''' + Dispatches incoming requests to either the Flask or Pylons apps depending + on the WSGI environ. + + Used to help transition from Pylons to Flask, and should be removed once + Pylons has been deprecated and all app requests are handled by Flask. + + Each app should handle a call to 'can_handle_request(environ)', responding + with a tuple: + (, , []) + where: + `bool` is True if the app can handle the payload url, + `app` is the wsgi app returning the answer + `origin` is an optional string to determine where in the app the url + will be handled, e.g. 'core' or 'extension'. + + Order of precedence if more than one app can handle a url: + Flask Extension > Pylons Extension > Flask Core > Pylons Core + ''' + + def __init__(self, apps=None): + # Dict of apps managed by this middleware {: , ...} + self.apps = apps or {} + + self.default_locale = config.get('ckan.locale_default', 'en') + self.locale_list = get_locales_from_config() + + def ask_around(self, environ): + '''Checks with all apps whether they can handle the incoming request + ''' + answers = [ + app._wsgi_app.can_handle_request(environ) + for name, app in self.apps.iteritems() + ] + # Sort answers by app name + answers = sorted(answers, key=lambda x: x[1]) + log.debug('Route support answers for {0} {1}: {2}'.format( + environ.get('REQUEST_METHOD'), environ.get('PATH_INFO'), + answers)) + + return answers + + def handle_i18n(self, environ): + ''' + Note: This function used to be the I18nMiddleware. + + Strips the locale code from the requested url + (eg '/sk/about' -> '/about') and sets environ variables for the + language selected: + + * CKAN_LANG is the language code eg en, fr + * CKAN_LANG_IS_DEFAULT is set to True or False + * CKAN_CURRENT_URL is set to the current application url + ''' + + # We only update once for a request so we can keep + # the language and original url which helps with 404 pages etc + if 'CKAN_LANG' not in environ: + path_parts = environ['PATH_INFO'].split('/') + if len(path_parts) > 1 and path_parts[1] in self.locale_list: + environ['CKAN_LANG'] = path_parts[1] + environ['CKAN_LANG_IS_DEFAULT'] = False + # rewrite url + if len(path_parts) > 2: + environ['PATH_INFO'] = '/'.join([''] + path_parts[2:]) + else: + environ['PATH_INFO'] = '/' + else: + environ['CKAN_LANG'] = self.default_locale + environ['CKAN_LANG_IS_DEFAULT'] = True + + # Current application url + path_info = environ['PATH_INFO'] + # sort out weird encodings + path_info = \ + '/'.join(urllib.quote(pce, '') for pce in path_info.split('/')) + + qs = environ.get('QUERY_STRING') + + if qs: + # sort out weird encodings + qs = urllib.quote(qs, '') + environ['CKAN_CURRENT_URL'] = '%s?%s' % (path_info, qs) + else: + environ['CKAN_CURRENT_URL'] = path_info + + def __call__(self, environ, start_response): + '''Determine which app to call by asking each app if it can handle the + url and method defined on the eviron''' + + # Process locale part on the incoming request URL so it doesn't affect + # the mapper queries + self.handle_i18n(environ) + + app_name = 'pylons_app' # currently defaulting to pylons app + answers = self.ask_around(environ) + available_handlers = [] + for answer in answers: + if len(answer) == 2: + can_handle, asked_app = answer + origin = 'core' + else: + can_handle, asked_app, origin = answer + if can_handle: + available_handlers.append('{0}_{1}'.format(asked_app, origin)) + + # Enforce order of precedence: + # Flask Extension > Pylons Extension > Flask Core > Pylons Core + if available_handlers: + if 'flask_app_extension' in available_handlers: + app_name = 'flask_app' + elif 'pylons_app_extension' in available_handlers: + app_name = 'pylons_app' + elif 'flask_app_core' in available_handlers: + app_name = 'flask_app' + + log.debug('Serving request via {0} app'.format(app_name)) + environ['ckan.app'] = app_name + if app_name == 'flask_app': + # This request will be served by Flask, but we still need the + # Pylons URL builder (Routes) to work + parts = urlparse.urlparse(config.get('ckan.site_url', + 'http://0.0.0.0:5000')) + request_config = routes_request_config() + request_config.host = str(parts.netloc + parts.path) + request_config.protocol = str(parts.scheme) + request_config.mapper = config['routes.map'] + + return self.apps[app_name](environ, start_response) + else: + # Although this request will be served by Pylons we still + # need an application context in order for the Flask URL + # builder to work and to be able to access the Flask config + flask_app = self.apps['flask_app']._wsgi_app + + with flask_app.test_request_context(): + return self.apps[app_name](environ, start_response) diff --git a/venv/lib/python2.7/site-packages/ckan/config/middleware/common_middleware.py b/venv/lib/python2.7/site-packages/ckan/config/middleware/common_middleware.py new file mode 100644 index 00000000..f87ccf7a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/config/middleware/common_middleware.py @@ -0,0 +1,93 @@ +# encoding: utf-8 + +"""Common middleware used by both Flask and Pylons app stacks.""" + +import urllib2 +import hashlib +import json +import cgi + +import sqlalchemy as sa +from webob.request import FakeCGIBody + + +class RootPathMiddleware(object): + ''' + Prevents the SCRIPT_NAME server variable conflicting with the ckan.root_url + config. The routes package uses the SCRIPT_NAME variable and appends to the + path and ckan addes the root url causing a duplication of the root path. + + This is a middleware to ensure that even redirects use this logic. + ''' + def __init__(self, app, config): + self.app = app + + def __call__(self, environ, start_response): + # Prevents the variable interfering with the root_path logic + if 'SCRIPT_NAME' in environ: + environ['SCRIPT_NAME'] = '' + + return self.app(environ, start_response) + + +class CloseWSGIInputMiddleware(object): + ''' + webob.request.Request has habit to create FakeCGIBody. This leads( + during file upload) to creating temporary files that are not closed. + For long lived processes this means that for each upload you will + spend the same amount of temporary space as size of uploaded + file additionally, until server restart(this will automatically + close temporary files). + + This middleware is supposed to close such files after each request. + ''' + def __init__(self, app, config): + self.app = app + + def __call__(self, environ, start_response): + wsgi_input = environ['wsgi.input'] + if isinstance(wsgi_input, FakeCGIBody): + for _, item in wsgi_input.vars.items(): + if not isinstance(item, cgi.FieldStorage): + continue + fp = getattr(item, 'fp', None) + if fp is not None: + fp.close() + return self.app(environ, start_response) + + +class TrackingMiddleware(object): + + def __init__(self, app, config): + self.app = app + self.engine = sa.create_engine(config.get('sqlalchemy.url')) + + def __call__(self, environ, start_response): + path = environ['PATH_INFO'] + method = environ.get('REQUEST_METHOD') + if path == '/_tracking' and method == 'POST': + # do the tracking + # get the post data + payload = environ['wsgi.input'].read() + parts = payload.split('&') + data = {} + for part in parts: + k, v = part.split('=') + data[k] = urllib2.unquote(v).decode("utf8") + start_response('200 OK', [('Content-Type', 'text/html')]) + # we want a unique anonomized key for each user so that we do + # not count multiple clicks from the same user. + key = ''.join([ + environ['HTTP_USER_AGENT'], + environ['REMOTE_ADDR'], + environ.get('HTTP_ACCEPT_LANGUAGE', ''), + environ.get('HTTP_ACCEPT_ENCODING', ''), + ]) + key = hashlib.md5(key).hexdigest() + # store key/data here + sql = '''INSERT INTO tracking_raw + (user_key, url, tracking_type) + VALUES (%s, %s, %s)''' + self.engine.execute(sql, key, data.get('url'), data.get('type')) + return [] + return self.app(environ, start_response) diff --git a/venv/lib/python2.7/site-packages/ckan/config/middleware/flask_app.py b/venv/lib/python2.7/site-packages/ckan/config/middleware/flask_app.py new file mode 100644 index 00000000..8da7db3f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/config/middleware/flask_app.py @@ -0,0 +1,444 @@ +# encoding: utf-8 + +import os +import re +import inspect +import itertools +import pkgutil + +from flask import Flask, Blueprint +from flask.ctx import _AppCtxGlobals +from flask.sessions import SessionInterface + +from werkzeug.exceptions import default_exceptions, HTTPException +from werkzeug.routing import Rule + +from flask_babel import Babel + +from beaker.middleware import SessionMiddleware +from paste.deploy.converters import asbool +from fanstatic import Fanstatic +from repoze.who.config import WhoConfig +from repoze.who.middleware import PluggableAuthenticationMiddleware + +import ckan.model as model +from ckan.lib import base +from ckan.lib import helpers +from ckan.lib import jinja_extensions +from ckan.common import config, g, request, ungettext +import ckan.lib.app_globals as app_globals +from ckan.plugins import PluginImplementations +from ckan.plugins.interfaces import IBlueprint, IMiddleware, ITranslation +from ckan.views import (identify_user, + set_cors_headers_for_response, + check_session_cookie, + set_controller_and_action + ) + + +import logging +log = logging.getLogger(__name__) + + +class CKANBabel(Babel): + def __init__(self, *pargs, **kwargs): + super(CKANBabel, self).__init__(*pargs, **kwargs) + self._i18n_path_idx = 0 + + @property + def domain(self): + default = super(CKANBabel, self).domain + multiple = self.app.config.get('BABEL_MULTIPLE_DOMAINS') + if not multiple: + return default + domains = multiple.split(';') + try: + return domains[self._i18n_path_idx] + except IndexError: + return default + + @property + def translation_directories(self): + self._i18n_path_idx = 0 + for path in super(CKANBabel, self).translation_directories: + yield path + self._i18n_path_idx += 1 + + +def make_flask_stack(conf, **app_conf): + """ This has to pass the flask app through all the same middleware that + Pylons used """ + + root = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + + debug = asbool(conf.get('debug', conf.get('DEBUG', False))) + testing = asbool(app_conf.get('testing', app_conf.get('TESTING', False))) + + app = flask_app = CKANFlask(__name__) + app.debug = debug + app.testing = testing + app.template_folder = os.path.join(root, 'templates') + app.app_ctx_globals_class = CKAN_AppCtxGlobals + app.url_rule_class = CKAN_Rule + + app.jinja_options = jinja_extensions.get_jinja_env_options() + # Update Flask config with the CKAN values. We use the common config + # object as values might have been modified on `load_environment` + if config: + app.config.update(config) + else: + app.config.update(conf) + app.config.update(app_conf) + + # Do all the Flask-specific stuff before adding other middlewares + + # Secret key needed for flask-debug-toolbar and sessions + if not app.config.get('SECRET_KEY'): + app.config['SECRET_KEY'] = config.get('beaker.session.secret') + if not app.config.get('SECRET_KEY'): + raise RuntimeError(u'You must provide a value for the secret key' + ' with the SECRET_KEY config option') + + if debug: + from flask_debugtoolbar import DebugToolbarExtension + app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False + DebugToolbarExtension(app) + + # Use Beaker as the Flask session interface + class BeakerSessionInterface(SessionInterface): + def open_session(self, app, request): + if 'beaker.session' in request.environ: + return request.environ['beaker.session'] + + def save_session(self, app, session, response): + session.save() + + namespace = 'beaker.session.' + session_opts = dict([(k.replace('beaker.', ''), v) + for k, v in config.iteritems() + if k.startswith(namespace)]) + if (not session_opts.get('session.data_dir') and + session_opts.get('session.type', 'file') == 'file'): + cache_dir = app_conf.get('cache_dir') or app_conf.get('cache.dir') + session_opts['session.data_dir'] = '{data_dir}/sessions'.format( + data_dir=cache_dir) + + app.wsgi_app = SessionMiddleware(app.wsgi_app, session_opts) + app.session_interface = BeakerSessionInterface() + + # Add Jinja2 extensions and filters + app.jinja_env.filters['empty_and_escape'] = \ + jinja_extensions.empty_and_escape + + # Common handlers for all requests + app.before_request(ckan_before_request) + app.after_request(ckan_after_request) + + # Template context processors + app.context_processor(helper_functions) + app.context_processor(c_object) + + @app.context_processor + def ungettext_alias(): + u''' + Provide `ungettext` as an alias of `ngettext` for backwards + compatibility + ''' + return dict(ungettext=ungettext) + + # Babel + pairs = [(os.path.join(root, u'i18n'), 'ckan')] + [ + (p.i18n_directory(), p.i18n_domain()) + for p in PluginImplementations(ITranslation) + ] + + i18n_dirs, i18n_domains = zip(*pairs) + + app.config[u'BABEL_TRANSLATION_DIRECTORIES'] = ';'.join(i18n_dirs) + app.config[u'BABEL_DOMAIN'] = 'ckan' + app.config[u'BABEL_MULTIPLE_DOMAINS'] = ';'.join(i18n_domains) + + babel = CKANBabel(app) + + babel.localeselector(get_locale) + + @app.route('/hello', methods=['GET']) + def hello_world(): + return 'Hello World, this is served by Flask' + + @app.route('/hello', methods=['POST']) + def hello_world_post(): + return 'Hello World, this was posted to Flask' + + # Auto-register all blueprints defined in the `views` folder + _register_core_blueprints(app) + _register_error_handler(app) + + # Set up each IBlueprint extension as a Flask Blueprint + for plugin in PluginImplementations(IBlueprint): + if hasattr(plugin, 'get_blueprint'): + plugin_blueprints = plugin.get_blueprint() + if not isinstance(plugin_blueprints, list): + plugin_blueprints = [plugin_blueprints] + for blueprint in plugin_blueprints: + app.register_extension_blueprint(blueprint) + + # Set flask routes in named_routes + for rule in app.url_map.iter_rules(): + if '.' not in rule.endpoint: + continue + controller, action = rule.endpoint.split('.') + needed = list(rule.arguments - set(rule.defaults or {})) + route = { + rule.endpoint: { + 'action': action, + 'controller': controller, + 'highlight_actions': action, + 'needed': needed + } + } + config['routes.named_routes'].update(route) + + # Start other middleware + for plugin in PluginImplementations(IMiddleware): + app = plugin.make_middleware(app, config) + + # Fanstatic + if debug: + fanstatic_config = { + 'versioning': True, + 'recompute_hashes': True, + 'minified': False, + 'bottom': True, + 'bundle': False, + } + else: + fanstatic_config = { + 'versioning': True, + 'recompute_hashes': False, + 'minified': True, + 'bottom': True, + 'bundle': True, + } + root_path = config.get('ckan.root_path', None) + if root_path: + root_path = re.sub('/{{LANG}}', '', root_path) + fanstatic_config['base_url'] = root_path + app = Fanstatic(app, **fanstatic_config) + + for plugin in PluginImplementations(IMiddleware): + try: + app = plugin.make_error_log_middleware(app, config) + except AttributeError: + log.critical('Middleware class {0} is missing the method' + 'make_error_log_middleware.' + .format(plugin.__class__.__name__)) + + # Initialize repoze.who + who_parser = WhoConfig(conf['here']) + who_parser.parse(open(app_conf['who.config_file'])) + + app = PluggableAuthenticationMiddleware( + app, + who_parser.identifiers, + who_parser.authenticators, + who_parser.challengers, + who_parser.mdproviders, + who_parser.request_classifier, + who_parser.challenge_decider, + logging.getLogger('repoze.who'), + logging.WARN, # ignored + who_parser.remote_user_key + ) + + # Update the main CKAN config object with the Flask specific keys + # that were set here or autogenerated + flask_config_keys = set(flask_app.config.keys()) - set(config.keys()) + for key in flask_config_keys: + config[key] = flask_app.config[key] + + # Add a reference to the actual Flask app so it's easier to access + app._wsgi_app = flask_app + + return app + + +def get_locale(): + u''' + Return the value of the `CKAN_LANG` key of the WSGI environ, + set by the I18nMiddleware based on the URL. + If no value is defined, it defaults to `ckan.locale_default` or `en`. + ''' + return request.environ.get( + u'CKAN_LANG', + config.get(u'ckan.locale_default', u'en')) + + +def ckan_before_request(): + u'''Common handler executed before all Flask requests''' + + # Update app_globals + app_globals.app_globals._check_uptodate() + + # Identify the user from the repoze cookie or the API header + # Sets g.user and g.userobj + identify_user() + + # Provide g.controller and g.action for backward compatibility + # with extensions + set_controller_and_action() + + +def ckan_after_request(response): + u'''Common handler executed after all Flask requests''' + + # Dispose of the SQLALchemy session + model.Session.remove() + + # Check session cookie + response = check_session_cookie(response) + + # Set CORS headers if necessary + response = set_cors_headers_for_response(response) + + # Default to cache-control private if it was not set + if response.cache_control.private is None: + response.cache_control.private = True + + return response + + +def helper_functions(): + u'''Make helper functions (`h`) available to Flask templates''' + if not helpers.helper_functions: + helpers.load_plugin_helpers() + return dict(h=helpers.helper_functions) + + +def c_object(): + u''' + Expose `c` as an alias of `g` in templates for backwards compatibility + ''' + return dict(c=g) + + +class CKAN_Rule(Rule): + + u'''Custom Flask url_rule_class. + + We use it to be able to flag routes defined in extensions as such + ''' + + def __init__(self, *args, **kwargs): + self.ckan_core = True + super(CKAN_Rule, self).__init__(*args, **kwargs) + + +class CKAN_AppCtxGlobals(_AppCtxGlobals): + + '''Custom Flask AppCtxGlobal class (flask.g).''' + + def __getattr__(self, name): + ''' + If flask.g doesn't have attribute `name`, fall back to CKAN's + app_globals object. + If the key is also not found in there, an AttributeError will be raised + ''' + return getattr(app_globals.app_globals, name) + + +class CKANFlask(Flask): + + '''Extend the Flask class with a special method called on incoming + requests by AskAppDispatcherMiddleware. + ''' + + app_name = 'flask_app' + + def can_handle_request(self, environ): + ''' + Decides whether it can handle a request with the Flask app by + matching the request environ against the route mapper + + Returns (True, 'flask_app', origin) if this is the case. + + `origin` can be either 'core' or 'extension' depending on where + the route was defined. + ''' + urls = self.url_map.bind_to_environ(environ) + + try: + rule, args = urls.match(return_rule=True) + origin = 'core' + if hasattr(rule, 'ckan_core') and not rule.ckan_core: + origin = 'extension' + log.debug('Flask route match, endpoint: {0}, args: {1}, ' + 'origin: {2}'.format(rule.endpoint, args, origin)) + + # Disable built-in flask's ability to prepend site root to + # generated url, as we are going to use locale and existing + # logic is not flexible enough for this purpose + environ['SCRIPT_NAME'] = '' + + return (True, self.app_name, origin) + except HTTPException: + return (False, self.app_name) + + def register_extension_blueprint(self, blueprint, **kwargs): + ''' + This method should be used to register blueprints that come from + extensions, so there's an opportunity to add extension-specific + options. + + Sets the rule property `ckan_core` to False, to indicate that the rule + applies to an extension route. + ''' + self.register_blueprint(blueprint, **kwargs) + + # Get the new blueprint rules + bp_rules = itertools.chain.from_iterable( + v for k, v in self.url_map._rules_by_endpoint.iteritems() + if k.startswith(u'{0}.'.format(blueprint.name)) + ) + + # This compare key will ensure the rule will be near the top. + top_compare_key = False, -100, [(-2, 0)] + for r in bp_rules: + r.ckan_core = False + r.match_compare_key = lambda: top_compare_key + + +def _register_core_blueprints(app): + u'''Register all blueprints defined in the `views` folder + ''' + def is_blueprint(mm): + return isinstance(mm, Blueprint) + + path = os.path.join(os.path.dirname(__file__), '..', '..', 'views') + + for loader, name, _ in pkgutil.iter_modules([path], 'ckan.views.'): + module = loader.find_module(name).load_module(name) + for blueprint in inspect.getmembers(module, is_blueprint): + app.register_blueprint(blueprint[1]) + log.debug(u'Registered core blueprint: {0!r}'.format(blueprint[0])) + + +def _register_error_handler(app): + u'''Register error handler''' + + def error_handler(e): + if isinstance(e, HTTPException): + extra_vars = {u'code': [e.code], u'content': e.description} + # TODO: Remove + g.code = [e.code] + + return base.render( + u'error_document_template.html', extra_vars), e.code + extra_vars = {u'code': [500], u'content': u'Internal server error'} + return base.render(u'error_document_template.html', extra_vars), 500 + + for code in default_exceptions: + app.register_error_handler(code, error_handler) + if not app.debug and not app.testing: + app.register_error_handler(Exception, error_handler) diff --git a/venv/lib/python2.7/site-packages/ckan/config/middleware/pylons_app.py b/venv/lib/python2.7/site-packages/ckan/config/middleware/pylons_app.py new file mode 100644 index 00000000..2d352324 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/config/middleware/pylons_app.py @@ -0,0 +1,286 @@ +# encoding: utf-8 + +import os +import re + +from pylons.wsgiapp import PylonsApp + +from beaker.middleware import CacheMiddleware, SessionMiddleware +from paste.cascade import Cascade +from paste.registry import RegistryManager +from paste.urlparser import StaticURLParser +from paste.deploy.converters import asbool +from paste.fileapp import _FileIter +from pylons.middleware import ErrorHandler, StatusCodeRedirect +from routes.middleware import RoutesMiddleware +from repoze.who.config import WhoConfig +from repoze.who.middleware import PluggableAuthenticationMiddleware +from fanstatic import Fanstatic + +from ckan.plugins import PluginImplementations +from ckan.plugins.interfaces import IMiddleware +import ckan.lib.uploader as uploader +from ckan.config.middleware import common_middleware +from ckan.common import config + +import logging +log = logging.getLogger(__name__) + + +def make_pylons_stack(conf, full_stack=True, static_files=True, + **app_conf): + """Create a Pylons WSGI application and return it + + ``conf`` + The inherited configuration for this application. Normally from + the [DEFAULT] section of the Paste ini file. + + ``full_stack`` + Whether this application provides a full WSGI stack (by default, + meaning it handles its own exceptions and errors). Disable + full_stack when this application is "managed" by another WSGI + middleware. + + ``static_files`` + Whether this application serves its own static files; disable + when another web server is responsible for serving them. + + ``app_conf`` + The application's local configuration. Normally specified in + the [app:] section of the Paste ini file (where + defaults to main). + + """ + # The Pylons WSGI app + app = pylons_app = CKANPylonsApp() + + for plugin in PluginImplementations(IMiddleware): + app = plugin.make_middleware(app, config) + + app = common_middleware.CloseWSGIInputMiddleware(app, config) + app = common_middleware.RootPathMiddleware(app, config) + # Routing/Session/Cache Middleware + app = RoutesMiddleware(app, config['routes.map']) + # we want to be able to retrieve the routes middleware to be able to update + # the mapper. We store it in the pylons config to allow this. + config['routes.middleware'] = app + app = SessionMiddleware(app, config) + app = CacheMiddleware(app, config) + + # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares) + # app = QueueLogMiddleware(app) + if asbool(config.get('ckan.use_pylons_response_cleanup_middleware', + True)): + app = execute_on_completion(app, config, + cleanup_pylons_response_string) + + # Fanstatic + if asbool(config.get('debug', False)): + fanstatic_config = { + 'versioning': True, + 'recompute_hashes': True, + 'minified': False, + 'bottom': True, + 'bundle': False, + } + else: + fanstatic_config = { + 'versioning': True, + 'recompute_hashes': False, + 'minified': True, + 'bottom': True, + 'bundle': True, + } + root_path = config.get('ckan.root_path', None) + if root_path: + root_path = re.sub('/{{LANG}}', '', root_path) + fanstatic_config['base_url'] = root_path + app = Fanstatic(app, **fanstatic_config) + + for plugin in PluginImplementations(IMiddleware): + try: + app = plugin.make_error_log_middleware(app, config) + except AttributeError: + log.critical('Middleware class {0} is missing the method' + 'make_error_log_middleware.' + .format(plugin.__class__.__name__)) + + if asbool(full_stack): + # Handle Python exceptions + app = ErrorHandler(app, conf, **config['pylons.errorware']) + + # Display error documents for 400, 403, 404 status codes (and + # 500 when debug is disabled) + if asbool(config['debug']): + app = StatusCodeRedirect(app, [400, 403, 404]) + else: + app = StatusCodeRedirect(app, [400, 403, 404, 500]) + + # Initialize repoze.who + who_parser = WhoConfig(conf['here']) + who_parser.parse(open(app_conf['who.config_file'])) + + app = PluggableAuthenticationMiddleware( + app, + who_parser.identifiers, + who_parser.authenticators, + who_parser.challengers, + who_parser.mdproviders, + who_parser.request_classifier, + who_parser.challenge_decider, + logging.getLogger('repoze.who'), + logging.WARN, # ignored + who_parser.remote_user_key + ) + + # Establish the Registry for this application + # The RegistryManager includes code to pop + # registry values after the stream has completed, + # so we need to prevent this with `streaming` set to True. + app = RegistryManager(app, streaming=True) + + if asbool(static_files): + # Serve static files + static_max_age = None if not asbool( + config.get('ckan.cache_enabled')) \ + else int(config.get('ckan.static_max_age', 3600)) + + static_app = StaticURLParser( + config['pylons.paths']['static_files'], + cache_max_age=static_max_age) + static_parsers = [static_app, app] + + storage_directory = uploader.get_storage_path() + if storage_directory: + path = os.path.join(storage_directory, 'storage') + try: + os.makedirs(path) + except OSError as e: + # errno 17 is file already exists + if e.errno != 17: + raise + + storage_app = StaticURLParser(path, cache_max_age=static_max_age) + static_parsers.insert(0, storage_app) + + # Configurable extra static file paths + extra_static_parsers = [] + for public_path in config.get( + 'extra_public_paths', '').split(','): + if public_path.strip(): + extra_static_parsers.append( + StaticURLParser(public_path.strip(), + cache_max_age=static_max_age) + ) + app = Cascade(extra_static_parsers + static_parsers) + + # Tracking + if asbool(config.get('ckan.tracking_enabled', 'false')): + app = common_middleware.TrackingMiddleware(app, config) + + # Add a reference to the actual Pylons app so it's easier to access + app._wsgi_app = pylons_app + + return app + + +class CKANPylonsApp(PylonsApp): + + app_name = 'pylons_app' + + def can_handle_request(self, environ): + ''' + Decides whether it can handle a request with the Pylons app by + matching the request environ against the route mapper + + Returns (True, 'pylons_app', origin) if this is the case. + + origin can be either 'core' or 'extension' depending on where + the route was defined. + + NOTE: There is currently a catch all route for GET requests to + point arbitrary urls to templates with the same name: + + map.connect('/*url', controller='template', action='view') + + This means that this function will match all GET requests. This + does not cause issues as the Pylons core routes are the last to + take precedence so the current behaviour is kept, but it's worth + keeping in mind. + ''' + + pylons_mapper = config['routes.map'] + match_route = pylons_mapper.routematch(environ=environ) + if match_route: + match, route = match_route + origin = 'core' + if hasattr(route, '_ckan_core') and not route._ckan_core: + origin = 'extension' + log.debug('Pylons route match: {0} Origin: {1}'.format( + match, origin)) + return (True, self.app_name, origin) + else: + return (False, self.app_name) + + +class CloseCallbackWrapper(object): + def __init__(self, iterable, callback, environ): + # pylons.fileapp expects app_iter to have `file` attribute. + self.file = iterable + self.callback = callback + self.environ = environ + + def __iter__(self): + """ + return a generator that passes through items from iterable + then calls callback(environ). + """ + try: + for item in self.file: + yield item + except GeneratorExit: + if hasattr(self.file, 'close'): + self.file.close() + raise + finally: + self.callback(self.environ) + + +class FileIterWrapper(CloseCallbackWrapper, _FileIter): + """Same CloseCallbackWrapper, just with _FileIter mixin. + + That will prevent pylons from converting file responses into + in-memori lists. + """ + pass + + +def execute_on_completion(application, config, callback): + """ + Call callback(environ) once complete response is sent + """ + + def inner(environ, start_response): + try: + result = application(environ, start_response) + except: + callback(environ) + raise + # paste.fileapp converts non-file responses into list + # In order to avoid interception of OOM Killer + # file responses wrapped into generator with + # _FileIter in parent tree. + klass = CloseCallbackWrapper + if isinstance(result, _FileIter): + klass = FileIterWrapper + return klass(result, callback, environ) + + return inner + + +def cleanup_pylons_response_string(environ): + try: + msg = 'response cleared by pylons response cleanup middleware' + environ['pylons.controller']._py_object.response._body = msg + except (KeyError, AttributeError): + pass diff --git a/venv/lib/python2.7/site-packages/ckan/config/resource_formats.json b/venv/lib/python2.7/site-packages/ckan/config/resource_formats.json new file mode 100644 index 00000000..d4749a99 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/config/resource_formats.json @@ -0,0 +1,77 @@ +[ + ["_comment", + "JSON field order as follows:", + ["Format", "Description", "Mimetype", ["List of alternative representations"]], + "where:", + " * Format - the short name for it, usually the file extension, because it will be displayed in many places, such as in the search results.", + " * Description - the name, human-friendly, to be displayed on the resource page. ", + " * Mimetype - canonical mimetype for the format. It must be unique to this resource format. It should be listed here: https://www.iana.org/assignments/media-types/media-types.xhtml or here: http://hg.python.org/cpython/file/2.7/Lib/mimetypes.py#l403", + " * List of alternative representations - these are other names that the user might type when they mean this format, or alternative mime-types or any other identifier. (They must be unique to this resource format.)" + ], + ["PPTX", "Powerpoint OOXML Presentation", "application/vnd.openxmlformats-officedocument.presentationml.presentation", []], + ["EXE", "Windows Executable Program", "application/x-msdownload", []], + ["DOC", "Word Document", "application/msword", []], + ["KML", "KML File", "application/vnd.google-earth.kml+xml", []], + ["XLS", "Excel Document", "application/vnd.ms-excel", ["Excel", "application/msexcel", "application/x-msexcel", "application/x-ms-excel", "application/x-excel", "application/x-dos_ms_excel", "application/xls", "application/x-xls"]], + ["WCS", "Web Coverage Service", "wcs", []], + ["JS", "JavaScript", "application/x-javascript", []], + ["MDB", "Access Database", "application/x-msaccess", []], + ["NetCDF", "NetCDF File", "application/netcdf", []], + ["ArcGIS Map Service", "ArcGIS Map Service", "ArcGIS Map Service", ["arcgis map service"]], + ["TSV", "Tab Separated Values File", "text/tab-separated-values", ["text/tsv"]], + ["WFS", "Web Feature Service", null, []], + ["WMTS", "Web Map Tile Service", null, []], + ["ArcGIS Online Map", "ArcGIS Online Map", "ArcGIS Online Map", ["web map application"]], + ["Perl", "Perl Script", "text/x-perl", []], + ["KMZ", "KMZ File", "application/vnd.google-earth.kmz+xml", ["application/vnd.google-earth.kmz"]], + ["OWL", "Web Ontology Language", "application/owl+xml", []], + ["N3", "N3 Triples", "application/x-n3", []], + ["ZIP", "Zip File", "application/zip", ["zip", "http://purl.org/NET/mediatypes/application/zip"]], + ["GZ", "Gzip File", "application/gzip", ["application/x-gzip"]], + ["QGIS", "QGIS File", "application/x-qgis", []], + ["ODS", "OpenDocument Spreadsheet", "application/vnd.oasis.opendocument.spreadsheet", []], + ["ODT", "OpenDocument Text", "application/vnd.oasis.opendocument.text", []], + ["JSON", "JavaScript Object Notation", "application/json", []], + ["BMP", "Bitmap Image File", "image/x-ms-bmp", []], + ["HTML", "Web Page", "text/html", ["htm", "http://purl.org/net/mediatypes/text/html"]], + ["RAR", "RAR Compressed File", "application/rar", []], + ["TIFF", "TIFF Image File", "image/tiff", []], + ["ODB", "OpenDocument Database", "application/vnd.oasis.opendocument.database", []], + ["TXT", "Text File", "text/plain", []], + ["DCR", "Adobe Shockwave format", "application/x-director", []], + ["ODF", "OpenDocument Math Formula", "application/vnd.oasis.opendocument.formula", []], + ["ODG", "OpenDocument Image", "application/vnd.oasis.opendocument.graphics", []], + ["XML", "XML File", "application/xml", ["text/xml", "http://purl.org/net/mediatypes/application/xml"]], + ["XLSX", "Excel OOXML Spreadsheet", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", []], + ["DOCX", "Word OOXML Document", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", []], + ["BIN", "Binary Data", "application/octet-stream", ["bin"]], + ["XSLT", "Extensible Stylesheet Language Transformations", "application/xslt+xml", []], + ["WMS", "Web Mapping Service", "WMS", ["wms"]], + ["SVG", "SVG vector image", "image/svg+xml", ["svg"]], + ["PPT", "Powerpoint Presentation", "application/vnd.ms-powerpoint", []], + ["ODP", "OpenDocument Presentation", "application/vnd.oasis.opendocument.presentation", []], + ["JPEG", "JPG Image File", "image/jpeg", ["jpeg", "jpg"]], + ["SPARQL", "SPARQL end-point", "application/sparql-results+xml", []], + ["GIF", "GIF Image File", "image/gif", []], + ["RDF", "RDF", "application/rdf+xml", ["rdf/xml"]], + ["E00", " ARC/INFO interchange file format", "application/x-e00", []], + ["PDF", "PDF File", "application/pdf", []], + ["CSV", "Comma Separated Values File", "text/csv", ["text/comma-separated-values"]], + ["ODC", "OpenDocument Chart", "application/vnd.oasis.opendocument.chart", []], + ["Atom Feed", "Atom Feed", "application/atom+xml", []], + ["MrSID", "MrSID", "image/x-mrsid", []], + ["ArcGIS Map Preview", "ArcGIS Map Preview", "ArcGIS Map Preview", ["arcgis map preview"]], + ["XYZ", "XYZ Chemical File", "chemical/x-xyz", []], + ["MOP", "MOPAC Input format", "chemical/x-mopac-input", []], + ["Esri REST", "Esri Rest API Endpoint", "Esri REST", ["arcgis_rest"]], + ["dBase", "dBase Database", "application/x-dbf", ["dbf"]], + ["MXD", "ESRI ArcGIS project file", "application/x-mxd", []], + ["TAR", "TAR Compressed File", "application/x-tar", []], + ["PNG", "PNG Image File", "image/png", []], + ["RSS", "RSS feed", "application/rss+xml", []], + ["GeoJSON", "Geographic JavaScript Object Notation", "application/geo+json", ["geojson"]], + ["SHP", "Shapefile", null, ["esri shapefile"]], + ["TORRENT", "Torrent", "application/x-bittorrent", ["bittorrent"]], + ["ICS", "iCalendar", "text/calendar", ["ifb", "iCal"]], + ["GTFS", "General Transit Feed Specification", null, []] +] diff --git a/venv/lib/python2.7/site-packages/ckan/config/routing.py b/venv/lib/python2.7/site-packages/ckan/config/routing.py new file mode 100644 index 00000000..60209d13 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/config/routing.py @@ -0,0 +1,347 @@ +# encoding: utf-8 + +"""Routes configuration + +The more specific and detailed routes should be defined first so they +may take precedent over the more generic routes. For more information +refer to the routes manual at http://routes.groovie.org/docs/ + +""" +import re + +from routes.mapper import SubMapper, Mapper as _Mapper + +import ckan.plugins as p +from ckan.common import config + +named_routes = {} + + +class Mapper(_Mapper): + ''' This Mapper allows us to intercept the connect calls used by routes + so that we can collect named routes and later use them to create links + via some helper functions like build_nav(). ''' + + def connect(self, *args, **kw): + '''Connect a new route, storing any named routes for later. + + This custom connect() method wraps the standard connect() method, + and additionally saves any named routes that are connected in a dict + ckan.routing.named_routes, which ends up being accessible via the + Pylons config as config['routes.named_routes']. + + Also takes some additional params: + + :param ckan_icon: name of the icon to be associated with this route, + e.g. 'group', 'time'. Available icons are listed here: + http://fortawesome.github.io/Font-Awesome/3.2.1/icons/ + :type ckan_icon: string + :param highlight_actions: space-separated list of controller actions + that should be treated as the same as this named route for menu + highlighting purposes, e.g. 'index search' + :type highlight_actions: string + + ''' + + ckan_icon = kw.pop('ckan_icon', None) + highlight_actions = kw.pop('highlight_actions', kw.get('action', '')) + ckan_core = kw.pop('ckan_core', None) + out = _Mapper.connect(self, *args, **kw) + route = self.matchlist[-1] + if ckan_core is not None: + route._ckan_core = ckan_core + if len(args) == 1 or args[0].startswith('_redirect_'): + return out + # we have a named route + needed = [] + matches = re.findall('\{([^:}]*)(\}|:)', args[1]) + for match in matches: + needed.append(match[0]) + route_data = { + 'icon': ckan_icon, + # needed lists the names of the parameters that need defining + # for the route to be generated + 'needed': needed, + 'controller': kw.get('controller'), + 'action': kw.get('action', ''), + 'highlight_actions': highlight_actions + } + named_routes[args[0]] = route_data + return out + + +def make_map(): + """Create, configure and return the routes Mapper""" + # import controllers here rather than at root level because + # pylons config is initialised by this point. + + # Helpers to reduce code clutter + GET = dict(method=['GET']) + PUT = dict(method=['PUT']) + POST = dict(method=['POST']) + DELETE = dict(method=['DELETE']) + GET_POST = dict(method=['GET', 'POST']) + PUT_POST = dict(method=['PUT', 'POST']) + PUT_POST_DELETE = dict(method=['PUT', 'POST', 'DELETE']) + OPTIONS = dict(method=['OPTIONS']) + + import ckan.lib.plugins as lib_plugins + lib_plugins.reset_package_plugins() + + map = Mapper(directory=config['pylons.paths']['controllers'], + always_scan=config['debug']) + map.minimization = False + map.explicit = True + + # CUSTOM ROUTES HERE + for plugin in p.PluginImplementations(p.IRoutes): + map = plugin.before_map(map) + + # The ErrorController route (handles 404/500 error pages); it should + # likely stay at the top, ensuring it can always be resolved. + map.connect('/error/{action}', controller='error', ckan_core=True) + map.connect('/error/{action}/{id}', controller='error', ckan_core=True) + + map.connect('*url', controller='home', action='cors_options', + conditions=OPTIONS, ckan_core=True) + + # Mark all routes added from extensions on the `before_map` extension point + # as non-core + for route in map.matchlist: + if not hasattr(route, '_ckan_core'): + route._ckan_core = False + + # CKAN API versioned. + register_list = [ + 'package', + 'dataset', + 'resource', + 'tag', + 'group', + 'revision', + 'licenses', + 'rating', + 'user', + 'activity' + ] + register_list_str = '|'.join(register_list) + + # /api ver 1, 2, 3 or none + with SubMapper(map, controller='api', path_prefix='/api{ver:/1|/2|/3|}', + ver='/1') as m: + m.connect('/search/{register}', action='search') + + # /api/util ver 1, 2 or none + with SubMapper(map, controller='api', path_prefix='/api{ver:/1|/2|}', + ver='/1') as m: + m.connect('/util/dataset/munge_name', action='munge_package_name') + m.connect('/util/dataset/munge_title_to_name', + action='munge_title_to_package_name') + m.connect('/util/tag/munge', action='munge_tag') + + ########### + ## /END API + ########### + + map.redirect('/packages', '/dataset') + map.redirect('/packages/{url:.*}', '/dataset/{url}') + map.redirect('/package', '/dataset') + map.redirect('/package/{url:.*}', '/dataset/{url}') + + with SubMapper(map, controller='package') as m: + m.connect('search', '/dataset', action='search', + highlight_actions='index search') + m.connect('dataset_new', '/dataset/new', action='new') + m.connect('/dataset/{action}', + requirements=dict(action='|'.join([ + 'list', + 'autocomplete', + 'search' + ]))) + + m.connect('/dataset/{action}/{id}/{revision}', action='read_ajax', + requirements=dict(action='|'.join([ + 'read', + 'edit', + 'history', + ]))) + m.connect('/dataset/{action}/{id}', + requirements=dict(action='|'.join([ + 'new_resource', + 'history', + 'read_ajax', + 'history_ajax', + 'follow', + 'activity', + 'groups', + 'unfollow', + 'delete', + 'api_data', + ]))) + m.connect('dataset_edit', '/dataset/edit/{id}', action='edit', + ckan_icon='pencil-square-o') + m.connect('dataset_followers', '/dataset/followers/{id}', + action='followers', ckan_icon='users') + m.connect('dataset_activity', '/dataset/activity/{id}', + action='activity', ckan_icon='clock-o') + m.connect('/dataset/activity/{id}/{offset}', action='activity') + m.connect('dataset_groups', '/dataset/groups/{id}', + action='groups', ckan_icon='users') + m.connect('dataset_resources', '/dataset/resources/{id}', + action='resources', ckan_icon='bars') + m.connect('dataset_read', '/dataset/{id}', action='read', + ckan_icon='sitemap') + m.connect('/dataset/{id}/resource/{resource_id}', + action='resource_read') + m.connect('/dataset/{id}/resource_delete/{resource_id}', + action='resource_delete') + m.connect('resource_edit', '/dataset/{id}/resource_edit/{resource_id}', + action='resource_edit', ckan_icon='pencil-square-o') + m.connect('/dataset/{id}/resource/{resource_id}/download', + action='resource_download') + m.connect('/dataset/{id}/resource/{resource_id}/download/{filename}', + action='resource_download') + m.connect('/dataset/{id}/resource/{resource_id}/embed', + action='resource_embedded_dataviewer') + m.connect('/dataset/{id}/resource/{resource_id}/viewer', + action='resource_embedded_dataviewer', width="960", + height="800") + m.connect('/dataset/{id}/resource/{resource_id}/preview', + action='resource_datapreview') + m.connect('views', '/dataset/{id}/resource/{resource_id}/views', + action='resource_views', ckan_icon='bars') + m.connect('new_view', '/dataset/{id}/resource/{resource_id}/new_view', + action='edit_view', ckan_icon='pencil-square-o') + m.connect('edit_view', + '/dataset/{id}/resource/{resource_id}/edit_view/{view_id}', + action='edit_view', ckan_icon='pencil-square-o') + m.connect('resource_view', + '/dataset/{id}/resource/{resource_id}/view/{view_id}', + action='resource_view') + m.connect('/dataset/{id}/resource/{resource_id}/view/', + action='resource_view') + + # group + map.redirect('/groups', '/group') + map.redirect('/groups/{url:.*}', '/group/{url}') + + # These named routes are used for custom group forms which will use the + # names below based on the group.type ('group' is the default type) + with SubMapper(map, controller='group') as m: + m.connect('group_index', '/group', action='index', + highlight_actions='index search') + m.connect('group_list', '/group/list', action='list') + m.connect('group_new', '/group/new', action='new') + + for action in [ + 'edit', + 'delete', + 'member_new', + 'member_delete', + 'history', + 'followers', + 'follow', + 'unfollow', + 'admins', + 'activity', + ]: + m.connect('group_' + action, + '/group/' + action + '/{id}', + action=action) + + m.connect('group_about', '/group/about/{id}', action='about', + ckan_icon='info-circle'), + m.connect('group_edit', '/group/edit/{id}', action='edit', + ckan_icon='pencil-square-o') + m.connect('group_members', '/group/members/{id}', action='members', + ckan_icon='users'), + m.connect('group_activity', '/group/activity/{id}/{offset}', + action='activity', ckan_icon='clock-o'), + m.connect('group_read', '/group/{id}', action='read', + ckan_icon='sitemap') + + # organizations these basically end up being the same as groups + with SubMapper(map, controller='organization') as m: + m.connect('organizations_index', '/organization', action='index') + m.connect('organization_index', '/organization', action='index') + m.connect('organization_new', '/organization/new', action='new') + for action in [ + 'delete', + 'admins', + 'member_new', + 'member_delete', + 'history']: + m.connect('organization_' + action, + '/organization/' + action + '/{id}', + action=action) + + m.connect('organization_activity', '/organization/activity/{id}/{offset}', + action='activity', ckan_icon='clock-o') + m.connect('organization_read', '/organization/{id}', action='read') + m.connect('organization_about', '/organization/about/{id}', + action='about', ckan_icon='info-circle') + m.connect('organization_read', '/organization/{id}', action='read', + ckan_icon='sitemap') + m.connect('organization_edit', '/organization/edit/{id}', + action='edit', ckan_icon='pencil-square-o') + m.connect('organization_members', '/organization/members/{id}', + action='members', ckan_icon='users') + m.connect('organization_bulk_process', + '/organization/bulk_process/{id}', + action='bulk_process', ckan_icon='sitemap') + lib_plugins.register_package_plugins(map) + lib_plugins.register_group_plugins(map) + + # tags + map.redirect('/tags', '/tag') + map.redirect('/tags/{url:.*}', '/tag/{url}') + map.redirect('/tag/read/{url:.*}', '/tag/{url}', + _redirect_code='301 Moved Permanently') + map.connect('/tag', controller='tag', action='index') + map.connect('/tag/{id}', controller='tag', action='read') + # users + map.redirect('/users/{url:.*}', '/user/{url}') + + with SubMapper(map, controller='revision') as m: + m.connect('/revision', action='index') + m.connect('/revision/edit/{id}', action='edit') + m.connect('/revision/diff/{id}', action='diff') + m.connect('/revision/list', action='list') + m.connect('/revision/{id}', action='read') + + with SubMapper(map, controller='ckan.controllers.storage:StorageController') as m: + m.connect('storage_file', '/storage/f/{label:.*}', + action='file') + + with SubMapper(map, controller='util') as m: + m.connect('/i18n/strings_{lang}.js', action='i18n_js_strings') + m.connect('/util/redirect', action='redirect') + m.connect('/testing/primer', action='primer') + m.connect('/testing/markup', action='markup') + + # robots.txt + map.connect('/(robots.txt)', controller='template', action='view') + + # Mark all unmarked routes added up until now as core routes + for route in map.matchlist: + if not hasattr(route, '_ckan_core'): + route._ckan_core = True + + for plugin in p.PluginImplementations(p.IRoutes): + map = plugin.after_map(map) + + # Mark all routes added from extensions on the `after_map` extension point + # as non-core + for route in map.matchlist: + if not hasattr(route, '_ckan_core'): + route._ckan_core = False + + # sometimes we get requests for favicon.ico we should redirect to + # the real favicon location. + map.redirect('/favicon.ico', config.get('ckan.favicon')) + + map.redirect('/*(url)/', '/{url}', + _redirect_code='301 Moved Permanently') + map.connect('/*url', controller='template', action='view', ckan_core=True) + + return map diff --git a/venv/lib/python2.7/site-packages/ckan/config/solr/schema.xml b/venv/lib/python2.7/site-packages/ckan/config/solr/schema.xml new file mode 100644 index 00000000..8e5018a2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/config/solr/schema.xml @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +index_id +text + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/venv/lib/python2.7/site-packages/ckan/config/who.ini b/venv/lib/python2.7/site-packages/ckan/config/who.ini new file mode 100644 index 00000000..885a497b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/config/who.ini @@ -0,0 +1,37 @@ +[plugin:auth_tkt] +use = ckan.lib.auth_tkt:make_plugin +# If no secret key is defined here, beaker.session.secret will be used +#secret = somesecret + +[plugin:friendlyform] +use = repoze.who.plugins.friendlyform:FriendlyFormPlugin +login_form_url= /user/login +login_handler_path = /login_generic +logout_handler_path = /user/logout +rememberer_name = auth_tkt +post_login_url = /user/logged_in +post_logout_url = /user/logged_out +charset = utf-8 + +#[plugin:basicauth] +#use = repoze.who.plugins.basicauth:make_plugin +#realm = 'CKAN' + +[general] +request_classifier = repoze.who.classifiers:default_request_classifier +challenge_decider = repoze.who.classifiers:default_challenge_decider + +[identifiers] +plugins = + friendlyform;browser + auth_tkt + +[authenticators] +plugins = + auth_tkt + ckan.lib.authenticator:UsernamePasswordAuthenticator + +[challengers] +plugins = + friendlyform;browser +# basicauth diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/__init__.py b/venv/lib/python2.7/site-packages/ckan/controllers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/admin.py b/venv/lib/python2.7/site-packages/ckan/controllers/admin.py new file mode 100644 index 00000000..3d2f4455 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/admin.py @@ -0,0 +1,191 @@ +# encoding: utf-8 + +from ckan.common import config + +import ckan.lib.base as base +import ckan.lib.helpers as h +import ckan.lib.app_globals as app_globals +import ckan.lib.navl.dictization_functions as dict_fns +import ckan.model as model +import ckan.logic as logic +import ckan.plugins as plugins +from home import CACHE_PARAMETERS + + +c = base.c +request = base.request +_ = base._ + + +def get_sysadmins(): + q = model.Session.query(model.User).filter(model.User.sysadmin == True, + model.User.state == 'active') + return q.all() + + +class AdminController(base.BaseController): + def __before__(self, action, **params): + super(AdminController, self).__before__(action, **params) + context = {'model': model, + 'user': c.user, 'auth_user_obj': c.userobj} + try: + logic.check_access('sysadmin', context, {}) + except logic.NotAuthorized: + base.abort(403, _('Need to be system administrator to administer')) + c.revision_change_state_allowed = True + + def _get_config_form_items(self): + # Styles for use in the form.select() macro. + styles = [{'text': 'Default', 'value': '/base/css/main.css'}, + {'text': 'Red', 'value': '/base/css/red.css'}, + {'text': 'Green', 'value': '/base/css/green.css'}, + {'text': 'Maroon', 'value': '/base/css/maroon.css'}, + {'text': 'Fuchsia', 'value': '/base/css/fuchsia.css'}] + + homepages = [{'value': '1', 'text': 'Introductory area, search, featured group and featured organization'}, + {'value': '2', 'text': 'Search, stats, introductory area, featured organization and featured group'}, + {'value': '3', 'text': 'Search, introductory area and stats'}] + + items = [ + {'name': 'ckan.site_title', 'control': 'input', 'label': _('Site Title'), 'placeholder': ''}, + {'name': 'ckan.main_css', 'control': 'select', 'options': styles, 'label': _('Style'), 'placeholder': ''}, + {'name': 'ckan.site_description', 'control': 'input', 'label': _('Site Tag Line'), 'placeholder': ''}, + {'name': 'ckan.site_logo', 'control': 'image_upload', 'label': _('Site Tag Logo'), 'placeholder': '', 'upload_enabled':h.uploads_enabled(), + 'field_url': 'ckan.site_logo', 'field_upload': 'logo_upload', 'field_clear': 'clear_logo_upload'}, + {'name': 'ckan.site_about', 'control': 'markdown', 'label': _('About'), 'placeholder': _('About page text')}, + {'name': 'ckan.site_intro_text', 'control': 'markdown', 'label': _('Intro Text'), 'placeholder': _('Text on home page')}, + {'name': 'ckan.site_custom_css', 'control': 'textarea', 'label': _('Custom CSS'), 'placeholder': _('Customisable css inserted into the page header')}, + {'name': 'ckan.homepage_style', 'control': 'select', 'options': homepages, 'label': _('Homepage'), 'placeholder': ''}, + ] + return items + + def reset_config(self): + '''FIXME: This method is probably not doing what people would expect. + It will reset the configuration to values cached when CKAN started. + If these were coming from the database during startup, that's the + ones that will get applied on reset, not the ones in the ini file. + Only after restarting the server and having CKAN reset the values + from the ini file (as the db ones are not there anymore) will these + be used. + ''' + + if 'cancel' in request.params: + h.redirect_to(controller='admin', action='config') + + if request.method == 'POST': + # remove sys info items + for item in self._get_config_form_items(): + name = item['name'] + model.delete_system_info(name) + # reset to values in config + app_globals.reset() + h.redirect_to(controller='admin', action='config') + + return base.render('admin/confirm_reset.html') + + def config(self): + + items = self._get_config_form_items() + data = request.POST + if 'save' in data: + try: + # really? + data_dict = logic.clean_dict( + dict_fns.unflatten( + logic.tuplize_dict( + logic.parse_params( + request.POST, ignore_keys=CACHE_PARAMETERS)))) + + del data_dict['save'] + + data = logic.get_action('config_option_update')( + {'user': c.user}, data_dict) + except logic.ValidationError as e: + errors = e.error_dict + error_summary = e.error_summary + vars = {'data': data, 'errors': errors, + 'error_summary': error_summary, 'form_items': items} + return base.render('admin/config.html', extra_vars=vars) + + h.redirect_to(controller='admin', action='config') + + schema = logic.schema.update_configuration_schema() + data = {} + for key in schema: + data[key] = config.get(key) + + vars = {'data': data, 'errors': {}, 'form_items': items} + return base.render('admin/config.html', + extra_vars=vars) + + def index(self): + #now pass the list of sysadmins + c.sysadmins = [a.name for a in get_sysadmins()] + + return base.render('admin/index.html') + + + def trash(self): + c.deleted_revisions = model.Session.query( + model.Revision).filter_by(state=model.State.DELETED) + c.deleted_packages = model.Session.query( + model.Package).filter_by(state=model.State.DELETED) + if not request.params or (len(request.params) == 1 and '__no_cache__' + in request.params): + return base.render('admin/trash.html') + else: + # NB: we repeat retrieval of of revisions + # this is obviously inefficient (but probably not *that* bad) + # but has to be done to avoid (odd) sqlalchemy errors (when doing + # purge packages) of form: "this object already exists in the + # session" + msgs = [] + if ('purge-packages' in request.params) or ('purge-revisions' in + request.params): + if 'purge-packages' in request.params: + revs_to_purge = [] + for pkg in c.deleted_packages: + revisions = [x[0] for x in pkg.all_related_revisions] + # ensure no accidental purging of other(non-deleted) + # packages initially just avoided purging revisions + # where non-deleted packages were affected + # however this lead to confusing outcomes e.g. + # we succesfully deleted revision in which package + # was deleted (so package now active again) but no + # other revisions + problem = False + for r in revisions: + affected_pkgs = set(r.packages).\ + difference(set(c.deleted_packages)) + if affected_pkgs: + msg = _('Cannot purge package %s as ' + 'associated revision %s includes ' + 'non-deleted packages %s') + msg = msg % (pkg.id, r.id, [pkg.id for r + in affected_pkgs]) + msgs.append(msg) + problem = True + break + if not problem: + revs_to_purge += [r.id for r in revisions] + model.Session.remove() + else: + revs_to_purge = [rev.id for rev in c.deleted_revisions] + revs_to_purge = list(set(revs_to_purge)) + for id in revs_to_purge: + revision = model.Session.query(model.Revision).get(id) + try: + # TODO deleting the head revision corrupts the edit + # page Ensure that whatever 'head' pointer is used + # gets moved down to the next revision + model.repo.purge_revision(revision, leave_record=False) + except Exception as inst: + msg = _('Problem purging revision %s: %s') % (id, inst) + msgs.append(msg) + h.flash_success(_('Purge complete')) + else: + msgs.append(_('Action not implemented.')) + + for msg in msgs: + h.flash_error(msg) + h.redirect_to(controller='admin', action='trash') diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/api.py b/venv/lib/python2.7/site-packages/ckan/controllers/api.py new file mode 100644 index 00000000..0093aed7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/api.py @@ -0,0 +1,624 @@ +# encoding: utf-8 + +import os.path +import logging +import cgi +import datetime +import glob +import urllib + +from webob.multidict import UnicodeMultiDict +from paste.util.multidict import MultiDict +from six import text_type + +import ckan.model as model +import ckan.logic as logic +import ckan.lib.base as base +import ckan.lib.helpers as h +import ckan.lib.search as search +import ckan.lib.navl.dictization_functions +import ckan.lib.jsonp as jsonp +import ckan.lib.munge as munge + +from ckan.views import identify_user + +from ckan.common import _, c, request, response + + +log = logging.getLogger(__name__) + +# shortcuts +get_action = logic.get_action +NotAuthorized = logic.NotAuthorized +NotFound = logic.NotFound +ValidationError = logic.ValidationError +DataError = ckan.lib.navl.dictization_functions.DataError + +IGNORE_FIELDS = ['q'] +CONTENT_TYPES = { + 'text': 'text/plain;charset=utf-8', + 'html': 'text/html;charset=utf-8', + 'json': 'application/json;charset=utf-8', + 'javascript': 'application/javascript;charset=utf-8', +} + + +class ApiController(base.BaseController): + + _actions = {} + + def __call__(self, environ, start_response): + # we need to intercept and fix the api version + # as it will have a "/" at the start + routes_dict = environ['pylons.routes_dict'] + api_version = routes_dict.get('ver') + if api_version: + api_version = api_version[1:] + routes_dict['ver'] = int(api_version) + + identify_user() + try: + context = {'model': model, 'user': c.user, + 'auth_user_obj': c.userobj} + logic.check_access('site_read', context) + except NotAuthorized: + response_msg = self._finish(403, + _('Not authorized to see this page')) + # Call start_response manually instead of the parent __call__ + # because we want to end the request instead of continuing. + response_msg = response_msg.encode('utf8') + body = '%i %s' % (response.status_int, response_msg) + start_response(body, response.headers.items()) + return [response_msg] + + # avoid status_code_redirect intercepting error responses + environ['pylons.status_code_redirect'] = True + return base.BaseController.__call__(self, environ, start_response) + + def _finish(self, status_int, response_data=None, + content_type='text'): + '''When a controller method has completed, call this method + to prepare the response. + @return response message - return this value from the controller + method + e.g. return self._finish(404, 'Package not found') + ''' + assert(isinstance(status_int, int)) + response.status_int = status_int + response_msg = '' + if response_data is not None: + response.headers['Content-Type'] = CONTENT_TYPES[content_type] + if content_type == 'json': + response_msg = h.json.dumps( + response_data, + for_json=True) # handle objects with for_json methods + else: + response_msg = response_data + # Support "JSONP" callback. + if (status_int == 200 and + 'callback' in request.params and + request.method == 'GET'): + # escape callback to remove '<', '&', '>' chars + callback = cgi.escape(request.params['callback']) + response_msg = self._wrap_jsonp(callback, response_msg) + response.headers['Content-Type'] = CONTENT_TYPES['javascript'] + return response_msg + + def _finish_ok(self, response_data=None, + content_type='json', + resource_location=None): + '''If a controller method has completed successfully then + calling this method will prepare the response. + @param resource_location - specify this if a new + resource has just been created. + @return response message - return this value from the controller + method + e.g. return self._finish_ok(pkg_dict) + ''' + if resource_location: + status_int = 201 + self._set_response_header('Location', resource_location) + else: + status_int = 200 + + return self._finish(status_int, response_data, content_type) + + def _finish_not_authz(self, extra_msg=None): + response_data = _('Access denied') + if extra_msg: + response_data = '%s - %s' % (response_data, extra_msg) + return self._finish(403, response_data, 'json') + + def _finish_not_found(self, extra_msg=None): + response_data = _('Not found') + if extra_msg: + response_data = '%s - %s' % (response_data, extra_msg) + return self._finish(404, response_data, 'json') + + def _finish_bad_request(self, extra_msg=None): + response_data = _('Bad request') + if extra_msg: + response_data = '%s - %s' % (response_data, extra_msg) + return self._finish(400, response_data, 'json') + + def _wrap_jsonp(self, callback, response_msg): + return '%s(%s);' % (callback, response_msg) + + def _set_response_header(self, name, value): + try: + value = str(value) + except Exception as inst: + msg = "Couldn't convert '%s' header value '%s' to string: %s" % \ + (name, value, inst) + raise Exception(msg) + response.headers[name] = value + + def get_api(self, ver=None): + response_data = {} + response_data['version'] = ver + return self._finish_ok(response_data) + + def action(self, logic_function, ver=None): + try: + function = get_action(logic_function) + except KeyError: + log.info('Can\'t find logic function: %s', logic_function) + return self._finish_bad_request( + _('Action name not known: %s') % logic_function) + + context = {'model': model, 'session': model.Session, 'user': c.user, + 'api_version': ver, 'auth_user_obj': c.userobj} + model.Session()._context = context + + return_dict = {'help': h.url_for(controller='api', + action='action', + logic_function='help_show', + ver=ver, + name=logic_function, + qualified=True, + ) + } + try: + side_effect_free = getattr(function, 'side_effect_free', False) + request_data = self._get_request_data( + try_url_params=side_effect_free) + except ValueError as inst: + log.info('Bad Action API request data: %s', inst) + return self._finish_bad_request( + _('JSON Error: %s') % inst) + if not isinstance(request_data, dict): + # this occurs if request_data is blank + log.info('Bad Action API request data - not dict: %r', + request_data) + return self._finish_bad_request( + _('Bad request data: %s') % + 'Request data JSON decoded to %r but ' + 'it needs to be a dictionary.' % request_data) + # if callback is specified we do not want to send that to the search + if 'callback' in request_data: + del request_data['callback'] + c.user = None + c.userobj = None + context['user'] = None + context['auth_user_obj'] = None + try: + result = function(context, request_data) + return_dict['success'] = True + return_dict['result'] = result + except DataError as e: + log.info('Format incorrect (Action API): %s - %s', + e.error, request_data) + return_dict['error'] = {'__type': 'Integrity Error', + 'message': e.error, + 'data': request_data} + return_dict['success'] = False + return self._finish(400, return_dict, content_type='json') + except NotAuthorized as e: + return_dict['error'] = {'__type': 'Authorization Error', + 'message': _('Access denied')} + return_dict['success'] = False + + if text_type(e): + return_dict['error']['message'] += u': %s' % e + + return self._finish(403, return_dict, content_type='json') + except NotFound as e: + return_dict['error'] = {'__type': 'Not Found Error', + 'message': _('Not found')} + if text_type(e): + return_dict['error']['message'] += u': %s' % e + return_dict['success'] = False + return self._finish(404, return_dict, content_type='json') + except ValidationError as e: + error_dict = e.error_dict + error_dict['__type'] = 'Validation Error' + return_dict['error'] = error_dict + return_dict['success'] = False + # CS nasty_string ignore + log.info('Validation error (Action API): %r', str(e.error_dict)) + return self._finish(409, return_dict, content_type='json') + except search.SearchQueryError as e: + return_dict['error'] = {'__type': 'Search Query Error', + 'message': 'Search Query is invalid: %r' % + e.args} + return_dict['success'] = False + return self._finish(400, return_dict, content_type='json') + except search.SearchError as e: + return_dict['error'] = {'__type': 'Search Error', + 'message': 'Search error: %r' % e.args} + return_dict['success'] = False + return self._finish(409, return_dict, content_type='json') + except search.SearchIndexError as e: + return_dict['error'] = { + '__type': 'Search Index Error', + 'message': 'Unable to add package to search index: %s' % + str(e)} + return_dict['success'] = False + return self._finish(500, return_dict, content_type='json') + return self._finish_ok(return_dict) + + def _get_action_from_map(self, action_map, register, subregister): + ''' Helper function to get the action function specified in + the action map''' + + # translate old package calls to use dataset + if register == 'package': + register = 'dataset' + + action = action_map.get((register, subregister)) + if not action: + action = action_map.get(register) + if action: + return get_action(action) + + def search(self, ver=None, register=None): + + log.debug('search %s params: %r', register, request.params) + if register == 'revision': + since_time = None + if 'since_id' in request.params: + id = request.params['since_id'] + if not id: + return self._finish_bad_request( + _(u'No revision specified')) + rev = model.Session.query(model.Revision).get(id) + if rev is None: + return self._finish_not_found( + _(u'There is no revision with id: %s') % id) + since_time = rev.timestamp + elif 'since_time' in request.params: + since_time_str = request.params['since_time'] + try: + since_time = h.date_str_to_datetime(since_time_str) + except ValueError as inst: + return self._finish_bad_request('ValueError: %s' % inst) + else: + return self._finish_bad_request( + _("Missing search term ('since_id=UUID' or " + + " 'since_time=TIMESTAMP')")) + revs = model.Session.query(model.Revision) \ + .filter(model.Revision.timestamp > since_time) \ + .order_by(model.Revision.timestamp) \ + .limit(50) # reasonable enough for a page + return self._finish_ok([rev.id for rev in revs]) + elif register in ['dataset', 'package', 'resource']: + try: + params = MultiDict(self._get_search_params(request.params)) + except ValueError as e: + return self._finish_bad_request( + _('Could not read parameters: %r' % e)) + + # if using API v2, default to returning the package ID if + # no field list is specified + if register in ['dataset', 'package'] and not params.get('fl'): + params['fl'] = 'id' if ver == 2 else 'name' + + try: + if register == 'resource': + query = search.query_for(model.Resource) + + # resource search still uses ckan query parser + options = search.QueryOptions() + for k, v in params.items(): + if (k in search.DEFAULT_OPTIONS.keys()): + options[k] = v + options.update(params) + options.username = c.user + options.search_tags = False + options.return_objects = False + query_fields = MultiDict() + for field, value in params.items(): + field = field.strip() + if field in search.DEFAULT_OPTIONS.keys() or \ + field in IGNORE_FIELDS: + continue + values = [value] + if isinstance(value, list): + values = value + for v in values: + query_fields.add(field, v) + + results = query.run( + query=params.get('q'), + fields=query_fields, + options=options + ) + else: + # For package searches in API v3 and higher, we can pass + # parameters straight to Solr. + if ver in [1, 2]: + # Otherwise, put all unrecognised ones into the q + # parameter + params = search.\ + convert_legacy_parameters_to_solr(params) + query = search.query_for(model.Package) + + # Remove any existing fq param and set the capacity to + # public + if 'fq' in params: + del params['fq'] + params['fq'] = '+capacity:public' + # if callback is specified we do not want to send that to + # the search + if 'callback' in params: + del params['callback'] + results = query.run(params) + return self._finish_ok(results) + except search.SearchError as e: + log.exception(e) + return self._finish_bad_request( + _('Bad search option: %s') % e) + else: + return self._finish_not_found( + _('Unknown register: %s') % register) + + @classmethod + def _get_search_params(cls, request_params): + if 'qjson' in request_params: + try: + qjson_param = request_params['qjson'].replace('\\\\u', '\\u') + params = h.json.loads(qjson_param, encoding='utf8') + except ValueError as e: + raise ValueError(_('Malformed qjson value: %r') + % e) + elif len(request_params) == 1 and \ + len(request_params.values()[0]) < 2 and \ + request_params.keys()[0].startswith('{'): + # e.g. {some-json}='1' or {some-json}='' + params = h.json.loads(request_params.keys()[0], encoding='utf8') + else: + params = request_params + if not isinstance(params, (UnicodeMultiDict, dict)): + msg = _('Request params must be in form ' + + 'of a json encoded dictionary.') + raise ValueError(msg) + return params + + @jsonp.jsonpify + def user_autocomplete(self): + q = request.params.get('q', '') + limit = request.params.get('limit', 20) + user_list = [] + if q: + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + + data_dict = {'q': q, 'limit': limit} + + user_list = get_action('user_autocomplete')(context, data_dict) + return user_list + + @jsonp.jsonpify + def group_autocomplete(self): + q = request.params.get('q', '') + t = request.params.get('type', None) + limit = request.params.get('limit', 20) + try: + limit = int(limit) + except: + limit = 20 + limit = min(50, limit) + + query = model.Group.search_by_name_or_title(q, t) + + def convert_to_dict(user): + out = {} + for k in ['id', 'name', 'title']: + out[k] = getattr(user, k) + return out + + query = query.limit(limit) + out = map(convert_to_dict, query.all()) + return out + + @jsonp.jsonpify + def organization_autocomplete(self): + q = request.params.get('q', '') + limit = request.params.get('limit', 20) + organization_list = [] + + if q: + context = {'user': c.user, 'model': model} + data_dict = {'q': q, 'limit': limit} + organization_list = \ + get_action('organization_autocomplete')(context, data_dict) + return organization_list + + def dataset_autocomplete(self): + q = request.params.get('incomplete', '') + limit = request.params.get('limit', 10) + package_dicts = [] + if q: + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + + data_dict = {'q': q, 'limit': limit} + + package_dicts = get_action('package_autocomplete')(context, + data_dict) + + resultSet = {'ResultSet': {'Result': package_dicts}} + return self._finish_ok(resultSet) + + def tag_autocomplete(self): + q = request.str_params.get('incomplete', '') + q = text_type(urllib.unquote(q), 'utf-8') + limit = request.params.get('limit', 10) + tag_names = [] + if q: + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + + data_dict = {'q': q, 'limit': limit} + + tag_names = get_action('tag_autocomplete')(context, data_dict) + + resultSet = { + 'ResultSet': { + 'Result': [{'Name': tag} for tag in tag_names] + } + } + return self._finish_ok(resultSet) + + def format_autocomplete(self): + q = request.params.get('incomplete', '') + limit = request.params.get('limit', 5) + formats = [] + if q: + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + data_dict = {'q': q, 'limit': limit} + formats = get_action('format_autocomplete')(context, data_dict) + + resultSet = { + 'ResultSet': { + 'Result': [{'Format': format} for format in formats] + } + } + return self._finish_ok(resultSet) + + def munge_package_name(self): + name = request.params.get('name') + munged_name = munge.munge_name(name) + return self._finish_ok(munged_name) + + def munge_title_to_package_name(self): + name = request.params.get('title') or request.params.get('name') + munged_name = munge.munge_title_to_name(name) + return self._finish_ok(munged_name) + + def munge_tag(self): + tag = request.params.get('tag') or request.params.get('name') + munged_tag = munge.munge_tag(tag) + return self._finish_ok(munged_tag) + + def i18n_js_translations(self, lang): + ''' translation strings for front end ''' + ckan_path = os.path.join(os.path.dirname(__file__), '..') + source = os.path.abspath(os.path.join(ckan_path, 'public', + 'base', 'i18n', '%s.js' % lang)) + response.headers['Content-Type'] = CONTENT_TYPES['json'] + if not os.path.exists(source): + return '{}' + f = open(source, 'r') + return(f) + + @classmethod + def _get_request_data(cls, try_url_params=False): + '''Returns a dictionary, extracted from a request. + + If there is no data, None or "" is returned. + ValueError will be raised if the data is not a JSON-formatted dict. + + The data is retrieved as a JSON-encoded dictionary from the request + body. Or, if the `try_url_params` argument is True and the request is + a GET request, then an attempt is made to read the data from the url + parameters of the request. + + try_url_params + If try_url_params is False, then the data_dict is read from the + request body. + + If try_url_params is True and the request is a GET request then the + data is read from the url parameters. The resulting dict will only + be 1 level deep, with the url-param fields being the keys. If a + single key has more than one value specified, then the value will + be a list of strings, otherwise just a string. + + ''' + def make_unicode(entity): + '''Cast bare strings and strings in lists or dicts to Unicode. ''' + if isinstance(entity, str): + return text_type(entity) + elif isinstance(entity, list): + new_items = [] + for item in entity: + new_items.append(make_unicode(item)) + return new_items + elif isinstance(entity, dict): + new_dict = {} + for key, val in entity.items(): + new_dict[key] = make_unicode(val) + return new_dict + else: + return entity + + cls.log.debug('Retrieving request params: %r', request.params) + cls.log.debug('Retrieving request POST: %r', request.POST) + cls.log.debug('Retrieving request GET: %r', request.GET) + request_data = None + if request.POST and request.content_type == 'multipart/form-data': + request_data = dict(request.POST) + elif request.POST: + try: + keys = request.POST.keys() + # Parsing breaks if there is a = in the value, so for now + # we will check if the data is actually all in a single key + if keys and request.POST[keys[0]] in [u'1', u'']: + request_data = keys[0] + else: + request_data = urllib.unquote_plus(request.body) + except Exception as inst: + msg = "Could not find the POST data: %r : %s" % \ + (request.POST, inst) + raise ValueError(msg) + + elif try_url_params and request.GET: + return request.GET.mixed() + + else: + try: + if request.method in ['POST', 'PUT']: + request_data = request.body + else: + request_data = None + except Exception as inst: + msg = "Could not extract request body data: %s" % \ + (inst) + raise ValueError(msg) + cls.log.debug('Retrieved request body: %r', request.body) + if not request_data: + if not try_url_params: + msg = "Invalid request. Please use POST method" \ + " for your request" + raise ValueError(msg) + else: + request_data = {} + if request_data and request.content_type != 'multipart/form-data': + try: + request_data = h.json.loads(request_data, encoding='utf8') + except ValueError as e: + raise ValueError('Error decoding JSON data. ' + 'Error: %r ' + 'JSON data extracted from the request: %r' % + (e, request_data)) + if not isinstance(request_data, dict): + raise ValueError('Request data JSON decoded to %r but ' + 'it needs to be a dictionary.' % request_data) + # ensure unicode values + for key, val in request_data.items(): + # if val is str then assume it is ascii, since json converts + # utf8 encoded JSON to unicode + request_data[key] = make_unicode(val) + cls.log.debug('Request data extracted: %r', request_data) + return request_data diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/error.py b/venv/lib/python2.7/site-packages/ckan/controllers/error.py new file mode 100644 index 00000000..41e5c82f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/error.py @@ -0,0 +1,65 @@ +# encoding: utf-8 + +import cgi + +from paste.urlparser import PkgResourcesParser +from pylons import request +from pylons.controllers.util import forward +from webhelpers.html.builder import literal + +from ckan.common import c +from ckan.lib.base import BaseController +from ckan.lib.base import render + + +class ErrorController(BaseController): + """Generates error documents as and when they are required. + + The ErrorDocuments middleware forwards to ErrorController when error + related status codes are returned from the application. + + This behaviour can be altered by changing the parameters to the + ErrorDocuments middleware in your config/middleware.py file. + + """ + + def document(self): + """Render the error document""" + original_request = request.environ.get('pylons.original_request') + original_response = request.environ.get('pylons.original_response') + # When a request (e.g. from a web-bot) is direct, not a redirect + # from a page. #1176 + if not original_response: + return 'There is no error.' + # Bypass error template for API operations. + if (original_request and + (original_request.path.startswith('/api') or + original_request.path.startswith('/fanstatic'))): + return original_response.body + # If the charset has been lost on the middleware stack, use the + # default one (utf-8) + if not original_response.charset and original_response.default_charset: + original_response.charset = original_response.default_charset + # Otherwise, decorate original response with error template. + content = literal(original_response.unicode_body) or \ + cgi.escape(request.GET.get('message', '')) + prefix = request.environ.get('SCRIPT_NAME', ''), + code = cgi.escape(request.GET.get('code', + str(original_response.status_int))), + extra_vars = {'code': code, 'content': content, 'prefix': prefix} + return render('error_document_template.html', extra_vars=extra_vars) + + def img(self, id): + """Serve Pylons' stock images""" + return self._serve_file('/'.join(['media/img', id])) + + def style(self, id): + """Serve Pylons' stock stylesheets""" + return self._serve_file('/'.join(['media/style', id])) + + def _serve_file(self, path): + """Call Paste's FileApp (a WSGI application) to serve the file + at the specified path + """ + request.environ['PATH_INFO'] = '/%s' % path + return forward(PkgResourcesParser('pylons', 'pylons')) diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/feed.py b/venv/lib/python2.7/site-packages/ckan/controllers/feed.py new file mode 100644 index 00000000..d14f0276 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/feed.py @@ -0,0 +1,575 @@ +# encoding: utf-8 + +""" +The feed controller produces Atom feeds of datasets. + + * datasets belonging to a particular group. + * datasets tagged with a particular tag. + * datasets that match an arbitrary search. + +TODO: document paged feeds + +Other feeds are available elsewhere in the code, but these provide feeds +of the revision history, rather than a feed of datasets. + + * ``ckan/controllers/group.py`` provides an atom feed of a group's + revision history. + * ``ckan/controllers/package.py`` provides an atom feed of a dataset's + revision history. + * ``ckan/controllers/revision.py`` provides an atom feed of the repository's + revision history. + +""" +# TODO fix imports +import logging +import urlparse + +from six import text_type +import webhelpers.feedgenerator + +import ckan.lib.base as base +import ckan.lib.helpers as h +import ckan.logic as logic +import ckan.model as model +import ckan.plugins as plugins + +from ckan.common import _, config, c, request, response, json + +# TODO make the item list configurable +ITEMS_LIMIT = 20 + +log = logging.getLogger(__name__) + + +def _package_search(data_dict): + """ + Helper method that wraps the package_search action. + + * unless overridden, sorts results by metadata_modified date + * unless overridden, sets a default item limit + """ + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + + if 'sort' not in data_dict or not data_dict['sort']: + data_dict['sort'] = 'metadata_modified desc' + + if 'rows' not in data_dict or not data_dict['rows']: + data_dict['rows'] = ITEMS_LIMIT + + # package_search action modifies the data_dict, so keep our copy intact. + query = logic.get_action('package_search')(context, data_dict.copy()) + + return query['count'], query['results'] + + +def _create_atom_id(resource_path, authority_name=None, date_string=None): + """ + Helper method that creates an atom id for a feed or entry. + + An id must be unique, and must not change over time. ie - once published, + it represents an atom feed or entry uniquely, and forever. See [4]: + + When an Atom Document is relocated, migrated, syndicated, + republished, exported, or imported, the content of its atom:id + element MUST NOT change. Put another way, an atom:id element + pertains to all instantiations of a particular Atom entry or feed; + revisions retain the same content in their atom:id elements. It is + suggested that the atom:id element be stored along with the + associated resource. + + resource_path + The resource path that uniquely identifies the feed or element. This + mustn't be something that changes over time for a given entry or feed. + And does not necessarily need to be resolvable. + + e.g. ``"/group/933f3857-79fd-4beb-a835-c0349e31ce76"`` could represent + the feed of datasets belonging to the identified group. + + authority_name + The domain name or email address of the publisher of the feed. See [3] + for more details. If ``None`` then the domain name is taken from the + config file. First trying ``ckan.feeds.authority_name``, and failing + that, it uses ``ckan.site_url``. Again, this should not change over + time. + + date_string + A string representing a date on which the authority_name is owned by + the publisher of the feed. + + e.g. ``"2012-03-22"`` + + Again, this should not change over time. + + If date_string is None, then an attempt is made to read the config + option ``ckan.feeds.date``. If that's not available, + then the date_string is not used in the generation of the atom id. + + Following the methods outlined in [1], [2] and [3], this function produces + tagURIs like: + ``"tag:thedatahub.org,2012:/group/933f3857-79fd-4beb-a835-c0349e31ce76"``. + + If not enough information is provide to produce a valid tagURI, then only + the resource_path is used, e.g.: :: + + "http://thedatahub.org/group/933f3857-79fd-4beb-a835-c0349e31ce76" + + or + + "/group/933f3857-79fd-4beb-a835-c0349e31ce76" + + The latter of which is only used if no site_url is available. And it + should be noted will result in an invalid feed. + + [1] http://web.archive.org/web/20110514113830/http://diveintomark.org/\ + archives/2004/05/28/howto-atom-id + [2] http://www.taguri.org/ + [3] http://tools.ietf.org/html/rfc4151#section-2.1 + [4] http://www.ietf.org/rfc/rfc4287 + """ + if authority_name is None: + authority_name = config.get('ckan.feeds.authority_name', '').strip() + if not authority_name: + site_url = config.get('ckan.site_url', '').strip() + authority_name = urlparse.urlparse(site_url).netloc + + if not authority_name: + log.warning('No authority_name available for feed generation. ' + 'Generated feed will be invalid.') + + if date_string is None: + date_string = config.get('ckan.feeds.date', '') + + if not date_string: + log.warning('No date_string available for feed generation. ' + 'Please set the "ckan.feeds.date" config value.') + + # Don't generate a tagURI without a date as it wouldn't be valid. + # This is best we can do, and if the site_url is not set, then + # this still results in an invalid feed. + site_url = config.get('ckan.site_url', '') + return '/'.join([site_url, resource_path]) + + tagging_entity = ','.join([authority_name, date_string]) + return ':'.join(['tag', tagging_entity, resource_path]) + + +class FeedController(base.BaseController): + base_url = config.get('ckan.site_url') + + def _alternate_url(self, params, **kwargs): + search_params = params.copy() + search_params.update(kwargs) + + # Can't count on the page sizes being the same on the search results + # view. So provide an alternate link to the first page, regardless + # of the page we're looking at in the feed. + search_params.pop('page', None) + return self._feed_url(search_params, + controller='package', + action='search') + + def _group_or_organization(self, obj_dict, is_org): + + data_dict, params = self._parse_url_params() + if is_org: + key = 'owner_org' + value = obj_dict['id'] + group_type = 'organization' + else: + key = 'groups' + value = obj_dict['name'] + group_type = 'group' + + data_dict['fq'] = '{0}:"{1}"'.format(key, value) + + item_count, results = _package_search(data_dict) + + navigation_urls = self._navigation_urls(params, + item_count=item_count, + limit=data_dict['rows'], + controller='feed', + action=group_type, + id=obj_dict['name']) + feed_url = self._feed_url(params, + controller='feed', + action=group_type, + id=obj_dict['name']) + + site_title = config.get('ckan.site_title', 'CKAN') + if is_org: + guid = _create_atom_id(u'/feeds/organization/%s.atom' % + obj_dict['name']) + alternate_url = self._alternate_url(params, + organization=obj_dict['name']) + desc = u'Recently created or updated datasets on %s '\ + 'by organization: "%s"' % (site_title, obj_dict['title']) + title = u'%s - Organization: "%s"' % (site_title, + obj_dict['title']) + + else: # is group + guid = _create_atom_id(u'/feeds/group/%s.atom' % + obj_dict['name']) + alternate_url = self._alternate_url(params, + groups=obj_dict['name']) + desc = u'Recently created or updated datasets on %s '\ + 'by group: "%s"' % (site_title, obj_dict['title']) + title = u'%s - Group: "%s"' %\ + (site_title, obj_dict['title']) + + return self.output_feed(results, + feed_title=title, + feed_description=desc, + feed_link=alternate_url, + feed_guid=guid, + feed_url=feed_url, + navigation_urls=navigation_urls) + + def group(self, id): + try: + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + group_dict = logic.get_action('group_show')(context, {'id': id}) + except logic.NotFound: + base.abort(404, _('Group not found')) + + return self._group_or_organization(group_dict, is_org=False) + + def organization(self, id): + try: + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + group_dict = logic.get_action('organization_show')(context, + {'id': id}) + except logic.NotFound: + base.abort(404, _('Organization not found')) + + return self._group_or_organization(group_dict, is_org=True) + + def tag(self, id): + data_dict, params = self._parse_url_params() + data_dict['fq'] = 'tags:"%s"' % id + + item_count, results = _package_search(data_dict) + + navigation_urls = self._navigation_urls(params, + item_count=item_count, + limit=data_dict['rows'], + controller='feed', + action='tag', + id=id) + + feed_url = self._feed_url(params, + controller='feed', + action='tag', + id=id) + + alternate_url = self._alternate_url(params, tags=id) + + site_title = config.get('ckan.site_title', 'CKAN') + + return self.output_feed(results, + feed_title=u'%s - Tag: "%s"' % + (site_title, id), + feed_description=u'Recently created or ' + 'updated datasets on %s by tag: "%s"' % + (site_title, id), + feed_link=alternate_url, + feed_guid=_create_atom_id + (u'/feeds/tag/%s.atom' % id), + feed_url=feed_url, + navigation_urls=navigation_urls) + + def general(self): + data_dict, params = self._parse_url_params() + data_dict['q'] = '*:*' + + item_count, results = _package_search(data_dict) + + navigation_urls = self._navigation_urls(params, + item_count=item_count, + limit=data_dict['rows'], + controller='feed', + action='general') + + feed_url = self._feed_url(params, + controller='feed', + action='general') + + alternate_url = self._alternate_url(params) + + site_title = config.get('ckan.site_title', 'CKAN') + + return self.output_feed(results, + feed_title=site_title, + feed_description=u'Recently created or ' + 'updated datasets on %s' % site_title, + feed_link=alternate_url, + feed_guid=_create_atom_id + (u'/feeds/dataset.atom'), + feed_url=feed_url, + navigation_urls=navigation_urls) + + # TODO check search params + def custom(self): + q = request.params.get('q', u'') + fq = '' + search_params = {} + for (param, value) in request.params.items(): + if param not in ['q', 'page', 'sort'] \ + and len(value) and not param.startswith('_'): + search_params[param] = value + fq += ' %s:"%s"' % (param, value) + + page = h.get_page_number(request.params) + + limit = ITEMS_LIMIT + data_dict = { + 'q': q, + 'fq': fq, + 'start': (page - 1) * limit, + 'rows': limit, + 'sort': request.params.get('sort', None), + } + + item_count, results = _package_search(data_dict) + + navigation_urls = self._navigation_urls(request.params, + item_count=item_count, + limit=data_dict['rows'], + controller='feed', + action='custom') + + feed_url = self._feed_url(request.params, + controller='feed', + action='custom') + + atom_url = h._url_with_params('/feeds/custom.atom', + search_params.items()) + + alternate_url = self._alternate_url(request.params) + + site_title = config.get('ckan.site_title', 'CKAN') + + return self.output_feed(results, + feed_title=u'%s - Custom query' % site_title, + feed_description=u'Recently created or updated' + ' datasets on %s. Custom query: \'%s\'' % + (site_title, q), + feed_link=alternate_url, + feed_guid=_create_atom_id(atom_url), + feed_url=feed_url, + navigation_urls=navigation_urls) + + def output_feed(self, results, feed_title, feed_description, + feed_link, feed_url, navigation_urls, feed_guid): + author_name = config.get('ckan.feeds.author_name', '').strip() or \ + config.get('ckan.site_id', '').strip() + author_link = config.get('ckan.feeds.author_link', '').strip() or \ + config.get('ckan.site_url', '').strip() + + # TODO language + feed_class = None + for plugin in plugins.PluginImplementations(plugins.IFeed): + if hasattr(plugin, 'get_feed_class'): + feed_class = plugin.get_feed_class() + + if not feed_class: + feed_class = _FixedAtom1Feed + + feed = feed_class( + feed_title, + feed_link, + feed_description, + language=u'en', + author_name=author_name, + author_link=author_link, + feed_guid=feed_guid, + feed_url=feed_url, + previous_page=navigation_urls['previous'], + next_page=navigation_urls['next'], + first_page=navigation_urls['first'], + last_page=navigation_urls['last'], + ) + + for pkg in results: + additional_fields = {} + + for plugin in plugins.PluginImplementations(plugins.IFeed): + if hasattr(plugin, 'get_item_additional_fields'): + additional_fields = plugin.get_item_additional_fields(pkg) + + feed.add_item( + title=pkg.get('title', ''), + link=self.base_url + h.url_for(controller='package', + action='read', + id=pkg['id']), + description=pkg.get('notes', ''), + updated=h.date_str_to_datetime(pkg.get('metadata_modified')), + published=h.date_str_to_datetime(pkg.get('metadata_created')), + unique_id=_create_atom_id(u'/dataset/%s' % pkg['id']), + author_name=pkg.get('author', ''), + author_email=pkg.get('author_email', ''), + categories=[t['name'] for t in pkg.get('tags', [])], + enclosure=webhelpers.feedgenerator.Enclosure( + h.url_for(controller='api', + register='package', + action='show', + id=pkg['name'], + ver='3', + qualified=True), + text_type(len(json.dumps(pkg))), # TODO fix this + u'application/json'), + **additional_fields + ) + response.content_type = feed.mime_type + return feed.writeString('utf-8') + + # CLASS PRIVATE METHODS # + + def _feed_url(self, query, controller, action, **kwargs): + """ + Constructs the url for the given action. Encoding the query + parameters. + """ + path = h.url_for(controller=controller, action=action, **kwargs) + return h._url_with_params(self.base_url + path, query.items()) + + def _navigation_urls(self, query, controller, action, + item_count, limit, **kwargs): + """ + Constructs and returns first, last, prev and next links for paging + """ + urls = dict((rel, None) for rel in 'previous next first last'.split()) + + page = int(query.get('page', 1)) + + # first: remove any page parameter + first_query = query.copy() + first_query.pop('page', None) + urls['first'] = self._feed_url(first_query, controller, + action, **kwargs) + + # last: add last page parameter + last_page = (item_count / limit) + min(1, item_count % limit) + last_query = query.copy() + last_query['page'] = last_page + urls['last'] = self._feed_url(last_query, controller, + action, **kwargs) + + # previous + if page > 1: + previous_query = query.copy() + previous_query['page'] = page - 1 + urls['previous'] = self._feed_url(previous_query, controller, + action, **kwargs) + else: + urls['previous'] = None + + # next + if page < last_page: + next_query = query.copy() + next_query['page'] = page + 1 + urls['next'] = self._feed_url(next_query, controller, + action, **kwargs) + else: + urls['next'] = None + + return urls + + def _parse_url_params(self): + """ + Constructs a search-query dict from the URL query parameters. + + Returns the constructed search-query dict, and the valid URL + query parameters. + """ + page = h.get_page_number(request.params) + + limit = ITEMS_LIMIT + data_dict = { + 'start': (page - 1) * limit, + 'rows': limit + } + + # Filter ignored query parameters + valid_params = ['page'] + params = dict((p, request.params.get(p)) for p in valid_params + if p in request.params) + return data_dict, params + + +# TODO paginated feed +class _FixedAtom1Feed(webhelpers.feedgenerator.Atom1Feed): + """ + The Atom1Feed defined in webhelpers doesn't provide all the fields we + might want to publish. + + * In Atom1Feed, each is created with identical and + fields. See [1] (webhelpers 1.2) for details. + + So, this class fixes that by allow an item to set both an and + field. + + * In Atom1Feed, the feed description is not used. So this class uses the + field to publish that. + + [1] https://bitbucket.org/bbangert/webhelpers/src/f5867a319abf/\ + webhelpers/feedgenerator.py#cl-373 + """ + + def add_item(self, *args, **kwargs): + """ + Drop the pubdate field from the new item. + """ + if 'pubdate' in kwargs: + kwargs.pop('pubdate') + defaults = {'updated': None, 'published': None} + defaults.update(kwargs) + super(_FixedAtom1Feed, self).add_item(*args, **defaults) + + def latest_post_date(self): + """ + Calculates the latest post date from the 'updated' fields, + rather than the 'pubdate' fields. + """ + updates = [item['updated'] for item in self.items + if item['updated'] is not None] + if not len(updates): # delegate to parent for default behaviour + return super(_FixedAtom1Feed, self).latest_post_date() + return max(updates) + + def add_item_elements(self, handler, item): + """ + Add the and fields to each entry that's written + to the handler. + """ + super(_FixedAtom1Feed, self).add_item_elements(handler, item) + + dfunc = webhelpers.feedgenerator.rfc3339_date + + if(item['updated']): + handler.addQuickElement(u'updated', + dfunc(item['updated']).decode('utf-8')) + + if(item['published']): + handler.addQuickElement(u'published', + dfunc(item['published']).decode('utf-8')) + + def add_root_elements(self, handler): + """ + Add additional feed fields. + + * Add the field from the feed description + * Add links other pages of the logical feed. + """ + super(_FixedAtom1Feed, self).add_root_elements(handler) + + handler.addQuickElement(u'subtitle', self.feed['description']) + + for page in ['previous', 'next', 'first', 'last']: + if self.feed.get(page + '_page', None): + handler.addQuickElement(u'link', u'', + {'rel': page, + 'href': + self.feed.get(page + '_page')}) diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/group.py b/venv/lib/python2.7/site-packages/ckan/controllers/group.py new file mode 100644 index 00000000..6aa3d1f7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/group.py @@ -0,0 +1,933 @@ +# encoding: utf-8 + +import logging +import datetime +from urllib import urlencode + +from pylons.i18n import get_lang +from six import string_types, text_type + +import ckan.lib.base as base +import ckan.lib.helpers as h +import ckan.lib.navl.dictization_functions as dict_fns +import ckan.logic as logic +import ckan.lib.search as search +import ckan.model as model +import ckan.authz as authz +import ckan.lib.plugins +import ckan.plugins as plugins +from ckan.common import OrderedDict, c, config, request, _ + +log = logging.getLogger(__name__) + +render = base.render +abort = base.abort + +NotFound = logic.NotFound +NotAuthorized = logic.NotAuthorized +ValidationError = logic.ValidationError +check_access = logic.check_access +get_action = logic.get_action +tuplize_dict = logic.tuplize_dict +clean_dict = logic.clean_dict +parse_params = logic.parse_params + +lookup_group_plugin = ckan.lib.plugins.lookup_group_plugin +lookup_group_controller = ckan.lib.plugins.lookup_group_controller + + +class GroupController(base.BaseController): + + group_types = ['group'] + + # hooks for subclasses + + def _group_form(self, group_type=None): + return lookup_group_plugin(group_type).group_form() + + def _form_to_db_schema(self, group_type=None): + return lookup_group_plugin(group_type).form_to_db_schema() + + def _db_to_form_schema(self, group_type=None): + '''This is an interface to manipulate data from the database + into a format suitable for the form (optional)''' + return lookup_group_plugin(group_type).db_to_form_schema() + + def _setup_template_variables(self, context, data_dict, group_type=None): + if 'type' not in data_dict: + data_dict['type'] = group_type + return lookup_group_plugin(group_type).\ + setup_template_variables(context, data_dict) + + def _new_template(self, group_type): + return lookup_group_plugin(group_type).new_template() + + def _index_template(self, group_type): + return lookup_group_plugin(group_type).index_template() + + def _about_template(self, group_type): + return lookup_group_plugin(group_type).about_template() + + def _read_template(self, group_type): + return lookup_group_plugin(group_type).read_template() + + def _history_template(self, group_type): + return lookup_group_plugin(group_type).history_template() + + def _edit_template(self, group_type): + return lookup_group_plugin(group_type).edit_template() + + def _activity_template(self, group_type): + return lookup_group_plugin(group_type).activity_template() + + def _admins_template(self, group_type): + return lookup_group_plugin(group_type).admins_template() + + def _bulk_process_template(self, group_type): + return lookup_group_plugin(group_type).bulk_process_template() + + # end hooks + def _replace_group_org(self, string): + ''' substitute organization for group if this is an org''' + return string + + def _action(self, action_name): + ''' select the correct group/org action ''' + return get_action(self._replace_group_org(action_name)) + + def _check_access(self, action_name, *args, **kw): + ''' select the correct group/org check_access ''' + return check_access(self._replace_group_org(action_name), *args, **kw) + + def _render_template(self, template_name, group_type): + ''' render the correct group/org template ''' + return render(self._replace_group_org(template_name), + extra_vars={'group_type': group_type}) + + def _guess_group_type(self, expecting_name=False): + """ + Guess the type of group from the URL. + * The default url '/group/xyz' returns None + * group_type is unicode + * this handles the case where there is a prefix on the URL + (such as /data/organization) + """ + parts = [x for x in request.path.split('/') if x] + + idx = -1 + if expecting_name: + idx = -2 + + gt = parts[idx] + + return gt + + def _ensure_controller_matches_group_type(self, id): + group = model.Group.get(id) + if group is None: + abort(404, _('Group not found')) + if group.type not in self.group_types: + abort(404, _('Incorrect group type')) + return group.type + + @classmethod + def add_group_type(cls, group_type): + ''' Notify this controller that it is to be used for a particular + group_type. (Called on plugin registration.) + ''' + cls.group_types.append(group_type) + + def index(self): + group_type = self._guess_group_type() + + page = h.get_page_number(request.params) or 1 + items_per_page = 21 + + context = {'model': model, 'session': model.Session, + 'user': c.user, 'for_view': True, + 'with_private': False} + + q = c.q = request.params.get('q', '') + sort_by = c.sort_by_selected = request.params.get('sort') + try: + self._check_access('site_read', context) + self._check_access('group_list', context) + except NotAuthorized: + abort(403, _('Not authorized to see this page')) + + # pass user info to context as needed to view private datasets of + # orgs correctly + if c.userobj: + context['user_id'] = c.userobj.id + context['user_is_admin'] = c.userobj.sysadmin + + try: + data_dict_global_results = { + 'all_fields': False, + 'q': q, + 'sort': sort_by, + 'type': group_type or 'group', + } + global_results = self._action('group_list')( + context, data_dict_global_results) + except ValidationError as e: + if e.error_dict and e.error_dict.get('message'): + msg = e.error_dict['message'] + else: + msg = str(e) + h.flash_error(msg) + c.page = h.Page([], 0) + return render(self._index_template(group_type), + extra_vars={'group_type': group_type}) + + data_dict_page_results = { + 'all_fields': True, + 'q': q, + 'sort': sort_by, + 'type': group_type or 'group', + 'limit': items_per_page, + 'offset': items_per_page * (page - 1), + 'include_extras': True + } + page_results = self._action('group_list')(context, + data_dict_page_results) + + c.page = h.Page( + collection=global_results, + page=page, + url=h.pager_url, + items_per_page=items_per_page, + ) + + c.page.items = page_results + return render(self._index_template(group_type), + extra_vars={'group_type': group_type}) + + def read(self, id, limit=20): + group_type = self._ensure_controller_matches_group_type( + id.split('@')[0]) + + context = {'model': model, 'session': model.Session, + 'user': c.user, + 'schema': self._db_to_form_schema(group_type=group_type), + 'for_view': True} + data_dict = {'id': id, 'type': group_type} + + # unicode format (decoded from utf8) + c.q = request.params.get('q', '') + + try: + # Do not query for the group datasets when dictizing, as they will + # be ignored and get requested on the controller anyway + data_dict['include_datasets'] = False + c.group_dict = self._action('group_show')(context, data_dict) + c.group = context['group'] + except (NotFound, NotAuthorized): + abort(404, _('Group not found')) + + self._read(id, limit, group_type) + return render(self._read_template(c.group_dict['type']), + extra_vars={'group_type': group_type}) + + def _read(self, id, limit, group_type): + ''' This is common code used by both read and bulk_process''' + context = {'model': model, 'session': model.Session, + 'user': c.user, + 'schema': self._db_to_form_schema(group_type=group_type), + 'for_view': True, 'extras_as_string': True} + + q = c.q = request.params.get('q', '') + # Search within group + if c.group_dict.get('is_organization'): + fq = 'owner_org:"%s"' % c.group_dict.get('id') + else: + fq = 'groups:"%s"' % c.group_dict.get('name') + + c.description_formatted = \ + h.render_markdown(c.group_dict.get('description')) + + context['return_query'] = True + + page = h.get_page_number(request.params) + + # most search operations should reset the page counter: + params_nopage = [(k, v) for k, v in request.params.items() + if k != 'page'] + sort_by = request.params.get('sort', None) + + def search_url(params): + controller = lookup_group_controller(group_type) + action = 'bulk_process' if c.action == 'bulk_process' else 'read' + url = h.url_for(controller=controller, action=action, id=id) + params = [(k, v.encode('utf-8') if isinstance(v, string_types) + else str(v)) for k, v in params] + return url + u'?' + urlencode(params) + + def drill_down_url(**by): + return h.add_url_param(alternative_url=None, + controller='group', action='read', + extras=dict(id=c.group_dict.get('name')), + new_params=by) + + c.drill_down_url = drill_down_url + + def remove_field(key, value=None, replace=None): + controller = lookup_group_controller(group_type) + return h.remove_url_param(key, value=value, replace=replace, + controller=controller, action='read', + extras=dict(id=c.group_dict.get('name'))) + + c.remove_field = remove_field + + def pager_url(q=None, page=None): + params = list(params_nopage) + params.append(('page', page)) + return search_url(params) + + try: + c.fields = [] + c.fields_grouped = {} + search_extras = {} + for (param, value) in request.params.items(): + if param not in ['q', 'page', 'sort'] \ + and len(value) and not param.startswith('_'): + if not param.startswith('ext_'): + c.fields.append((param, value)) + q += ' %s: "%s"' % (param, value) + if param not in c.fields_grouped: + c.fields_grouped[param] = [value] + else: + c.fields_grouped[param].append(value) + else: + search_extras[param] = value + + facets = OrderedDict() + + default_facet_titles = {'organization': _('Organizations'), + 'groups': _('Groups'), + 'tags': _('Tags'), + 'res_format': _('Formats'), + 'license_id': _('Licenses')} + + for facet in h.facets(): + if facet in default_facet_titles: + facets[facet] = default_facet_titles[facet] + else: + facets[facet] = facet + + # Facet titles + self._update_facet_titles(facets, group_type) + + c.facet_titles = facets + + data_dict = { + 'q': q, + 'fq': fq, + 'include_private': True, + 'facet.field': facets.keys(), + 'rows': limit, + 'sort': sort_by, + 'start': (page - 1) * limit, + 'extras': search_extras + } + + context_ = dict((k, v) for (k, v) in context.items() + if k != 'schema') + query = get_action('package_search')(context_, data_dict) + + c.page = h.Page( + collection=query['results'], + page=page, + url=pager_url, + item_count=query['count'], + items_per_page=limit + ) + + c.group_dict['package_count'] = query['count'] + + c.search_facets = query['search_facets'] + c.search_facets_limits = {} + for facet in c.search_facets.keys(): + limit = int(request.params.get('_%s_limit' % facet, + config.get('search.facets.default', 10))) + c.search_facets_limits[facet] = limit + c.page.items = query['results'] + + c.sort_by_selected = sort_by + + except search.SearchError as se: + log.error('Group search error: %r', se.args) + c.query_error = True + c.page = h.Page(collection=[]) + + self._setup_template_variables(context, {'id': id}, + group_type=group_type) + + def _update_facet_titles(self, facets, group_type): + for plugin in plugins.PluginImplementations(plugins.IFacets): + facets = plugin.group_facets( + facets, group_type, None) + + def bulk_process(self, id): + ''' Allow bulk processing of datasets for an organization. Make + private/public or delete. For organization admins.''' + + group_type = self._ensure_controller_matches_group_type( + id.split('@')[0]) + + # check we are org admin + + context = {'model': model, 'session': model.Session, + 'user': c.user, + 'schema': self._db_to_form_schema(group_type=group_type), + 'for_view': True, 'extras_as_string': True} + data_dict = {'id': id, 'type': group_type} + + try: + self._check_access('bulk_update_public', context, {'org_id': id}) + # Do not query for the group datasets when dictizing, as they will + # be ignored and get requested on the controller anyway + data_dict['include_datasets'] = False + c.group_dict = self._action('group_show')(context, data_dict) + c.group = context['group'] + except NotFound: + abort(404, _('Group not found')) + except NotAuthorized: + abort(403, _('User %r not authorized to edit %s') % (c.user, id)) + + if not c.group_dict['is_organization']: + # FIXME: better error + raise Exception('Must be an organization') + + # use different form names so that ie7 can be detected + form_names = set(["bulk_action.public", "bulk_action.delete", + "bulk_action.private"]) + actions_in_form = set(request.params.keys()) + actions = form_names.intersection(actions_in_form) + # If no action then just show the datasets + if not actions: + # unicode format (decoded from utf8) + limit = 500 + self._read(id, limit, group_type) + c.packages = c.page.items + return render(self._bulk_process_template(group_type), + extra_vars={'group_type': group_type}) + + # ie7 puts all buttons in form params but puts submitted one twice + for key, value in dict(request.params.dict_of_lists()).items(): + if len(value) == 2: + action = key.split('.')[-1] + break + else: + # normal good browser form submission + action = actions.pop().split('.')[-1] + + # process the action first find the datasets to perform the action on. + # they are prefixed by dataset_ in the form data + datasets = [] + for param in request.params: + if param.startswith('dataset_'): + datasets.append(param[8:]) + + action_functions = { + 'private': 'bulk_update_private', + 'public': 'bulk_update_public', + 'delete': 'bulk_update_delete', + } + + data_dict = {'datasets': datasets, 'org_id': c.group_dict['id']} + + try: + get_action(action_functions[action])(context, data_dict) + except NotAuthorized: + abort(403, _('Not authorized to perform bulk update')) + h.redirect_to(group_type + '_bulk_process', id=id) + + def new(self, data=None, errors=None, error_summary=None): + if data and 'type' in data: + group_type = data['type'] + else: + group_type = self._guess_group_type(True) + if data: + data['type'] = group_type + + context = {'model': model, 'session': model.Session, + 'user': c.user, + 'save': 'save' in request.params, + 'parent': request.params.get('parent', None)} + try: + self._check_access('group_create', context) + except NotAuthorized: + abort(403, _('Unauthorized to create a group')) + + if context['save'] and not data and request.method == 'POST': + return self._save_new(context, group_type) + + data = data or {} + if not data.get('image_url', '').startswith('http'): + data.pop('image_url', None) + + errors = errors or {} + error_summary = error_summary or {} + vars = {'data': data, 'errors': errors, + 'error_summary': error_summary, 'action': 'new', + 'group_type': group_type} + + self._setup_template_variables(context, data, group_type=group_type) + c.form = render(self._group_form(group_type=group_type), + extra_vars=vars) + return render(self._new_template(group_type), + extra_vars={'group_type': group_type}) + + def edit(self, id, data=None, errors=None, error_summary=None): + group_type = self._ensure_controller_matches_group_type( + id.split('@')[0]) + + context = {'model': model, 'session': model.Session, + 'user': c.user, + 'save': 'save' in request.params, + 'for_edit': True, + 'parent': request.params.get('parent', None) + } + data_dict = {'id': id, 'include_datasets': False} + + if context['save'] and not data and request.method == 'POST': + return self._save_edit(id, context) + + try: + data_dict['include_datasets'] = False + old_data = self._action('group_show')(context, data_dict) + c.grouptitle = old_data.get('title') + c.groupname = old_data.get('name') + data = data or old_data + except (NotFound, NotAuthorized): + abort(404, _('Group not found')) + + group = context.get("group") + c.group = group + c.group_dict = self._action('group_show')(context, data_dict) + + try: + self._check_access('group_update', context) + except NotAuthorized: + abort(403, _('User %r not authorized to edit %s') % (c.user, id)) + + errors = errors or {} + vars = {'data': data, 'errors': errors, + 'error_summary': error_summary, 'action': 'edit', + 'group_type': group_type} + + self._setup_template_variables(context, data, group_type=group_type) + c.form = render(self._group_form(group_type), extra_vars=vars) + return render(self._edit_template(c.group.type), + extra_vars={'group_type': group_type}) + + def _save_new(self, context, group_type=None): + try: + data_dict = clean_dict(dict_fns.unflatten( + tuplize_dict(parse_params(request.params)))) + data_dict['type'] = group_type or 'group' + context['message'] = data_dict.get('log_message', '') + data_dict['users'] = [{'name': c.user, 'capacity': 'admin'}] + group = self._action('group_create')(context, data_dict) + + # Redirect to the appropriate _read route for the type of group + h.redirect_to(group['type'] + '_read', id=group['name']) + except (NotFound, NotAuthorized) as e: + abort(404, _('Group not found')) + except dict_fns.DataError: + abort(400, _(u'Integrity Error')) + except ValidationError as e: + errors = e.error_dict + error_summary = e.error_summary + return self.new(data_dict, errors, error_summary) + + def _force_reindex(self, grp): + ''' When the group name has changed, we need to force a reindex + of the datasets within the group, otherwise they will stop + appearing on the read page for the group (as they're connected via + the group name)''' + group = model.Group.get(grp['name']) + for dataset in group.packages(): + search.rebuild(dataset.name) + + def _save_edit(self, id, context): + try: + data_dict = clean_dict(dict_fns.unflatten( + tuplize_dict(parse_params(request.params)))) + context['message'] = data_dict.get('log_message', '') + data_dict['id'] = id + context['allow_partial_update'] = True + group = self._action('group_update')(context, data_dict) + if id != group['name']: + self._force_reindex(group) + + h.redirect_to('%s_read' % group['type'], id=group['name']) + except (NotFound, NotAuthorized) as e: + abort(404, _('Group not found')) + except dict_fns.DataError: + abort(400, _(u'Integrity Error')) + except ValidationError as e: + errors = e.error_dict + error_summary = e.error_summary + return self.edit(id, data_dict, errors, error_summary) + + def authz(self, id): + group = model.Group.get(id) + if group is None: + abort(404, _('Group not found')) + group_type = group.type + if group_type not in self.group_types: + abort(404, _('Incorrect group type')) + c.groupname = group.name + c.grouptitle = group.display_name + + try: + context = \ + {'model': model, 'user': c.user, 'group': group} + self._check_access('group_edit_permissions', context) + c.authz_editable = True + c.group = context['group'] + except NotAuthorized: + c.authz_editable = False + if not c.authz_editable: + abort(403, + _('User %r not authorized to edit %s authorizations') % + (c.user, id)) + + roles = self._handle_update_of_authz(group) + self._prepare_authz_info_for_render(roles) + return render('group/authz.html', + extra_vars={'group_type': group_type}) + + def delete(self, id): + group_type = self._ensure_controller_matches_group_type(id) + + if 'cancel' in request.params: + h.redirect_to(group_type + '_edit', id=id) + + context = {'model': model, 'session': model.Session, + 'user': c.user} + + try: + self._check_access('group_delete', context, {'id': id}) + except NotAuthorized: + abort(403, _('Unauthorized to delete group %s') % '') + + try: + if request.method == 'POST': + self._action('group_delete')(context, {'id': id}) + if group_type == 'organization': + h.flash_notice(_('Organization has been deleted.')) + elif group_type == 'group': + h.flash_notice(_('Group has been deleted.')) + else: + h.flash_notice(_('%s has been deleted.') + % _(group_type.capitalize())) + h.redirect_to(group_type + '_index') + c.group_dict = self._action('group_show')(context, {'id': id}) + except NotAuthorized: + abort(403, _('Unauthorized to delete group %s') % '') + except NotFound: + abort(404, _('Group not found')) + except ValidationError as e: + h.flash_error(e.error_dict['message']) + h.redirect_to(controller='organization', action='read', id=id) + return self._render_template('group/confirm_delete.html', group_type) + + def members(self, id): + group_type = self._ensure_controller_matches_group_type(id) + + context = {'model': model, 'session': model.Session, + 'user': c.user} + + data_dict = {'id': id} + try: + check_access('group_edit_permissions', context, data_dict) + except NotAuthorized: + abort(403, + _('User %r not authorized to edit members of %s') % (c.user, + id)) + try: + c.members = self._action('member_list')( + context, {'id': id, 'object_type': 'user'} + ) + data_dict['include_datasets'] = False + c.group_dict = self._action('group_show')(context, data_dict) + except NotFound: + abort(404, _('Group not found')) + + return self._render_template('group/members.html', group_type) + + def member_new(self, id): + group_type = self._ensure_controller_matches_group_type(id) + + context = {'model': model, 'session': model.Session, + 'user': c.user} + try: + self._check_access('group_member_create', context, {'id': id}) + except NotAuthorized: + abort(403, _('Unauthorized to create group %s members') % '') + + try: + data_dict = {'id': id} + data_dict['include_datasets'] = False + c.group_dict = self._action('group_show')(context, data_dict) + c.roles = self._action('member_roles_list')( + context, {'group_type': group_type} + ) + + if request.method == 'POST': + data_dict = clean_dict(dict_fns.unflatten( + tuplize_dict(parse_params(request.params)))) + data_dict['id'] = id + + email = data_dict.get('email') + + if email: + user_data_dict = { + 'email': email, + 'group_id': data_dict['id'], + 'role': data_dict['role'] + } + del data_dict['email'] + user_dict = self._action('user_invite')( + context, user_data_dict) + data_dict['username'] = user_dict['name'] + + c.group_dict = self._action('group_member_create')( + context, data_dict) + + h.redirect_to(group_type + '_members', id=id) + else: + user = request.params.get('user') + if user: + c.user_dict = \ + get_action('user_show')(context, {'id': user}) + c.user_role = \ + authz.users_role_for_group_or_org(id, user) or 'member' + else: + c.user_role = 'member' + except NotAuthorized: + abort(403, _('Unauthorized to add member to group %s') % '') + except NotFound: + abort(404, _('Group not found')) + except ValidationError as e: + h.flash_error(e.error_summary) + return self._render_template('group/member_new.html', group_type) + + def member_delete(self, id): + group_type = self._ensure_controller_matches_group_type(id) + + if 'cancel' in request.params: + h.redirect_to(group_type + '_members', id=id) + + context = {'model': model, 'session': model.Session, + 'user': c.user} + + try: + self._check_access('group_member_delete', context, {'id': id}) + except NotAuthorized: + abort(403, _('Unauthorized to delete group %s members') % '') + + try: + user_id = request.params.get('user') + if request.method == 'POST': + self._action('group_member_delete')( + context, {'id': id, 'user_id': user_id}) + h.flash_notice(_('Group member has been deleted.')) + h.redirect_to(group_type + '_members', id=id) + c.user_dict = self._action('user_show')(context, {'id': user_id}) + c.user_id = user_id + c.group_id = id + except NotAuthorized: + abort(403, _('Unauthorized to delete group %s members') % '') + except NotFound: + abort(404, _('Group not found')) + return self._render_template('group/confirm_delete_member.html', + group_type) + + def history(self, id): + group_type = self._ensure_controller_matches_group_type(id) + if 'diff' in request.params or 'selected1' in request.params: + try: + params = {'id': request.params.getone('group_name'), + 'diff': request.params.getone('selected1'), + 'oldid': request.params.getone('selected2'), + } + except KeyError: + if 'group_name' in dict(request.params): + id = request.params.getone('group_name') + c.error = \ + _('Select two revisions before doing the comparison.') + else: + params['diff_entity'] = 'group' + h.redirect_to(controller='revision', action='diff', **params) + + context = {'model': model, 'session': model.Session, + 'user': c.user, + 'schema': self._db_to_form_schema()} + data_dict = {'id': id} + try: + c.group_dict = self._action('group_show')(context, data_dict) + c.group_revisions = self._action('group_revision_list')(context, + data_dict) + # TODO: remove + # Still necessary for the authz check in group/layout.html + c.group = context['group'] + except (NotFound, NotAuthorized): + abort(404, _('Group not found')) + + format = request.params.get('format', '') + if format == 'atom': + # Generate and return Atom 1.0 document. + from webhelpers.feedgenerator import Atom1Feed + feed = Atom1Feed( + title=_(u'CKAN Group Revision History'), + link=h.url_for( + group_type + '_read', + id=c.group_dict['name']), + description=_(u'Recent changes to CKAN Group: ') + + c.group_dict['display_name'], + language=text_type(get_lang()), + ) + for revision_dict in c.group_revisions: + revision_date = h.date_str_to_datetime( + revision_dict['timestamp']) + try: + dayHorizon = int(request.params.get('days')) + except: + dayHorizon = 30 + dayAge = (datetime.datetime.now() - revision_date).days + if dayAge >= dayHorizon: + break + if revision_dict['message']: + item_title = u'%s' % revision_dict['message'].\ + split('\n')[0] + else: + item_title = u'%s' % revision_dict['id'] + item_link = h.url_for(controller='revision', action='read', + id=revision_dict['id']) + item_description = _('Log message: ') + item_description += '%s' % (revision_dict['message'] or '') + item_author_name = revision_dict['author'] + item_pubdate = revision_date + feed.add_item( + title=item_title, + link=item_link, + description=item_description, + author_name=item_author_name, + pubdate=item_pubdate, + ) + feed.content_type = 'application/atom+xml' + return feed.writeString('utf-8') + return render(self._history_template(group_type), + extra_vars={'group_type': group_type}) + + def activity(self, id, offset=0): + '''Render this group's public activity stream page.''' + + group_type = self._ensure_controller_matches_group_type(id) + context = {'model': model, 'session': model.Session, + 'user': c.user, 'for_view': True} + try: + c.group_dict = self._get_group_dict(id) + except (NotFound, NotAuthorized): + abort(404, _('Group not found')) + + try: + # Add the group's activity stream (already rendered to HTML) to the + # template context for the group/read.html + # template to retrieve later. + c.group_activity_stream = self._action('group_activity_list_html')( + context, {'id': c.group_dict['id'], 'offset': offset}) + + except ValidationError as error: + base.abort(400) + + return render(self._activity_template(group_type), + extra_vars={'group_type': group_type}) + + def follow(self, id): + '''Start following this group.''' + self._ensure_controller_matches_group_type(id) + context = {'model': model, + 'session': model.Session, + 'user': c.user} + data_dict = {'id': id} + try: + get_action('follow_group')(context, data_dict) + group_dict = get_action('group_show')(context, data_dict) + h.flash_success(_("You are now following {0}").format( + group_dict['title'])) + except ValidationError as e: + error_message = (e.message or e.error_summary + or e.error_dict) + h.flash_error(error_message) + except NotAuthorized as e: + h.flash_error(e.message) + h.redirect_to(controller='group', action='read', id=id) + + def unfollow(self, id): + '''Stop following this group.''' + self._ensure_controller_matches_group_type(id) + context = {'model': model, + 'session': model.Session, + 'user': c.user} + data_dict = {'id': id} + try: + get_action('unfollow_group')(context, data_dict) + group_dict = get_action('group_show')(context, data_dict) + h.flash_success(_("You are no longer following {0}").format( + group_dict['title'])) + except ValidationError as e: + error_message = (e.message or e.error_summary + or e.error_dict) + h.flash_error(error_message) + except (NotFound, NotAuthorized) as e: + error_message = e.message + h.flash_error(error_message) + h.redirect_to(controller='group', action='read', id=id) + + def followers(self, id): + group_type = self._ensure_controller_matches_group_type(id) + context = {'model': model, 'session': model.Session, + 'user': c.user} + c.group_dict = self._get_group_dict(id) + try: + c.followers = \ + get_action('group_follower_list')(context, {'id': id}) + except NotAuthorized: + abort(403, _('Unauthorized to view followers %s') % '') + return render('group/followers.html', + extra_vars={'group_type': group_type}) + + def admins(self, id): + group_type = self._ensure_controller_matches_group_type(id) + c.group_dict = self._get_group_dict(id) + c.admins = authz.get_group_or_org_admin_ids(id) + return render(self._admins_template(c.group_dict['type']), + extra_vars={'group_type': group_type}) + + def about(self, id): + group_type = self._ensure_controller_matches_group_type(id) + context = {'model': model, 'session': model.Session, + 'user': c.user} + c.group_dict = self._get_group_dict(id) + group_type = c.group_dict['type'] + self._setup_template_variables(context, {'id': id}, + group_type=group_type) + return render(self._about_template(group_type), + extra_vars={'group_type': group_type}) + + def _get_group_dict(self, id): + ''' returns the result of group_show action or aborts if there is a + problem ''' + context = {'model': model, 'session': model.Session, + 'user': c.user, + 'for_view': True} + try: + return self._action('group_show')( + context, {'id': id, 'include_datasets': False}) + except (NotFound, NotAuthorized): + abort(404, _('Group not found')) diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/home.py b/venv/lib/python2.7/site-packages/ckan/controllers/home.py new file mode 100644 index 00000000..bce91660 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/home.py @@ -0,0 +1,99 @@ +# encoding: utf-8 + +from pylons import cache +import sqlalchemy.exc + +import ckan.logic as logic +import ckan.lib.search as search +import ckan.lib.base as base +import ckan.model as model +import ckan.lib.helpers as h + +from ckan.common import _, config, c + +CACHE_PARAMETERS = ['__cache', '__no_cache__'] + + +class HomeController(base.BaseController): + repo = model.repo + + def __before__(self, action, **env): + try: + base.BaseController.__before__(self, action, **env) + context = {'model': model, 'user': c.user, + 'auth_user_obj': c.userobj} + logic.check_access('site_read', context) + except logic.NotAuthorized: + base.abort(403, _('Not authorized to see this page')) + except (sqlalchemy.exc.ProgrammingError, + sqlalchemy.exc.OperationalError) as e: + # postgres and sqlite errors for missing tables + msg = str(e) + if ('relation' in msg and 'does not exist' in msg) or \ + ('no such table' in msg): + # table missing, major database problem + base.abort(503, _('This site is currently off-line. Database ' + 'is not initialised.')) + # TODO: send an email to the admin person (#1285) + else: + raise + + def index(self): + try: + # package search + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + data_dict = { + 'q': '*:*', + 'facet.field': h.facets(), + 'rows': 4, + 'start': 0, + 'sort': 'views_recent desc', + 'fq': 'capacity:"public"' + } + query = logic.get_action('package_search')( + context, data_dict) + c.search_facets = query['search_facets'] + c.package_count = query['count'] + c.datasets = query['results'] + + c.facet_titles = { + 'organization': _('Organizations'), + 'groups': _('Groups'), + 'tags': _('Tags'), + 'res_format': _('Formats'), + 'license': _('Licenses'), + } + + except search.SearchError: + c.package_count = 0 + + if c.userobj and not c.userobj.email: + url = h.url_for('user.edit') + msg = _('Please update your profile' + ' and add your email address. ') % url + \ + _('%s uses your email address' + ' if you need to reset your password.') \ + % config.get('ckan.site_title') + h.flash_notice(msg, allow_html=True) + + return base.render('home/index.html', cache_force=True) + + def license(self): + return base.render('home/license.html') + + def about(self): + return base.render('home/about.html') + + def cache(self, id): + '''Manual way to clear the caches''' + if id == 'clear': + wui_caches = ['stats'] + for cache_name in wui_caches: + cache_ = cache.get_cache(cache_name, type='dbm') + cache_.clear() + return 'Cleared caches: %s' % ', '.join(wui_caches) + + def cors_options(self, url=None): + # just return 200 OK and empty data + return '' diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/organization.py b/venv/lib/python2.7/site-packages/ckan/controllers/organization.py new file mode 100644 index 00000000..292b8af4 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/organization.py @@ -0,0 +1,30 @@ +# encoding: utf-8 + +import re + +import ckan.controllers.group as group +import ckan.plugins as plugins + + +class OrganizationController(group.GroupController): + ''' The organization controller is for Organizations, which are implemented + as Groups with is_organization=True and group_type='organization'. It works + the same as the group controller apart from: + * templates and logic action/auth functions are sometimes customized + (switched using _replace_group_org) + * 'bulk_process' action only works for organizations + + Nearly all the code for both is in the GroupController (for historical + reasons). + ''' + + group_types = ['organization'] + + def _replace_group_org(self, string): + ''' substitute organization for group if this is an org''' + return re.sub('^group', 'organization', string) + + def _update_facet_titles(self, facets, group_type): + for plugin in plugins.PluginImplementations(plugins.IFacets): + facets = plugin.organization_facets( + facets, group_type, None) diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/package.py b/venv/lib/python2.7/site-packages/ckan/controllers/package.py new file mode 100644 index 00000000..fb28d93a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/package.py @@ -0,0 +1,1617 @@ +# encoding: utf-8 + +import logging +from urllib import urlencode +import datetime +import mimetypes +import cgi + +from ckan.common import config +from paste.deploy.converters import asbool +import paste.fileapp +from six import string_types, text_type + +import ckan.logic as logic +import ckan.lib.base as base +import ckan.lib.i18n as i18n +import ckan.lib.maintain as maintain +import ckan.lib.navl.dictization_functions as dict_fns +import ckan.lib.helpers as h +import ckan.model as model +import ckan.lib.datapreview as datapreview +import ckan.lib.plugins +import ckan.lib.uploader as uploader +import ckan.plugins as p +import ckan.lib.render + +from ckan.common import OrderedDict, _, json, request, c, response +from home import CACHE_PARAMETERS + +log = logging.getLogger(__name__) + +render = base.render +abort = base.abort + +NotFound = logic.NotFound +NotAuthorized = logic.NotAuthorized +ValidationError = logic.ValidationError +check_access = logic.check_access +get_action = logic.get_action +tuplize_dict = logic.tuplize_dict +clean_dict = logic.clean_dict +parse_params = logic.parse_params +flatten_to_string_key = logic.flatten_to_string_key + +lookup_package_plugin = ckan.lib.plugins.lookup_package_plugin + + +def _encode_params(params): + return [(k, v.encode('utf-8') if isinstance(v, string_types) else str(v)) + for k, v in params] + + +def url_with_params(url, params): + params = _encode_params(params) + return url + u'?' + urlencode(params) + + +def search_url(params, package_type=None): + if not package_type or package_type == 'dataset': + url = h.url_for(controller='package', action='search') + else: + url = h.url_for('{0}_search'.format(package_type)) + return url_with_params(url, params) + + +class PackageController(base.BaseController): + + def _package_form(self, package_type=None): + return lookup_package_plugin(package_type).package_form() + + def _setup_template_variables(self, context, data_dict, package_type=None): + return lookup_package_plugin(package_type).\ + setup_template_variables(context, data_dict) + + def _new_template(self, package_type): + return lookup_package_plugin(package_type).new_template() + + def _edit_template(self, package_type): + return lookup_package_plugin(package_type).edit_template() + + def _search_template(self, package_type): + return lookup_package_plugin(package_type).search_template() + + def _read_template(self, package_type): + return lookup_package_plugin(package_type).read_template() + + def _history_template(self, package_type): + return lookup_package_plugin(package_type).history_template() + + def _resource_form(self, package_type): + # backwards compatibility with plugins not inheriting from + # DefaultDatasetPlugin and not implmenting resource_form + plugin = lookup_package_plugin(package_type) + if hasattr(plugin, 'resource_form'): + result = plugin.resource_form() + if result is not None: + return result + return lookup_package_plugin().resource_form() + + def _resource_template(self, package_type): + # backwards compatibility with plugins not inheriting from + # DefaultDatasetPlugin and not implmenting resource_template + plugin = lookup_package_plugin(package_type) + if hasattr(plugin, 'resource_template'): + result = plugin.resource_template() + if result is not None: + return result + return lookup_package_plugin().resource_template() + + def _guess_package_type(self, expecting_name=False): + """ + Guess the type of package from the URL handling the case + where there is a prefix on the URL (such as /data/package) + """ + + # Special case: if the rot URL '/' has been redirected to the package + # controller (e.g. by an IRoutes extension) then there's nothing to do + # here. + if request.path == '/': + return 'dataset' + + parts = [x for x in request.path.split('/') if x] + + idx = -1 + if expecting_name: + idx = -2 + + pt = parts[idx] + if pt == 'package': + pt = 'dataset' + + return pt + + def search(self): + from ckan.lib.search import SearchError, SearchQueryError + + package_type = self._guess_package_type() + + try: + context = {'model': model, 'user': c.user, + 'auth_user_obj': c.userobj} + check_access('site_read', context) + except NotAuthorized: + abort(403, _('Not authorized to see this page')) + + # unicode format (decoded from utf8) + q = c.q = request.params.get('q', u'') + c.query_error = False + page = h.get_page_number(request.params) + + limit = int(config.get('ckan.datasets_per_page', 20)) + + # most search operations should reset the page counter: + params_nopage = [(k, v) for k, v in request.params.items() + if k != 'page'] + + def drill_down_url(alternative_url=None, **by): + return h.add_url_param(alternative_url=alternative_url, + controller='package', action='search', + new_params=by) + + c.drill_down_url = drill_down_url + + def remove_field(key, value=None, replace=None): + return h.remove_url_param(key, value=value, replace=replace, + controller='package', action='search', + alternative_url=package_type) + + c.remove_field = remove_field + + sort_by = request.params.get('sort', None) + params_nosort = [(k, v) for k, v in params_nopage if k != 'sort'] + + def _sort_by(fields): + """ + Sort by the given list of fields. + + Each entry in the list is a 2-tuple: (fieldname, sort_order) + + eg - [('metadata_modified', 'desc'), ('name', 'asc')] + + If fields is empty, then the default ordering is used. + """ + params = params_nosort[:] + + if fields: + sort_string = ', '.join('%s %s' % f for f in fields) + params.append(('sort', sort_string)) + return search_url(params, package_type) + + c.sort_by = _sort_by + if not sort_by: + c.sort_by_fields = [] + else: + c.sort_by_fields = [field.split()[0] + for field in sort_by.split(',')] + + def pager_url(q=None, page=None): + params = list(params_nopage) + params.append(('page', page)) + return search_url(params, package_type) + + c.search_url_params = urlencode(_encode_params(params_nopage)) + + try: + c.fields = [] + # c.fields_grouped will contain a dict of params containing + # a list of values eg {'tags':['tag1', 'tag2']} + c.fields_grouped = {} + search_extras = {} + fq = '' + for (param, value) in request.params.items(): + if param not in ['q', 'page', 'sort'] \ + and len(value) and not param.startswith('_'): + if not param.startswith('ext_'): + c.fields.append((param, value)) + fq += ' %s:"%s"' % (param, value) + if param not in c.fields_grouped: + c.fields_grouped[param] = [value] + else: + c.fields_grouped[param].append(value) + else: + search_extras[param] = value + + context = {'model': model, 'session': model.Session, + 'user': c.user, 'for_view': True, + 'auth_user_obj': c.userobj} + + # Unless changed via config options, don't show other dataset + # types any search page. Potential alternatives are do show them + # on the default search page (dataset) or on one other search page + search_all_type = config.get( + 'ckan.search.show_all_types', 'dataset') + search_all = False + + try: + # If the "type" is set to True or False, convert to bool + # and we know that no type was specified, so use traditional + # behaviour of applying this only to dataset type + search_all = asbool(search_all_type) + search_all_type = 'dataset' + # Otherwise we treat as a string representing a type + except ValueError: + search_all = True + + if not package_type: + package_type = 'dataset' + + if not search_all or package_type != search_all_type: + # Only show datasets of this particular type + fq += ' +dataset_type:{type}'.format(type=package_type) + + facets = OrderedDict() + + default_facet_titles = { + 'organization': _('Organizations'), + 'groups': _('Groups'), + 'tags': _('Tags'), + 'res_format': _('Formats'), + 'license_id': _('Licenses'), + } + + for facet in h.facets(): + if facet in default_facet_titles: + facets[facet] = default_facet_titles[facet] + else: + facets[facet] = facet + + # Facet titles + for plugin in p.PluginImplementations(p.IFacets): + facets = plugin.dataset_facets(facets, package_type) + + c.facet_titles = facets + + data_dict = { + 'q': q, + 'fq': fq.strip(), + 'facet.field': facets.keys(), + 'rows': limit, + 'start': (page - 1) * limit, + 'sort': sort_by, + 'extras': search_extras, + 'include_private': asbool(config.get( + 'ckan.search.default_include_private', True)), + } + + query = get_action('package_search')(context, data_dict) + c.sort_by_selected = query['sort'] + + c.page = h.Page( + collection=query['results'], + page=page, + url=pager_url, + item_count=query['count'], + items_per_page=limit + ) + c.search_facets = query['search_facets'] + c.page.items = query['results'] + except SearchQueryError as se: + # User's search parameters are invalid, in such a way that is not + # achievable with the web interface, so return a proper error to + # discourage spiders which are the main cause of this. + log.info('Dataset search query rejected: %r', se.args) + abort(400, _('Invalid search query: {error_message}') + .format(error_message=str(se))) + except SearchError as se: + # May be bad input from the user, but may also be more serious like + # bad code causing a SOLR syntax error, or a problem connecting to + # SOLR + log.error('Dataset search error: %r', se.args) + c.query_error = True + c.search_facets = {} + c.page = h.Page(collection=[]) + except NotAuthorized: + abort(403, _('Not authorized to see this page')) + + c.search_facets_limits = {} + for facet in c.search_facets.keys(): + try: + limit = int(request.params.get('_%s_limit' % facet, + int(config.get('search.facets.default', 10)))) + except ValueError: + abort(400, _('Parameter "{parameter_name}" is not ' + 'an integer').format( + parameter_name='_%s_limit' % facet)) + c.search_facets_limits[facet] = limit + + self._setup_template_variables(context, {}, + package_type=package_type) + + return render(self._search_template(package_type), + extra_vars={'dataset_type': package_type}) + + def resources(self, id): + context = {'model': model, 'session': model.Session, + 'user': c.user, 'for_view': True, + 'auth_user_obj': c.userobj} + data_dict = {'id': id, 'include_tracking': True} + + try: + check_access('package_update', context, data_dict) + except NotFound: + abort(404, _('Dataset not found')) + except NotAuthorized: + abort(403, _('User %r not authorized to edit %s') % (c.user, id)) + # check if package exists + try: + c.pkg_dict = get_action('package_show')(context, data_dict) + c.pkg = context['package'] + except (NotFound, NotAuthorized): + abort(404, _('Dataset not found')) + + package_type = c.pkg_dict['type'] or 'dataset' + self._setup_template_variables(context, {'id': id}, + package_type=package_type) + + return render('package/resources.html', + extra_vars={'dataset_type': package_type}) + + def read(self, id): + context = {'model': model, 'session': model.Session, + 'user': c.user, 'for_view': True, + 'auth_user_obj': c.userobj} + data_dict = {'id': id, 'include_tracking': True} + + # interpret @ or @ suffix + split = id.split('@') + if len(split) == 2: + data_dict['id'], revision_ref = split + if model.is_id(revision_ref): + context['revision_id'] = revision_ref + else: + try: + date = h.date_str_to_datetime(revision_ref) + context['revision_date'] = date + except TypeError as e: + abort(400, _('Invalid revision format: %r') % e.args) + except ValueError as e: + abort(400, _('Invalid revision format: %r') % e.args) + elif len(split) > 2: + abort(400, _('Invalid revision format: %r') % + 'Too many "@" symbols') + + # check if package exists + try: + c.pkg_dict = get_action('package_show')(context, data_dict) + c.pkg = context['package'] + except (NotFound, NotAuthorized): + abort(404, _('Dataset not found')) + + # used by disqus plugin + c.current_package_id = c.pkg.id + + # can the resources be previewed? + for resource in c.pkg_dict['resources']: + # Backwards compatibility with preview interface + resource['can_be_previewed'] = self._resource_preview( + {'resource': resource, 'package': c.pkg_dict}) + + resource_views = get_action('resource_view_list')( + context, {'id': resource['id']}) + resource['has_views'] = len(resource_views) > 0 + + package_type = c.pkg_dict['type'] or 'dataset' + self._setup_template_variables(context, {'id': id}, + package_type=package_type) + + template = self._read_template(package_type) + try: + return render(template, + extra_vars={'dataset_type': package_type}) + except ckan.lib.render.TemplateNotFound as e: + msg = _( + "Viewing datasets of type \"{package_type}\" is " + "not supported ({file_!r}).".format( + package_type=package_type, + file_=e.message + ) + ) + abort(404, msg) + + assert False, "We should never get here" + + def history(self, id): + + if 'diff' in request.params or 'selected1' in request.params: + try: + params = {'id': request.params.getone('pkg_name'), + 'diff': request.params.getone('selected1'), + 'oldid': request.params.getone('selected2'), + } + except KeyError: + if 'pkg_name' in dict(request.params): + id = request.params.getone('pkg_name') + c.error = \ + _('Select two revisions before doing the comparison.') + else: + params['diff_entity'] = 'package' + h.redirect_to(controller='revision', action='diff', **params) + + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj, + 'for_view': True} + data_dict = {'id': id} + try: + c.pkg_dict = get_action('package_show')(context, data_dict) + c.pkg_revisions = get_action('package_revision_list')(context, + data_dict) + # TODO: remove + # Still necessary for the authz check in group/layout.html + c.pkg = context['package'] + + except NotAuthorized: + abort(403, _('Unauthorized to read package %s') % '') + except NotFound: + abort(404, _('Dataset not found')) + + format = request.params.get('format', '') + if format == 'atom': + # Generate and return Atom 1.0 document. + from webhelpers.feedgenerator import Atom1Feed + feed = Atom1Feed( + title=_(u'CKAN Dataset Revision History'), + link=h.url_for(controller='revision', action='read', + id=c.pkg_dict['name']), + description=_(u'Recent changes to CKAN Dataset: ') + + (c.pkg_dict['title'] or ''), + language=text_type(i18n.get_lang()), + ) + for revision_dict in c.pkg_revisions: + revision_date = h.date_str_to_datetime( + revision_dict['timestamp']) + try: + dayHorizon = int(request.params.get('days')) + except: + dayHorizon = 30 + dayAge = (datetime.datetime.now() - revision_date).days + if dayAge >= dayHorizon: + break + if revision_dict['message']: + item_title = u'%s' % revision_dict['message'].\ + split('\n')[0] + else: + item_title = u'%s' % revision_dict['id'] + item_link = h.url_for(controller='revision', action='read', + id=revision_dict['id']) + item_description = _('Log message: ') + item_description += '%s' % (revision_dict['message'] or '') + item_author_name = revision_dict['author'] + item_pubdate = revision_date + feed.add_item( + title=item_title, + link=item_link, + description=item_description, + author_name=item_author_name, + pubdate=item_pubdate, + ) + response.headers['Content-Type'] = 'application/atom+xml' + return feed.writeString('utf-8') + + package_type = c.pkg_dict['type'] or 'dataset' + + return render( + self._history_template(c.pkg_dict.get('type', package_type)), + extra_vars={'dataset_type': package_type}) + + def new(self, data=None, errors=None, error_summary=None): + if data and 'type' in data: + package_type = data['type'] + else: + package_type = self._guess_package_type(True) + + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj, + 'save': 'save' in request.params} + + # Package needs to have a organization group in the call to + # check_access and also to save it + try: + check_access('package_create', context) + except NotAuthorized: + abort(403, _('Unauthorized to create a package')) + + if context['save'] and not data and request.method == 'POST': + return self._save_new(context, package_type=package_type) + + data = data or clean_dict(dict_fns.unflatten(tuplize_dict(parse_params( + request.params, ignore_keys=CACHE_PARAMETERS)))) + c.resources_json = h.json.dumps(data.get('resources', [])) + # convert tags if not supplied in data + if data and not data.get('tag_string'): + data['tag_string'] = ', '.join( + h.dict_list_reduce(data.get('tags', {}), 'name')) + + errors = errors or {} + error_summary = error_summary or {} + # in the phased add dataset we need to know that + # we have already completed stage 1 + stage = ['active'] + if data.get('state', '').startswith('draft'): + stage = ['active', 'complete'] + + # if we are creating from a group then this allows the group to be + # set automatically + data['group_id'] = request.params.get('group') or \ + request.params.get('groups__0__id') + + form_snippet = self._package_form(package_type=package_type) + form_vars = {'data': data, 'errors': errors, + 'error_summary': error_summary, + 'action': 'new', 'stage': stage, + 'dataset_type': package_type, + } + c.errors_json = h.json.dumps(errors) + + self._setup_template_variables(context, {}, + package_type=package_type) + + new_template = self._new_template(package_type) + return render(new_template, + extra_vars={'form_vars': form_vars, + 'form_snippet': form_snippet, + 'dataset_type': package_type}) + + def resource_edit(self, id, resource_id, data=None, errors=None, + error_summary=None): + + context = {'model': model, 'session': model.Session, + 'api_version': 3, 'for_edit': True, + 'user': c.user, 'auth_user_obj': c.userobj} + data_dict = {'id': id} + + try: + check_access('package_update', context, data_dict) + except NotAuthorized: + abort(403, _('User %r not authorized to edit %s') % (c.user, id)) + + if request.method == 'POST' and not data: + data = data or \ + clean_dict(dict_fns.unflatten(tuplize_dict(parse_params( + request.POST)))) + # we don't want to include save as it is part of the form + del data['save'] + + data['package_id'] = id + try: + if resource_id: + data['id'] = resource_id + get_action('resource_update')(context, data) + else: + get_action('resource_create')(context, data) + except ValidationError as e: + errors = e.error_dict + error_summary = e.error_summary + return self.resource_edit(id, resource_id, data, + errors, error_summary) + except NotAuthorized: + abort(403, _('Unauthorized to edit this resource')) + h.redirect_to(controller='package', action='resource_read', id=id, + resource_id=resource_id) + + pkg_dict = get_action('package_show')(context, {'id': id}) + if pkg_dict['state'].startswith('draft'): + # dataset has not yet been fully created + resource_dict = get_action('resource_show')(context, + {'id': resource_id}) + return self.new_resource(id, data=resource_dict) + # resource is fully created + try: + resource_dict = get_action('resource_show')(context, + {'id': resource_id}) + except NotFound: + abort(404, _('Resource not found')) + c.pkg_dict = pkg_dict + c.resource = resource_dict + # set the form action + c.form_action = h.url_for(controller='package', + action='resource_edit', + resource_id=resource_id, + id=id) + if not data: + data = resource_dict + + package_type = pkg_dict['type'] or 'dataset' + + errors = errors or {} + error_summary = error_summary or {} + vars = {'data': data, 'errors': errors, + 'error_summary': error_summary, 'action': 'edit', + 'resource_form_snippet': self._resource_form(package_type), + 'dataset_type': package_type} + return render('package/resource_edit.html', extra_vars=vars) + + def new_resource(self, id, data=None, errors=None, error_summary=None): + ''' FIXME: This is a temporary action to allow styling of the + forms. ''' + if request.method == 'POST' and not data: + save_action = request.params.get('save') + data = data or \ + clean_dict(dict_fns.unflatten(tuplize_dict(parse_params( + request.POST)))) + # we don't want to include save as it is part of the form + del data['save'] + resource_id = data['id'] + del data['id'] + + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + + # see if we have any data that we are trying to save + data_provided = False + for key, value in data.iteritems(): + if ((value or isinstance(value, cgi.FieldStorage)) + and key != 'resource_type'): + data_provided = True + break + + if not data_provided and save_action != "go-dataset-complete": + if save_action == 'go-dataset': + # go to final stage of adddataset + h.redirect_to(controller='package', action='edit', id=id) + # see if we have added any resources + try: + data_dict = get_action('package_show')(context, {'id': id}) + except NotAuthorized: + abort(403, _('Unauthorized to update dataset')) + except NotFound: + abort(404, _('The dataset {id} could not be found.' + ).format(id=id)) + if not len(data_dict['resources']): + # no data so keep on page + msg = _('You must add at least one data resource') + # On new templates do not use flash message + + if asbool(config.get('ckan.legacy_templates')): + h.flash_error(msg) + h.redirect_to(controller='package', + action='new_resource', id=id) + else: + errors = {} + error_summary = {_('Error'): msg} + return self.new_resource(id, data, errors, + error_summary) + # XXX race condition if another user edits/deletes + data_dict = get_action('package_show')(context, {'id': id}) + get_action('package_update')( + dict(context, allow_state_change=True), + dict(data_dict, state='active')) + h.redirect_to(controller='package', action='read', id=id) + + data['package_id'] = id + try: + if resource_id: + data['id'] = resource_id + get_action('resource_update')(context, data) + else: + get_action('resource_create')(context, data) + except ValidationError as e: + errors = e.error_dict + error_summary = e.error_summary + return self.new_resource(id, data, errors, error_summary) + except NotAuthorized: + abort(403, _('Unauthorized to create a resource')) + except NotFound: + abort(404, _('The dataset {id} could not be found.' + ).format(id=id)) + if save_action == 'go-metadata': + # XXX race condition if another user edits/deletes + data_dict = get_action('package_show')(context, {'id': id}) + get_action('package_update')( + dict(context, allow_state_change=True), + dict(data_dict, state='active')) + h.redirect_to(controller='package', action='read', id=id) + elif save_action == 'go-dataset': + # go to first stage of add dataset + h.redirect_to(controller='package', action='edit', id=id) + elif save_action == 'go-dataset-complete': + # go to first stage of add dataset + h.redirect_to(controller='package', action='read', id=id) + else: + # add more resources + h.redirect_to(controller='package', action='new_resource', + id=id) + + # get resources for sidebar + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + try: + pkg_dict = get_action('package_show')(context, {'id': id}) + except NotFound: + abort(404, _('The dataset {id} could not be found.').format(id=id)) + try: + check_access( + 'resource_create', context, {"package_id": pkg_dict["id"]}) + except NotAuthorized: + abort(403, _('Unauthorized to create a resource for this package')) + + package_type = pkg_dict['type'] or 'dataset' + + errors = errors or {} + error_summary = error_summary or {} + vars = {'data': data, 'errors': errors, + 'error_summary': error_summary, 'action': 'new', + 'resource_form_snippet': self._resource_form(package_type), + 'dataset_type': package_type} + vars['pkg_name'] = id + # required for nav menu + vars['pkg_dict'] = pkg_dict + template = 'package/new_resource_not_draft.html' + if pkg_dict['state'].startswith('draft'): + vars['stage'] = ['complete', 'active'] + template = 'package/new_resource.html' + return render(template, extra_vars=vars) + + def edit(self, id, data=None, errors=None, error_summary=None): + package_type = self._get_package_type(id) + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj, + 'save': 'save' in request.params} + + if context['save'] and not data and request.method == 'POST': + return self._save_edit(id, context, package_type=package_type) + try: + c.pkg_dict = get_action('package_show')(dict(context, + for_view=True), + {'id': id}) + context['for_edit'] = True + old_data = get_action('package_show')(context, {'id': id}) + # old data is from the database and data is passed from the + # user if there is a validation error. Use users data if there. + if data: + old_data.update(data) + data = old_data + except (NotFound, NotAuthorized): + abort(404, _('Dataset not found')) + # are we doing a multiphase add? + if data.get('state', '').startswith('draft'): + c.form_action = h.url_for(controller='package', action='new') + c.form_style = 'new' + return self.new(data=data, errors=errors, + error_summary=error_summary) + + c.pkg = context.get("package") + c.resources_json = h.json.dumps(data.get('resources', [])) + + try: + check_access('package_update', context) + except NotAuthorized: + abort(403, _('User %r not authorized to edit %s') % (c.user, id)) + # convert tags if not supplied in data + if data and not data.get('tag_string'): + data['tag_string'] = ', '.join(h.dict_list_reduce( + c.pkg_dict.get('tags', {}), 'name')) + errors = errors or {} + form_snippet = self._package_form(package_type=package_type) + form_vars = {'data': data, 'errors': errors, + 'error_summary': error_summary, 'action': 'edit', + 'dataset_type': package_type, + } + c.errors_json = h.json.dumps(errors) + + self._setup_template_variables(context, {'id': id}, + package_type=package_type) + + # we have already completed stage 1 + form_vars['stage'] = ['active'] + if data.get('state', '').startswith('draft'): + form_vars['stage'] = ['active', 'complete'] + + edit_template = self._edit_template(package_type) + return render(edit_template, + extra_vars={'form_vars': form_vars, + 'form_snippet': form_snippet, + 'dataset_type': package_type}) + + def read_ajax(self, id, revision=None): + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj, + 'revision_id': revision} + try: + data = get_action('package_show')(context, {'id': id}) + except (NotFound, NotAuthorized): + abort(404, _('Dataset not found')) + + data.pop('tags') + data = flatten_to_string_key(data) + response.headers['Content-Type'] = 'application/json;charset=utf-8' + return h.json.dumps(data) + + def history_ajax(self, id): + + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + data_dict = {'id': id} + try: + pkg_revisions = get_action('package_revision_list')( + context, data_dict) + except NotAuthorized: + abort(403, _('Unauthorized to read package %s') % '') + except NotFound: + abort(404, _('Dataset not found')) + + data = [] + approved = False + for num, revision in enumerate(pkg_revisions): + if not approved and revision['approved_timestamp']: + current_approved, approved = True, True + else: + current_approved = False + + data.append({'revision_id': revision['id'], + 'message': revision['message'], + 'timestamp': revision['timestamp'], + 'author': revision['author'], + 'approved': bool(revision['approved_timestamp']), + 'current_approved': current_approved}) + + response.headers['Content-Type'] = 'application/json;charset=utf-8' + return h.json.dumps(data) + + def _get_package_type(self, id): + """ + Given the id of a package this method will return the type of the + package, or 'dataset' if no type is currently set + """ + pkg = model.Package.get(id) + if pkg: + return pkg.type or 'dataset' + return None + + def _tag_string_to_list(self, tag_string): + ''' This is used to change tags from a sting to a list of dicts ''' + out = [] + for tag in tag_string.split(','): + tag = tag.strip() + if tag: + out.append({'name': tag, + 'state': 'active'}) + return out + + def _save_new(self, context, package_type=None): + # The staged add dataset used the new functionality when the dataset is + # partially created so we need to know if we actually are updating or + # this is a real new. + is_an_update = False + ckan_phase = request.params.get('_ckan_phase') + from ckan.lib.search import SearchIndexError + try: + data_dict = clean_dict(dict_fns.unflatten( + tuplize_dict(parse_params(request.POST)))) + if ckan_phase: + # prevent clearing of groups etc + context['allow_partial_update'] = True + # sort the tags + if 'tag_string' in data_dict: + data_dict['tags'] = self._tag_string_to_list( + data_dict['tag_string']) + if data_dict.get('pkg_name'): + is_an_update = True + # This is actually an update not a save + data_dict['id'] = data_dict['pkg_name'] + del data_dict['pkg_name'] + # don't change the dataset state + data_dict['state'] = 'draft' + # this is actually an edit not a save + pkg_dict = get_action('package_update')(context, data_dict) + + if request.params['save'] == 'go-metadata': + # redirect to add metadata + url = h.url_for(controller='package', + action='new_metadata', + id=pkg_dict['name']) + else: + # redirect to add dataset resources + url = h.url_for(controller='package', + action='new_resource', + id=pkg_dict['name']) + h.redirect_to(url) + # Make sure we don't index this dataset + if request.params['save'] not in ['go-resource', + 'go-metadata']: + data_dict['state'] = 'draft' + # allow the state to be changed + context['allow_state_change'] = True + + data_dict['type'] = package_type + context['message'] = data_dict.get('log_message', '') + pkg_dict = get_action('package_create')(context, data_dict) + + if ckan_phase: + # redirect to add dataset resources + url = h.url_for(controller='package', + action='new_resource', + id=pkg_dict['name']) + h.redirect_to(url) + + self._form_save_redirect(pkg_dict['name'], 'new', + package_type=package_type) + except NotAuthorized: + abort(403, _('Unauthorized to read package %s') % '') + except NotFound as e: + abort(404, _('Dataset not found')) + except dict_fns.DataError: + abort(400, _(u'Integrity Error')) + except SearchIndexError as e: + try: + exc_str = text_type(repr(e.args)) + except Exception: # We don't like bare excepts + exc_str = text_type(str(e)) + abort(500, _(u'Unable to add package to search index.') + exc_str) + except ValidationError as e: + errors = e.error_dict + error_summary = e.error_summary + if is_an_update: + # we need to get the state of the dataset to show the stage we + # are on. + pkg_dict = get_action('package_show')(context, data_dict) + data_dict['state'] = pkg_dict['state'] + return self.edit(data_dict['id'], data_dict, + errors, error_summary) + data_dict['state'] = 'none' + return self.new(data_dict, errors, error_summary) + + def _save_edit(self, name_or_id, context, package_type=None): + from ckan.lib.search import SearchIndexError + log.debug('Package save request name: %s POST: %r', + name_or_id, request.POST) + try: + data_dict = clean_dict(dict_fns.unflatten( + tuplize_dict(parse_params(request.POST)))) + if '_ckan_phase' in data_dict: + # we allow partial updates to not destroy existing resources + context['allow_partial_update'] = True + if 'tag_string' in data_dict: + data_dict['tags'] = self._tag_string_to_list( + data_dict['tag_string']) + del data_dict['_ckan_phase'] + del data_dict['save'] + context['message'] = data_dict.get('log_message', '') + data_dict['id'] = name_or_id + pkg = get_action('package_update')(context, data_dict) + c.pkg = context['package'] + c.pkg_dict = pkg + + self._form_save_redirect(pkg['name'], 'edit', + package_type=package_type) + except NotAuthorized: + abort(403, _('Unauthorized to read package %s') % id) + except NotFound as e: + abort(404, _('Dataset not found')) + except dict_fns.DataError: + abort(400, _(u'Integrity Error')) + except SearchIndexError as e: + try: + exc_str = text_type(repr(e.args)) + except Exception: # We don't like bare excepts + exc_str = text_type(str(e)) + abort(500, _(u'Unable to update search index.') + exc_str) + except ValidationError as e: + errors = e.error_dict + error_summary = e.error_summary + return self.edit(name_or_id, data_dict, errors, error_summary) + + def _form_save_redirect(self, pkgname, action, package_type=None): + '''This redirects the user to the CKAN package/read page, + unless there is request parameter giving an alternate location, + perhaps an external website. + @param pkgname - Name of the package just edited + @param action - What the action of the edit was + ''' + assert action in ('new', 'edit') + url = request.params.get('return_to') or \ + config.get('package_%s_return_url' % action) + if url: + url = url.replace('', pkgname) + else: + if package_type is None or package_type == 'dataset': + url = h.url_for(controller='package', action='read', + id=pkgname) + else: + url = h.url_for('{0}_read'.format(package_type), id=pkgname) + h.redirect_to(url) + + def delete(self, id): + + if 'cancel' in request.params: + h.redirect_to(controller='package', action='edit', id=id) + + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + + try: + if request.method == 'POST': + get_action('package_delete')(context, {'id': id}) + h.flash_notice(_('Dataset has been deleted.')) + h.redirect_to(controller='package', action='search') + c.pkg_dict = get_action('package_show')(context, {'id': id}) + dataset_type = c.pkg_dict['type'] or 'dataset' + except NotAuthorized: + abort(403, _('Unauthorized to delete package %s') % '') + except NotFound: + abort(404, _('Dataset not found')) + return render('package/confirm_delete.html', + extra_vars={'dataset_type': dataset_type}) + + def resource_delete(self, id, resource_id): + + if 'cancel' in request.params: + h.redirect_to(controller='package', action='resource_edit', + resource_id=resource_id, id=id) + + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + + try: + check_access('package_delete', context, {'id': id}) + except NotAuthorized: + abort(403, _('Unauthorized to delete package %s') % '') + + try: + if request.method == 'POST': + get_action('resource_delete')(context, {'id': resource_id}) + h.flash_notice(_('Resource has been deleted.')) + pkg_dict = get_action('package_show')(None, {'id': id}) + if pkg_dict['state'].startswith('draft'): + h.redirect_to(controller='package', action='new_resource', + id=id) + else: + h.redirect_to(controller='package', action='read', id=id) + c.resource_dict = get_action('resource_show')( + context, {'id': resource_id}) + c.pkg_id = id + except NotAuthorized: + abort(403, _('Unauthorized to delete resource %s') % '') + except NotFound: + abort(404, _('Resource not found')) + return render('package/confirm_delete_resource.html', + {'dataset_type': self._get_package_type(id)}) + + def resource_read(self, id, resource_id): + context = {'model': model, 'session': model.Session, + 'user': c.user, + 'auth_user_obj': c.userobj, + 'for_view': True} + + try: + c.package = get_action('package_show')(context, {'id': id}) + except (NotFound, NotAuthorized): + abort(404, _('Dataset not found')) + + for resource in c.package.get('resources', []): + if resource['id'] == resource_id: + c.resource = resource + break + if not c.resource: + abort(404, _('Resource not found')) + + # required for nav menu + c.pkg = context['package'] + c.pkg_dict = c.package + dataset_type = c.pkg.type or 'dataset' + + # get package license info + license_id = c.package.get('license_id') + try: + c.package['isopen'] = model.Package.\ + get_license_register()[license_id].isopen() + except KeyError: + c.package['isopen'] = False + + # Deprecated: c.datastore_api - use h.action_url instead + c.datastore_api = '%s/api/action' % \ + config.get('ckan.site_url', '').rstrip('/') + + c.resource['can_be_previewed'] = self._resource_preview( + {'resource': c.resource, 'package': c.package}) + + resource_views = get_action('resource_view_list')( + context, {'id': resource_id}) + c.resource['has_views'] = len(resource_views) > 0 + + current_resource_view = None + view_id = request.GET.get('view_id') + if c.resource['can_be_previewed'] and not view_id: + current_resource_view = None + elif c.resource['has_views']: + if view_id: + current_resource_view = [rv for rv in resource_views + if rv['id'] == view_id] + if len(current_resource_view) == 1: + current_resource_view = current_resource_view[0] + else: + abort(404, _('Resource view not found')) + else: + current_resource_view = resource_views[0] + + vars = {'resource_views': resource_views, + 'current_resource_view': current_resource_view, + 'dataset_type': dataset_type} + + template = self._resource_template(dataset_type) + return render(template, extra_vars=vars) + + @maintain.deprecated('Resource preview is deprecated. Please use the new ' + 'resource views') + def _resource_preview(self, data_dict): + '''Deprecated in 2.3''' + return bool(datapreview.get_preview_plugin(data_dict, + return_first=True)) + + def resource_download(self, id, resource_id, filename=None): + """ + Provides a direct download by either redirecting the user to the url + stored or downloading an uploaded file directly. + """ + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + + try: + rsc = get_action('resource_show')(context, {'id': resource_id}) + get_action('package_show')(context, {'id': id}) + except (NotFound, NotAuthorized): + abort(404, _('Resource not found')) + + if rsc.get('url_type') == 'upload': + upload = uploader.get_resource_uploader(rsc) + filepath = upload.get_path(rsc['id']) + fileapp = paste.fileapp.FileApp(filepath) + try: + status, headers, app_iter = request.call_application(fileapp) + except OSError: + abort(404, _('Resource data not found')) + response.headers.update(dict(headers)) + content_type, content_enc = mimetypes.guess_type( + rsc.get('url', '')) + if content_type: + response.headers['Content-Type'] = content_type + response.status = status + return app_iter + elif 'url' not in rsc: + abort(404, _('No download is available')) + h.redirect_to(rsc['url']) + + def follow(self, id): + '''Start following this dataset.''' + context = {'model': model, + 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + data_dict = {'id': id} + try: + get_action('follow_dataset')(context, data_dict) + package_dict = get_action('package_show')(context, data_dict) + h.flash_success(_("You are now following {0}").format( + package_dict['title'])) + except ValidationError as e: + error_message = (e.message or e.error_summary + or e.error_dict) + h.flash_error(error_message) + except NotAuthorized as e: + h.flash_error(e.message) + h.redirect_to(controller='package', action='read', id=id) + + def unfollow(self, id): + '''Stop following this dataset.''' + context = {'model': model, + 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + data_dict = {'id': id} + try: + get_action('unfollow_dataset')(context, data_dict) + package_dict = get_action('package_show')(context, data_dict) + h.flash_success(_("You are no longer following {0}").format( + package_dict['title'])) + except ValidationError as e: + error_message = (e.message or e.error_summary + or e.error_dict) + h.flash_error(error_message) + except (NotFound, NotAuthorized) as e: + error_message = e.message + h.flash_error(error_message) + h.redirect_to(controller='package', action='read', id=id) + + def followers(self, id=None): + context = {'model': model, 'session': model.Session, + 'user': c.user, 'for_view': True, + 'auth_user_obj': c.userobj} + + data_dict = {'id': id} + try: + c.pkg_dict = get_action('package_show')(context, data_dict) + c.pkg = context['package'] + c.followers = get_action('dataset_follower_list')( + context, {'id': c.pkg_dict['id']}) + + dataset_type = c.pkg.type or 'dataset' + except NotFound: + abort(404, _('Dataset not found')) + except NotAuthorized: + abort(403, _('Unauthorized to read package %s') % id) + + return render('package/followers.html', + {'dataset_type': dataset_type}) + + def groups(self, id): + context = {'model': model, 'session': model.Session, + 'user': c.user, 'for_view': True, + 'auth_user_obj': c.userobj, 'use_cache': False} + data_dict = {'id': id} + try: + c.pkg_dict = get_action('package_show')(context, data_dict) + dataset_type = c.pkg_dict['type'] or 'dataset' + except (NotFound, NotAuthorized): + abort(404, _('Dataset not found')) + + if request.method == 'POST': + new_group = request.POST.get('group_added') + if new_group: + data_dict = {"id": new_group, + "object": id, + "object_type": 'package', + "capacity": 'public'} + try: + get_action('member_create')(context, data_dict) + except NotFound: + abort(404, _('Group not found')) + + removed_group = None + for param in request.POST: + if param.startswith('group_remove'): + removed_group = param.split('.')[-1] + break + if removed_group: + data_dict = {"id": removed_group, + "object": id, + "object_type": 'package'} + + try: + get_action('member_delete')(context, data_dict) + except NotFound: + abort(404, _('Group not found')) + h.redirect_to(controller='package', action='groups', id=id) + + context['is_member'] = True + users_groups = get_action('group_list_authz')(context, data_dict) + + pkg_group_ids = set(group['id'] for group + in c.pkg_dict.get('groups', [])) + user_group_ids = set(group['id'] for group + in users_groups) + + c.group_dropdown = [[group['id'], group['display_name']] + for group in users_groups if + group['id'] not in pkg_group_ids] + + for group in c.pkg_dict.get('groups', []): + group['user_member'] = (group['id'] in user_group_ids) + + return render('package/group_list.html', + {'dataset_type': dataset_type}) + + def activity(self, id): + '''Render this package's public activity stream page.''' + + context = {'model': model, 'session': model.Session, + 'user': c.user, 'for_view': True, + 'auth_user_obj': c.userobj} + data_dict = {'id': id} + try: + c.pkg_dict = get_action('package_show')(context, data_dict) + c.pkg = context['package'] + c.package_activity_stream = get_action( + 'package_activity_list_html')( + context, {'id': c.pkg_dict['id']}) + dataset_type = c.pkg_dict['type'] or 'dataset' + except NotFound: + abort(404, _('Dataset not found')) + except NotAuthorized: + abort(403, _('Unauthorized to read dataset %s') % id) + + return render('package/activity.html', + {'dataset_type': dataset_type}) + + def resource_embedded_dataviewer(self, id, resource_id, + width=500, height=500): + """ + Embedded page for a read-only resource dataview. Allows + for width and height to be specified as part of the + querystring (as well as accepting them via routes). + """ + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj} + + try: + c.resource = get_action('resource_show')(context, + {'id': resource_id}) + c.package = get_action('package_show')(context, {'id': id}) + c.resource_json = h.json.dumps(c.resource) + + # double check that the resource belongs to the specified package + if not c.resource['id'] in [r['id'] + for r in c.package['resources']]: + raise NotFound + dataset_type = c.package['type'] or 'dataset' + + except (NotFound, NotAuthorized): + abort(404, _('Resource not found')) + + # Construct the recline state + state_version = int(request.params.get('state_version', '1')) + recline_state = self._parse_recline_state(request.params) + if recline_state is None: + abort(400, ('"state" parameter must be a valid recline ' + 'state (version %d)' % state_version)) + + c.recline_state = h.json.dumps(recline_state) + + c.width = max(int(request.params.get('width', width)), 100) + c.height = max(int(request.params.get('height', height)), 100) + c.embedded = True + + return render('package/resource_embedded_dataviewer.html', + extra_vars={'dataset_type': dataset_type}) + + def _parse_recline_state(self, params): + state_version = int(request.params.get('state_version', '1')) + if state_version != 1: + return None + + recline_state = {} + for k, v in request.params.items(): + try: + v = h.json.loads(v) + except ValueError: + pass + recline_state[k] = v + + recline_state.pop('width', None) + recline_state.pop('height', None) + recline_state['readOnly'] = True + + # previous versions of recline setup used elasticsearch_url attribute + # for data api url - see http://trac.ckan.org/ticket/2639 + # fix by relocating this to url attribute which is the default location + if 'dataset' in recline_state and \ + 'elasticsearch_url' in recline_state['dataset']: + recline_state['dataset']['url'] = \ + recline_state['dataset']['elasticsearch_url'] + + # Ensure only the currentView is available + # default to grid view if none specified + if not recline_state.get('currentView', None): + recline_state['currentView'] = 'grid' + for k in recline_state.keys(): + if k.startswith('view-') and \ + not k.endswith(recline_state['currentView']): + recline_state.pop(k) + return recline_state + + def resource_views(self, id, resource_id): + package_type = self._get_package_type(id.split('@')[0]) + context = {'model': model, 'session': model.Session, + 'user': c.user, 'for_view': True, + 'auth_user_obj': c.userobj} + data_dict = {'id': id} + + try: + check_access('package_update', context, data_dict) + except NotAuthorized: + abort(403, _('User %r not authorized to edit %s') % (c.user, id)) + # check if package exists + try: + c.pkg_dict = get_action('package_show')(context, data_dict) + c.pkg = context['package'] + except (NotFound, NotAuthorized): + abort(404, _('Dataset not found')) + + try: + c.resource = get_action('resource_show')(context, + {'id': resource_id}) + c.views = get_action('resource_view_list')(context, + {'id': resource_id}) + + except NotFound: + abort(404, _('Resource not found')) + except NotAuthorized: + abort(403, _('Unauthorized to read resource %s') % id) + + self._setup_template_variables(context, {'id': id}, + package_type=package_type) + + return render('package/resource_views.html') + + def edit_view(self, id, resource_id, view_id=None): + package_type = self._get_package_type(id.split('@')[0]) + context = {'model': model, 'session': model.Session, + 'user': c.user, 'for_view': True, + 'auth_user_obj': c.userobj} + + # update resource should tell us early if the user has privilages. + try: + check_access('resource_update', context, {'id': resource_id}) + except NotAuthorized as e: + abort(403, _('User %r not authorized to edit %s') % (c.user, id)) + + # get resource and package data + try: + c.pkg_dict = get_action('package_show')(context, {'id': id}) + c.pkg = context['package'] + except (NotFound, NotAuthorized): + abort(404, _('Dataset not found')) + try: + c.resource = get_action('resource_show')(context, + {'id': resource_id}) + except (NotFound, NotAuthorized): + abort(404, _('Resource not found')) + + data = {} + errors = {} + error_summary = {} + view_type = None + to_preview = False + + if request.method == 'POST': + request.POST.pop('save', None) + to_preview = request.POST.pop('preview', False) + if to_preview: + context['preview'] = True + to_delete = request.POST.pop('delete', None) + data = clean_dict(dict_fns.unflatten(tuplize_dict(parse_params( + request.params, ignore_keys=CACHE_PARAMETERS)))) + data['resource_id'] = resource_id + + try: + if to_delete: + data['id'] = view_id + get_action('resource_view_delete')(context, data) + elif view_id: + data['id'] = view_id + data = get_action('resource_view_update')(context, data) + else: + data = get_action('resource_view_create')(context, data) + except ValidationError as e: + # Could break preview if validation error + to_preview = False + errors = e.error_dict + error_summary = e.error_summary + except NotAuthorized: + # This should never happen unless the user maliciously changed + # the resource_id in the url. + abort(403, _('Unauthorized to edit resource')) + else: + if not to_preview: + h.redirect_to(controller='package', + action='resource_views', + id=id, resource_id=resource_id) + + # view_id exists only when updating + if view_id: + try: + old_data = get_action('resource_view_show')(context, + {'id': view_id}) + data = data or old_data + view_type = old_data.get('view_type') + # might as well preview when loading good existing view + if not errors: + to_preview = True + except (NotFound, NotAuthorized): + abort(404, _('View not found')) + + view_type = view_type or request.GET.get('view_type') + data['view_type'] = view_type + view_plugin = datapreview.get_view_plugin(view_type) + if not view_plugin: + abort(404, _('View Type Not found')) + + self._setup_template_variables(context, {'id': id}, + package_type=package_type) + + data_dict = {'package': c.pkg_dict, 'resource': c.resource, + 'resource_view': data} + + view_template = view_plugin.view_template(context, data_dict) + form_template = view_plugin.form_template(context, data_dict) + + vars = {'form_template': form_template, + 'view_template': view_template, + 'data': data, + 'errors': errors, + 'error_summary': error_summary, + 'to_preview': to_preview, + 'datastore_available': p.plugin_loaded('datastore')} + vars.update( + view_plugin.setup_template_variables(context, data_dict) or {}) + vars.update(data_dict) + + if view_id: + return render('package/edit_view.html', extra_vars=vars) + + return render('package/new_view.html', extra_vars=vars) + + def resource_view(self, id, resource_id, view_id=None): + ''' + Embedded page for a resource view. + + Depending on the type, different views are loaded. This could be an + img tag where the image is loaded directly or an iframe that embeds a + webpage or a recline preview. + ''' + context = {'model': model, + 'session': model.Session, + 'user': c.user, + 'auth_user_obj': c.userobj} + + try: + package = get_action('package_show')(context, {'id': id}) + except (NotFound, NotAuthorized): + abort(404, _('Dataset not found')) + + try: + resource = get_action('resource_show')( + context, {'id': resource_id}) + except (NotFound, NotAuthorized): + abort(404, _('Resource not found')) + + view = None + if request.params.get('resource_view', ''): + try: + view = json.loads(request.params.get('resource_view', '')) + except ValueError: + abort(409, _('Bad resource view data')) + elif view_id: + try: + view = get_action('resource_view_show')( + context, {'id': view_id}) + except (NotFound, NotAuthorized): + abort(404, _('Resource view not found')) + + if not view or not isinstance(view, dict): + abort(404, _('Resource view not supplied')) + + return h.rendered_resource_view(view, resource, package, embed=True) + + def resource_datapreview(self, id, resource_id): + ''' + Embedded page for a resource data-preview. + + Depending on the type, different previews are loaded. This could be an + img tag where the image is loaded directly or an iframe that embeds a + webpage, or a recline preview. + ''' + context = { + 'model': model, + 'session': model.Session, + 'user': c.user, + 'auth_user_obj': c.userobj + } + + try: + c.resource = get_action('resource_show')(context, + {'id': resource_id}) + c.package = get_action('package_show')(context, {'id': id}) + + data_dict = {'resource': c.resource, 'package': c.package} + + preview_plugin = datapreview.get_preview_plugin(data_dict) + + if preview_plugin is None: + abort(409, _('No preview has been defined.')) + + preview_plugin.setup_template_variables(context, data_dict) + c.resource_json = json.dumps(c.resource) + dataset_type = c.package['type'] or 'dataset' + except (NotFound, NotAuthorized): + abort(404, _('Resource not found')) + else: + return render(preview_plugin.preview_template(context, data_dict), + extra_vars={'dataset_type': dataset_type}) diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/revision.py b/venv/lib/python2.7/site-packages/ckan/controllers/revision.py new file mode 100644 index 00000000..ace9cb32 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/revision.py @@ -0,0 +1,193 @@ +# encoding: utf-8 + +from datetime import datetime, timedelta + +from pylons.i18n import get_lang +from six import text_type + +import ckan.logic as logic +import ckan.lib.base as base +import ckan.model as model +import ckan.lib.helpers as h + +from ckan.common import _, c, request + + +class RevisionController(base.BaseController): + + def __before__(self, action, **env): + base.BaseController.__before__(self, action, **env) + + context = {'model': model, 'user': c.user, + 'auth_user_obj': c.userobj} + if c.user: + try: + logic.check_access('revision_change_state', context) + c.revision_change_state_allowed = True + except logic.NotAuthorized: + c.revision_change_state_allowed = False + else: + c.revision_change_state_allowed = False + try: + logic.check_access('site_read', context) + except logic.NotAuthorized: + base.abort(403, _('Not authorized to see this page')) + + def index(self): + return self.list() + + def list(self): + format = request.params.get('format', '') + if format == 'atom': + # Generate and return Atom 1.0 document. + from webhelpers.feedgenerator import Atom1Feed + feed = Atom1Feed( + title=_(u'CKAN Repository Revision History'), + link=h.url_for(controller='revision', action='list', id=''), + description=_(u'Recent changes to the CKAN repository.'), + language=text_type(get_lang()), + ) + # TODO: make this configurable? + # we do not want the system to fall over! + maxresults = 200 + try: + dayHorizon = int(request.params.get('days', 5)) + except: + dayHorizon = 5 + ourtimedelta = timedelta(days=-dayHorizon) + since_when = datetime.now() + ourtimedelta + revision_query = model.repo.history() + revision_query = revision_query.filter( + model.Revision.timestamp >= since_when).filter( + model.Revision.id != None) + revision_query = revision_query.limit(maxresults) + for revision in revision_query: + package_indications = [] + revision_changes = model.repo.list_changes(revision) + resource_revisions = revision_changes[model.Resource] + package_extra_revisions = revision_changes[model.PackageExtra] + for package in revision.packages: + if not package: + # package is None sometimes - I don't know why, + # but in the meantime while that is fixed, + # avoid an exception here + continue + if package.private: + continue + number = len(package.all_revisions) + package_revision = None + count = 0 + for pr in package.all_revisions: + count += 1 + if pr.revision.id == revision.id: + package_revision = pr + break + if package_revision and package_revision.state == \ + model.State.DELETED: + transition = 'deleted' + elif package_revision and count == number: + transition = 'created' + else: + transition = 'updated' + for resource_revision in resource_revisions: + if resource_revision.package_id == package.id: + transition += ':resources' + break + for package_extra_revision in package_extra_revisions: + if package_extra_revision.package_id == \ + package.id: + if package_extra_revision.key == \ + 'date_updated': + transition += ':date_updated' + break + indication = "%s:%s" % (package.name, transition) + package_indications.append(indication) + pkgs = u'[%s]' % ' '.join(package_indications) + item_title = u'r%s ' % (revision.id) + item_title += pkgs + if revision.message: + item_title += ': %s' % (revision.message or '') + item_link = h.url_for(controller='revision', action='read', id=revision.id) + item_description = _('Datasets affected: %s.\n') % pkgs + item_description += '%s' % (revision.message or '') + item_author_name = revision.author + item_pubdate = revision.timestamp + feed.add_item( + title=item_title, + link=item_link, + description=item_description, + author_name=item_author_name, + pubdate=item_pubdate, + ) + feed.content_type = 'application/atom+xml' + return feed.writeString('utf-8') + else: + query = model.Session.query(model.Revision) + c.page = h.Page( + collection=query, + page=h.get_page_number(request.params), + url=h.pager_url, + items_per_page=20 + ) + return base.render('revision/list.html') + + def read(self, id=None): + if id is None: + base.abort(404) + c.revision = model.Session.query(model.Revision).get(id) + if c.revision is None: + base.abort(404) + + pkgs = model.Session.query(model.PackageRevision).\ + filter_by(revision=c.revision) + c.packages = [pkg.continuity for pkg in pkgs if not pkg.private] + pkgtags = model.Session.query(model.PackageTagRevision).\ + filter_by(revision=c.revision) + c.pkgtags = [pkgtag.continuity for pkgtag in pkgtags + if not pkgtag.package.private] + grps = model.Session.query(model.GroupRevision).\ + filter_by(revision=c.revision) + c.groups = [grp.continuity for grp in grps] + return base.render('revision/read.html') + + def diff(self, id=None): + if 'diff' not in request.params or 'oldid' not in request.params: + base.abort(400) + c.revision_from = model.Session.query(model.Revision).get( + request.params.getone('oldid')) + c.revision_to = model.Session.query(model.Revision).get( + request.params.getone('diff')) + + c.diff_entity = request.params.get('diff_entity') + if c.diff_entity == 'package': + c.pkg = model.Package.by_name(id) + diff = c.pkg.diff(c.revision_to, c.revision_from) + elif c.diff_entity == 'group': + c.group = model.Group.by_name(id) + diff = c.group.diff(c.revision_to, c.revision_from) + else: + base.abort(400) + + c.diff = diff.items() + c.diff.sort() + return base.render('revision/diff.html') + + def edit(self, id=None): + if id is None: + base.abort(404) + revision = model.Session.query(model.Revision).get(id) + if revision is None: + base.abort(404) + action = request.params.get('action', '') + if action in ['delete', 'undelete']: + # this should be at a lower level (e.g. logic layer) + if not c.revision_change_state_allowed: + base.abort(403) + if action == 'delete': + revision.state = model.State.DELETED + elif action == 'undelete': + revision.state = model.State.ACTIVE + model.Session.commit() + h.flash_success(_('Revision updated')) + h.redirect_to( + h.url_for(controller='revision', action='read', id=id)) diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/storage.py b/venv/lib/python2.7/site-packages/ckan/controllers/storage.py new file mode 100644 index 00000000..536bef67 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/storage.py @@ -0,0 +1,98 @@ +# encoding: utf-8 + +''' + +Note: This is the old file store controller for CKAN < 2.2. +If you are looking for how the file uploads work, you should check +`lib/uploader.py` and the `resource_download` method of the package +controller. + +''' +import os +import re + +from ofs import get_impl +from paste.fileapp import FileApp + +from ckan.lib.base import BaseController, request, config, h, abort + + +from logging import getLogger +log = getLogger(__name__) + + +BUCKET = config.get('ckan.storage.bucket', 'default') +key_prefix = config.get('ckan.storage.key_prefix', 'file/') + +_eq_re = re.compile(r"^(.*)(=[0-9]*)$") + + +def create_pairtree_marker(folder): + """ Creates the pairtree marker for tests if it doesn't exist """ + if not folder[:-1] == '/': + folder = folder + '/' + + directory = os.path.dirname(folder) + if not os.path.exists(directory): + os.makedirs(directory) + + target = os.path.join(directory, 'pairtree_version0_1') + if os.path.exists(target): + return + + open(target, 'wb').close() + + +def get_ofs(): + """Return a configured instance of the appropriate OFS driver. + """ + storage_backend = config['ofs.impl'] + kw = {} + for k, v in config.items(): + if not k.startswith('ofs.') or k == 'ofs.impl': + continue + kw[k[4:]] = v + + # Make sure we have created the marker file to avoid pairtree issues + if storage_backend == 'pairtree' and 'storage_dir' in kw: + create_pairtree_marker(kw['storage_dir']) + + ofs = get_impl(storage_backend)(**kw) + return ofs + + +class StorageController(BaseController): + '''Upload to storage backend. + ''' + _ofs_impl = None + + @property + def ofs(self): + if not StorageController._ofs_impl: + StorageController._ofs_impl = get_ofs() + return StorageController._ofs_impl + + def file(self, label): + exists = self.ofs.exists(BUCKET, label) + if not exists: + # handle erroneous trailing slash by redirecting to url w/o slash + if label.endswith('/'): + label = label[:-1] + # This may be best being cached_url until we have moved it into + # permanent storage + file_url = h.url_for('storage_file', label=label) + h.redirect_to(file_url) + else: + abort(404) + + file_url = self.ofs.get_url(BUCKET, label) + if file_url.startswith("file://"): + metadata = self.ofs.get_metadata(BUCKET, label) + filepath = file_url[len("file://"):] + headers = { + # 'Content-Disposition':'attachment; filename="%s"' % label, + 'Content-Type': metadata.get('_format', 'text/plain')} + fapp = FileApp(filepath, headers=None, **headers) + return fapp(request.environ, self.start_response) + else: + h.redirect_to(file_url.encode('ascii', 'ignore')) diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/tag.py b/venv/lib/python2.7/site-packages/ckan/controllers/tag.py new file mode 100644 index 00000000..e30b600f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/tag.py @@ -0,0 +1,80 @@ +# encoding: utf-8 + +from ckan.common import config +from paste.deploy.converters import asbool + +import ckan.logic as logic +import ckan.model as model +import ckan.lib.base as base +import ckan.lib.helpers as h +from ckan.lib.alphabet_paginate import AlphaPage + +from ckan.common import _, request, c + + +LIMIT = 25 + + +class TagController(base.BaseController): + + def __before__(self, action, **env): + base.BaseController.__before__(self, action, **env) + try: + context = {'model': model, 'user': c.user, + 'auth_user_obj': c.userobj} + logic.check_access('site_read', context) + except logic.NotAuthorized: + base.abort(403, _('Not authorized to see this page')) + + def index(self): + c.q = request.params.get('q', '') + + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj, + 'for_view': True} + + data_dict = {'all_fields': True} + + if c.q: + page = h.get_page_number(request.params) + data_dict['q'] = c.q + data_dict['limit'] = LIMIT + data_dict['offset'] = (page - 1) * LIMIT + data_dict['return_objects'] = True + + results = logic.get_action('tag_list')(context, data_dict) + + if c.q: + c.page = h.Page( + collection=results, + page=page, + item_count=len(results), + items_per_page=LIMIT + ) + c.page.items = results + else: + c.page = AlphaPage( + collection=results, + page=request.params.get('page', 'A'), + alpha_attribute='name', + other_text=_('Other'), + ) + + return base.render('tag/index.html') + + def read(self, id): + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj, + 'for_view': True} + + data_dict = {'id': id} + try: + c.tag = logic.get_action('tag_show')(context, data_dict) + except logic.NotFound: + base.abort(404, _('Tag not found')) + + if asbool(config.get('ckan.legacy_templates', False)): + return base.render('tag/read.html') + else: + h.redirect_to(controller='package', action='search', + tags=c.tag.get('name')) diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/template.py b/venv/lib/python2.7/site-packages/ckan/controllers/template.py new file mode 100644 index 00000000..d51f891d --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/template.py @@ -0,0 +1,45 @@ +# encoding: utf-8 + +import ckan.lib.base as base +import ckan.lib.render +from ckan.common import response + + +class TemplateController(base.BaseController): + + def view(self, url): + u"""By default, the final controller tried to fulfill the request + when no other routes match. It may be used to display a template + when all else fails, e.g.:: + + def view(self, url): + return render('/%s' % url) + + Or if you're using Mako and want to explicitly send a 404 (Not + Found) response code when the requested template doesn't exist:: + + import mako.exceptions + + def view(self, url): + try: + return render('/%s' % url) + except mako.exceptions.TopLevelLookupException: + abort(404) + + By default this controller aborts the request with a 404 (Not + Found) + """ + if url.endswith(u'.txt'): + response.headers[b'Content-Type'] = b'text/plain; charset=utf-8' + # Default content-type is text/html + try: + return base.render(url) + except ckan.lib.render.TemplateNotFound: + if url.endswith(u'.html'): + base.abort(404) + url += u'.html' + response.headers[b'Content-Type'] = b'text/html; charset=utf-8' + try: + return base.render(url) + except ckan.lib.render.TemplateNotFound: + base.abort(404) diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/user.py b/venv/lib/python2.7/site-packages/ckan/controllers/user.py new file mode 100644 index 00000000..23d143bd --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/user.py @@ -0,0 +1,748 @@ +# encoding: utf-8 + +import logging + +from ckan.common import config +from paste.deploy.converters import asbool +from six import text_type + +import ckan.lib.base as base +import ckan.model as model +import ckan.lib.helpers as h +import ckan.authz as authz +import ckan.logic as logic +import ckan.logic.schema as schema +import ckan.lib.captcha as captcha +import ckan.lib.mailer as mailer +import ckan.lib.navl.dictization_functions as dictization_functions +import ckan.lib.authenticator as authenticator +import ckan.plugins as p + +from ckan.common import _, c, request, response + +log = logging.getLogger(__name__) + + +abort = base.abort +render = base.render + +check_access = logic.check_access +get_action = logic.get_action +NotFound = logic.NotFound +NotAuthorized = logic.NotAuthorized +ValidationError = logic.ValidationError +UsernamePasswordError = logic.UsernamePasswordError + +DataError = dictization_functions.DataError +unflatten = dictization_functions.unflatten + + +def set_repoze_user(user_id): + '''Set the repoze.who cookie to match a given user_id''' + if 'repoze.who.plugins' in request.environ: + rememberer = request.environ['repoze.who.plugins']['friendlyform'] + identity = {'repoze.who.userid': user_id} + response.headerlist += rememberer.remember(request.environ, + identity) + + +class UserController(base.BaseController): + def __before__(self, action, **env): + base.BaseController.__before__(self, action, **env) + try: + context = {'model': model, 'user': c.user, + 'auth_user_obj': c.userobj} + check_access('site_read', context) + except NotAuthorized: + if c.action not in ('login', 'request_reset', 'perform_reset',): + abort(403, _('Not authorized to see this page')) + + # hooks for subclasses + new_user_form = 'user/new_user_form.html' + edit_user_form = 'user/edit_user_form.html' + + def _new_form_to_db_schema(self): + return schema.user_new_form_schema() + + def _db_to_new_form_schema(self): + '''This is an interface to manipulate data from the database + into a format suitable for the form (optional)''' + + def _edit_form_to_db_schema(self): + return schema.user_edit_form_schema() + + def _db_to_edit_form_schema(self): + '''This is an interface to manipulate data from the database + into a format suitable for the form (optional)''' + + def _setup_template_variables(self, context, data_dict): + c.is_sysadmin = authz.is_sysadmin(c.user) + try: + user_dict = get_action('user_show')(context, data_dict) + except NotFound: + h.flash_error(_('Not authorized to see this page')) + h.redirect_to(controller='user', action='login') + except NotAuthorized: + abort(403, _('Not authorized to see this page')) + + c.user_dict = user_dict + c.is_myself = user_dict['name'] == c.user + c.about_formatted = h.render_markdown(user_dict['about']) + + # end hooks + + def _get_repoze_handler(self, handler_name): + '''Returns the URL that repoze.who will respond to and perform a + login or logout.''' + return getattr(request.environ['repoze.who.plugins']['friendlyform'], + handler_name) + + def index(self): + page = h.get_page_number(request.params) + c.q = request.params.get('q', '') + c.order_by = request.params.get('order_by', 'name') + + context = {'return_query': True, 'user': c.user, + 'auth_user_obj': c.userobj} + + data_dict = {'q': c.q, + 'order_by': c.order_by} + + limit = int( + request.params.get('limit', config.get('ckan.user_list_limit', 20)) + ) + try: + check_access('user_list', context, data_dict) + except NotAuthorized: + abort(403, _('Not authorized to see this page')) + + users_list = get_action('user_list')(context, data_dict) + + c.page = h.Page( + collection=users_list, + page=page, + url=h.pager_url, + item_count=users_list.count(), + items_per_page=limit + ) + return render('user/list.html') + + def read(self, id=None): + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj, + 'for_view': True} + data_dict = {'id': id, + 'user_obj': c.userobj, + 'include_datasets': True, + 'include_num_followers': True} + + self._setup_template_variables(context, data_dict) + + # The legacy templates have the user's activity stream on the user + # profile page, new templates do not. + if asbool(config.get('ckan.legacy_templates', False)): + c.user_activity_stream = get_action('user_activity_list_html')( + context, {'id': c.user_dict['id']}) + + return render('user/read.html') + + def me(self, locale=None): + if not c.user: + h.redirect_to(locale=locale, controller='user', action='login', + id=None) + user_ref = c.userobj.get_reference_preferred_for_uri() + h.redirect_to(locale=locale, controller='user', action='dashboard') + + def register(self, data=None, errors=None, error_summary=None): + context = {'model': model, 'session': model.Session, 'user': c.user, + 'auth_user_obj': c.userobj} + try: + check_access('user_create', context) + except NotAuthorized: + abort(403, _('Unauthorized to register as a user.')) + + return self.new(data, errors, error_summary) + + def new(self, data=None, errors=None, error_summary=None): + '''GET to display a form for registering a new user. + or POST the form data to actually do the user registration. + ''' + context = {'model': model, + 'session': model.Session, + 'user': c.user, + 'auth_user_obj': c.userobj, + 'schema': self._new_form_to_db_schema(), + 'save': 'save' in request.params} + + try: + check_access('user_create', context) + except NotAuthorized: + abort(403, _('Unauthorized to create a user')) + + if context['save'] and not data and request.method == 'POST': + return self._save_new(context) + + if c.user and not data and not authz.is_sysadmin(c.user): + # #1799 Don't offer the registration form if already logged in + return render('user/logout_first.html') + + data = data or {} + errors = errors or {} + error_summary = error_summary or {} + vars = {'data': data, 'errors': errors, 'error_summary': error_summary} + + c.is_sysadmin = authz.is_sysadmin(c.user) + c.form = render(self.new_user_form, extra_vars=vars) + return render('user/new.html') + + def delete(self, id): + '''Delete user with id passed as parameter''' + context = {'model': model, + 'session': model.Session, + 'user': c.user, + 'auth_user_obj': c.userobj} + data_dict = {'id': id} + + try: + get_action('user_delete')(context, data_dict) + user_index = h.url_for(controller='user', action='index') + h.redirect_to(user_index) + except NotAuthorized: + msg = _('Unauthorized to delete user with id "{user_id}".') + abort(403, msg.format(user_id=id)) + + def generate_apikey(self, id): + '''Cycle the API key of a user''' + context = {'model': model, + 'session': model.Session, + 'user': c.user, + 'auth_user_obj': c.userobj, + } + if id is None: + if c.userobj: + id = c.userobj.id + else: + abort(400, _('No user specified')) + data_dict = {'id': id} + + try: + result = get_action('user_generate_apikey')(context, data_dict) + except NotAuthorized: + abort(403, _('Unauthorized to edit user %s') % '') + except NotFound: + abort(404, _('User not found')) + + h.flash_success(_('Profile updated')) + h.redirect_to(controller='user', action='read', id=result['name']) + + def _save_new(self, context): + try: + data_dict = logic.clean_dict(unflatten( + logic.tuplize_dict(logic.parse_params(request.params)))) + context['message'] = data_dict.get('log_message', '') + captcha.check_recaptcha(request) + user = get_action('user_create')(context, data_dict) + except NotAuthorized: + abort(403, _('Unauthorized to create user %s') % '') + except NotFound as e: + abort(404, _('User not found')) + except DataError: + abort(400, _(u'Integrity Error')) + except captcha.CaptchaError: + error_msg = _(u'Bad Captcha. Please try again.') + h.flash_error(error_msg) + return self.new(data_dict) + except ValidationError as e: + errors = e.error_dict + error_summary = e.error_summary + return self.new(data_dict, errors, error_summary) + if not c.user: + # log the user in programatically + set_repoze_user(data_dict['name']) + h.redirect_to(controller='user', action='me') + else: + # #1799 User has managed to register whilst logged in - warn user + # they are not re-logged in as new user. + h.flash_success(_('User "%s" is now registered but you are still ' + 'logged in as "%s" from before') % + (data_dict['name'], c.user)) + if authz.is_sysadmin(c.user): + # the sysadmin created a new user. We redirect him to the + # activity page for the newly created user + h.redirect_to(controller='user', + action='activity', + id=data_dict['name']) + else: + return render('user/logout_first.html') + + def edit(self, id=None, data=None, errors=None, error_summary=None): + context = {'save': 'save' in request.params, + 'schema': self._edit_form_to_db_schema(), + 'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj + } + if id is None: + if c.userobj: + id = c.userobj.id + else: + abort(400, _('No user specified')) + data_dict = {'id': id} + + try: + check_access('user_update', context, data_dict) + except NotAuthorized: + abort(403, _('Unauthorized to edit a user.')) + + if context['save'] and not data and request.method == 'POST': + return self._save_edit(id, context) + + try: + old_data = get_action('user_show')(context, data_dict) + + schema = self._db_to_edit_form_schema() + if schema: + old_data, errors = \ + dictization_functions.validate(old_data, schema, context) + + c.display_name = old_data.get('display_name') + c.user_name = old_data.get('name') + + data = data or old_data + + except NotAuthorized: + abort(403, _('Unauthorized to edit user %s') % '') + except NotFound: + abort(404, _('User not found')) + + user_obj = context.get('user_obj') + + if not (authz.is_sysadmin(c.user) + or c.user == user_obj.name): + abort(403, _('User %s not authorized to edit %s') % + (str(c.user), id)) + + errors = errors or {} + vars = {'data': data, 'errors': errors, 'error_summary': error_summary} + + self._setup_template_variables({'model': model, + 'session': model.Session, + 'user': c.user}, + data_dict) + + c.is_myself = True + c.show_email_notifications = asbool( + config.get('ckan.activity_streams_email_notifications')) + c.form = render(self.edit_user_form, extra_vars=vars) + + return render('user/edit.html') + + def _save_edit(self, id, context): + try: + if id in (c.userobj.id, c.userobj.name): + current_user = True + else: + current_user = False + old_username = c.userobj.name + + data_dict = logic.clean_dict(unflatten( + logic.tuplize_dict(logic.parse_params(request.params)))) + context['message'] = data_dict.get('log_message', '') + data_dict['id'] = id + + email_changed = data_dict['email'] != c.userobj.email + + if (data_dict['password1'] and data_dict['password2']) \ + or email_changed: + identity = {'login': c.user, + 'password': data_dict['old_password']} + auth = authenticator.UsernamePasswordAuthenticator() + + if auth.authenticate(request.environ, identity) != c.user: + raise UsernamePasswordError + + # MOAN: Do I really have to do this here? + if 'activity_streams_email_notifications' not in data_dict: + data_dict['activity_streams_email_notifications'] = False + + user = get_action('user_update')(context, data_dict) + h.flash_success(_('Profile updated')) + + if current_user and data_dict['name'] != old_username: + # Changing currently logged in user's name. + # Update repoze.who cookie to match + set_repoze_user(data_dict['name']) + h.redirect_to(controller='user', action='read', id=user['name']) + except NotAuthorized: + abort(403, _('Unauthorized to edit user %s') % id) + except NotFound as e: + abort(404, _('User not found')) + except DataError: + abort(400, _(u'Integrity Error')) + except ValidationError as e: + errors = e.error_dict + error_summary = e.error_summary + return self.edit(id, data_dict, errors, error_summary) + except UsernamePasswordError: + errors = {'oldpassword': [_('Password entered was incorrect')]} + error_summary = {_('Old Password'): _('incorrect password')} + return self.edit(id, data_dict, errors, error_summary) + + def login(self, error=None): + # Do any plugin login stuff + for item in p.PluginImplementations(p.IAuthenticator): + item.login() + + if 'error' in request.params: + h.flash_error(request.params['error']) + + if not c.user: + came_from = request.params.get('came_from') + if not came_from: + came_from = h.url_for(controller='user', action='logged_in') + c.login_handler = h.url_for( + self._get_repoze_handler('login_handler_path'), + came_from=came_from) + if error: + vars = {'error_summary': {'': error}} + else: + vars = {} + return render('user/login.html', extra_vars=vars) + else: + return render('user/logout_first.html') + + def logged_in(self): + # redirect if needed + came_from = request.params.get('came_from', '') + if h.url_is_local(came_from): + return h.redirect_to(str(came_from)) + + if c.user: + context = None + data_dict = {'id': c.user} + + user_dict = get_action('user_show')(context, data_dict) + + return self.me() + else: + err = _('Login failed. Bad username or password.') + if asbool(config.get('ckan.legacy_templates', 'false')): + h.flash_error(err) + h.redirect_to(controller='user', + action='login', came_from=came_from) + else: + return self.login(error=err) + + def logout(self): + # Do any plugin logout stuff + for item in p.PluginImplementations(p.IAuthenticator): + item.logout() + url = h.url_for(controller='user', action='logged_out_page') + h.redirect_to(self._get_repoze_handler('logout_handler_path') + + '?came_from=' + url, parse_url=True) + + def logged_out(self): + # redirect if needed + came_from = request.params.get('came_from', '') + if h.url_is_local(came_from): + return h.redirect_to(str(came_from)) + h.redirect_to(controller='user', action='logged_out_page') + + def logged_out_page(self): + return render('user/logout.html') + + def request_reset(self): + context = {'model': model, 'session': model.Session, 'user': c.user, + 'auth_user_obj': c.userobj} + data_dict = {'id': request.params.get('user')} + try: + check_access('request_reset', context) + except NotAuthorized: + abort(403, _('Unauthorized to request reset password.')) + + if request.method == 'POST': + id = request.params.get('user') + + context = {'model': model, + 'user': c.user} + + data_dict = {'id': id} + user_obj = None + try: + user_dict = get_action('user_show')(context, data_dict) + user_obj = context['user_obj'] + except NotFound: + # Try searching the user + del data_dict['id'] + data_dict['q'] = id + + if id and len(id) > 2: + user_list = get_action('user_list')(context, data_dict) + if len(user_list) == 1: + # This is ugly, but we need the user object for the + # mailer, + # and user_list does not return them + del data_dict['q'] + data_dict['id'] = user_list[0]['id'] + user_dict = get_action('user_show')(context, data_dict) + user_obj = context['user_obj'] + elif len(user_list) > 1: + h.flash_error(_('"%s" matched several users') % (id)) + else: + h.flash_error(_('No such user: %s') % id) + else: + h.flash_error(_('No such user: %s') % id) + + if user_obj: + try: + mailer.send_reset_link(user_obj) + h.flash_success(_('Please check your inbox for ' + 'a reset code.')) + h.redirect_to(u'home.index') + except mailer.MailerException as e: + h.flash_error(_('Could not send reset link: %s') % + text_type(e)) + return render('user/request_reset.html') + + def perform_reset(self, id): + # FIXME 403 error for invalid key is a non helpful page + context = {'model': model, 'session': model.Session, + 'user': id, + 'keep_email': True} + + try: + check_access('user_reset', context) + except NotAuthorized: + abort(403, _('Unauthorized to reset password.')) + + try: + data_dict = {'id': id} + user_dict = get_action('user_show')(context, data_dict) + + user_obj = context['user_obj'] + except NotFound as e: + abort(404, _('User not found')) + + c.reset_key = request.params.get('key') + if not mailer.verify_reset_link(user_obj, c.reset_key): + h.flash_error(_('Invalid reset key. Please try again.')) + abort(403) + + if request.method == 'POST': + try: + context['reset_password'] = True + user_state = user_dict['state'] + new_password = self._get_form_password() + user_dict['password'] = new_password + username = request.params.get('name') + if (username is not None and username != ''): + user_dict['name'] = username + user_dict['reset_key'] = c.reset_key + user_dict['state'] = model.State.ACTIVE + user = get_action('user_update')(context, user_dict) + mailer.create_reset_key(user_obj) + + h.flash_success(_("Your password has been reset.")) + h.redirect_to(u'home.index') + except NotAuthorized: + h.flash_error(_('Unauthorized to edit user %s') % id) + except NotFound as e: + h.flash_error(_('User not found')) + except DataError: + h.flash_error(_(u'Integrity Error')) + except ValidationError as e: + h.flash_error(u'%r' % e.error_dict) + except ValueError as ve: + h.flash_error(text_type(ve)) + user_dict['state'] = user_state + + c.user_dict = user_dict + return render('user/perform_reset.html') + + def _get_form_password(self): + password1 = request.params.getone('password1') + password2 = request.params.getone('password2') + if (password1 is not None and password1 != ''): + if not len(password1) >= 4: + raise ValueError(_('Your password must be 4 ' + 'characters or longer.')) + elif not password1 == password2: + raise ValueError(_('The passwords you entered' + ' do not match.')) + return password1 + raise ValueError(_('You must provide a password')) + + def followers(self, id=None): + context = {'for_view': True, 'user': c.user, + 'auth_user_obj': c.userobj} + data_dict = {'id': id, 'user_obj': c.userobj, + 'include_num_followers': True} + self._setup_template_variables(context, data_dict) + f = get_action('user_follower_list') + try: + c.followers = f(context, {'id': c.user_dict['id']}) + except NotAuthorized: + abort(403, _('Unauthorized to view followers %s') % '') + return render('user/followers.html') + + def activity(self, id, offset=0): + '''Render this user's public activity stream page.''' + + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj, + 'for_view': True} + data_dict = {'id': id, 'user_obj': c.userobj, + 'include_num_followers': True} + try: + check_access('user_show', context, data_dict) + except NotAuthorized: + abort(403, _('Not authorized to see this page')) + + self._setup_template_variables(context, data_dict) + + try: + c.user_activity_stream = get_action('user_activity_list_html')( + context, {'id': c.user_dict['id'], 'offset': offset}) + except ValidationError: + base.abort(400) + + return render('user/activity_stream.html') + + def _get_dashboard_context(self, filter_type=None, filter_id=None, q=None): + '''Return a dict needed by the dashboard view to determine context.''' + + def display_name(followee): + '''Return a display name for a user, group or dataset dict.''' + display_name = followee.get('display_name') + fullname = followee.get('fullname') + title = followee.get('title') + name = followee.get('name') + return display_name or fullname or title or name + + if (filter_type and filter_id): + context = { + 'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj, + 'for_view': True + } + data_dict = {'id': filter_id, 'include_num_followers': True} + followee = None + + action_functions = { + 'dataset': 'package_show', + 'user': 'user_show', + 'group': 'group_show', + 'organization': 'organization_show', + } + action_function = logic.get_action( + action_functions.get(filter_type)) + # Is this a valid type? + if action_function is None: + abort(404, _('Follow item not found')) + try: + followee = action_function(context, data_dict) + except (NotFound, NotAuthorized): + abort(404, _('{0} not found').format(filter_type)) + + if followee is not None: + return { + 'filter_type': filter_type, + 'q': q, + 'context': display_name(followee), + 'selected_id': followee.get('id'), + 'dict': followee, + } + + return { + 'filter_type': filter_type, + 'q': q, + 'context': _('Everything'), + 'selected_id': False, + 'dict': None, + } + + def dashboard(self, id=None, offset=0): + context = {'model': model, 'session': model.Session, + 'user': c.user, 'auth_user_obj': c.userobj, + 'for_view': True} + data_dict = {'id': id, 'user_obj': c.userobj, 'offset': offset} + self._setup_template_variables(context, data_dict) + + q = request.params.get('q', u'') + filter_type = request.params.get('type', u'') + filter_id = request.params.get('name', u'') + + c.followee_list = get_action('followee_list')( + context, {'id': c.userobj.id, 'q': q}) + c.dashboard_activity_stream_context = self._get_dashboard_context( + filter_type, filter_id, q) + c.dashboard_activity_stream = h.dashboard_activity_stream( + c.userobj.id, filter_type, filter_id, offset + ) + + # Mark the user's new activities as old whenever they view their + # dashboard page. + get_action('dashboard_mark_activities_old')(context, {}) + + return render('user/dashboard.html') + + def dashboard_datasets(self): + context = {'for_view': True, 'user': c.user, + 'auth_user_obj': c.userobj} + data_dict = {'user_obj': c.userobj, 'include_datasets': True} + self._setup_template_variables(context, data_dict) + return render('user/dashboard_datasets.html') + + def dashboard_organizations(self): + context = {'for_view': True, 'user': c.user, + 'auth_user_obj': c.userobj} + data_dict = {'user_obj': c.userobj} + self._setup_template_variables(context, data_dict) + return render('user/dashboard_organizations.html') + + def dashboard_groups(self): + context = {'for_view': True, 'user': c.user, + 'auth_user_obj': c.userobj} + data_dict = {'user_obj': c.userobj} + self._setup_template_variables(context, data_dict) + return render('user/dashboard_groups.html') + + def follow(self, id): + '''Start following this user.''' + context = {'model': model, + 'session': model.Session, + 'user': c.user, + 'auth_user_obj': c.userobj} + data_dict = {'id': id, 'include_num_followers': True} + try: + get_action('follow_user')(context, data_dict) + user_dict = get_action('user_show')(context, data_dict) + h.flash_success(_("You are now following {0}").format( + user_dict['display_name'])) + except ValidationError as e: + error_message = (e.message or e.error_summary + or e.error_dict) + h.flash_error(error_message) + except NotAuthorized as e: + h.flash_error(e.message) + h.redirect_to(controller='user', action='read', id=id) + + def unfollow(self, id): + '''Stop following this user.''' + context = {'model': model, + 'session': model.Session, + 'user': c.user, + 'auth_user_obj': c.userobj} + data_dict = {'id': id, 'include_num_followers': True} + try: + get_action('unfollow_user')(context, data_dict) + user_dict = get_action('user_show')(context, data_dict) + h.flash_success(_("You are no longer following {0}").format( + user_dict['display_name'])) + except (NotFound, NotAuthorized) as e: + error_message = e.message + h.flash_error(error_message) + except ValidationError as e: + error_message = (e.error_summary or e.message + or e.error_dict) + h.flash_error(error_message) + h.redirect_to(controller='user', action='read', id=id) diff --git a/venv/lib/python2.7/site-packages/ckan/controllers/util.py b/venv/lib/python2.7/site-packages/ckan/controllers/util.py new file mode 100644 index 00000000..4a41bfb0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/controllers/util.py @@ -0,0 +1,42 @@ +# encoding: utf-8 + +import re + +import ckan.lib.base as base +import ckan.lib.i18n as i18n +import ckan.lib.helpers as h +from ckan.common import _ + + +class UtilController(base.BaseController): + ''' Controller for functionality that has no real home''' + + def redirect(self): + ''' redirect to the url parameter. ''' + url = base.request.params.get('url') + if not url: + base.abort(400, _('Missing Value') + ': url') + + if h.url_is_local(url): + return h.redirect_to(url) + else: + base.abort(403, _('Redirecting to external site is not allowed.')) + + def primer(self): + ''' Render all html components out onto a single page. + This is useful for development/styling of ckan. ''' + return base.render('development/primer.html') + + def markup(self): + ''' Render all html elements out onto a single page. + This is useful for development/styling of ckan. ''' + return base.render('development/markup.html') + + def i18_js_strings(self, lang): + ''' This is used to produce the translations for javascript. ''' + i18n.set_lang(lang) + html = base.render('js_strings.html', cache_force=True) + html = re.sub('<[^\>]*>', '', html) + header = "text/javascript; charset=utf-8" + base.response.headers['Content-type'] = header + return html diff --git a/venv/lib/python2.7/site-packages/ckan/exceptions.py b/venv/lib/python2.7/site-packages/ckan/exceptions.py new file mode 100644 index 00000000..370b6b3e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/exceptions.py @@ -0,0 +1,34 @@ +# encoding: utf-8 + + +class CkanException(Exception): + pass + + +class EmptyRevisionException(CkanException): + pass + + +class CkanUrlException(Exception): + pass + + +class CkanVersionException(Exception): + '''Exception raised by + :py:func:`~ckan.plugins.toolkit.requires_ckan_version` if the required CKAN + version is not available. + + ''' + pass + + +class CkanConfigurationException(Exception): + pass + + +class HelperError(Exception): + """Raised if an attempt to access an undefined helper is made. + + Normally, this would be a subclass of AttributeError, but Jinja2 will + catch and ignore them. We want this to be an explicit failure re #2908. + """ diff --git a/venv/lib/python2.7/site-packages/ckan/i18n/__init__.py b/venv/lib/python2.7/site-packages/ckan/i18n/__init__.py new file mode 100644 index 00000000..caf2aafa --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/i18n/__init__.py @@ -0,0 +1,3 @@ +# encoding: utf-8 + +# Need some content here to avoid the packaging stripping it out diff --git a/venv/lib/python2.7/site-packages/ckan/i18n/check_po_files.py b/venv/lib/python2.7/site-packages/ckan/i18n/check_po_files.py new file mode 100644 index 00000000..ea0d58bb --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/i18n/check_po_files.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# encoding: utf-8 + +'''Script for checking for common translation mistakes in po files, see: + + paster check-po-files --help + +for usage. +''' +import polib +import re +import paste.script.command + + +def simple_conv_specs(s): + '''Return the simple Python string conversion specifiers in the string s. + + e.g. ['%s', '%i'] + + See http://docs.python.org/library/stdtypes.html#string-formatting + + ''' + simple_conv_specs_re = re.compile('\%\w') + return simple_conv_specs_re.findall(s) + + +def mapping_keys(s): + '''Return a sorted list of the mapping keys in the string s. + + e.g. ['%(name)s', '%(age)i'] + + See http://docs.python.org/library/stdtypes.html#string-formatting + + ''' + mapping_keys_re = re.compile('\%\([^\)]*\)\w') + return sorted(mapping_keys_re.findall(s)) + + +def replacement_fields(s): + '''Return a sorted list of the Python replacement fields in the string s. + + e.g. ['{}', '{2}', '{object}', '{target}'] + + See http://docs.python.org/library/string.html#formatstrings + + ''' + repl_fields_re = re.compile('\{[^\}]*\}') + return sorted(repl_fields_re.findall(s)) + + +class CheckPoFiles(paste.script.command.Command): + + usage = "[FILE] ..." + group_name = 'ckan' + summary = 'Check po files for common mistakes' + parser = paste.script.command.Command.standard_parser(verbose=True) + + def command(self): + + for path in self.args: + print(u'Checking file {}'.format(path)) + errors = check_po_file(path) + if errors: + for msgid, msgstr in errors: + print("Format specifiers don't match:") + print(u' {0} -> {1}'.format( + msgid, msgstr.encode('ascii', 'replace'))) + + +def check_po_file(path): + errors = [] + + def check_translation(validator, msgid, msgstr): + if not validator(msgid) == validator(msgstr): + errors.append((msgid, msgstr)) + + po = polib.pofile(path) + for entry in po.translated_entries(): + if entry.msgid_plural and entry.msgstr_plural: + for function in (simple_conv_specs, mapping_keys, + replacement_fields): + for key, msgstr in entry.msgstr_plural.iteritems(): + if key == '0': + check_translation(function, entry.msgid, + entry.msgstr_plural[key]) + else: + check_translation(function, entry.msgid_plural, + entry.msgstr_plural[key]) + elif entry.msgstr: + for function in (simple_conv_specs, mapping_keys, + replacement_fields): + check_translation(function, entry.msgid, entry.msgstr) + + return errors diff --git a/venv/lib/python2.7/site-packages/ckan/i18n/ckan.pot b/venv/lib/python2.7/site-packages/ckan/i18n/ckan.pot new file mode 100644 index 00000000..190d06c3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/i18n/ckan.pot @@ -0,0 +1,4665 @@ +# Translations template for ckan. +# Copyright (C) 2018 ORGANIZATION +# This file is distributed under the same license as the ckan project. +# FIRST AUTHOR , 2018. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: ckan 2.8.0b0\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2018-03-27 16:10+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.5.3\n" + +#: ckanext/datapusher/helpers.py:21 +msgid "Complete" +msgstr "" + +#: ckanext/datapusher/helpers.py:22 +msgid "Pending" +msgstr "" + +#: ckanext/datapusher/helpers.py:23 +msgid "Submitting" +msgstr "" + +#: ckan/controllers/package.py:681 ckanext/datapusher/helpers.py:24 +msgid "Error" +msgstr "" + +#: ckanext/datapusher/helpers.py:29 +msgid "Not Uploaded Yet" +msgstr "" + +#: ckan/controllers/package.py:613 ckan/controllers/package.py:1076 +#: ckan/controllers/package.py:1096 ckan/controllers/package.py:1163 +#: ckan/controllers/package.py:1346 ckan/controllers/package.py:1424 +#: ckan/controllers/package.py:1455 ckan/controllers/package.py:1563 +#: ckan/controllers/package.py:1614 ckanext/datapusher/plugin.py:59 +#: ckanext/datastore/controller.py:80 ckanext/resourceproxy/controller.py:33 +msgid "Resource not found" +msgstr "" + +#: ckan/controllers/api.py:66 ckan/controllers/group.py:156 +#: ckan/controllers/home.py:27 ckan/controllers/package.py:144 +#: ckan/controllers/package.py:315 ckan/controllers/revision.py:34 +#: ckan/controllers/tag.py:27 ckan/controllers/user.py:58 +#: ckan/controllers/user.py:83 ckan/controllers/user.py:86 +#: ckan/controllers/user.py:117 ckan/controllers/user.py:598 +#: ckan/views/dashboard.py:24 ckan/views/user.py:59 ckan/views/user.py:62 +#: ckan/views/user.py:87 ckan/views/user.py:110 ckan/views/user.py:476 +#: ckanext/datapusher/plugin.py:68 +msgid "Not authorized to see this page" +msgstr "" + +#: ckanext/datapusher/templates-bs2/datapusher/resource_data.html:12 +#: ckanext/datapusher/templates/datapusher/resource_data.html:12 +msgid "Upload to DataStore" +msgstr "" + +#: ckanext/datapusher/templates-bs2/datapusher/resource_data.html:19 +#: ckanext/datapusher/templates/datapusher/resource_data.html:19 +msgid "Upload error:" +msgstr "" + +#: ckanext/datapusher/templates-bs2/datapusher/resource_data.html:25 +#: ckanext/datapusher/templates-bs2/datapusher/resource_data.html:27 +#: ckanext/datapusher/templates/datapusher/resource_data.html:25 +#: ckanext/datapusher/templates/datapusher/resource_data.html:27 +msgid "Error:" +msgstr "" + +#: ckanext/datapusher/templates-bs2/datapusher/resource_data.html:36 +#: ckanext/datapusher/templates/datapusher/resource_data.html:36 +msgid "Error traceback:" +msgstr "" + +#: ckanext/datapusher/templates-bs2/datapusher/resource_data.html:48 +#: ckanext/datapusher/templates/datapusher/resource_data.html:48 +msgid "Status" +msgstr "" + +#: ckanext/datapusher/templates-bs2/datapusher/resource_data.html:52 +#: ckanext/datapusher/templates/datapusher/resource_data.html:52 +msgid "Last updated" +msgstr "" + +#: ckanext/datapusher/templates-bs2/datapusher/resource_data.html:56 +#: ckanext/datapusher/templates/datapusher/resource_data.html:56 +msgid "Never" +msgstr "" + +#: ckanext/datapusher/templates-bs2/datapusher/resource_data.html:62 +#: ckanext/datapusher/templates/datapusher/resource_data.html:62 +msgid "Upload Log" +msgstr "" + +#: ckanext/datapusher/templates-bs2/datapusher/resource_data.html:76 +#: ckanext/datapusher/templates/datapusher/resource_data.html:76 +msgid "Details" +msgstr "" + +#: ckanext/datapusher/templates-bs2/datapusher/resource_data.html:83 +#: ckanext/datapusher/templates/datapusher/resource_data.html:83 +msgid "End of log" +msgstr "" + +#: ckanext/datapusher/templates-bs2/package/resource_edit_base.html:5 +#: ckanext/datapusher/templates/package/resource_edit_base.html:5 +msgid "DataStore" +msgstr "" + +#: ckanext/datastore/controller.py:53 +#, python-format +msgid "format: must be one of %s" +msgstr "" + +#: ckanext/datastore/controller.py:65 +msgid "DataStore resource not found" +msgstr "" + +#: ckanext/datastore/controller.py:101 +msgid "" +"Data Dictionary saved. Any type overrides will take effect when the resource " +"is next uploaded to DataStore" +msgstr "" + +#: ckanext/datastore/backend/postgres.py:1032 +msgid "" +"The data was invalid (for example: a numeric value is out of range or was " +"inserted into a text field)." +msgstr "" + +#: ckanext/datastore/logic/action.py:258 ckanext/datastore/logic/action.py:286 +#: ckanext/datastore/logic/action.py:344 ckanext/datastore/logic/action.py:457 +msgid "Resource \"{0}\" was not found." +msgstr "" + +#: ckanext/datastore/logic/auth.py:19 +msgid "User {0} not authorized to update resource {1}" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:19 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:21 +msgid "CKAN Data API" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:23 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:25 +msgid "Access resource data via a web API with powerful query support" +msgstr "" + +#: ckanext/datastore/templates/ajax_snippets/api_info.html:26 +msgid "" +" Further information in the main CKAN Data API and DataStore documentation.

" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:33 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:35 +msgid "Endpoints" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:37 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:39 +msgid "The Data API can be accessed via the following actions of the CKAN action API." +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:42 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:44 +msgid "Create" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:46 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:48 +msgid "Update / Insert" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:50 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:52 +msgid "Query" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:54 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:56 +msgid "Query (via SQL)" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:66 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:68 +msgid "Querying" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:70 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:72 +msgid "Query example (first 5 results)" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:75 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:77 +msgid "Query example (results containing 'jones')" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:80 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:82 +msgid "Query example (via SQL statement)" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:91 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:93 +msgid "Example: Javascript" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:95 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:97 +msgid "A simple ajax (JSONP) request to the data API using jQuery." +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:116 +#: ckanext/datastore/templates/ajax_snippets/api_info.html:118 +msgid "Example: Python" +msgstr "" + +#: ckan/templates/group/member_new.html:60 +#: ckanext/datastore/templates-bs2/datastore/dictionary.html:26 +#: ckanext/datastore/templates/datastore/dictionary.html:20 +msgid "Save" +msgstr "" + +#: ckanext/datastore/templates-bs2/datastore/dictionary.html:16 +#: ckanext/datastore/templates/datastore/snippets/dictionary_form.html:3 +msgid "Field {num}." +msgstr "" + +#: ckanext/datastore/templates/datastore/snippets/dictionary_form.html:12 +msgid "Type Override" +msgstr "" + +#: ckanext/datastore/templates-bs2/datastore/dictionary.html:18 +#: ckanext/datastore/templates-bs2/package/resource_read.html:20 +#: ckanext/datastore/templates/datastore/snippets/dictionary_form.html:20 +#: ckanext/datastore/templates/package/resource_read.html:21 +#: ckanext/datatablesview/templates/datatables/datatables_form.html:18 +msgid "Label" +msgstr "" + +#: ckan/templates/group/snippets/group_form.html:20 +#: ckan/templates/organization/snippets/organization_form.html:20 +#: ckan/templates/package/snippets/package_basic_fields.html:19 +#: ckan/templates/package/snippets/resource_form.html:34 +#: ckan/templates/package/snippets/view_form.html:9 +#: ckanext/datastore/templates-bs2/datastore/dictionary.html:21 +#: ckanext/datastore/templates-bs2/package/resource_read.html:21 +#: ckanext/datastore/templates/datastore/snippets/dictionary_form.html:24 +#: ckanext/datastore/templates/package/resource_read.html:22 +msgid "Description" +msgstr "" + +#: ckanext/datastore/templates-bs2/package/resource_edit_base.html:6 +#: ckanext/datastore/templates-bs2/package/resource_read.html:14 +#: ckanext/datastore/templates/package/resource_edit_base.html:6 +#: ckanext/datastore/templates/package/resource_read.html:14 +msgid "Data Dictionary" +msgstr "" + +#: ckanext/datastore/templates-bs2/package/resource_read.html:18 +#: ckanext/datastore/templates/package/resource_read.html:19 +#: ckanext/datatablesview/templates/datatables/datatables_form.html:17 +msgid "Column" +msgstr "" + +#: ckanext/datastore/templates-bs2/package/resource_read.html:19 +#: ckanext/datastore/templates/package/resource_read.html:20 +msgid "Type" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/activity-stream.js:97 +#: ckan/public-bs2/base/javascript/modules/popover-context.js:62 +#: ckan/public/base/javascript/modules/activity-stream.js:97 +#: ckan/public/base/javascript/modules/popover-context.js:62 +#: ckan/templates/tests/mock_json_resource_preview_template.html:7 +#: ckan/templates/tests/mock_resource_preview_template.html:7 +#: ckanext/datastore/templates-bs2/package/snippets/data_api_button.html:8 +#: ckanext/datastore/templates/package/snippets/data_api_button.html:7 +#: ckanext/example_theme_docs/v18_snippet_api/fanstatic/example_theme_popover.js:21 +#: ckanext/example_theme_docs/v19_01_error/fanstatic/example_theme_popover.js:21 +#: ckanext/example_theme_docs/v19_02_error_handling/fanstatic/example_theme_popover.js:8 +#: ckanext/example_theme_docs/v20_pubsub/fanstatic/example_theme_popover.js:8 +#: ckanext/example_theme_docs/v21_custom_jquery_plugin/fanstatic/example_theme_popover.js:8 +#: ckanext/reclineview/theme/templates/recline_view.html:14 +#: ckanext/textview/theme/templates/text_view.html:9 +msgid "Loading..." +msgstr "" + +#: ckanext/datastore/templates-bs2/package/snippets/data_api_button.html:10 +#: ckanext/datastore/templates/package/snippets/data_api_button.html:9 +msgid "Data API" +msgstr "" + +#: ckanext/datastore/templates-bs2/ajax_snippets/api_info.html:24 +msgid "" +" Further information in the main CKAN Data API and DataStore documentation.

" +msgstr "" + +#: ckanext/datatablesview/plugin.py:47 ckanext/reclineview/plugin.py:128 +msgid "Table" +msgstr "" + +#: ckanext/datatablesview/templates/datatables/datatables_form.html:6 +msgid "Responsive display" +msgstr "" + +#: ckanext/datatablesview/templates/datatables/datatables_form.html:12 +msgid "Show Columns" +msgstr "" + +#: ckanext/datatablesview/templates/datatables/datatables_view.html:28 +msgid "Hide/Unhide Columns" +msgstr "" + +#: ckanext/example_iconfigurer/templates/admin/config.html:11 +msgid "Datasets per page" +msgstr "" + +#: ckanext/example_iconfigurer/templates/admin/config.html:13 +msgid "Test conf" +msgstr "" + +#: ckan/templates/group/read.html:13 ckan/templates/organization/read.html:19 +#: ckan/templates/package/search.html:29 ckan/templates/snippets/sort_by.html:14 +#: ckanext/example_idatasetform/templates/package/search.html:12 +msgid "Relevance" +msgstr "" + +#: ckan/templates/group/index.html:20 ckan/templates/group/read.html:14 +#: ckan/templates/organization/bulk_process.html:97 +#: ckan/templates/organization/index.html:20 +#: ckan/templates/organization/read.html:20 ckan/templates/package/search.html:30 +#: ckan/templates/snippets/search_form.html:4 +#: ckan/templates/snippets/simple_search.html:10 +#: ckan/templates/snippets/sort_by.html:15 +#: ckanext/example_idatasetform/templates/package/search.html:13 +msgid "Name Ascending" +msgstr "" + +#: ckan/templates/group/index.html:20 ckan/templates/group/read.html:15 +#: ckan/templates/organization/bulk_process.html:98 +#: ckan/templates/organization/index.html:20 +#: ckan/templates/organization/read.html:21 ckan/templates/package/search.html:31 +#: ckan/templates/snippets/search_form.html:4 +#: ckan/templates/snippets/simple_search.html:10 +#: ckan/templates/snippets/sort_by.html:16 +#: ckanext/example_idatasetform/templates/package/search.html:14 +msgid "Name Descending" +msgstr "" + +#: ckan/templates/group/read.html:16 +#: ckan/templates/organization/bulk_process.html:99 +#: ckan/templates/organization/read.html:22 ckan/templates/package/search.html:32 +#: ckan/templates/package/snippets/resource_form.html:53 +#: ckan/templates/snippets/sort_by.html:17 +#: ckanext/example_idatasetform/templates/package/search.html:15 +msgid "Last Modified" +msgstr "" + +#: ckanext/example_idatasetform/templates/package/search.html:16 +msgid "Custom Field Ascending" +msgstr "" + +#: ckanext/example_idatasetform/templates/package/search.html:17 +msgid "Custom Field Descending" +msgstr "" + +#: ckan/templates/group/read.html:17 ckan/templates/organization/read.html:23 +#: ckan/templates/package/search.html:33 +#: ckan/templates/snippets/package_item.html:50 +#: ckan/templates/snippets/popular.html:3 ckan/templates/snippets/sort_by.html:19 +#: ckanext/example_idatasetform/templates/package/search.html:18 +msgid "Popular" +msgstr "" + +#: ckanext/example_idatasetform/templates/package/snippets/additional_info.html:6 +#: ckanext/example_idatasetform/templates/package/snippets/package_basic_fields.html:4 +#: ckanext/example_idatasetform/templates/package/snippets/resource_form.html:6 +msgid "Custom Text" +msgstr "" + +#: ckanext/example_idatasetform/templates/package/snippets/package_basic_fields.html:4 +msgid "custom text" +msgstr "" + +#: ckanext/example_idatasetform/templates/package/snippets/package_metadata_fields.html:11 +msgid "Country Code" +msgstr "" + +#: ckanext/example_idatasetform/templates/package/snippets/resource_form.html:6 +msgid "custom resource text" +msgstr "" + +#: ckanext/example_itranslation/templates/home/index.html:4 +msgid "This is an untranslated string" +msgstr "" + +#: ckanext/example_theme_docs/v10_custom_snippet/templates/snippets/example_theme_most_popular_groups.html:20 +#: ckanext/example_theme_docs/v11_HTML_and_CSS/templates/snippets/example_theme_most_popular_groups.html:19 +msgid "This group has no description" +msgstr "" + +#: ckan/templates/group/snippets/group_item.html:32 +#: ckan/templates/organization/snippets/organization_item.html:31 +#: ckanext/example_theme_docs/v10_custom_snippet/templates/snippets/example_theme_most_popular_groups.html:23 +#: ckanext/example_theme_docs/v11_HTML_and_CSS/templates/snippets/example_theme_most_popular_groups.html:22 +msgid "{num} Dataset" +msgid_plural "{num} Datasets" +msgstr[0] "" +msgstr[1] "" + +#: ckan/templates/group/snippets/group_item.html:34 +#: ckan/templates/organization/snippets/organization_item.html:33 +#: ckanext/example_theme_docs/v10_custom_snippet/templates/snippets/example_theme_most_popular_groups.html:25 +#: ckanext/example_theme_docs/v11_HTML_and_CSS/templates/snippets/example_theme_most_popular_groups.html:24 +msgid "0 Datasets" +msgstr "" + +#: ckanext/example_theme_docs/v12_extra_public_dir/templates/home/snippets/promoted.html:4 +msgid "CKAN's data previewing tool has many powerful features" +msgstr "" + +#: ckan/templates/group/followers.html:3 ckan/templates/group/followers.html:6 +#: ckan/templates/group/snippets/info.html:32 +#: ckan/templates/package/followers.html:3 ckan/templates/package/followers.html:6 +#: ckan/templates/package/snippets/info.html:24 +#: ckan/templates/snippets/context/group.html:13 +#: ckan/templates/snippets/context/user.html:15 +#: ckan/templates/snippets/organization.html:55 +#: ckan/templates/user/followers.html:3 ckan/templates/user/followers.html:7 +#: ckan/templates/user/read_base.html:49 +#: ckanext/example_theme_docs/v18_snippet_api/templates/ajax_snippets/example_theme_popover.html:12 +msgid "Followers" +msgstr "" + +#: ckan/logic/__init__.py:99 ckan/logic/action/__init__.py:60 +#: ckan/templates/package/edit_base.html:21 ckan/templates/package/resources.html:5 +#: ckan/templates/package/snippets/package_context.html:12 +#: ckan/templates/package/snippets/resources.html:20 +#: ckan/templates/snippets/context/dataset.html:13 +#: ckanext/example_theme_docs/v18_snippet_api/templates/ajax_snippets/example_theme_popover.html:15 +msgid "Resources" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/image-upload.js:80 +#: ckan/public/base/javascript/modules/image-upload.js:80 +#: ckan/templates/macros/form.html:424 ckanext/imageview/plugin.py:27 +#: ckanext/imageview/plugin.py:32 +msgid "Image" +msgstr "" + +#: ckanext/imageview/theme/templates/image_form.html:3 +msgid "Image url" +msgstr "" + +#: ckanext/imageview/theme/templates/image_form.html:3 +msgid "eg. http://example.com/image.jpg (if blank uses resource url)" +msgstr "" + +#: ckanext/reclineview/plugin.py:101 +msgid "Data Explorer" +msgstr "" + +#: ckanext/reclineview/plugin.py:171 +#: ckanext/reclineview/theme/public/recline_view.js:200 +#: ckanext/reclineview/theme/public/recline_view.min.js:14 +msgid "Graph" +msgstr "" + +#: ckanext/reclineview/plugin.py:231 +#: ckanext/reclineview/theme/public/recline_view.js:207 +#: ckanext/reclineview/theme/public/recline_view.min.js:14 +msgid "Map" +msgstr "" + +#: ckanext/reclineview/theme/public/recline_view.js:28 +#: ckanext/reclineview/theme/public/recline_view.min.js:1 +msgid "error loading view" +msgstr "" + +#: ckanext/reclineview/theme/public/recline_view.js:75 +#: ckanext/reclineview/theme/public/recline_view.min.js:5 +msgid "Could not load view" +msgstr "" + +#: ckanext/reclineview/theme/public/recline_view.js:77 +#: ckanext/reclineview/theme/public/recline_view.min.js:5 +msgid "DataStore returned an error" +msgstr "" + +#: ckanext/reclineview/theme/public/recline_view.js:79 +#: ckanext/reclineview/theme/public/recline_view.min.js:5 +msgid "DataProxy returned an error" +msgstr "" + +#: ckanext/reclineview/theme/public/recline_view.js:193 +#: ckanext/reclineview/theme/public/recline_view.min.js:14 +msgid "Grid" +msgstr "" + +#: ckan/templates/package/snippets/view_form_filters.html:46 +#: ckanext/reclineview/theme/public/recline_view.js:215 +#: ckanext/reclineview/theme/public/recline_view.min.js:14 +msgid "Filters" +msgstr "" + +#: ckanext/reclineview/theme/templates/recline_graph_form.html:3 +#: ckanext/reclineview/theme/templates/recline_map_form.html:3 +msgid "Row offset" +msgstr "" + +#: ckanext/reclineview/theme/templates/recline_graph_form.html:3 +#: ckanext/reclineview/theme/templates/recline_map_form.html:3 +msgid "eg: 0" +msgstr "" + +#: ckanext/reclineview/theme/templates/recline_graph_form.html:4 +#: ckanext/reclineview/theme/templates/recline_map_form.html:4 +msgid "Number of rows" +msgstr "" + +#: ckanext/reclineview/theme/templates/recline_graph_form.html:4 +#: ckanext/reclineview/theme/templates/recline_map_form.html:4 +msgid "eg: 100" +msgstr "" + +#: ckanext/reclineview/theme/templates/recline_graph_form.html:6 +msgid "Graph type" +msgstr "" + +#: ckanext/reclineview/theme/templates/recline_graph_form.html:7 +msgid "Group (Axis 1)" +msgstr "" + +#: ckanext/reclineview/theme/templates/recline_graph_form.html:8 +msgid "Series (Axis 2)" +msgstr "" + +#: ckanext/reclineview/theme/templates/recline_map_form.html:6 +msgid "Field type" +msgstr "" + +#: ckanext/reclineview/theme/templates/recline_map_form.html:7 +msgid "Latitude field" +msgstr "" + +#: ckanext/reclineview/theme/templates/recline_map_form.html:8 +msgid "Longitude field" +msgstr "" + +#: ckanext/reclineview/theme/templates/recline_map_form.html:9 +msgid "GeoJSON field" +msgstr "" + +#: ckanext/reclineview/theme/templates/recline_map_form.html:10 +msgid "Auto zoom to features" +msgstr "" + +#: ckanext/reclineview/theme/templates/recline_map_form.html:11 +msgid "Cluster markers" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:10 +msgid "Total number of Datasets" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:17 +#: ckanext/stats/templates/ckanext/stats/index.html:40 +msgid "Date" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:18 +msgid "Total datasets" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:33 +#: ckanext/stats/templates/ckanext/stats/index.html:179 +msgid "Dataset Revisions per Week" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:41 +msgid "All dataset revisions" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:42 +msgid "New datasets" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:58 +#: ckanext/stats/templates/ckanext/stats/index.html:180 +msgid "Top Rated Datasets" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:64 +msgid "Average rating" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:65 +msgid "Number of ratings" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:79 +msgid "No ratings" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:84 +#: ckanext/stats/templates/ckanext/stats/index.html:181 +msgid "Most Edited Datasets" +msgstr "" + +#: ckan/logic/converters.py:148 ckan/logic/validators.py:151 +#: ckan/logic/validators.py:193 ckan/templates/package/read_base.html:19 +#: ckan/tests/config/test_middleware.py:619 +#: ckanext/stats/templates/ckanext/stats/index.html:89 +msgid "Dataset" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:90 +msgid "Number of edits" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:103 +msgid "No edited datasets" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:108 +#: ckanext/stats/templates/ckanext/stats/index.html:182 +msgid "Largest Groups" +msgstr "" + +#: ckan/logic/converters.py:173 ckan/logic/validators.py:246 +#: ckanext/stats/templates/ckanext/stats/index.html:113 +msgid "Group" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:114 +msgid "Number of datasets" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:127 +msgid "No groups" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:132 +#: ckanext/stats/templates/ckanext/stats/index.html:183 +msgid "Top Tags" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:136 +msgid "Tag Name" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:137 +#: ckanext/stats/templates/ckanext/stats/index.html:157 +msgid "Number of Datasets" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:152 +#: ckanext/stats/templates/ckanext/stats/index.html:184 +msgid "Users Creating Most Datasets" +msgstr "" + +#: ckan/logic/converters.py:123 ckan/logic/validators.py:216 +#: ckan/logic/validators.py:233 ckan/logic/validators.py:714 +#: ckan/templates/group/members.html:14 ckan/templates/organization/members.html:19 +#: ckanext/stats/templates/ckanext/stats/index.html:156 +msgid "User" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:175 +msgid "Statistics Menu" +msgstr "" + +#: ckanext/stats/templates/ckanext/stats/index.html:178 +msgid "Total Number of Datasets" +msgstr "" + +#: ckanext/textview/plugin.py:67 ckanext/textview/plugin.py:69 +msgid "Text" +msgstr "" + +#: ckanext/webpageview/plugin.py:22 ckanext/webpageview/plugin.py:27 +msgid "Website" +msgstr "" + +#: ckanext/webpageview/theme/templates/webpage_form.html:3 +msgid "Web Page url" +msgstr "" + +#: ckanext/webpageview/theme/templates/webpage_form.html:3 +msgid "eg. http://example.com (if blank uses resource url)" +msgstr "" + +#: ckan/templates/dataviewer/snippets/data_preview.html:23 +#: ckan/templates/package/snippets/resource_view.html:64 +#: ckanext/webpageview/theme/templates/webpage_view.html:2 +msgid "Your browser does not support iframes." +msgstr "" + +#: ckan/authz.py:207 +#, python-format +msgid "Authorization function not found: %s" +msgstr "" + +#: ckan/authz.py:219 ckan/templates/header.html:11 +msgid "Admin" +msgstr "" + +#: ckan/authz.py:223 +msgid "Editor" +msgstr "" + +#: ckan/authz.py:227 +msgid "Member" +msgstr "" + +#: ckan/controllers/admin.py:34 ckan/views/admin.py:76 +msgid "Need to be system administrator to administer" +msgstr "" + +#: ckan/controllers/admin.py:50 ckan/templates/admin/config.html:14 +msgid "Site Title" +msgstr "" + +#: ckan/controllers/admin.py:51 ckan/templates/admin/config.html:16 +msgid "Style" +msgstr "" + +#: ckan/controllers/admin.py:52 ckan/templates/admin/config.html:18 +msgid "Site Tag Line" +msgstr "" + +#: ckan/controllers/admin.py:53 +msgid "Site Tag Logo" +msgstr "" + +#: ckan/controllers/admin.py:55 ckan/templates/admin/config.html:25 +#: ckan/templates/group/about.html:3 ckan/templates/group/read_base.html:19 +#: ckan/templates/header.html:88 ckan/templates/home/about.html:3 +#: ckan/templates/home/about.html:6 ckan/templates/home/about.html:16 +#: ckan/templates/organization/about.html:3 +#: ckan/templates/organization/read_base.html:19 +#: ckan/templates/user/edit_user_form.html:14 +msgid "About" +msgstr "" + +#: ckan/controllers/admin.py:55 ckan/templates/admin/config.html:25 +msgid "About page text" +msgstr "" + +#: ckan/controllers/admin.py:56 ckan/templates/admin/config.html:27 +msgid "Intro Text" +msgstr "" + +#: ckan/controllers/admin.py:56 ckan/templates/admin/config.html:27 +msgid "Text on home page" +msgstr "" + +#: ckan/controllers/admin.py:57 ckan/templates/admin/config.html:29 +msgid "Custom CSS" +msgstr "" + +#: ckan/controllers/admin.py:57 ckan/templates/admin/config.html:29 +msgid "Customisable css inserted into the page header" +msgstr "" + +#: ckan/controllers/admin.py:58 ckan/templates/admin/config.html:31 +msgid "Homepage" +msgstr "" + +#: ckan/controllers/admin.py:161 ckan/views/admin.py:175 +#, python-format +msgid "" +"Cannot purge package %s as associated revision %s includes non-deleted " +"packages %s" +msgstr "" + +#: ckan/controllers/admin.py:183 ckan/views/admin.py:197 +#, python-format +msgid "Problem purging revision %s: %s" +msgstr "" + +#: ckan/controllers/admin.py:185 ckan/views/admin.py:199 +msgid "Purge complete" +msgstr "" + +#: ckan/controllers/admin.py:187 ckan/views/admin.py:201 +msgid "Action not implemented." +msgstr "" + +#: ckan/controllers/api.py:127 ckan/controllers/api.py:218 ckan/views/api.py:112 +#: ckan/views/api.py:299 +msgid "Access denied" +msgstr "" + +#: ckan/controllers/api.py:133 ckan/controllers/api.py:227 +#: ckan/logic/action/create.py:911 ckan/logic/converters.py:123 +#: ckan/logic/converters.py:148 ckan/logic/converters.py:173 +#: ckan/logic/validators.py:151 ckan/logic/validators.py:172 +#: ckan/logic/validators.py:193 ckan/logic/validators.py:202 +#: ckan/logic/validators.py:216 ckan/logic/validators.py:233 +#: ckan/logic/validators.py:246 ckan/logic/validators.py:270 +#: ckan/logic/validators.py:714 ckan/views/api.py:119 ckan/views/api.py:308 +msgid "Not found" +msgstr "" + +#: ckan/controllers/api.py:139 ckan/views/api.py:126 +msgid "Bad request" +msgstr "" + +#: ckan/controllers/api.py:167 +#, python-format +msgid "Action name not known: %s" +msgstr "" + +#: ckan/controllers/api.py:188 ckan/views/api.py:268 +#, python-format +msgid "JSON Error: %s" +msgstr "" + +#: ckan/controllers/api.py:194 ckan/views/api.py:274 +#, python-format +msgid "Bad request data: %s" +msgstr "" + +#: ckan/controllers/api.py:283 +msgid "No revision specified" +msgstr "" + +#: ckan/controllers/api.py:287 +#, python-format +msgid "There is no revision with id: %s" +msgstr "" + +#: ckan/controllers/api.py:297 +msgid "Missing search term ('since_id=UUID' or 'since_time=TIMESTAMP')" +msgstr "" + +#: ckan/controllers/api.py:309 +#, python-format +msgid "Could not read parameters: %r" +msgstr "" + +#: ckan/controllers/api.py:370 +#, python-format +msgid "Bad search option: %s" +msgstr "" + +#: ckan/controllers/api.py:373 +#, python-format +msgid "Unknown register: %s" +msgstr "" + +#: ckan/controllers/api.py:382 +#, python-format +msgid "Malformed qjson value: %r" +msgstr "" + +#: ckan/controllers/api.py:392 +msgid "Request params must be in form of a json encoded dictionary." +msgstr "" + +#: ckan/controllers/feed.py:234 ckan/controllers/group.py:128 +#: ckan/controllers/group.py:226 ckan/controllers/group.py:394 +#: ckan/controllers/group.py:504 ckan/controllers/group.py:537 +#: ckan/controllers/group.py:567 ckan/controllers/group.py:578 +#: ckan/controllers/group.py:632 ckan/controllers/group.py:653 +#: ckan/controllers/group.py:714 ckan/controllers/group.py:746 +#: ckan/controllers/group.py:779 ckan/controllers/group.py:836 +#: ckan/controllers/group.py:933 ckan/controllers/package.py:1265 +#: ckan/controllers/package.py:1280 ckan/logic/action/create.py:1373 +#: ckan/views/feed.py:143 +msgid "Group not found" +msgstr "" + +#: ckan/controllers/feed.py:245 ckan/logic/action/create.py:1373 +#: ckan/views/feed.py:160 +msgid "Organization not found" +msgstr "" + +#: ckan/controllers/group.py:130 ckan/controllers/group.py:581 +msgid "Incorrect group type" +msgstr "" + +#: ckan/controllers/group.py:306 ckan/controllers/home.py:61 +#: ckan/controllers/package.py:256 ckan/lib/helpers.py:1036 +#: ckan/templates/header.html:87 ckan/templates/organization/edit_base.html:5 +#: ckan/templates/organization/edit_base.html:8 +#: ckan/templates/organization/index.html:3 +#: ckan/templates/organization/index.html:6 +#: ckan/templates/organization/index.html:18 +#: ckan/templates/organization/read_base.html:3 +#: ckan/templates/organization/read_base.html:6 ckan/templates/package/base.html:14 +#: ckan/views/home.py:46 +msgid "Organizations" +msgstr "" + +#: ckan/controllers/group.py:307 ckan/controllers/home.py:62 +#: ckan/controllers/package.py:257 ckan/lib/helpers.py:1037 +#: ckan/templates/group/base_form_page.html:6 ckan/templates/group/edit.html:4 +#: ckan/templates/group/edit_base.html:3 ckan/templates/group/edit_base.html:8 +#: ckan/templates/group/index.html:3 ckan/templates/group/index.html:6 +#: ckan/templates/group/index.html:18 ckan/templates/group/members.html:3 +#: ckan/templates/group/read_base.html:3 ckan/templates/group/read_base.html:6 +#: ckan/templates/header.html:88 ckan/templates/package/group_list.html:5 +#: ckan/templates/package/read_base.html:20 ckan/templates/revision/diff.html:16 +#: ckan/templates/revision/read.html:84 ckan/tests/config/test_middleware.py:632 +#: ckan/views/home.py:47 +msgid "Groups" +msgstr "" + +#: ckan/controllers/group.py:308 ckan/controllers/home.py:63 +#: ckan/controllers/package.py:258 ckan/lib/helpers.py:1038 +#: ckan/logic/__init__.py:110 +#: ckan/templates/package/snippets/package_basic_fields.html:24 +#: ckan/templates/snippets/context/dataset.html:17 ckan/templates/tag/index.html:3 +#: ckan/templates/tag/index.html:6 ckan/templates/tag/index.html:12 +#: ckan/views/home.py:48 +msgid "Tags" +msgstr "" + +#: ckan/controllers/group.py:309 ckan/controllers/home.py:64 +#: ckan/controllers/package.py:259 ckan/lib/helpers.py:1039 ckan/views/home.py:49 +msgid "Formats" +msgstr "" + +#: ckan/controllers/group.py:310 ckan/controllers/home.py:65 +#: ckan/controllers/package.py:260 ckan/lib/helpers.py:1040 ckan/views/home.py:50 +msgid "Licenses" +msgstr "" + +#: ckan/controllers/group.py:396 ckan/controllers/group.py:513 +#: ckan/controllers/package.py:345 ckan/controllers/package.py:576 +#: ckan/controllers/package.py:789 ckan/controllers/package.py:1409 +#: ckan/controllers/package.py:1443 +#, python-format +msgid "User %r not authorized to edit %s" +msgstr "" + +#: ckan/controllers/group.py:443 +msgid "Not authorized to perform bulk update" +msgstr "" + +#: ckan/controllers/group.py:461 +msgid "Unauthorized to create a group" +msgstr "" + +#: ckan/controllers/group.py:539 ckan/controllers/group.py:569 +#: ckan/controllers/package.py:944 ckan/controllers/package.py:992 +#: ckan/controllers/user.py:250 ckan/controllers/user.py:380 +#: ckan/controllers/user.py:551 ckan/views/user.py:193 ckan/views/user.py:300 +#: ckan/views/user.py:620 +msgid "Integrity Error" +msgstr "" + +#: ckan/controllers/group.py:595 +#, python-format +msgid "User %r not authorized to edit %s authorizations" +msgstr "" + +#: ckan/controllers/group.py:615 ckan/controllers/group.py:630 +#, python-format +msgid "Unauthorized to delete group %s" +msgstr "" + +#: ckan/controllers/group.py:621 +msgid "Organization has been deleted." +msgstr "" + +#: ckan/controllers/group.py:623 +msgid "Group has been deleted." +msgstr "" + +#: ckan/controllers/group.py:625 +#, python-format +msgid "%s has been deleted." +msgstr "" + +#: ckan/controllers/group.py:657 +#, python-format +msgid "User %r not authorized to edit members of %s" +msgstr "" + +#: ckan/controllers/group.py:670 +#, python-format +msgid "Unauthorized to create group %s members" +msgstr "" + +#: ckan/controllers/group.py:712 +#, python-format +msgid "Unauthorized to add member to group %s" +msgstr "" + +#: ckan/controllers/group.py:731 ckan/controllers/group.py:744 +#, python-format +msgid "Unauthorized to delete group %s members" +msgstr "" + +#: ckan/controllers/group.py:738 +msgid "Group member has been deleted." +msgstr "" + +#: ckan/controllers/group.py:762 ckan/controllers/package.py:436 +msgid "Select two revisions before doing the comparison." +msgstr "" + +#: ckan/controllers/group.py:786 +msgid "CKAN Group Revision History" +msgstr "" + +#: ckan/controllers/group.py:790 +msgid "Recent changes to CKAN Group: " +msgstr "" + +#: ckan/controllers/group.py:811 ckan/controllers/package.py:487 +msgid "Log message: " +msgstr "" + +#: ckan/controllers/group.py:861 ckan/controllers/package.py:1193 +#: ckan/controllers/user.py:719 ckan/views/user.py:650 +msgid "You are now following {0}" +msgstr "" + +#: ckan/controllers/group.py:881 ckan/controllers/package.py:1212 +#: ckan/controllers/user.py:739 ckan/views/user.py:672 +msgid "You are no longer following {0}" +msgstr "" + +#: ckan/controllers/group.py:901 ckan/controllers/user.py:584 +#: ckan/views/user.py:697 +#, python-format +msgid "Unauthorized to view followers %s" +msgstr "" + +#: ckan/controllers/home.py:35 +msgid "This site is currently off-line. Database is not initialised." +msgstr "" + +#: ckan/controllers/home.py:73 ckan/views/home.py:58 +#, python-format +msgid "Please update your profile and add your email address. " +msgstr "" + +#: ckan/controllers/home.py:75 ckan/views/home.py:60 +#, python-format +msgid "%s uses your email address if you need to reset your password." +msgstr "" + +#: ckan/controllers/package.py:304 +msgid "Invalid search query: {error_message}" +msgstr "" + +#: ckan/controllers/package.py:323 +msgid "Parameter \"{parameter_name}\" is not an integer" +msgstr "" + +#: ckan/controllers/package.py:343 ckan/controllers/package.py:351 +#: ckan/controllers/package.py:389 ckan/controllers/package.py:456 +#: ckan/controllers/package.py:775 ckan/controllers/package.py:823 +#: ckan/controllers/package.py:841 ckan/controllers/package.py:942 +#: ckan/controllers/package.py:990 ckan/controllers/package.py:1042 +#: ckan/controllers/package.py:1089 ckan/controllers/package.py:1237 +#: ckan/controllers/package.py:1253 ckan/controllers/package.py:1316 +#: ckan/controllers/package.py:1415 ckan/controllers/package.py:1450 +#: ckan/controllers/package.py:1557 +msgid "Dataset not found" +msgstr "" + +#: ckan/controllers/package.py:377 ckan/controllers/package.py:379 +#: ckan/controllers/package.py:381 +#, python-format +msgid "Invalid revision format: %r" +msgstr "" + +#: ckan/controllers/package.py:415 +msgid "Viewing datasets of type \"{package_type}\" is not supported ({file_!r})." +msgstr "" + +#: ckan/controllers/package.py:454 ckan/controllers/package.py:839 +#: ckan/controllers/package.py:940 ckan/controllers/package.py:988 +#: ckan/controllers/package.py:1239 +#, python-format +msgid "Unauthorized to read package %s" +msgstr "" + +#: ckan/controllers/package.py:463 +msgid "CKAN Dataset Revision History" +msgstr "" + +#: ckan/controllers/package.py:466 +msgid "Recent changes to CKAN Dataset: " +msgstr "" + +#: ckan/controllers/package.py:522 +msgid "Unauthorized to create a package" +msgstr "" + +#: ckan/controllers/package.py:598 +msgid "Unauthorized to edit this resource" +msgstr "" + +#: ckan/controllers/package.py:666 +msgid "Unauthorized to update dataset" +msgstr "" + +#: ckan/controllers/package.py:668 ckan/controllers/package.py:705 +#: ckan/controllers/package.py:731 +msgid "The dataset {id} could not be found." +msgstr "" + +#: ckan/controllers/package.py:672 +msgid "You must add at least one data resource" +msgstr "" + +#: ckan/controllers/package.py:703 +msgid "Unauthorized to create a resource" +msgstr "" + +#: ckan/controllers/package.py:736 +msgid "Unauthorized to create a resource for this package" +msgstr "" + +#: ckan/controllers/package.py:950 +msgid "Unable to add package to search index." +msgstr "" + +#: ckan/controllers/package.py:998 +msgid "Unable to update search index." +msgstr "" + +#: ckan/controllers/package.py:1035 +msgid "Dataset has been deleted." +msgstr "" + +#: ckan/controllers/package.py:1040 ckan/controllers/package.py:1058 +#, python-format +msgid "Unauthorized to delete package %s" +msgstr "" + +#: ckan/controllers/package.py:1063 +msgid "Resource has been deleted." +msgstr "" + +#: ckan/controllers/package.py:1074 +#, python-format +msgid "Unauthorized to delete resource %s" +msgstr "" + +#: ckan/controllers/package.py:1133 ckan/controllers/package.py:1576 +msgid "Resource view not found" +msgstr "" + +#: ckan/controllers/package.py:1172 +msgid "Resource data not found" +msgstr "" + +#: ckan/controllers/package.py:1181 +msgid "No download is available" +msgstr "" + +#: ckan/controllers/package.py:1318 +#, python-format +msgid "Unauthorized to read dataset %s" +msgstr "" + +#: ckan/controllers/package.py:1426 +#, python-format +msgid "Unauthorized to read resource %s" +msgstr "" + +#: ckan/controllers/package.py:1490 +msgid "Unauthorized to edit resource" +msgstr "" + +#: ckan/controllers/package.py:1508 +msgid "View not found" +msgstr "" + +#: ckan/controllers/package.py:1514 +msgid "View Type Not found" +msgstr "" + +#: ckan/controllers/package.py:1570 +msgid "Bad resource view data" +msgstr "" + +#: ckan/controllers/package.py:1579 +msgid "Resource view not supplied" +msgstr "" + +#: ckan/controllers/package.py:1608 +msgid "No preview has been defined." +msgstr "" + +#: ckan/controllers/revision.py:45 +msgid "CKAN Repository Revision History" +msgstr "" + +#: ckan/controllers/revision.py:47 +msgid "Recent changes to the CKAN repository." +msgstr "" + +#: ckan/controllers/revision.py:111 +#, python-format +msgid "Datasets affected: %s.\n" +msgstr "" + +#: ckan/controllers/revision.py:191 +msgid "Revision updated" +msgstr "" + +#: ckan/controllers/tag.py:60 +msgid "Other" +msgstr "" + +#: ckan/controllers/tag.py:74 +msgid "Tag not found" +msgstr "" + +#: ckan/controllers/user.py:162 ckan/views/user.py:290 +msgid "Unauthorized to register as a user." +msgstr "" + +#: ckan/controllers/user.py:180 +msgid "Unauthorized to create a user" +msgstr "" + +#: ckan/controllers/user.py:211 ckan/views/user.py:426 +msgid "Unauthorized to delete user with id \"{user_id}\"." +msgstr "" + +#: ckan/controllers/user.py:225 ckan/controllers/user.py:288 ckan/views/user.py:168 +#: ckan/views/user.py:444 +msgid "No user specified" +msgstr "" + +#: ckan/controllers/user.py:231 ckan/controllers/user.py:313 +#: ckan/controllers/user.py:376 ckan/controllers/user.py:547 ckan/views/user.py:218 +#: ckan/views/user.py:246 ckan/views/user.py:450 ckan/views/user.py:616 +#, python-format +msgid "Unauthorized to edit user %s" +msgstr "" + +#: ckan/controllers/user.py:233 ckan/controllers/user.py:248 +#: ckan/controllers/user.py:315 ckan/controllers/user.py:378 +#: ckan/controllers/user.py:523 ckan/controllers/user.py:549 +#: ckan/logic/auth/update.py:179 ckan/views/user.py:220 ckan/views/user.py:248 +#: ckan/views/user.py:315 ckan/views/user.py:452 ckan/views/user.py:573 +#: ckan/views/user.py:618 +msgid "User not found" +msgstr "" + +#: ckan/controllers/user.py:235 ckan/controllers/user.py:368 ckan/views/user.py:226 +#: ckan/views/user.py:454 +msgid "Profile updated" +msgstr "" + +#: ckan/controllers/user.py:246 ckan/views/user.py:313 +#, python-format +msgid "Unauthorized to create user %s" +msgstr "" + +#: ckan/controllers/user.py:252 ckan/views/user.py:306 +msgid "Bad Captcha. Please try again." +msgstr "" + +#: ckan/controllers/user.py:266 ckan/views/user.py:325 +#, python-format +msgid "" +"User \"%s\" is now registered but you are still logged in as \"%s\" from " +"before" +msgstr "" + +#: ckan/controllers/user.py:294 ckan/views/user.py:174 +msgid "Unauthorized to edit a user." +msgstr "" + +#: ckan/controllers/user.py:321 ckan/views/user.py:252 +#, python-format +msgid "User %s not authorized to edit %s" +msgstr "" + +#: ckan/controllers/user.py:386 ckan/views/user.py:210 +msgid "Password entered was incorrect" +msgstr "" + +#: ckan/controllers/user.py:387 ckan/templates/user/edit_user_form.html:29 +#: ckan/views/user.py:212 +msgid "Old Password" +msgstr "" + +#: ckan/controllers/user.py:387 ckan/views/user.py:212 +msgid "incorrect password" +msgstr "" + +#: ckan/controllers/user.py:427 ckan/views/user.py:386 +msgid "Login failed. Bad username or password." +msgstr "" + +#: ckan/controllers/user.py:460 ckan/views/user.py:504 +msgid "Unauthorized to request reset password." +msgstr "" + +#: ckan/controllers/user.py:489 ckan/views/user.py:530 +#, python-format +msgid "\"%s\" matched several users" +msgstr "" + +#: ckan/controllers/user.py:491 ckan/controllers/user.py:493 ckan/views/user.py:532 +#: ckan/views/user.py:534 +#, python-format +msgid "No such user: %s" +msgstr "" + +#: ckan/controllers/user.py:498 ckan/views/user.py:542 +msgid "Please check your inbox for a reset code." +msgstr "" + +#: ckan/controllers/user.py:502 ckan/views/user.py:546 +#, python-format +msgid "Could not send reset link: %s" +msgstr "" + +#: ckan/controllers/user.py:515 ckan/views/user.py:568 +msgid "Unauthorized to reset password." +msgstr "" + +#: ckan/controllers/user.py:527 ckan/views/user.py:577 +msgid "Invalid reset key. Please try again." +msgstr "" + +#: ckan/controllers/user.py:544 ckan/views/user.py:613 +msgid "Your password has been reset." +msgstr "" + +#: ckan/controllers/user.py:566 +msgid "Your password must be 4 characters or longer." +msgstr "" + +#: ckan/controllers/user.py:569 ckan/views/user.py:592 +msgid "The passwords you entered do not match." +msgstr "" + +#: ckan/controllers/user.py:572 ckan/views/user.py:595 +msgid "You must provide a password" +msgstr "" + +#: ckan/controllers/user.py:640 ckan/views/dashboard.py:58 +msgid "Follow item not found" +msgstr "" + +#: ckan/controllers/user.py:644 ckan/views/dashboard.py:62 +msgid "{0} not found" +msgstr "" + +#: ckan/controllers/user.py:658 ckan/views/dashboard.py:76 +msgid "Everything" +msgstr "" + +#: ckan/controllers/util.py:18 ckan/logic/action/__init__.py:62 +msgid "Missing Value" +msgstr "" + +#: ckan/controllers/util.py:23 +msgid "Redirecting to external site is not allowed." +msgstr "" + +#: ckan/lib/activity_streams.py:60 +msgid "{actor} added the tag {tag} to the dataset {dataset}" +msgstr "" + +#: ckan/lib/activity_streams.py:63 +msgid "{actor} updated the group {group}" +msgstr "" + +#: ckan/lib/activity_streams.py:66 +msgid "{actor} updated the organization {organization}" +msgstr "" + +#: ckan/lib/activity_streams.py:69 +msgid "{actor} updated the dataset {dataset}" +msgstr "" + +#: ckan/lib/activity_streams.py:72 +msgid "{actor} changed the extra {extra} of the dataset {dataset}" +msgstr "" + +#: ckan/lib/activity_streams.py:75 +msgid "{actor} updated the resource {resource} in the dataset {dataset}" +msgstr "" + +#: ckan/lib/activity_streams.py:78 +msgid "{actor} updated their profile" +msgstr "" + +#: ckan/lib/activity_streams.py:81 +msgid "{actor} deleted the group {group}" +msgstr "" + +#: ckan/lib/activity_streams.py:84 +msgid "{actor} deleted the organization {organization}" +msgstr "" + +#: ckan/lib/activity_streams.py:87 +msgid "{actor} deleted the dataset {dataset}" +msgstr "" + +#: ckan/lib/activity_streams.py:90 +msgid "{actor} deleted the extra {extra} from the dataset {dataset}" +msgstr "" + +#: ckan/lib/activity_streams.py:93 +msgid "{actor} deleted the resource {resource} from the dataset {dataset}" +msgstr "" + +#: ckan/lib/activity_streams.py:97 +msgid "{actor} created the group {group}" +msgstr "" + +#: ckan/lib/activity_streams.py:100 +msgid "{actor} created the organization {organization}" +msgstr "" + +#: ckan/lib/activity_streams.py:103 +msgid "{actor} created the dataset {dataset}" +msgstr "" + +#: ckan/lib/activity_streams.py:106 +msgid "{actor} added the extra {extra} to the dataset {dataset}" +msgstr "" + +#: ckan/lib/activity_streams.py:109 +msgid "{actor} added the resource {resource} to the dataset {dataset}" +msgstr "" + +#: ckan/lib/activity_streams.py:112 +msgid "{actor} signed up" +msgstr "" + +#: ckan/lib/activity_streams.py:115 +msgid "{actor} removed the tag {tag} from the dataset {dataset}" +msgstr "" + +#: ckan/lib/activity_streams.py:118 +msgid "{actor} started following {dataset}" +msgstr "" + +#: ckan/lib/activity_streams.py:121 +msgid "{actor} started following {user}" +msgstr "" + +#: ckan/lib/activity_streams.py:124 +msgid "{actor} started following {group}" +msgstr "" + +#: ckan/lib/datapreview.py:265 ckan/templates/group/edit_base.html:16 +#: ckan/templates/organization/edit_base.html:17 +#: ckan/templates/package/resource_read.html:38 +#: ckan/templates/package/resource_views.html:4 +msgid "View" +msgstr "" + +#: ckan/lib/email_notifications.py:103 +msgid "{n} new activity from {site_title}" +msgid_plural "{n} new activities from {site_title}" +msgstr[0] "" +msgstr[1] "" + +#: ckan/lib/formatters.py:19 +msgid "January" +msgstr "" + +#: ckan/lib/formatters.py:23 +msgid "February" +msgstr "" + +#: ckan/lib/formatters.py:27 +msgid "March" +msgstr "" + +#: ckan/lib/formatters.py:31 +msgid "April" +msgstr "" + +#: ckan/lib/formatters.py:35 +msgid "May" +msgstr "" + +#: ckan/lib/formatters.py:39 +msgid "June" +msgstr "" + +#: ckan/lib/formatters.py:43 +msgid "July" +msgstr "" + +#: ckan/lib/formatters.py:47 +msgid "August" +msgstr "" + +#: ckan/lib/formatters.py:51 +msgid "September" +msgstr "" + +#: ckan/lib/formatters.py:55 +msgid "October" +msgstr "" + +#: ckan/lib/formatters.py:59 +msgid "November" +msgstr "" + +#: ckan/lib/formatters.py:63 +msgid "December" +msgstr "" + +#: ckan/lib/formatters.py:114 +msgid "Just now" +msgstr "" + +#: ckan/lib/formatters.py:116 +msgid "{mins} minute ago" +msgid_plural "{mins} minutes ago" +msgstr[0] "" +msgstr[1] "" + +#: ckan/lib/formatters.py:119 +msgid "{hours} hour ago" +msgid_plural "{hours} hours ago" +msgstr[0] "" +msgstr[1] "" + +#: ckan/lib/formatters.py:125 +msgid "{days} day ago" +msgid_plural "{days} days ago" +msgstr[0] "" +msgstr[1] "" + +#: ckan/lib/formatters.py:128 +msgid "{months} month ago" +msgid_plural "{months} months ago" +msgstr[0] "" +msgstr[1] "" + +#: ckan/lib/formatters.py:130 +msgid "over {years} year ago" +msgid_plural "over {years} years ago" +msgstr[0] "" +msgstr[1] "" + +#: ckan/lib/formatters.py:146 +msgid "{month} {day}, {year}, {hour:02}:{min:02} ({timezone})" +msgstr "" + +#: ckan/lib/formatters.py:151 +msgid "{month} {day}, {year}" +msgstr "" + +#: ckan/lib/formatters.py:167 +msgid "{bytes} bytes" +msgstr "" + +#: ckan/lib/formatters.py:169 +msgid "{kibibytes} KiB" +msgstr "" + +#: ckan/lib/formatters.py:171 +msgid "{mebibytes} MiB" +msgstr "" + +#: ckan/lib/formatters.py:173 +msgid "{gibibytes} GiB" +msgstr "" + +#: ckan/lib/formatters.py:175 +msgid "{tebibytes} TiB" +msgstr "" + +#: ckan/lib/formatters.py:187 +msgid "{n}" +msgstr "" + +#: ckan/lib/formatters.py:189 +msgid "{k}k" +msgstr "" + +#: ckan/lib/formatters.py:191 +msgid "{m}M" +msgstr "" + +#: ckan/lib/formatters.py:193 +msgid "{g}G" +msgstr "" + +#: ckan/lib/formatters.py:195 +msgid "{t}T" +msgstr "" + +#: ckan/lib/formatters.py:197 +msgid "{p}P" +msgstr "" + +#: ckan/lib/formatters.py:199 +msgid "{e}E" +msgstr "" + +#: ckan/lib/formatters.py:201 +msgid "{z}Z" +msgstr "" + +#: ckan/lib/formatters.py:203 +msgid "{y}Y" +msgstr "" + +#: ckan/lib/helpers.py:1251 +msgid "Update your avatar at gravatar.com" +msgstr "" + +#: ckan/lib/helpers.py:1523 ckan/lib/helpers.py:1536 +msgid "Unknown" +msgstr "" + +#: ckan/lib/helpers.py:1587 +msgid "Unnamed resource" +msgstr "" + +#: ckan/lib/helpers.py:1627 +msgid "Created new dataset." +msgstr "" + +#: ckan/lib/helpers.py:1629 +msgid "Edited resources." +msgstr "" + +#: ckan/lib/helpers.py:1631 +msgid "Edited settings." +msgstr "" + +#: ckan/lib/helpers.py:1877 +msgid "{number} view" +msgid_plural "{number} views" +msgstr[0] "" +msgstr[1] "" + +#: ckan/lib/helpers.py:1879 +msgid "{number} recent view" +msgid_plural "{number} recent views" +msgstr[0] "" +msgstr[1] "" + +#: ckan/lib/mailer.py:46 +#, python-format +msgid "%s <%s>" +msgstr "" + +#: ckan/lib/mailer.py:116 +msgid "No recipient email address available!" +msgstr "" + +#: ckan/lib/mailer.py:134 ckan/templates/home/snippets/stats.html:17 +msgid "organization" +msgstr "" + +#: ckan/lib/mailer.py:135 ckan/templates/home/snippets/stats.html:23 +msgid "group" +msgstr "" + +#: ckan/lib/navl/dictization_functions.py:16 +#: ckan/lib/navl/dictization_functions.py:19 +#: ckan/lib/navl/dictization_functions.py:22 +#: ckan/lib/navl/dictization_functions.py:25 +#: ckan/lib/navl/dictization_functions.py:28 +#: ckan/lib/navl/dictization_functions.py:31 +#: ckan/lib/navl/dictization_functions.py:34 +#: ckan/lib/navl/dictization_functions.py:37 ckan/lib/navl/validators.py:27 +#: ckan/lib/navl/validators.py:34 ckan/lib/navl/validators.py:54 +#: ckan/logic/action/get.py:2033 ckan/logic/action/update.py:261 +#: ckan/logic/validators.py:615 +msgid "Missing value" +msgstr "" + +#: ckan/lib/navl/validators.py:68 +#, python-format +msgid "The input field %(name)s was not expected." +msgstr "" + +#: ckan/lib/navl/validators.py:120 +msgid "Please enter an integer value" +msgstr "" + +#: ckan/lib/navl/validators.py:126 +msgid "Must be a Unicode string value" +msgstr "" + +#: ckan/logic/__init__.py:99 ckan/logic/action/__init__.py:60 +msgid "Package resource(s) invalid" +msgstr "" + +#: ckan/logic/__init__.py:106 ckan/logic/__init__.py:108 +#: ckan/logic/action/__init__.py:62 ckan/logic/action/__init__.py:64 +msgid "Extras" +msgstr "" + +#: ckan/logic/converters.py:76 ckan/logic/converters.py:91 +#, python-format +msgid "Tag vocabulary \"%s\" does not exist" +msgstr "" + +#: ckan/logic/converters.py:182 +msgid "Could not parse as valid JSON" +msgstr "" + +#: ckan/logic/validators.py:35 ckan/logic/validators.py:44 +msgid "An organization must be provided" +msgstr "" + +#: ckan/logic/validators.py:49 +msgid "Organization does not exist" +msgstr "" + +#: ckan/logic/validators.py:54 +msgid "You cannot add a dataset to this organization" +msgstr "" + +#: ckan/logic/validators.py:94 +msgid "Invalid integer" +msgstr "" + +#: ckan/logic/validators.py:99 +msgid "Must be a natural number" +msgstr "" + +#: ckan/logic/validators.py:105 +msgid "Must be a postive integer" +msgstr "" + +#: ckan/logic/validators.py:132 +msgid "Date format incorrect" +msgstr "" + +#: ckan/logic/validators.py:141 +msgid "No links are allowed in the log_message." +msgstr "" + +#: ckan/logic/validators.py:161 +msgid "Dataset id already exists" +msgstr "" + +#: ckan/logic/validators.py:202 +msgid "Resource" +msgstr "" + +#: ckan/logic/validators.py:256 +msgid "That group name or ID does not exist." +msgstr "" + +#: ckan/logic/validators.py:270 +msgid "Activity type" +msgstr "" + +#: ckan/logic/validators.py:333 +msgid "Names must be strings" +msgstr "" + +#: ckan/logic/validators.py:337 +msgid "That name cannot be used" +msgstr "" + +#: ckan/logic/validators.py:340 +#, python-format +msgid "Must be at least %s characters long" +msgstr "" + +#: ckan/logic/validators.py:342 ckan/logic/validators.py:631 +#, python-format +msgid "Name must be a maximum of %i characters long" +msgstr "" + +#: ckan/logic/validators.py:345 +msgid "Must be purely lowercase alphanumeric (ascii) characters and these symbols: -_" +msgstr "" + +#: ckan/logic/validators.py:363 +msgid "That URL is already in use." +msgstr "" + +#: ckan/logic/validators.py:368 +#, python-format +msgid "Name \"%s\" length is less than minimum %s" +msgstr "" + +#: ckan/logic/validators.py:372 +#, python-format +msgid "Name \"%s\" length is more than maximum %s" +msgstr "" + +#: ckan/logic/validators.py:378 +#, python-format +msgid "Version must be a maximum of %i characters long" +msgstr "" + +#: ckan/logic/validators.py:396 +#, python-format +msgid "Duplicate key \"%s\"" +msgstr "" + +#: ckan/logic/validators.py:412 +msgid "Group name already exists in database" +msgstr "" + +#: ckan/logic/validators.py:418 +#, python-format +msgid "Tag \"%s\" length is less than minimum %s" +msgstr "" + +#: ckan/logic/validators.py:422 +#, python-format +msgid "Tag \"%s\" length is more than maximum %i" +msgstr "" + +#: ckan/logic/validators.py:430 +#, python-format +msgid "Tag \"%s\" must be alphanumeric characters or symbols: -_." +msgstr "" + +#: ckan/logic/validators.py:438 +#, python-format +msgid "Tag \"%s\" must not be uppercase" +msgstr "" + +#: ckan/logic/validators.py:547 +msgid "User names must be strings" +msgstr "" + +#: ckan/logic/validators.py:562 +msgid "That login name is not available." +msgstr "" + +#: ckan/logic/validators.py:566 +msgid "That login name can not be modified." +msgstr "" + +#: ckan/logic/validators.py:577 +msgid "Please enter both passwords" +msgstr "" + +#: ckan/logic/validators.py:585 +msgid "Passwords must be strings" +msgstr "" + +#: ckan/logic/validators.py:589 +msgid "Your password must be 8 characters or longer" +msgstr "" + +#: ckan/logic/validators.py:598 +msgid "The passwords you entered do not match" +msgstr "" + +#: ckan/logic/validators.py:619 +msgid "" +"Edit not allowed as it looks like spam. Please avoid links in your " +"description." +msgstr "" + +#: ckan/logic/validators.py:628 +#, python-format +msgid "Name must be at least %s characters long" +msgstr "" + +#: ckan/logic/validators.py:636 +msgid "That vocabulary name is already in use." +msgstr "" + +#: ckan/logic/validators.py:642 +#, python-format +msgid "Cannot change value of key from %s to %s. This key is read-only" +msgstr "" + +#: ckan/logic/validators.py:651 +msgid "Tag vocabulary was not found." +msgstr "" + +#: ckan/logic/validators.py:664 +#, python-format +msgid "Tag %s does not belong to vocabulary %s" +msgstr "" + +#: ckan/logic/validators.py:670 +msgid "No tag name" +msgstr "" + +#: ckan/logic/validators.py:683 +#, python-format +msgid "Tag %s already belongs to vocabulary %s" +msgstr "" + +#: ckan/logic/validators.py:706 +msgid "Please provide a valid URL" +msgstr "" + +#: ckan/logic/validators.py:720 +msgid "role does not exist." +msgstr "" + +#: ckan/logic/validators.py:749 +msgid "Datasets with no organization can't be private." +msgstr "" + +#: ckan/logic/validators.py:755 +msgid "Not a list" +msgstr "" + +#: ckan/logic/validators.py:758 +msgid "Not a string" +msgstr "" + +#: ckan/logic/validators.py:790 +msgid "This parent would create a loop in the hierarchy" +msgstr "" + +#: ckan/logic/validators.py:800 +msgid "\"filter_fields\" and \"filter_values\" should have the same length" +msgstr "" + +#: ckan/logic/validators.py:811 +msgid "\"filter_fields\" is required when \"filter_values\" is filled" +msgstr "" + +#: ckan/logic/validators.py:814 +msgid "\"filter_values\" is required when \"filter_fields\" is filled" +msgstr "" + +#: ckan/logic/validators.py:828 +msgid "There is a schema field with the same name" +msgstr "" + +#: ckan/logic/validators.py:854 +msgid "Email {email} is not a valid format" +msgstr "" + +#: ckan/logic/action/create.py:184 ckan/logic/action/create.py:668 +#, python-format +msgid "REST API: Create object %s" +msgstr "" + +#: ckan/logic/action/create.py:547 +#, python-format +msgid "REST API: Create package relationship: %s %s %s" +msgstr "" + +#: ckan/logic/action/create.py:588 +#, python-format +msgid "REST API: Create member object %s" +msgstr "" + +#: ckan/logic/action/create.py:807 +msgid "Trying to create an organization as a group" +msgstr "" + +#: ckan/logic/action/create.py:896 +msgid "You must supply a package id or name (parameter \"package\")." +msgstr "" + +#: ckan/logic/action/create.py:899 +msgid "You must supply a rating (parameter \"rating\")." +msgstr "" + +#: ckan/logic/action/create.py:904 +msgid "Rating must be an integer value." +msgstr "" + +#: ckan/logic/action/create.py:908 +#, python-format +msgid "Rating must be between %i and %i." +msgstr "" + +#: ckan/logic/action/create.py:1078 +msgid "Error sending the invite email, the user was not created: {0}" +msgstr "" + +#: ckan/logic/action/create.py:1254 ckan/logic/action/create.py:1261 +msgid "You must be logged in to follow users" +msgstr "" + +#: ckan/logic/action/create.py:1274 +msgid "You cannot follow yourself" +msgstr "" + +#: ckan/logic/action/create.py:1282 ckan/logic/action/create.py:1339 +#: ckan/logic/action/create.py:1478 +msgid "You are already following {0}" +msgstr "" + +#: ckan/logic/action/create.py:1313 ckan/logic/action/create.py:1321 +msgid "You must be logged in to follow a dataset." +msgstr "" + +#: ckan/logic/action/create.py:1379 +msgid "User {username} does not exist." +msgstr "" + +#: ckan/logic/action/create.py:1454 ckan/logic/action/create.py:1462 +msgid "You must be logged in to follow a group." +msgstr "" + +#: ckan/logic/action/delete.py:54 +msgid " Delete User: {0}" +msgstr "" + +#: ckan/logic/action/delete.py:92 +#, python-format +msgid "REST API: Delete Package: %s" +msgstr "" + +#: ckan/logic/action/delete.py:276 ckan/logic/action/delete.py:372 +#, python-format +msgid "REST API: Delete %s" +msgstr "" + +#: ckan/logic/action/delete.py:318 +#, python-format +msgid "REST API: Delete Member: %s" +msgstr "" + +#: ckan/logic/action/delete.py:358 +msgid "Organization cannot be deleted while it still has datasets" +msgstr "" + +#: ckan/logic/action/delete.py:560 ckan/logic/action/delete.py:586 +#: ckan/logic/action/get.py:2432 ckan/logic/action/update.py:906 +msgid "id not in data" +msgstr "" + +#: ckan/logic/action/delete.py:564 ckan/logic/action/get.py:2435 +#: ckan/logic/action/update.py:910 +#, python-format +msgid "Could not find vocabulary \"%s\"" +msgstr "" + +#: ckan/logic/action/delete.py:594 +#, python-format +msgid "Could not find tag \"%s\"" +msgstr "" + +#: ckan/logic/action/delete.py:607 ckan/logic/action/delete.py:611 +msgid "You must be logged in to unfollow something." +msgstr "" + +#: ckan/logic/action/delete.py:622 +msgid "You are not following {0}." +msgstr "" + +#: ckan/logic/action/get.py:1095 ckan/logic/action/update.py:77 +#: ckan/logic/action/update.py:91 +msgid "Resource was not found." +msgstr "" + +#: ckan/logic/action/get.py:2037 +msgid "Do not specify if using \"query\" parameter" +msgstr "" + +#: ckan/logic/action/get.py:2046 +msgid "Must be : pair(s)" +msgstr "" + +#: ckan/logic/action/get.py:2078 +msgid "Field \"{field}\" not recognised in resource_search." +msgstr "" + +#: ckan/logic/action/update.py:265 ckan/logic/action/update.py:991 +msgid "Package was not found." +msgstr "" + +#: ckan/logic/action/update.py:308 ckan/logic/action/update.py:526 +#: ckan/logic/action/update.py:1009 +#, python-format +msgid "REST API: Update object %s" +msgstr "" + +#: ckan/logic/action/update.py:405 +#, python-format +msgid "REST API: Update package relationship: %s %s %s" +msgstr "" + +#: ckan/logic/action/update.py:766 +msgid "TaskStatus was not found." +msgstr "" + +#: ckan/logic/action/update.py:995 +msgid "Organization was not found." +msgstr "" + +#: ckan/logic/auth/create.py:27 ckan/logic/auth/create.py:45 +#, python-format +msgid "User %s not authorized to create packages" +msgstr "" + +#: ckan/logic/auth/create.py:31 ckan/logic/auth/update.py:45 +#, python-format +msgid "User %s not authorized to edit these groups" +msgstr "" + +#: ckan/logic/auth/create.py:38 +#, python-format +msgid "User %s not authorized to add dataset to this organization" +msgstr "" + +#: ckan/logic/auth/create.py:61 +msgid "No dataset id provided, cannot check auth." +msgstr "" + +#: ckan/logic/auth/create.py:68 ckan/logic/auth/delete.py:34 +#: ckan/logic/auth/get.py:137 ckan/logic/auth/update.py:63 +msgid "No package found for this resource, cannot check auth." +msgstr "" + +#: ckan/logic/auth/create.py:76 +#, python-format +msgid "User %s not authorized to create resources on dataset %s" +msgstr "" + +#: ckan/logic/auth/create.py:108 +#, python-format +msgid "User %s not authorized to edit these packages" +msgstr "" + +#: ckan/logic/auth/create.py:119 +#, python-format +msgid "User %s not authorized to create groups" +msgstr "" + +#: ckan/logic/auth/create.py:129 +#, python-format +msgid "User %s not authorized to create organizations" +msgstr "" + +#: ckan/logic/auth/create.py:145 +msgid "User {user} not authorized to create users via the API" +msgstr "" + +#: ckan/logic/auth/create.py:148 +msgid "Not authorized to create users" +msgstr "" + +#: ckan/logic/auth/create.py:189 +msgid "Group was not found." +msgstr "" + +#: ckan/logic/auth/create.py:220 +#, python-format +msgid "User %s not authorized to add members" +msgstr "" + +#: ckan/logic/auth/create.py:244 ckan/logic/auth/update.py:115 +#, python-format +msgid "User %s not authorized to edit group %s" +msgstr "" + +#: ckan/logic/auth/delete.py:40 +#, python-format +msgid "User %s not authorized to delete resource %s" +msgstr "" + +#: ckan/logic/auth/delete.py:56 +msgid "Resource view not found, cannot check auth." +msgstr "" + +#: ckan/logic/auth/delete.py:73 +#, python-format +msgid "User %s not authorized to delete relationship %s" +msgstr "" + +#: ckan/logic/auth/delete.py:82 +#, python-format +msgid "User %s not authorized to delete groups" +msgstr "" + +#: ckan/logic/auth/delete.py:86 +#, python-format +msgid "User %s not authorized to delete group %s" +msgstr "" + +#: ckan/logic/auth/delete.py:103 +#, python-format +msgid "User %s not authorized to delete organizations" +msgstr "" + +#: ckan/logic/auth/delete.py:107 +#, python-format +msgid "User %s not authorized to delete organization %s" +msgstr "" + +#: ckan/logic/auth/delete.py:120 +#, python-format +msgid "User %s not authorized to delete task_status" +msgstr "" + +#: ckan/logic/auth/get.py:13 ckan/logic/auth/get.py:270 +msgid "Not authorized" +msgstr "" + +#: ckan/logic/auth/get.py:109 +#, python-format +msgid "User %s not authorized to read these packages" +msgstr "" + +#: ckan/logic/auth/get.py:124 +#, python-format +msgid "User %s not authorized to read package %s" +msgstr "" + +#: ckan/logic/auth/get.py:143 +#, python-format +msgid "User %s not authorized to read resource %s" +msgstr "" + +#: ckan/logic/auth/get.py:170 +#, python-format +msgid "User %s not authorized to read group %s" +msgstr "" + +#: ckan/logic/auth/get.py:237 +msgid "You must be logged in to access your dashboard." +msgstr "" + +#: ckan/logic/auth/update.py:39 +#, python-format +msgid "User %s not authorized to edit package %s" +msgstr "" + +#: ckan/logic/auth/update.py:71 +#, python-format +msgid "User %s not authorized to edit resource %s" +msgstr "" + +#: ckan/logic/auth/update.py:100 +#, python-format +msgid "User %s not authorized to change state of package %s" +msgstr "" + +#: ckan/logic/auth/update.py:128 +#, python-format +msgid "User %s not authorized to edit organization %s" +msgstr "" + +#: ckan/logic/auth/update.py:145 +#, python-format +msgid "User %s not authorized to change state of group %s" +msgstr "" + +#: ckan/logic/auth/update.py:162 +#, python-format +msgid "User %s not authorized to edit permissions of group %s" +msgstr "" + +#: ckan/logic/auth/update.py:190 +msgid "Have to be logged in to edit user" +msgstr "" + +#: ckan/logic/auth/update.py:198 +#, python-format +msgid "User %s not authorized to edit user %s" +msgstr "" + +#: ckan/logic/auth/update.py:209 +msgid "User {0} not authorized to update user {1}" +msgstr "" + +#: ckan/logic/auth/update.py:217 +#, python-format +msgid "User %s not authorized to change state of revision" +msgstr "" + +#: ckan/logic/auth/update.py:226 +#, python-format +msgid "User %s not authorized to update task_status table" +msgstr "" + +#: ckan/logic/auth/update.py:240 +#, python-format +msgid "User %s not authorized to update term_translation table" +msgstr "" + +#: ckan/model/license.py:223 +msgid "License not specified" +msgstr "" + +#: ckan/model/license.py:233 +msgid "Open Data Commons Public Domain Dedication and License (PDDL)" +msgstr "" + +#: ckan/model/license.py:243 +msgid "Open Data Commons Open Database License (ODbL)" +msgstr "" + +#: ckan/model/license.py:253 +msgid "Open Data Commons Attribution License" +msgstr "" + +#: ckan/model/license.py:264 +msgid "Creative Commons CCZero" +msgstr "" + +#: ckan/model/license.py:273 +msgid "Creative Commons Attribution" +msgstr "" + +#: ckan/model/license.py:283 +msgid "Creative Commons Attribution Share-Alike" +msgstr "" + +#: ckan/model/license.py:292 +msgid "GNU Free Documentation License" +msgstr "" + +#: ckan/model/license.py:302 +msgid "Other (Open)" +msgstr "" + +#: ckan/model/license.py:312 +msgid "Other (Public Domain)" +msgstr "" + +#: ckan/model/license.py:322 +msgid "Other (Attribution)" +msgstr "" + +#: ckan/model/license.py:334 +msgid "UK Open Government Licence (OGL)" +msgstr "" + +#: ckan/model/license.py:342 +msgid "Creative Commons Non-Commercial (Any)" +msgstr "" + +#: ckan/model/license.py:350 +msgid "Other (Non-Commercial)" +msgstr "" + +#: ckan/model/license.py:358 +msgid "Other (Not Open)" +msgstr "" + +#: ckan/model/package_relationship.py:54 +#, python-format +msgid "depends on %s" +msgstr "" + +#: ckan/model/package_relationship.py:54 +#, python-format +msgid "is a dependency of %s" +msgstr "" + +#: ckan/model/package_relationship.py:55 +#, python-format +msgid "derives from %s" +msgstr "" + +#: ckan/model/package_relationship.py:55 +#, python-format +msgid "has derivation %s" +msgstr "" + +#: ckan/model/package_relationship.py:56 +#, python-format +msgid "links to %s" +msgstr "" + +#: ckan/model/package_relationship.py:56 +#, python-format +msgid "is linked from %s" +msgstr "" + +#: ckan/model/package_relationship.py:57 +#, python-format +msgid "is a child of %s" +msgstr "" + +#: ckan/model/package_relationship.py:57 +#, python-format +msgid "is a parent of %s" +msgstr "" + +#: ckan/model/package_relationship.py:61 +#, python-format +msgid "has sibling %s" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/api-info.js:96 +#: ckan/public/base/javascript/modules/api-info.js:96 +msgid "There is no API data to load for this resource" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/api-info.js:124 +#: ckan/public/base/javascript/modules/api-info.js:124 +msgid "Failed to load data API information" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/autocomplete.js:195 +#: ckan/public/base/javascript/modules/autocomplete.js:195 +msgid "Start typing…" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/autocomplete.js:195 +#: ckan/public/base/javascript/modules/autocomplete.js:195 +msgid "No matches found" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/autocomplete.js:204 +#: ckan/public/base/javascript/modules/autocomplete.js:204 +#, python-format +msgid "Input is too short, must be at least one character" +msgid_plural "Input is too short, must be at least %(num)d characters" +msgstr[0] "" +msgstr[1] "" + +#: ckan/public-bs2/base/javascript/modules/basic-form.js:4 +#: ckan/public/base/javascript/modules/basic-form.js:4 +msgid "There are unsaved modifications to this form" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/confirm-action.js:97 +#: ckan/public/base/javascript/modules/confirm-action.js:101 +msgid "Please Confirm Action" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/confirm-action.js:100 +#: ckan/public/base/javascript/modules/confirm-action.js:104 +msgid "Are you sure you want to perform this action?" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/confirm-action.js:102 +#: ckan/public/base/javascript/modules/confirm-action.js:106 +#: ckan/templates/user/new_user_form.html:9 +#: ckan/templates/user/perform_reset.html:26 +msgid "Confirm" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/confirm-action.js:103 +#: ckan/public-bs2/base/javascript/modules/resource-reorder.js:59 +#: ckan/public-bs2/base/javascript/modules/resource-view-reorder.js:53 +#: ckan/public/base/javascript/modules/confirm-action.js:107 +#: ckan/public/base/javascript/modules/resource-reorder.js:59 +#: ckan/public/base/javascript/modules/resource-view-reorder.js:53 +#: ckan/templates/admin/confirm_reset.html:9 +#: ckan/templates/group/confirm_delete.html:14 +#: ckan/templates/group/confirm_delete_member.html:15 +#: ckan/templates/organization/confirm_delete.html:14 +#: ckan/templates/organization/confirm_delete_member.html:15 +#: ckan/templates/package/confirm_delete.html:15 +#: ckan/templates/package/confirm_delete_resource.html:14 +msgid "Cancel" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/follow.js:70 +#: ckan/public/base/javascript/modules/follow.js:70 +#: ckan/templates/snippets/follow_button.html:9 +msgid "Unfollow" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/follow.js:73 +#: ckan/public/base/javascript/modules/follow.js:73 +#: ckan/templates/snippets/follow_button.html:14 +msgid "Follow" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/image-upload.js:60 +#: ckan/public/base/javascript/modules/image-upload.js:60 +msgid "Link" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/image-upload.js:61 +#: ckan/public/base/javascript/modules/image-upload.js:61 +msgid "Link to a URL on the internet (you can also link to an API)" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/image-upload.js:68 +#: ckan/public/base/javascript/modules/image-upload.js:68 +msgid "Upload" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/image-upload.js:72 +#: ckan/public/base/javascript/modules/image-upload.js:72 +#: ckan/templates/group/snippets/group_item.html:43 +#: ckan/templates/macros/form.html:241 ckan/templates/snippets/search_form.html:69 +msgid "Remove" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/image-upload.js:87 +#: ckan/public/base/javascript/modules/image-upload.js:87 +msgid "Upload a file on your computer" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/image-upload.js:110 +#: ckan/public-bs2/base/javascript/modules/image-upload.js:178 +#: ckan/public-bs2/base/javascript/modules/slug-preview.js:56 +#: ckan/public/base/javascript/modules/image-upload.js:110 +#: ckan/public/base/javascript/modules/image-upload.js:178 +#: ckan/public/base/javascript/modules/slug-preview.js:56 +#: ckan/templates/group/snippets/group_form.html:18 +#: ckan/templates/organization/snippets/organization_form.html:18 +#: ckan/templates/package/snippets/package_basic_fields.html:13 +#: ckan/templates/package/snippets/resource_form.html:26 +msgid "URL" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/image-upload.js:119 +#: ckan/public-bs2/base/javascript/modules/image-upload.js:209 +#: ckan/public/base/javascript/modules/image-upload.js:119 +#: ckan/public/base/javascript/modules/image-upload.js:209 +msgid "File" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/resource-reorder.js:56 +#: ckan/public-bs2/base/javascript/modules/resource-view-reorder.js:50 +#: ckan/public/base/javascript/modules/resource-reorder.js:56 +#: ckan/public/base/javascript/modules/resource-view-reorder.js:50 +msgid "Save order" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/resource-reorder.js:69 +#: ckan/public-bs2/base/javascript/modules/resource-view-reorder.js:59 +#: ckan/public/base/javascript/modules/resource-reorder.js:69 +#: ckan/public/base/javascript/modules/resource-view-reorder.js:59 +msgid "Saving..." +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/resource-upload-field.js:57 +#: ckan/public/base/javascript/modules/resource-upload-field.js:57 +msgid "Upload a file" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/resource-upload-field.js:144 +#: ckan/public/base/javascript/modules/resource-upload-field.js:144 +msgid "An Error Occurred" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/resource-upload-field.js:201 +#: ckan/public/base/javascript/modules/resource-upload-field.js:201 +msgid "Unable to upload file" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/resource-upload-field.js:252 +#: ckan/public/base/javascript/modules/resource-upload-field.js:252 +msgid "Unable to authenticate upload" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/resource-upload-field.js:260 +#: ckan/public/base/javascript/modules/resource-upload-field.js:260 +msgid "Resource uploaded" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/resource-upload-field.js:266 +#: ckan/public/base/javascript/modules/resource-upload-field.js:266 +msgid "Unable to get data for uploaded file" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/resource-upload-field.js:272 +#: ckan/public/base/javascript/modules/resource-upload-field.js:272 +msgid "" +"You are uploading a file. Are you sure you want to navigate away and stop " +"this upload?" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/resource-view-filters.js:9 +#: ckan/public/base/javascript/modules/resource-view-filters.js:9 +#: ckan/templates/package/snippets/view_form_filters.html:16 +msgid "Add Filter" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/resource-view-filters.js:52 +#: ckan/public/base/javascript/modules/resource-view-filters.js:52 +msgid "Select a field" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/slug-preview.js:57 +#: ckan/public/base/javascript/modules/slug-preview.js:57 +#: ckan/templates/group/edit_base.html:20 ckan/templates/group/members.html:28 +#: ckan/templates/organization/bulk_process.html:65 +#: ckan/templates/organization/edit.html:3 +#: ckan/templates/organization/edit_base.html:22 +#: ckan/templates/organization/members.html:33 +#: ckan/templates/package/edit_base.html:11 +#: ckan/templates/package/resource_edit.html:3 +#: ckan/templates/package/resource_edit_base.html:12 +#: ckan/templates/package/snippets/resource_item.html:56 +msgid "Edit" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/table-toggle-more.js:25 +#: ckan/public/base/javascript/modules/table-toggle-more.js:25 +msgid "Show more" +msgstr "" + +#: ckan/public-bs2/base/javascript/modules/table-toggle-more.js:26 +#: ckan/public/base/javascript/modules/table-toggle-more.js:26 +msgid "Hide" +msgstr "" + +#: ckan/public-bs2/base/test/spec/i18n.spec.js:13 +#: ckan/public-bs2/base/test/spec/module.spec.js:385 +#: ckan/public/base/test/spec/i18n.spec.js:13 +#: ckan/public/base/test/spec/module.spec.js:385 +msgid "foo" +msgstr "" + +#: ckan/public-bs2/base/test/spec/i18n.spec.js:17 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:46 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:50 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:54 +#: ckan/public/base/test/spec/i18n.spec.js:17 +#: ckan/public/base/test/spec/i18n.spec.js:46 +#: ckan/public/base/test/spec/i18n.spec.js:50 +#: ckan/public/base/test/spec/i18n.spec.js:54 +msgid "no translation" +msgid_plural "no translations" +msgstr[0] "" +msgstr[1] "" + +#: ckan/public-bs2/base/test/spec/i18n.spec.js:22 +#: ckan/public/base/test/spec/i18n.spec.js:22 +#, python-format +msgid "hello %(name)s!" +msgstr "" + +#: ckan/public-bs2/base/test/spec/i18n.spec.js:29 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:76 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:81 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:86 +#: ckan/public/base/test/spec/i18n.spec.js:29 +#: ckan/public/base/test/spec/i18n.spec.js:76 +#: ckan/public/base/test/spec/i18n.spec.js:81 +#: ckan/public/base/test/spec/i18n.spec.js:86 +#, python-format +msgid "no %(attr)s translation" +msgid_plural "no %(attr)s translations" +msgstr[0] "" +msgstr[1] "" + +#: ckan/public-bs2/base/test/spec/i18n.spec.js:39 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:40 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:41 +#: ckan/public-bs2/base/test/spec/module.spec.js:395 +#: ckan/public-bs2/base/test/spec/module.spec.js:396 +#: ckan/public-bs2/base/test/spec/module.spec.js:397 +#: ckan/public/base/test/spec/i18n.spec.js:39 +#: ckan/public/base/test/spec/i18n.spec.js:40 +#: ckan/public/base/test/spec/i18n.spec.js:41 +#: ckan/public/base/test/spec/module.spec.js:395 +#: ckan/public/base/test/spec/module.spec.js:396 +#: ckan/public/base/test/spec/module.spec.js:397 +msgid "bar" +msgid_plural "bars" +msgstr[0] "" +msgstr[1] "" + +#: ckan/public-bs2/base/test/spec/i18n.spec.js:61 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:65 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:69 +#: ckan/public/base/test/spec/i18n.spec.js:61 +#: ckan/public/base/test/spec/i18n.spec.js:65 +#: ckan/public/base/test/spec/i18n.spec.js:69 +#, python-format +msgid "%(color)s shirt" +msgid_plural "%(color)s shirts" +msgstr[0] "" +msgstr[1] "" + +#: ckan/public-bs2/base/test/spec/i18n.spec.js:93 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:94 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:95 +#: ckan/public/base/test/spec/i18n.spec.js:93 +#: ckan/public/base/test/spec/i18n.spec.js:94 +#: ckan/public/base/test/spec/i18n.spec.js:95 +#, python-format +msgid "%(num)d item" +msgid_plural "%(num)d items" +msgstr[0] "" +msgstr[1] "" + +#: ckan/public-bs2/base/test/spec/i18n.spec.js:100 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:105 +#: ckan/public-bs2/base/test/spec/i18n.spec.js:110 +#: ckan/public/base/test/spec/i18n.spec.js:100 +#: ckan/public/base/test/spec/i18n.spec.js:105 +#: ckan/public/base/test/spec/i18n.spec.js:110 +#, python-format +msgid "%(num)d missing translation" +msgid_plural "%(num)d missing translations" +msgstr[0] "" +msgstr[1] "" + +#: ckan/templates/error_document_template.html:3 +#, python-format +msgid "Error %(error_code)s" +msgstr "" + +#: ckan/templates/footer.html:9 +msgid "About {0}" +msgstr "" + +#: ckan/templates/footer.html:15 +msgid "CKAN API" +msgstr "" + +#: ckan/templates/footer.html:16 +msgid "CKAN Association" +msgstr "" + +#: ckan/templates/footer.html:24 +msgid "" +"Powered by CKAN" +msgstr "" + +#: ckan/templates/header.html:9 +msgid "Sysadmin settings" +msgstr "" + +#: ckan/templates/header.html:16 +msgid "View profile" +msgstr "" + +#: ckan/templates/header.html:23 +#, python-format +msgid "Dashboard (%(num)d new item)" +msgid_plural "Dashboard (%(num)d new items)" +msgstr[0] "" +msgstr[1] "" + +#: ckan/templates/header.html:27 ckan/templates/user/dashboard.html:6 +msgid "Dashboard" +msgstr "" + +#: ckan/templates/header.html:33 ckan/templates/user/dashboard.html:16 +msgid "Edit settings" +msgstr "" + +#: ckan/templates/header.html:35 +msgid "Settings" +msgstr "" + +#: ckan/templates/header.html:40 ckan/templates/header.html:42 +msgid "Log out" +msgstr "" + +#: ckan/templates/header.html:52 ckan/templates/user/logout_first.html:14 +msgid "Log in" +msgstr "" + +#: ckan/templates/header.html:54 ckan/templates/user/new.html:3 +msgid "Register" +msgstr "" + +#: ckan/templates/group/read_base.html:17 +#: ckan/templates/group/snippets/info.html:36 ckan/templates/header.html:87 +#: ckan/templates/organization/bulk_process.html:20 +#: ckan/templates/organization/edit_base.html:23 +#: ckan/templates/organization/read_base.html:17 ckan/templates/package/base.html:7 +#: ckan/templates/package/base.html:17 ckan/templates/package/base.html:21 +#: ckan/templates/package/search.html:4 +#: ckan/templates/package/snippets/new_package_breadcrumb.html:1 +#: ckan/templates/revision/diff.html:11 ckan/templates/revision/read.html:65 +#: ckan/templates/snippets/context/group.html:17 +#: ckan/templates/snippets/context/user.html:19 +#: ckan/templates/snippets/organization.html:59 ckan/templates/user/read.html:11 +#: ckan/templates/user/read_base.html:19 ckan/templates/user/read_base.html:53 +msgid "Datasets" +msgstr "" + +#: ckan/templates/header.html:94 +msgid "Search Datasets" +msgstr "" + +#: ckan/templates/header.html:95 ckan/templates/home/snippets/search.html:11 +#: ckan/templates/snippets/simple_search.html:5 +#: ckan/templates/user/snippets/user_search.html:6 +msgid "Search" +msgstr "" + +#: ckan/templates/page.html:6 +msgid "Skip to content" +msgstr "" + +#: ckan/templates/activity_streams/activity_stream_items.html:9 +msgid "Load less" +msgstr "" + +#: ckan/templates/activity_streams/activity_stream_items.html:17 +msgid "Load more" +msgstr "" + +#: ckan/templates/activity_streams/activity_stream_items.html:23 +msgid "No activities are within this activity stream" +msgstr "" + +#: ckan/templates/admin/base.html:3 +msgid "Administration" +msgstr "" + +#: ckan/templates/admin/base.html:8 +msgid "Sysadmins" +msgstr "" + +#: ckan/templates/admin/base.html:9 +msgid "Config" +msgstr "" + +#: ckan/templates/admin/base.html:10 ckan/templates/admin/trash.html:29 +msgid "Trash" +msgstr "" + +#: ckan/templates/admin/config.html:23 ckan/templates/macros/autoform.html:62 +msgid "Site logo" +msgstr "" + +#: ckan/templates/admin/config.html:35 ckan/templates/admin/confirm_reset.html:7 +msgid "Are you sure you want to reset the config?" +msgstr "" + +#: ckan/templates/admin/config.html:35 +msgid "Reset" +msgstr "" + +#: ckan/templates/admin/config.html:36 +msgid "Update Config" +msgstr "" + +#: ckan/templates/admin/config.html:45 +msgid "CKAN config options" +msgstr "" + +#: ckan/templates/admin/config.html:52 +#, python-format +msgid "" +"

Site Title: This is the title of this CKAN instance It " +"appears in various places throughout CKAN.

Style: " +"Choose from a list of simple variations of the main colour scheme to get a " +"very quick custom theme working.

Site Tag Logo: This " +"is the logo that appears in the header of all the CKAN instance " +"templates.

About: This text will appear on this CKAN " +"instances about page.

Intro " +"Text: This text will appear on this CKAN instances home page as a welcome to visitors.

" +"

Custom CSS: This is a block of CSS that appears in " +"<head> tag of every page. If you wish to customize the " +"templates more fully we recommend reading the documentation.

" +"

Homepage: This is for choosing a predefined layout for " +"the modules that appear on your homepage.

" +msgstr "" + +#: ckan/templates/admin/confirm_reset.html:3 +#: ckan/templates/admin/confirm_reset.html:10 +msgid "Confirm Reset" +msgstr "" + +#: ckan/templates/admin/index.html:15 +msgid "Administer CKAN" +msgstr "" + +#: ckan/templates/admin/index.html:20 +#, python-format +msgid "" +"

As a sysadmin user you have full control over this CKAN instance. Proceed" +" with care!

For guidance on using sysadmin features, see the CKAN sysadmin guide

" +msgstr "" + +#: ckan/templates/admin/trash.html:20 +msgid "Purge" +msgstr "" + +#: ckan/templates/admin/trash.html:32 +msgid "

Purge deleted datasets forever and irreversibly.

" +msgstr "" + +#: ckan/templates/dataviewer/snippets/data_preview.html:9 +msgid "This resource can not be previewed at the moment." +msgstr "" + +#: ckan/templates/dataviewer/snippets/data_preview.html:11 +#: ckan/templates/package/resource_read.html:133 +#: ckan/templates/package/snippets/resource_view.html:34 +msgid "Click here for more information." +msgstr "" + +#: ckan/templates/dataviewer/snippets/data_preview.html:18 +#: ckan/templates/package/snippets/resource_view.html:41 +msgid "Download resource" +msgstr "" + +#: ckan/templates/dataviewer/snippets/no_preview.html:3 +msgid "No preview available." +msgstr "" + +#: ckan/templates/dataviewer/snippets/no_preview.html:5 +msgid "More details..." +msgstr "" + +#: ckan/templates/dataviewer/snippets/no_preview.html:12 +#, python-format +msgid "No handler defined for data type: %(type)s." +msgstr "" + +#: ckan/templates/development/snippets/form.html:5 +msgid "Standard" +msgstr "" + +#: ckan/templates/development/snippets/form.html:5 +msgid "Standard Input" +msgstr "" + +#: ckan/templates/development/snippets/form.html:6 +msgid "Medium" +msgstr "" + +#: ckan/templates/development/snippets/form.html:6 +msgid "Medium Width Input" +msgstr "" + +#: ckan/templates/development/snippets/form.html:7 +msgid "Full" +msgstr "" + +#: ckan/templates/development/snippets/form.html:7 +msgid "Full Width Input" +msgstr "" + +#: ckan/templates/development/snippets/form.html:8 +msgid "Large" +msgstr "" + +#: ckan/templates/development/snippets/form.html:8 +msgid "Large Input" +msgstr "" + +#: ckan/templates/development/snippets/form.html:9 +msgid "Prepend" +msgstr "" + +#: ckan/templates/development/snippets/form.html:9 +msgid "Prepend Input" +msgstr "" + +#: ckan/templates/development/snippets/form.html:13 +msgid "Custom Field (empty)" +msgstr "" + +#: ckan/templates/development/snippets/form.html:19 +#: ckan/templates/snippets/custom_form_fields.html:20 +#: ckan/templates/snippets/custom_form_fields.html:37 +msgid "Custom Field" +msgstr "" + +#: ckan/templates/development/snippets/form.html:22 +msgid "Markdown" +msgstr "" + +#: ckan/templates/development/snippets/form.html:23 +msgid "Textarea" +msgstr "" + +#: ckan/templates/development/snippets/form.html:24 +msgid "Select" +msgstr "" + +#: ckan/templates/group/activity_stream.html:3 +#: ckan/templates/group/activity_stream.html:6 +#: ckan/templates/group/read_base.html:18 +#: ckan/templates/organization/activity_stream.html:3 +#: ckan/templates/organization/activity_stream.html:6 +#: ckan/templates/organization/read_base.html:18 +#: ckan/templates/package/activity.html:3 ckan/templates/package/activity.html:6 +#: ckan/templates/package/read_base.html:21 +#: ckan/templates/user/activity_stream.html:3 +#: ckan/templates/user/activity_stream.html:6 ckan/templates/user/read_base.html:20 +msgid "Activity Stream" +msgstr "" + +#: ckan/templates/group/admins.html:3 ckan/templates/group/admins.html:6 +#: ckan/templates/organization/admins.html:3 +#: ckan/templates/organization/admins.html:6 +msgid "Administrators" +msgstr "" + +#: ckan/templates/group/base_form_page.html:7 +msgid "Add a Group" +msgstr "" + +#: ckan/templates/group/base_form_page.html:11 +msgid "Group Form" +msgstr "" + +#: ckan/templates/group/confirm_delete.html:3 +#: ckan/templates/group/confirm_delete.html:15 +#: ckan/templates/group/confirm_delete_member.html:3 +#: ckan/templates/group/confirm_delete_member.html:16 +#: ckan/templates/organization/confirm_delete.html:3 +#: ckan/templates/organization/confirm_delete.html:15 +#: ckan/templates/organization/confirm_delete_member.html:3 +#: ckan/templates/organization/confirm_delete_member.html:16 +#: ckan/templates/package/confirm_delete.html:3 +#: ckan/templates/package/confirm_delete.html:16 +#: ckan/templates/package/confirm_delete_resource.html:3 +#: ckan/templates/package/confirm_delete_resource.html:15 +msgid "Confirm Delete" +msgstr "" + +#: ckan/templates/group/confirm_delete.html:11 +msgid "Are you sure you want to delete group - {name}?" +msgstr "" + +#: ckan/templates/group/confirm_delete_member.html:11 +#: ckan/templates/organization/confirm_delete_member.html:11 +msgid "Are you sure you want to delete member - {name}?" +msgstr "" + +#: ckan/templates/group/edit.html:7 ckan/templates/group/edit_base.html:3 +#: ckan/templates/group/edit_base.html:11 ckan/templates/group/read_base.html:12 +#: ckan/templates/organization/edit_base.html:11 +#: ckan/templates/organization/read_base.html:12 +#: ckan/templates/package/read_base.html:14 +#: ckan/templates/package/resource_read.html:31 ckan/templates/user/edit.html:8 +#: ckan/templates/user/edit_base.html:3 ckan/templates/user/read_base.html:14 +msgid "Manage" +msgstr "" + +#: ckan/templates/group/edit.html:12 +msgid "Edit Group" +msgstr "" + +#: ckan/templates/group/edit_base.html:21 ckan/templates/group/members.html:3 +#: ckan/templates/organization/edit_base.html:24 +#: ckan/templates/organization/members.html:3 +msgid "Members" +msgstr "" + +#: ckan/templates/group/history.html:3 ckan/templates/group/history.html:6 +#: ckan/templates/package/history.html:3 ckan/templates/package/history.html:6 +msgid "History" +msgstr "" + +#: ckan/templates/group/index.html:13 ckan/templates/user/dashboard_groups.html:7 +msgid "Add Group" +msgstr "" + +#: ckan/templates/group/index.html:20 +msgid "Search groups..." +msgstr "" + +#: ckan/templates/group/index.html:29 +msgid "There are currently no groups for this site" +msgstr "" + +#: ckan/templates/group/index.html:31 ckan/templates/organization/index.html:31 +msgid "How about creating one?" +msgstr "" + +#: ckan/templates/group/member_new.html:8 +#: ckan/templates/organization/member_new.html:10 +msgid "Back to all members" +msgstr "" + +#: ckan/templates/group/member_new.html:10 +#: ckan/templates/organization/member_new.html:7 +#: ckan/templates/organization/member_new.html:12 +msgid "Edit Member" +msgstr "" + +#: ckan/templates/group/member_new.html:10 ckan/templates/group/member_new.html:64 +#: ckan/templates/group/members.html:6 +#: ckan/templates/organization/member_new.html:7 +#: ckan/templates/organization/member_new.html:12 +#: ckan/templates/organization/member_new.html:63 +#: ckan/templates/organization/members.html:8 +msgid "Add Member" +msgstr "" + +#: ckan/templates/group/member_new.html:18 +#: ckan/templates/organization/member_new.html:19 +msgid "Existing User" +msgstr "" + +#: ckan/templates/group/member_new.html:21 +#: ckan/templates/organization/member_new.html:22 +msgid "If you wish to add an existing user, search for their username below." +msgstr "" + +#: ckan/templates/group/member_new.html:38 +#: ckan/templates/organization/member_new.html:39 +msgid "or" +msgstr "" + +#: ckan/templates/group/member_new.html:42 +#: ckan/templates/organization/member_new.html:43 +msgid "New User" +msgstr "" + +#: ckan/templates/group/member_new.html:45 +#: ckan/templates/organization/member_new.html:46 +msgid "If you wish to invite a new user, enter their email address." +msgstr "" + +#: ckan/templates/group/member_new.html:55 ckan/templates/group/members.html:15 +#: ckan/templates/organization/member_new.html:54 +#: ckan/templates/organization/members.html:20 +msgid "Role" +msgstr "" + +#: ckan/templates/group/member_new.html:58 ckan/templates/group/members.html:31 +#: ckan/templates/organization/member_new.html:57 +#: ckan/templates/organization/members.html:36 +msgid "Are you sure you want to delete this member?" +msgstr "" + +#: ckan/templates/group/member_new.html:58 ckan/templates/group/members.html:31 +#: ckan/templates/group/snippets/group_form.html:38 +#: ckan/templates/organization/bulk_process.html:47 +#: ckan/templates/organization/member_new.html:57 +#: ckan/templates/organization/members.html:36 +#: ckan/templates/organization/snippets/organization_form.html:38 +#: ckan/templates/package/edit_view.html:19 +#: ckan/templates/package/snippets/package_form.html:39 +#: ckan/templates/package/snippets/resource_form.html:67 +#: ckan/templates/revision/read.html:24 ckan/templates/user/edit_user_form.html:45 +msgid "Delete" +msgstr "" + +#: ckan/templates/group/member_new.html:77 +#: ckan/templates/organization/member_new.html:76 +msgid "What are roles?" +msgstr "" + +#: ckan/templates/group/member_new.html:80 +msgid "" +"

Admin: Can edit group information, as well as manage " +"organization members.

Member: Can add/remove datasets" +" from groups

" +msgstr "" + +#: ckan/templates/group/new.html:3 ckan/templates/group/new.html:5 +#: ckan/templates/group/new.html:7 +msgid "Create a Group" +msgstr "" + +#: ckan/templates/group/new_group_form.html:17 +msgid "Update Group" +msgstr "" + +#: ckan/templates/group/new_group_form.html:19 +msgid "Create Group" +msgstr "" + +#: ckan/templates/group/read.html:19 ckan/templates/organization/read.html:25 +#: ckan/templates/snippets/search_form.html:3 +msgid "Search datasets..." +msgstr "" + +#: ckan/templates/group/snippets/feeds.html:3 +msgid "Datasets in group: {group}" +msgstr "" + +#: ckan/templates/group/snippets/feeds.html:4 +#: ckan/templates/organization/snippets/feeds.html:4 +msgid "Recent Revision History" +msgstr "" + +#: ckan/templates/group/snippets/group_form.html:10 +#: ckan/templates/organization/snippets/organization_form.html:10 +#: ckan/templates/package/snippets/resource_form.html:30 +msgid "Name" +msgstr "" + +#: ckan/templates/group/snippets/group_form.html:10 +msgid "My Group" +msgstr "" + +#: ckan/templates/group/snippets/group_form.html:18 +msgid "my-group" +msgstr "" + +#: ckan/templates/group/snippets/group_form.html:20 +msgid "A little information about my group..." +msgstr "" + +#: ckan/templates/group/snippets/group_form.html:38 +msgid "Are you sure you want to delete this Group?" +msgstr "" + +#: ckan/templates/group/snippets/group_form.html:41 +msgid "Save Group" +msgstr "" + +#: ckan/templates/group/snippets/group_item.html:38 +#: ckan/templates/group/snippets/group_item.html:39 +msgid "View {name}" +msgstr "" + +#: ckan/templates/group/snippets/group_item.html:43 +msgid "Remove dataset from this group" +msgstr "" + +#: ckan/templates/group/snippets/helper.html:4 +msgid "What are Groups?" +msgstr "" + +#: ckan/templates/group/snippets/helper.html:8 +msgid "" +" You can use CKAN Groups to create and manage collections of datasets. This " +"could be to catalogue datasets for a particular project or team, or on a " +"particular theme, or as a very simple way to help people find and search your" +" own published datasets. " +msgstr "" + +#: ckan/templates/group/snippets/history_revisions.html:10 +#: ckan/templates/package/snippets/history_revisions.html:10 +msgid "Compare" +msgstr "" + +#: ckan/templates/group/snippets/info.html:16 +#: ckan/templates/organization/bulk_process.html:72 +#: ckan/templates/package/read.html:21 +#: ckan/templates/package/snippets/package_basic_fields.html:118 +#: ckan/templates/snippets/organization.html:37 +#: ckan/templates/snippets/package_item.html:42 +msgid "Deleted" +msgstr "" + +#: ckan/templates/group/snippets/info.html:24 +#: ckan/templates/package/snippets/package_context.html:7 +#: ckan/templates/snippets/organization.html:45 +msgid "read more" +msgstr "" + +#: ckan/templates/group/snippets/revisions_table.html:7 +#: ckan/templates/package/snippets/revisions_table.html:7 +#: ckan/templates/revision/read.html:5 ckan/templates/revision/read.html:9 +#: ckan/templates/revision/read.html:39 +#: ckan/templates/revision/snippets/revisions_list.html:4 +msgid "Revision" +msgstr "" + +#: ckan/templates/group/snippets/revisions_table.html:8 +#: ckan/templates/package/snippets/revisions_table.html:8 +#: ckan/templates/revision/read.html:53 +#: ckan/templates/revision/snippets/revisions_list.html:5 +msgid "Timestamp" +msgstr "" + +#: ckan/templates/group/snippets/revisions_table.html:9 +#: ckan/templates/package/snippets/additional_info.html:25 +#: ckan/templates/package/snippets/additional_info.html:30 +#: ckan/templates/package/snippets/package_metadata_fields.html:14 +#: ckan/templates/package/snippets/revisions_table.html:9 +#: ckan/templates/revision/read.html:50 +#: ckan/templates/revision/snippets/revisions_list.html:6 +msgid "Author" +msgstr "" + +#: ckan/templates/group/snippets/revisions_table.html:10 +#: ckan/templates/package/snippets/revisions_table.html:10 +#: ckan/templates/revision/read.html:56 +#: ckan/templates/revision/snippets/revisions_list.html:8 +msgid "Log Message" +msgstr "" + +#: ckan/templates/home/index.html:4 +msgid "Welcome" +msgstr "" + +#: ckan/templates/home/snippets/about_text.html:1 +msgid "" +"

CKAN is the world’s leading open-source data portal platform.

CKAN" +" is a complete out-of-the-box software solution that makes data accessible " +"and usable – by providing tools to streamline publishing, sharing, finding " +"and using data (including storage of data and provision of robust data APIs)." +" CKAN is aimed at data publishers (national and regional governments, " +"companies and organizations) wanting to make their data open and " +"available.

CKAN is used by governments and user groups worldwide and " +"powers a variety of official and community data portals including portals for" +" local, national and international government, such as the UK’s data.gov.uk and the European Union’s publicdata.eu, the Brazilian dados.gov.br, Dutch and Netherland " +"government portals, as well as city and municipal sites in the US, UK, " +"Argentina, Finland and elsewhere.

CKAN: http://ckan.org/
CKAN Tour: http://ckan.org/tour/
Features " +"overview: http://ckan.org/features/

" +msgstr "" + +#: ckan/templates/home/snippets/promoted.html:8 +msgid "Welcome to CKAN" +msgstr "" + +#: ckan/templates/home/snippets/promoted.html:10 +msgid "" +"This is a nice introductory paragraph about CKAN or the site in general. We " +"don't have any copy to go here yet but soon we will " +msgstr "" + +#: ckan/templates/home/snippets/promoted.html:19 +msgid "This is a featured section" +msgstr "" + +#: ckan/templates/home/snippets/search.html:2 +msgid "E.g. environment" +msgstr "" + +#: ckan/templates/home/snippets/search.html:6 +msgid "Search data" +msgstr "" + +#: ckan/templates/home/snippets/search.html:8 +msgid "Search datasets" +msgstr "" + +#: ckan/templates/home/snippets/search.html:16 +msgid "Popular tags" +msgstr "" + +#: ckan/templates/home/snippets/stats.html:5 +msgid "{0} statistics" +msgstr "" + +#: ckan/templates/home/snippets/stats.html:11 +msgid "dataset" +msgstr "" + +#: ckan/templates/home/snippets/stats.html:11 +msgid "datasets" +msgstr "" + +#: ckan/templates/home/snippets/stats.html:17 +msgid "organizations" +msgstr "" + +#: ckan/templates/home/snippets/stats.html:23 +msgid "groups" +msgstr "" + +#: ckan/templates/macros/form.html:126 +#, python-format +msgid "" +"You can use Markdown formatting here" +msgstr "" + +#: ckan/templates/macros/form.html:277 +msgid "This field is required" +msgstr "" + +#: ckan/templates/macros/form.html:277 +msgid "Custom" +msgstr "" + +#: ckan/templates/macros/form.html:302 +msgid "The form contains invalid entries:" +msgstr "" + +#: ckan/templates/macros/form.html:407 +msgid "Required field" +msgstr "" + +#: ckan/templates/macros/form.html:422 +msgid "http://example.com/my-image.jpg" +msgstr "" + +#: ckan/templates/macros/form.html:423 +msgid "Image URL" +msgstr "" + +#: ckan/templates/macros/form.html:438 +msgid "Clear Upload" +msgstr "" + +#: ckan/templates/organization/base_form_page.html:5 +msgid "Organization Form" +msgstr "" + +#: ckan/templates/organization/bulk_process.html:3 +#: ckan/templates/organization/bulk_process.html:11 +msgid "Edit datasets" +msgstr "" + +#: ckan/templates/organization/bulk_process.html:16 +msgid " found for \"{query}\"" +msgstr "" + +#: ckan/templates/organization/bulk_process.html:18 +msgid "Sorry no datasets found for \"{query}\"" +msgstr "" + +#: ckan/templates/organization/bulk_process.html:37 +msgid "Make public" +msgstr "" + +#: ckan/templates/organization/bulk_process.html:41 +msgid "Make private" +msgstr "" + +#: ckan/templates/organization/bulk_process.html:70 +#: ckan/templates/package/read.html:18 ckan/templates/snippets/package_item.html:40 +msgid "Draft" +msgstr "" + +#: ckan/templates/organization/bulk_process.html:75 +#: ckan/templates/package/read.html:11 +#: ckan/templates/package/snippets/package_basic_fields.html:98 +#: ckan/templates/snippets/package_item.html:31 +#: ckan/templates/snippets/private.html:2 ckan/templates/user/read_base.html:82 +#: ckan/templates/user/read_base.html:96 +msgid "Private" +msgstr "" + +#: ckan/templates/organization/bulk_process.html:88 +msgid "This organization has no datasets associated to it" +msgstr "" + +#: ckan/templates/organization/confirm_delete.html:11 +msgid "Are you sure you want to delete organization - {name}?" +msgstr "" + +#: ckan/templates/organization/edit.html:6 +#: ckan/templates/organization/snippets/info.html:13 +#: ckan/templates/organization/snippets/info.html:16 +msgid "Edit Organization" +msgstr "" + +#: ckan/templates/organization/index.html:13 +#: ckan/templates/user/dashboard_organizations.html:7 +msgid "Add Organization" +msgstr "" + +#: ckan/templates/organization/index.html:20 +msgid "Search organizations..." +msgstr "" + +#: ckan/templates/organization/index.html:29 +msgid "There are currently no organizations for this site" +msgstr "" + +#: ckan/templates/organization/member_new.html:31 +#: ckan/templates/user/edit_user_form.html:8 +#: ckan/templates/user/logout_first.html:10 +#: ckan/templates/user/new_user_form.html:5 +#: ckan/templates/user/perform_reset.html:22 ckan/templates/user/read_base.html:76 +#: ckan/templates/user/request_reset.html:16 +#: ckan/templates/user/snippets/login_form.html:20 +msgid "Username" +msgstr "" + +#: ckan/templates/organization/member_new.html:49 +msgid "Email address" +msgstr "" + +#: ckan/templates/organization/member_new.html:59 +msgid "Update Member" +msgstr "" + +#: ckan/templates/organization/member_new.html:79 +msgid "" +"

Admin: Can add/edit and delete datasets, as well as " +"manage organization members.

Editor: Can add and edit" +" datasets, but not manage organization members.

" +"

Member: Can view the organization's private datasets, but" +" not add new datasets.

" +msgstr "" + +#: ckan/templates/organization/members.html:14 +msgid "{count} member" +msgid_plural "{count} members" +msgstr[0] "" +msgstr[1] "" + +#: ckan/templates/organization/new.html:3 ckan/templates/organization/new.html:5 +#: ckan/templates/organization/new.html:7 ckan/templates/organization/new.html:12 +msgid "Create an Organization" +msgstr "" + +#: ckan/templates/organization/new_organization_form.html:17 +msgid "Update Organization" +msgstr "" + +#: ckan/templates/organization/new_organization_form.html:19 +msgid "Create Organization" +msgstr "" + +#: ckan/templates/organization/snippets/feeds.html:3 +msgid "Datasets in organization: {group}" +msgstr "" + +#: ckan/templates/organization/snippets/help.html:4 +#: ckan/templates/organization/snippets/helper.html:4 +msgid "What are Organizations?" +msgstr "" + +#: ckan/templates/organization/snippets/help.html:7 +msgid "" +"

Organizations act like publishing departments for datasets (for example, " +"the Department of Health). This means that datasets can be published by and " +"belong to a department instead of an individual user.

Within " +"organizations, admins can assign roles and authorise its members, giving " +"individual users the right to publish datasets from that particular " +"organisation (e.g. Office of National Statistics).

" +msgstr "" + +#: ckan/templates/organization/snippets/helper.html:8 +msgid "" +" CKAN Organizations are used to create, manage and publish collections of " +"datasets. Users can have different roles within an Organization, depending on" +" their level of authorisation to create, edit and publish. " +msgstr "" + +#: ckan/templates/organization/snippets/organization_form.html:10 +msgid "My Organization" +msgstr "" + +#: ckan/templates/organization/snippets/organization_form.html:18 +msgid "my-organization" +msgstr "" + +#: ckan/templates/organization/snippets/organization_form.html:20 +msgid "A little information about my organization..." +msgstr "" + +#: ckan/templates/organization/snippets/organization_form.html:38 +msgid "" +"Are you sure you want to delete this Organization? Note*: Deleting cannot be " +"performed while public or private datasets belong to this organization." +msgstr "" + +#: ckan/templates/organization/snippets/organization_form.html:41 +msgid "Save Organization" +msgstr "" + +#: ckan/templates/organization/snippets/organization_item.html:42 +#: ckan/templates/organization/snippets/organization_item.html:43 +msgid "View {organization_name}" +msgstr "" + +#: ckan/templates/package/base.html:22 ckan/templates/package/new.html:9 +#: ckan/templates/package/snippets/new_package_breadcrumb.html:2 +msgid "Create Dataset" +msgstr "" + +#: ckan/templates/package/base_form_page.html:22 +msgid "What are datasets?" +msgstr "" + +#: ckan/templates/package/base_form_page.html:25 +msgid "" +" A CKAN Dataset is a collection of data resources (such as files), together " +"with a description and other information, at a fixed URL. Datasets are what " +"users see when searching for data. " +msgstr "" + +#: ckan/templates/package/confirm_delete.html:12 +msgid "Are you sure you want to delete dataset - {name}?" +msgstr "" + +#: ckan/templates/package/confirm_delete_resource.html:11 +msgid "Are you sure you want to delete resource - {name}?" +msgstr "" + +#: ckan/templates/package/edit_base.html:16 +msgid "View dataset" +msgstr "" + +#: ckan/templates/package/edit_base.html:20 +msgid "Edit metadata" +msgstr "" + +#: ckan/templates/package/edit_view.html:3 ckan/templates/package/edit_view.html:4 +#: ckan/templates/package/edit_view.html:8 ckan/templates/package/edit_view.html:12 +msgid "Edit view" +msgstr "" + +#: ckan/templates/package/edit_view.html:20 ckan/templates/package/new_view.html:28 +#: ckan/templates/package/snippets/resource_item.html:32 +msgid "Preview" +msgstr "" + +#: ckan/templates/package/edit_view.html:21 +msgid "Update" +msgstr "" + +#: ckan/templates/package/group_list.html:14 +msgid "Associate this group with this dataset" +msgstr "" + +#: ckan/templates/package/group_list.html:14 +msgid "Add to group" +msgstr "" + +#: ckan/templates/package/group_list.html:23 +msgid "There are no groups associated with this dataset" +msgstr "" + +#: ckan/templates/package/new_package_form.html:15 +msgid "Update Dataset" +msgstr "" + +#: ckan/templates/package/new_resource.html:5 +msgid "Add data to the dataset" +msgstr "" + +#: ckan/templates/package/new_resource.html:11 +#: ckan/templates/package/new_resource_not_draft.html:8 +msgid "Add New Resource" +msgstr "" + +#: ckan/templates/package/new_resource_not_draft.html:3 +#: ckan/templates/package/new_resource_not_draft.html:4 +msgid "Add resource" +msgstr "" + +#: ckan/templates/package/new_resource_not_draft.html:16 +msgid "New resource" +msgstr "" + +#: ckan/templates/package/new_view.html:3 ckan/templates/package/new_view.html:4 +#: ckan/templates/package/new_view.html:8 ckan/templates/package/new_view.html:12 +msgid "Add view" +msgstr "" + +#: ckan/templates/package/new_view.html:19 +msgid "" +" Data Explorer views may be slow and unreliable unless the DataStore " +"extension is enabled. For more information, please see the Data Explorer " +"documentation. " +msgstr "" + +#: ckan/templates/package/new_view.html:29 +#: ckan/templates/package/snippets/resource_form.html:83 +msgid "Add" +msgstr "" + +#: ckan/templates/package/read_base.html:32 +#, python-format +msgid "" +"This is an old revision of this dataset, as edited at %(timestamp)s. It may " +"differ significantly from the current revision." +msgstr "" + +#: ckan/templates/package/resource_edit_base.html:17 +msgid "All resources" +msgstr "" + +#: ckan/templates/package/resource_edit_base.html:19 +msgid "View resource" +msgstr "" + +#: ckan/templates/package/resource_edit_base.html:24 +#: ckan/templates/package/resource_edit_base.html:30 +msgid "Edit resource" +msgstr "" + +#: ckan/templates/package/resource_edit_base.html:26 +msgid "Views" +msgstr "" + +#: ckan/templates/package/resource_read.html:40 +msgid "API Endpoint" +msgstr "" + +#: ckan/templates/package/resource_read.html:42 +#: ckan/templates/package/snippets/resource_item.html:47 +msgid "Go to resource" +msgstr "" + +#: ckan/templates/package/resource_read.html:44 +#: ckan/templates/package/snippets/resource_item.html:44 +msgid "Download" +msgstr "" + +#: ckan/templates/package/resource_read.html:76 +#: ckan/templates/package/resource_read.html:78 +msgid "URL:" +msgstr "" + +#: ckan/templates/package/resource_read.html:86 +msgid "From the dataset abstract" +msgstr "" + +#: ckan/templates/package/resource_read.html:88 +#, python-format +msgid "Source: %(dataset)s" +msgstr "" + +#: ckan/templates/package/resource_read.html:127 +msgid "There are no views created for this resource yet." +msgstr "" + +#: ckan/templates/package/resource_read.html:131 +msgid "Not seeing the views you were expecting?" +msgstr "" + +#: ckan/templates/package/resource_read.html:136 +msgid "Here are some reasons you may not be seeing expected views:" +msgstr "" + +#: ckan/templates/package/resource_read.html:138 +msgid "No view has been created that is suitable for this resource" +msgstr "" + +#: ckan/templates/package/resource_read.html:139 +msgid "The site administrators may not have enabled the relevant view plugins" +msgstr "" + +#: ckan/templates/package/resource_read.html:140 +msgid "" +"If a view requires the DataStore, the DataStore plugin may not be enabled, or" +" the data may not have been pushed to the DataStore, or the DataStore hasn't " +"finished processing the data yet" +msgstr "" + +#: ckan/templates/package/resource_read.html:162 +msgid "Additional Information" +msgstr "" + +#: ckan/templates/package/resource_read.html:166 +#: ckan/templates/package/snippets/additional_info.html:6 +#: ckan/templates/revision/diff.html:43 +#: ckan/templates/snippets/additional_info.html:11 +msgid "Field" +msgstr "" + +#: ckan/templates/package/resource_read.html:167 +#: ckan/templates/package/snippets/additional_info.html:7 +#: ckan/templates/snippets/additional_info.html:12 +msgid "Value" +msgstr "" + +#: ckan/templates/package/resource_read.html:172 +msgid "Data last updated" +msgstr "" + +#: ckan/templates/package/resource_read.html:173 +#: ckan/templates/package/resource_read.html:177 +#: ckan/templates/package/resource_read.html:181 +#: ckan/templates/package/resource_read.html:185 +msgid "unknown" +msgstr "" + +#: ckan/templates/package/resource_read.html:176 +msgid "Metadata last updated" +msgstr "" + +#: ckan/templates/package/resource_read.html:180 +#: ckan/templates/package/snippets/additional_info.html:70 +msgid "Created" +msgstr "" + +#: ckan/templates/package/resource_read.html:184 +#: ckan/templates/package/snippets/resource_form.html:39 +#: ckan/templates/package/snippets/resource_info.html:16 +msgid "Format" +msgstr "" + +#: ckan/templates/package/resource_read.html:188 +#: ckan/templates/package/snippets/package_basic_fields.html:30 +#: ckan/templates/snippets/license.html:21 +msgid "License" +msgstr "" + +#: ckan/templates/package/resource_views.html:10 +msgid "New view" +msgstr "" + +#: ckan/templates/package/resource_views.html:28 +msgid "This resource has no views" +msgstr "" + +#: ckan/templates/package/resources.html:8 +msgid "Add new resource" +msgstr "" + +#: ckan/templates/package/resources.html:20 +#: ckan/templates/package/snippets/resources_list.html:26 +#, python-format +msgid "" +"

This dataset has no data, why not add" +" some?

" +msgstr "" + +#: ckan/templates/package/search.html:52 +msgid "API" +msgstr "" + +#: ckan/templates/package/search.html:53 +msgid "API Docs" +msgstr "" + +#: ckan/templates/package/search.html:55 +msgid "full {format} dump" +msgstr "" + +#: ckan/templates/package/search.html:56 +#, python-format +msgid "" +" You can also access this registry using the %(api_link)s (see " +"%(api_doc_link)s) or download a %(dump_link)s. " +msgstr "" + +#: ckan/templates/package/search.html:60 +#, python-format +msgid "" +" You can also access this registry using the %(api_link)s (see " +"%(api_doc_link)s). " +msgstr "" + +#: ckan/templates/package/view_edit_base.html:9 +msgid "All views" +msgstr "" + +#: ckan/templates/package/view_edit_base.html:12 +msgid "View view" +msgstr "" + +#: ckan/templates/package/view_edit_base.html:37 +msgid "View preview" +msgstr "" + +#: ckan/templates/package/snippets/additional_info.html:2 +#: ckan/templates/snippets/additional_info.html:7 +msgid "Additional Info" +msgstr "" + +#: ckan/templates/package/snippets/additional_info.html:14 +#: ckan/templates/package/snippets/package_metadata_fields.html:6 +msgid "Source" +msgstr "" + +#: ckan/templates/package/snippets/additional_info.html:37 +#: ckan/templates/package/snippets/additional_info.html:42 +#: ckan/templates/package/snippets/package_metadata_fields.html:20 +msgid "Maintainer" +msgstr "" + +#: ckan/templates/package/snippets/additional_info.html:49 +#: ckan/templates/package/snippets/package_metadata_fields.html:10 +msgid "Version" +msgstr "" + +#: ckan/templates/package/snippets/additional_info.html:56 +#: ckan/templates/package/snippets/package_basic_fields.html:114 +#: ckan/templates/user/read_base.html:91 +msgid "State" +msgstr "" + +#: ckan/templates/package/snippets/additional_info.html:62 +msgid "Last Updated" +msgstr "" + +#: ckan/templates/package/snippets/cannot_create_package.html:10 +msgid "Before you can create a dataset you need to create an organization." +msgstr "" + +#: ckan/templates/package/snippets/cannot_create_package.html:13 +msgid "Create a new organization" +msgstr "" + +#: ckan/templates/package/snippets/cannot_create_package.html:18 +msgid "There are no organizations to which you can assign this dataset." +msgstr "" + +#: ckan/templates/package/snippets/cannot_create_package.html:19 +msgid "Ask a system administrator to create an organization before you can continue." +msgstr "" + +#: ckan/templates/package/snippets/package_basic_fields.html:4 +#: ckan/templates/package/snippets/view_form.html:8 +msgid "Title" +msgstr "" + +#: ckan/templates/package/snippets/package_basic_fields.html:4 +msgid "eg. A descriptive title" +msgstr "" + +#: ckan/templates/package/snippets/package_basic_fields.html:13 +msgid "eg. my-dataset" +msgstr "" + +#: ckan/templates/package/snippets/package_basic_fields.html:19 +msgid "eg. Some useful notes about the data" +msgstr "" + +#: ckan/templates/package/snippets/package_basic_fields.html:24 +msgid "eg. economy, mental health, government" +msgstr "" + +#: ckan/templates/package/snippets/package_basic_fields.html:45 +msgid "" +" License definitions and additional information can be found at opendefinition.org " +msgstr "" + +#: ckan/templates/package/snippets/package_basic_fields.html:76 +#: ckan/templates/snippets/organization.html:23 +msgid "Organization" +msgstr "" + +#: ckan/templates/package/snippets/package_basic_fields.html:80 +msgid "No organization" +msgstr "" + +#: ckan/templates/package/snippets/package_basic_fields.html:95 +msgid "Visibility" +msgstr "" + +#: ckan/templates/package/snippets/package_basic_fields.html:98 +msgid "Public" +msgstr "" + +#: ckan/templates/package/snippets/package_basic_fields.html:117 +msgid "Active" +msgstr "" + +#: ckan/templates/package/snippets/package_form.html:28 +msgid "" +"The data license you select above only applies to the contents of any " +"resource files that you add to this dataset. By submitting this form, you " +"agree to release the metadata values that you enter into the form " +"under the Open " +"Database License." +msgstr "" + +#: ckan/templates/package/snippets/package_form.html:39 +msgid "Are you sure you want to delete this dataset?" +msgstr "" + +#: ckan/templates/package/snippets/package_form.html:43 +msgid "Next: Add Data" +msgstr "" + +#: ckan/templates/package/snippets/package_metadata_fields.html:6 +msgid "http://example.com/dataset.json" +msgstr "" + +#: ckan/templates/package/snippets/package_metadata_fields.html:10 +msgid "1.0" +msgstr "" + +#: ckan/templates/package/snippets/package_metadata_fields.html:14 +#: ckan/templates/package/snippets/package_metadata_fields.html:20 +#: ckan/templates/user/new_user_form.html:6 +msgid "Joe Bloggs" +msgstr "" + +#: ckan/templates/package/snippets/package_metadata_fields.html:16 +msgid "Author Email" +msgstr "" + +#: ckan/templates/package/snippets/package_metadata_fields.html:16 +#: ckan/templates/package/snippets/package_metadata_fields.html:22 +#: ckan/templates/user/new_user_form.html:7 +msgid "joe@example.com" +msgstr "" + +#: ckan/templates/package/snippets/package_metadata_fields.html:22 +msgid "Maintainer Email" +msgstr "" + +#: ckan/templates/package/snippets/resource_edit_form.html:12 +msgid "Update Resource" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:26 +msgid "Data" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:26 +msgid "http://example.com/external-data.csv" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:30 +msgid "eg. January 2011 Gold Prices" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:34 +msgid "Some useful notes about the data" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:39 +msgid "eg. CSV, XML or JSON" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:42 +msgid "This will be guessed automatically. Leave blank if you wish" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:53 +msgid "eg. 2012-06-05" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:55 +msgid "File Size" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:55 +msgid "eg. 1024" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:57 +#: ckan/templates/package/snippets/resource_form.html:59 +msgid "MIME Type" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:57 +#: ckan/templates/package/snippets/resource_form.html:59 +msgid "eg. application/json" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:67 +msgid "Are you sure you want to delete this resource?" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:73 +msgid "Previous" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:76 +msgid "Save & add another" +msgstr "" + +#: ckan/templates/package/snippets/resource_form.html:79 +msgid "Finish" +msgstr "" + +#: ckan/templates/package/snippets/resource_help.html:2 +msgid "What's a resource?" +msgstr "" + +#: ckan/templates/package/snippets/resource_help.html:4 +msgid "A resource can be any file or link to a file containing useful data." +msgstr "" + +#: ckan/templates/package/snippets/resource_item.html:23 +msgid "Explore" +msgstr "" + +#: ckan/templates/package/snippets/resource_item.html:35 +msgid "More information" +msgstr "" + +#: ckan/templates/package/snippets/resource_view.html:10 +msgid "Fullscreen" +msgstr "" + +#: ckan/templates/package/snippets/resource_view.html:18 +msgid "Embed" +msgstr "" + +#: ckan/templates/package/snippets/resource_view.html:32 +msgid "This resource view is not available at the moment." +msgstr "" + +#: ckan/templates/package/snippets/resource_view.html:74 +msgid "Embed resource view" +msgstr "" + +#: ckan/templates/package/snippets/resource_view.html:77 +msgid "" +"You can copy and paste the embed code into a CMS or blog software that " +"supports raw HTML" +msgstr "" + +#: ckan/templates/package/snippets/resource_view.html:80 +msgid "Width" +msgstr "" + +#: ckan/templates/package/snippets/resource_view.html:83 +msgid "Height" +msgstr "" + +#: ckan/templates/package/snippets/resource_view.html:86 +msgid "Code" +msgstr "" + +#: ckan/templates/package/snippets/resource_views_list.html:8 +msgid "Resource Preview" +msgstr "" + +#: ckan/templates/package/snippets/resources_list.html:13 +msgid "Data and Resources" +msgstr "" + +#: ckan/templates/package/snippets/resources_list.html:30 +msgid "This dataset has no data" +msgstr "" + +#: ckan/templates/package/snippets/revisions_table.html:24 +#, python-format +msgid "Read dataset as of %s" +msgstr "" + +#: ckan/templates/package/snippets/stages.html:23 +#: ckan/templates/package/snippets/stages.html:25 +msgid "Create dataset" +msgstr "" + +#: ckan/templates/package/snippets/stages.html:30 +#: ckan/templates/package/snippets/stages.html:34 +#: ckan/templates/package/snippets/stages.html:36 +msgid "Add data" +msgstr "" + +#: ckan/templates/package/snippets/view_form.html:8 +msgid "eg. My View" +msgstr "" + +#: ckan/templates/package/snippets/view_form.html:9 +msgid "eg. Information about my view" +msgstr "" + +#: ckan/templates/package/snippets/view_form_filters.html:28 +msgid "Remove Filter" +msgstr "" + +#: ckan/templates/package/snippets/view_help.html:2 +msgid "What's a view?" +msgstr "" + +#: ckan/templates/package/snippets/view_help.html:4 +msgid "A view is a representation of the data held against a resource" +msgstr "" + +#: ckan/templates/revision/diff.html:6 +msgid "Differences" +msgstr "" + +#: ckan/templates/revision/diff.html:13 ckan/templates/revision/diff.html:18 +#: ckan/templates/revision/diff.html:23 +msgid "Revision Differences" +msgstr "" + +#: ckan/templates/revision/diff.html:44 +msgid "Difference" +msgstr "" + +#: ckan/templates/revision/diff.html:54 +msgid "No Differences" +msgstr "" + +#: ckan/templates/revision/list.html:3 ckan/templates/revision/list.html:6 +#: ckan/templates/revision/list.html:10 +msgid "Revision History" +msgstr "" + +#: ckan/templates/revision/list.html:6 ckan/templates/revision/read.html:8 +msgid "Revisions" +msgstr "" + +#: ckan/templates/revision/read.html:30 +msgid "Undelete" +msgstr "" + +#: ckan/templates/revision/read.html:64 +msgid "Changes" +msgstr "" + +#: ckan/templates/revision/read.html:74 +msgid "Datasets' Tags" +msgstr "" + +#: ckan/templates/revision/snippets/revisions_list.html:7 +msgid "Entity" +msgstr "" + +#: ckan/templates/snippets/activity_item.html:3 +msgid "New activity item" +msgstr "" + +#: ckan/templates/snippets/add_dataset.html:6 +msgid "Add Dataset" +msgstr "" + +#: ckan/templates/snippets/datapusher_status.html:8 +msgid "Datapusher status: {status}." +msgstr "" + +#: ckan/templates/snippets/disqus_trackback.html:2 +msgid "Trackback URL" +msgstr "" + +#: ckan/templates/snippets/facet_list.html:82 +msgid "Show More {facet_type}" +msgstr "" + +#: ckan/templates/snippets/facet_list.html:85 +msgid "Show Only Popular {facet_type}" +msgstr "" + +#: ckan/templates/snippets/facet_list.html:89 +msgid "There are no {facet_type} that match this search" +msgstr "" + +#: ckan/templates/snippets/home_breadcrumb_item.html:2 +msgid "Home" +msgstr "" + +#: ckan/templates/snippets/language_selector.html:3 +msgid "Language" +msgstr "" + +#: ckan/templates/snippets/language_selector.html:11 +#: ckan/templates/snippets/search_form.html:42 +#: ckan/templates/snippets/simple_search.html:15 +#: ckan/templates/snippets/sort_by.html:22 +msgid "Go" +msgstr "" + +#: ckan/templates/snippets/license.html:14 +msgid "No License Provided" +msgstr "" + +#: ckan/templates/snippets/license.html:28 +msgid "This dataset satisfies the Open Definition." +msgstr "" + +#: ckan/templates/snippets/organization.html:48 +msgid "There is no description for this organization" +msgstr "" + +#: ckan/templates/snippets/package_item.html:57 +msgid "This dataset has no description" +msgstr "" + +#: ckan/templates/snippets/search_form.html:33 +#: ckan/templates/snippets/simple_search.html:8 +#: ckan/templates/snippets/sort_by.html:12 +msgid "Order by" +msgstr "" + +#: ckan/templates/snippets/search_form.html:74 +msgid "Filter Results" +msgstr "" + +#: ckan/templates/snippets/search_form.html:81 +msgid "

Please try another search.

" +msgstr "" + +#: ckan/templates/snippets/search_form.html:87 +msgid "" +"

There was an error while searching. " +"Please try again.

" +msgstr "" + +#: ckan/templates/snippets/search_result_text.html:15 +msgid "{number} dataset found for \"{query}\"" +msgid_plural "{number} datasets found for \"{query}\"" +msgstr[0] "" +msgstr[1] "" + +#: ckan/templates/snippets/search_result_text.html:16 +msgid "No datasets found for \"{query}\"" +msgstr "" + +#: ckan/templates/snippets/search_result_text.html:17 +msgid "{number} dataset found" +msgid_plural "{number} datasets found" +msgstr[0] "" +msgstr[1] "" + +#: ckan/templates/snippets/search_result_text.html:18 +msgid "No datasets found" +msgstr "" + +#: ckan/templates/snippets/search_result_text.html:21 +msgid "{number} group found for \"{query}\"" +msgid_plural "{number} groups found for \"{query}\"" +msgstr[0] "" +msgstr[1] "" + +#: ckan/templates/snippets/search_result_text.html:22 +msgid "No groups found for \"{query}\"" +msgstr "" + +#: ckan/templates/snippets/search_result_text.html:23 +msgid "{number} group found" +msgid_plural "{number} groups found" +msgstr[0] "" +msgstr[1] "" + +#: ckan/templates/snippets/search_result_text.html:24 +msgid "No groups found" +msgstr "" + +#: ckan/templates/snippets/search_result_text.html:27 +msgid "{number} organization found for \"{query}\"" +msgid_plural "{number} organizations found for \"{query}\"" +msgstr[0] "" +msgstr[1] "" + +#: ckan/templates/snippets/search_result_text.html:28 +msgid "No organizations found for \"{query}\"" +msgstr "" + +#: ckan/templates/snippets/search_result_text.html:29 +msgid "{number} organization found" +msgid_plural "{number} organizations found" +msgstr[0] "" +msgstr[1] "" + +#: ckan/templates/snippets/search_result_text.html:30 +msgid "No organizations found" +msgstr "" + +#: ckan/templates/snippets/social.html:5 +msgid "Social" +msgstr "" + +#: ckan/templates/snippets/subscribe.html:2 +msgid "Subscribe" +msgstr "" + +#: ckan/templates/snippets/subscribe.html:4 +#: ckan/templates/user/edit_user_form.html:12 +#: ckan/templates/user/new_user_form.html:7 ckan/templates/user/read_base.html:82 +msgid "Email" +msgstr "" + +#: ckan/templates/snippets/subscribe.html:5 +msgid "RSS" +msgstr "" + +#: ckan/templates/snippets/context/user.html:23 +#: ckan/templates/user/read_base.html:57 +msgid "Edits" +msgstr "" + +#: ckan/templates/tag/index.html:33 ckan/templates/tag/index.html:34 +msgid "Search Tags" +msgstr "" + +#: ckan/templates/user/dashboard.html:19 ckan/templates/user/dashboard.html:37 +msgid "News feed" +msgstr "" + +#: ckan/templates/user/dashboard.html:20 +#: ckan/templates/user/dashboard_datasets.html:12 +msgid "My Datasets" +msgstr "" + +#: ckan/templates/user/dashboard.html:21 +#: ckan/templates/user/dashboard_organizations.html:12 +msgid "My Organizations" +msgstr "" + +#: ckan/templates/user/dashboard.html:22 +#: ckan/templates/user/dashboard_groups.html:12 +msgid "My Groups" +msgstr "" + +#: ckan/templates/user/dashboard.html:39 +msgid "Activity from items that I'm following" +msgstr "" + +#: ckan/templates/user/dashboard_datasets.html:17 ckan/templates/user/read.html:20 +msgid "You haven't created any datasets." +msgstr "" + +#: ckan/templates/user/dashboard_datasets.html:19 +#: ckan/templates/user/dashboard_groups.html:22 +#: ckan/templates/user/dashboard_organizations.html:23 +#: ckan/templates/user/read.html:22 +msgid "Create one now?" +msgstr "" + +#: ckan/templates/user/dashboard_groups.html:20 +msgid "You are not a member of any groups." +msgstr "" + +#: ckan/templates/user/dashboard_organizations.html:21 +msgid "You are not a member of any organizations." +msgstr "" + +#: ckan/templates/user/edit.html:6 ckan/templates/user/edit_base.html:3 +#: ckan/templates/user/list.html:6 ckan/templates/user/list.html:13 +#: ckan/templates/user/read_base.html:5 ckan/templates/user/read_base.html:8 +#: ckan/templates/user/snippets/user_search.html:2 +msgid "Users" +msgstr "" + +#: ckan/templates/user/edit.html:17 +msgid "Account Info" +msgstr "" + +#: ckan/templates/user/edit.html:19 +msgid " Your profile lets other CKAN users know about who you are and what you do. " +msgstr "" + +#: ckan/templates/user/edit_user_form.html:7 +msgid "Change details" +msgstr "" + +#: ckan/templates/user/edit_user_form.html:10 +msgid "Full name" +msgstr "" + +#: ckan/templates/user/edit_user_form.html:10 +msgid "eg. Joe Bloggs" +msgstr "" + +#: ckan/templates/user/edit_user_form.html:12 +msgid "eg. joe@example.com" +msgstr "" + +#: ckan/templates/user/edit_user_form.html:14 +msgid "A little information about yourself" +msgstr "" + +#: ckan/templates/user/edit_user_form.html:17 +msgid "Subscribe to notification emails" +msgstr "" + +#: ckan/templates/user/edit_user_form.html:26 +msgid "Change password" +msgstr "" + +#: ckan/templates/user/edit_user_form.html:29 +msgid "Sysadmin Password" +msgstr "" + +#: ckan/templates/user/edit_user_form.html:37 +#: ckan/templates/user/logout_first.html:11 +#: ckan/templates/user/new_user_form.html:8 +#: ckan/templates/user/perform_reset.html:25 +#: ckan/templates/user/snippets/login_form.html:22 +msgid "Password" +msgstr "" + +#: ckan/templates/user/edit_user_form.html:39 +msgid "Confirm Password" +msgstr "" + +#: ckan/templates/user/edit_user_form.html:45 +msgid "Are you sure you want to delete this User?" +msgstr "" + +#: ckan/templates/user/edit_user_form.html:50 +msgid "Are you sure you want to regenerate the API key?" +msgstr "" + +#: ckan/templates/user/edit_user_form.html:50 +msgid "Regenerate API Key" +msgstr "" + +#: ckan/templates/user/edit_user_form.html:54 +msgid "Update Profile" +msgstr "" + +#: ckan/templates/user/list.html:3 ckan/templates/user/snippets/user_search.html:11 +msgid "All Users" +msgstr "" + +#: ckan/templates/user/login.html:3 ckan/templates/user/login.html:6 +#: ckan/templates/user/login.html:12 +#: ckan/templates/user/snippets/login_form.html:28 +msgid "Login" +msgstr "" + +#: ckan/templates/user/login.html:25 +msgid "Need an Account?" +msgstr "" + +#: ckan/templates/user/login.html:27 +msgid "Then sign right up, it only takes a minute." +msgstr "" + +#: ckan/templates/user/login.html:30 +msgid "Create an Account" +msgstr "" + +#: ckan/templates/user/login.html:42 +msgid "Forgotten your password?" +msgstr "" + +#: ckan/templates/user/login.html:44 +msgid "No problem, use our password recovery form to reset it." +msgstr "" + +#: ckan/templates/user/login.html:47 +msgid "Forgot your password?" +msgstr "" + +#: ckan/templates/user/logout.html:3 ckan/templates/user/logout.html:9 +msgid "Logged Out" +msgstr "" + +#: ckan/templates/user/logout.html:11 +msgid "You are now logged out." +msgstr "" + +#: ckan/templates/user/logout_first.html:9 +msgid "You're already logged in as {user}." +msgstr "" + +#: ckan/templates/user/logout_first.html:9 +msgid "Logout" +msgstr "" + +#: ckan/templates/user/logout_first.html:12 +#: ckan/templates/user/snippets/login_form.html:24 +msgid "Remember me" +msgstr "" + +#: ckan/templates/user/logout_first.html:20 +msgid "You're already logged in" +msgstr "" + +#: ckan/templates/user/logout_first.html:22 +msgid "You need to log out before you can log in with another account." +msgstr "" + +#: ckan/templates/user/logout_first.html:23 +msgid "Log out now" +msgstr "" + +#: ckan/templates/user/new.html:6 +msgid "Registration" +msgstr "" + +#: ckan/templates/user/new.html:14 +msgid "Register for an Account" +msgstr "" + +#: ckan/templates/user/new.html:26 +msgid "Why Sign Up?" +msgstr "" + +#: ckan/templates/user/new.html:28 +msgid "Create datasets, groups and other exciting things" +msgstr "" + +#: ckan/templates/user/new_user_form.html:5 +msgid "username" +msgstr "" + +#: ckan/templates/user/new_user_form.html:6 +msgid "Full Name" +msgstr "" + +#: ckan/templates/user/new_user_form.html:19 +msgid "Create Account" +msgstr "" + +#: ckan/templates/user/perform_reset.html:4 +#: ckan/templates/user/perform_reset.html:14 +msgid "Reset Your Password" +msgstr "" + +#: ckan/templates/user/perform_reset.html:7 +msgid "Password Reset" +msgstr "" + +#: ckan/templates/user/perform_reset.html:21 +msgid "You can also change username. It can not be modified later." +msgstr "" + +#: ckan/templates/user/perform_reset.html:29 +msgid "Update Password" +msgstr "" + +#: ckan/templates/user/perform_reset.html:43 +#: ckan/templates/user/request_reset.html:32 +msgid "How does this work?" +msgstr "" + +#: ckan/templates/user/perform_reset.html:45 +msgid "Simply enter a new password and we'll update your account" +msgstr "" + +#: ckan/templates/user/read.html:27 +msgid "User hasn't created any datasets." +msgstr "" + +#: ckan/templates/user/read_base.html:39 +msgid "You have not provided a biography." +msgstr "" + +#: ckan/templates/user/read_base.html:41 +msgid "This user has no biography." +msgstr "" + +#: ckan/templates/user/read_base.html:73 +msgid "Open ID" +msgstr "" + +#: ckan/templates/user/read_base.html:82 ckan/templates/user/read_base.html:96 +msgid "This means only you can see this" +msgstr "" + +#: ckan/templates/user/read_base.html:87 +msgid "Member Since" +msgstr "" + +#: ckan/templates/user/read_base.html:96 +msgid "API Key" +msgstr "" + +#: ckan/templates/user/request_reset.html:3 +#: ckan/templates/user/request_reset.html:13 +msgid "Reset your password" +msgstr "" + +#: ckan/templates/user/request_reset.html:6 +msgid "Password reset" +msgstr "" + +#: ckan/templates/user/request_reset.html:19 +msgid "Request reset" +msgstr "" + +#: ckan/templates/user/request_reset.html:34 +msgid "" +"Enter your username into the box and we will send you an email with a link to" +" enter a new password." +msgstr "" + +#: ckan/templates/user/snippets/followee_dropdown.html:15 +#: ckan/templates/user/snippets/followee_dropdown.html:16 +msgid "Activity from:" +msgstr "" + +#: ckan/templates/user/snippets/followee_dropdown.html:23 +msgid "Search list..." +msgstr "" + +#: ckan/templates/user/snippets/followee_dropdown.html:44 +msgid "You are not following anything" +msgstr "" + +#: ckan/templates/user/snippets/followers.html:9 +msgid "No followers" +msgstr "" + +#: ckan/templates/user/snippets/user_search.html:5 +msgid "Search Users" +msgstr "" + +#: ckan/views/user.py:588 +msgid "Your password must be 8 characters or longer." +msgstr "" + diff --git a/venv/lib/python2.7/site-packages/ckan/include/__init__.py b/venv/lib/python2.7/site-packages/ckan/include/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/venv/lib/python2.7/site-packages/ckan/include/rcssmin.py b/venv/lib/python2.7/site-packages/ckan/include/rcssmin.py new file mode 100644 index 00000000..3f0435f0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/include/rcssmin.py @@ -0,0 +1,360 @@ +#!/usr/bin/env python +# -*- coding: ascii -*- +# +# Copyright 2011, 2012 +# Andr\xe9 Malo or his licensors, as applicable +# +# 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""" +============== + CSS Minifier +============== + +CSS Minifier. + +The minifier is based on the semantics of the `YUI compressor`_\, which itself +is based on `the rule list by Isaac Schlueter`_\. + +This module is a re-implementation aiming for speed instead of maximum +compression, so it can be used at runtime (rather than during a preprocessing +step). RCSSmin does syntactical compression only (removing spaces, comments +and possibly semicolons). It does not provide semantic compression (like +removing empty blocks, collapsing redundant properties etc). It does, however, +support various CSS hacks (by keeping them working as intended). + +Here's a feature list: + +- Strings are kept, except that escaped newlines are stripped +- Space/Comments before the very end or before various characters are + stripped: ``:{});=>+],!`` (The colon (``:``) is a special case, a single + space is kept if it's outside a ruleset.) +- Space/Comments at the very beginning or after various characters are + stripped: ``{}(=:>+[,!`` +- Optional space after unicode escapes is kept, resp. replaced by a simple + space +- whitespaces inside ``url()`` definitions are stripped +- Comments starting with an exclamation mark (``!``) can be kept optionally. +- All other comments and/or whitespace characters are replaced by a single + space. +- Multiple consecutive semicolons are reduced to one +- The last semicolon within a ruleset is stripped +- CSS Hacks supported: + + - IE7 hack (``>/**/``) + - Mac-IE5 hack (``/*\*/.../**/``) + - The boxmodelhack is supported naturally because it relies on valid CSS2 + strings + - Between ``:first-line`` and the following comma or curly brace a space is + inserted. (apparently it's needed for IE6) + - Same for ``:first-letter`` + +rcssmin.c is a reimplementation of rcssmin.py in C and improves runtime up to +factor 50 or so (depending on the input). + +Both python 2 (>= 2.4) and python 3 are supported. + +.. _YUI compressor: https://github.com/yui/yuicompressor/ + +.. _the rule list by Isaac Schlueter: https://github.com/isaacs/cssmin/tree/ +""" +__author__ = "Andr\xe9 Malo" +__author__ = getattr(__author__, 'decode', lambda x: __author__)('latin-1') +__docformat__ = "restructuredtext en" +__license__ = "Apache License, Version 2.0" +__version__ = '1.0.0' +__all__ = ['cssmin'] + +import re as _re + + +def _make_cssmin(python_only=False): + """ + Generate CSS minifier. + + :Parameters: + `python_only` : ``bool`` + Use only the python variant. If true, the c extension is not even + tried to be loaded. + + :Return: Minifier + :Rtype: ``callable`` + """ + # pylint: disable = W0612 + # ("unused" variables) + + # pylint: disable = R0911, R0912, R0914, R0915 + # (too many anything) + + if not python_only: + try: + import _rcssmin + except ImportError: + pass + else: + return _rcssmin.cssmin + + nl = r'(?:[\n\f]|\r\n?)' # pylint: disable = C0103 + spacechar = r'[\r\n\f\040\t]' + + unicoded = r'[0-9a-fA-F]{1,6}(?:[\040\n\t\f]|\r\n?)?' + escaped = r'[^\n\r\f0-9a-fA-F]' + escape = r'(?:\\(?:%(unicoded)s|%(escaped)s))' % locals() + + nmchar = r'[^\000-\054\056\057\072-\100\133-\136\140\173-\177]' + #nmstart = r'[^\000-\100\133-\136\140\173-\177]' + #ident = (r'(?:' + # r'-?(?:%(nmstart)s|%(escape)s)%(nmchar)s*(?:%(escape)s%(nmchar)s*)*' + #r')') % locals() + + comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' + + # only for specific purposes. The bang is grouped: + _bang_comment = r'(?:/\*(!?)[^*]*\*+(?:[^/*][^*]*\*+)*/)' + + string1 = \ + r'(?:\047[^\047\\\r\n\f]*(?:\\[^\r\n\f][^\047\\\r\n\f]*)*\047)' + string2 = r'(?:"[^"\\\r\n\f]*(?:\\[^\r\n\f][^"\\\r\n\f]*)*")' + strings = r'(?:%s|%s)' % (string1, string2) + + nl_string1 = \ + r'(?:\047[^\047\\\r\n\f]*(?:\\(?:[^\r]|\r\n?)[^\047\\\r\n\f]*)*\047)' + nl_string2 = r'(?:"[^"\\\r\n\f]*(?:\\(?:[^\r]|\r\n?)[^"\\\r\n\f]*)*")' + nl_strings = r'(?:%s|%s)' % (nl_string1, nl_string2) + + uri_nl_string1 = r'(?:\047[^\047\\]*(?:\\(?:[^\r]|\r\n?)[^\047\\]*)*\047)' + uri_nl_string2 = r'(?:"[^"\\]*(?:\\(?:[^\r]|\r\n?)[^"\\]*)*")' + uri_nl_strings = r'(?:%s|%s)' % (uri_nl_string1, uri_nl_string2) + + nl_escaped = r'(?:\\%(nl)s)' % locals() + + space = r'(?:%(spacechar)s|%(comment)s)' % locals() + + ie7hack = r'(?:>/\*\*/)' + + uri = (r'(?:' + r'(?:[^\000-\040"\047()\\\177]*' + r'(?:%(escape)s[^\000-\040"\047()\\\177]*)*)' + r'(?:' + r'(?:%(spacechar)s+|%(nl_escaped)s+)' + r'(?:' + r'(?:[^\000-\040"\047()\\\177]|%(escape)s|%(nl_escaped)s)' + r'[^\000-\040"\047()\\\177]*' + r'(?:%(escape)s[^\000-\040"\047()\\\177]*)*' + r')+' + r')*' + r')') % locals() + + nl_unesc_sub = _re.compile(nl_escaped).sub + + uri_space_sub = _re.compile(( + r'(%(escape)s+)|%(spacechar)s+|%(nl_escaped)s+' + ) % locals()).sub + uri_space_subber = lambda m: m.groups()[0] or '' + + space_sub_simple = _re.compile(( + r'[\r\n\f\040\t;]+|(%(comment)s+)' + ) % locals()).sub + space_sub_banged = _re.compile(( + r'[\r\n\f\040\t;]+|(%(_bang_comment)s+)' + ) % locals()).sub + + post_esc_sub = _re.compile(r'[\r\n\f\t]+').sub + + main_sub = _re.compile(( + r'([^\\"\047u>@\r\n\f\040\t/;:{}]+)' + r'|(?<=[{}(=:>+[,!])(%(space)s+)' + r'|^(%(space)s+)' + r'|(%(space)s+)(?=(([:{});=>+\],!])|$)?)' + r'|;(%(space)s*(?:;%(space)s*)*)(?=(\})?)' + r'|(\{)' + r'|(\})' + r'|(%(strings)s)' + r'|(?@\r\n\f\040\t/;:{}]*)' + ) % locals()).sub + + #print main_sub.__self__.pattern + + def main_subber(keep_bang_comments): + """ Make main subber """ + in_macie5, in_rule, at_media = [0], [0], [0] + + if keep_bang_comments: + space_sub = space_sub_banged + def space_subber(match): + """ Space|Comment subber """ + if match.lastindex: + group1, group2 = match.group(1, 2) + if group2: + if group1.endswith(r'\*/'): + in_macie5[0] = 1 + else: + in_macie5[0] = 0 + return group1 + elif group1: + if group1.endswith(r'\*/'): + if in_macie5[0]: + return '' + in_macie5[0] = 1 + return r'/*\*/' + elif in_macie5[0]: + in_macie5[0] = 0 + return '/**/' + return '' + else: + space_sub = space_sub_simple + def space_subber(match): + """ Space|Comment subber """ + if match.lastindex: + if match.group(1).endswith(r'\*/'): + if in_macie5[0]: + return '' + in_macie5[0] = 1 + return r'/*\*/' + elif in_macie5[0]: + in_macie5[0] = 0 + return '/**/' + return '' + + def fn_space_post(group): + """ space with token after """ + if group(5) is None or ( + group(6) == ':' and not in_rule[0] and not at_media[0]): + return ' ' + space_sub(space_subber, group(4)) + return space_sub(space_subber, group(4)) + + def fn_semicolon(group): + """ ; handler """ + return ';' + space_sub(space_subber, group(7)) + + def fn_semicolon2(group): + """ ; handler """ + if in_rule[0]: + return space_sub(space_subber, group(7)) + return ';' + space_sub(space_subber, group(7)) + + def fn_open(group): + """ { handler """ + # pylint: disable = W0613 + if at_media[0]: + at_media[0] -= 1 + else: + in_rule[0] = 1 + return '{' + + def fn_close(group): + """ } handler """ + # pylint: disable = W0613 + in_rule[0] = 0 + return '}' + + def fn_media(group): + """ @media handler """ + at_media[0] += 1 + return group(13) + + def fn_ie7hack(group): + """ IE7 Hack handler """ + if not in_rule[0] and not at_media[0]: + in_macie5[0] = 0 + return group(14) + space_sub(space_subber, group(15)) + return '>' + space_sub(space_subber, group(15)) + + table = ( + None, + None, + None, + None, + fn_space_post, # space with token after + fn_space_post, # space with token after + fn_space_post, # space with token after + fn_semicolon, # semicolon + fn_semicolon2, # semicolon + fn_open, # { + fn_close, # } + lambda g: g(11), # string + lambda g: 'url(%s)' % uri_space_sub(uri_space_subber, g(12)), + # url(...) + fn_media, # @media + None, + fn_ie7hack, # ie7hack + None, + lambda g: g(16) + ' ' + space_sub(space_subber, g(17)), + # :first-line|letter followed + # by [{,] (apparently space + # needed for IE6) + lambda g: nl_unesc_sub('', g(18)), # nl_string + lambda g: post_esc_sub(' ', g(19)), # escape + ) + + def func(match): + """ Main subber """ + idx, group = match.lastindex, match.group + if idx > 3: + return table[idx](group) + + # shortcuts for frequent operations below: + elif idx == 1: # not interesting + return group(1) + #else: # space with token before or at the beginning + return space_sub(space_subber, group(idx)) + + return func + + def cssmin(style, keep_bang_comments=False): # pylint: disable = W0621 + """ + Minify CSS. + + :Parameters: + `style` : ``str`` + CSS to minify + + `keep_bang_comments` : ``bool`` + Keep comments starting with an exclamation mark? (``/*!...*/``) + + :Return: Minified style + :Rtype: ``str`` + """ + return main_sub(main_subber(keep_bang_comments), style) + + return cssmin + +cssmin = _make_cssmin() + + +if __name__ == '__main__': + def main(): + """ Main """ + import sys as _sys + keep_bang_comments = ( + '-b' in _sys.argv[1:] + or '-bp' in _sys.argv[1:] + or '-pb' in _sys.argv[1:] + ) + if '-p' in _sys.argv[1:] or '-bp' in _sys.argv[1:] \ + or '-pb' in _sys.argv[1:]: + global cssmin # pylint: disable = W0603 + cssmin = _make_cssmin(python_only=True) + _sys.stdout.write(cssmin( + _sys.stdin.read(), keep_bang_comments=keep_bang_comments + )) + main() diff --git a/venv/lib/python2.7/site-packages/ckan/include/rjsmin.py b/venv/lib/python2.7/site-packages/ckan/include/rjsmin.py new file mode 100644 index 00000000..13e66e0e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/include/rjsmin.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python +# -*- coding: ascii -*- +# +# Copyright 2011, 2012 +# Andr\xe9 Malo or his licensors, as applicable +# +# 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""" +===================== + Javascript Minifier +===================== + +rJSmin is a javascript minifier written in python. + +The minifier is based on the semantics of `jsmin.c by Douglas Crockford`_\. + +The module is a re-implementation aiming for speed, so it can be used at +runtime (rather than during a preprocessing step). Usually it produces the +same results as the original ``jsmin.c``. It differs in the following ways: + +- there is no error detection: unterminated string, regex and comment + literals are treated as regular javascript code and minified as such. +- Control characters inside string and regex literals are left untouched; they + are not converted to spaces (nor to \n) +- Newline characters are not allowed inside string and regex literals, except + for line continuations in string literals (ECMA-5). +- "return /regex/" is recognized correctly. +- "+ ++" and "- --" sequences are not collapsed to '+++' or '---' +- rJSmin does not handle streams, but only complete strings. (However, the + module provides a "streamy" interface). + +Since most parts of the logic are handled by the regex engine it's way +faster than the original python port of ``jsmin.c`` by Baruch Even. The speed +factor varies between about 6 and 55 depending on input and python version +(it gets faster the more compressed the input already is). Compared to the +speed-refactored python port by Dave St.Germain the performance gain is less +dramatic but still between 1.2 and 7. See the docs/BENCHMARKS file for +details. + +rjsmin.c is a reimplementation of rjsmin.py in C and speeds it up even more. + +Both python 2 and python 3 are supported. + +.. _jsmin.c by Douglas Crockford: + http://www.crockford.com/javascript/jsmin.c +""" +__author__ = "Andr\xe9 Malo" +__author__ = getattr(__author__, 'decode', lambda x: __author__)('latin-1') +__docformat__ = "restructuredtext en" +__license__ = "Apache License, Version 2.0" +__version__ = '1.0.3' +__all__ = ['jsmin'] + +import re as _re + + +def _make_jsmin(python_only=False): + """ + Generate JS minifier based on `jsmin.c by Douglas Crockford`_ + + .. _jsmin.c by Douglas Crockford: + http://www.crockford.com/javascript/jsmin.c + + :Parameters: + `python_only` : ``bool`` + Use only the python variant. If true, the c extension is not even + tried to be loaded. + + :Return: Minifier + :Rtype: ``callable`` + """ + # pylint: disable = R0912, R0914, W0612 + if not python_only: + try: + import _rjsmin + except ImportError: + pass + else: + return _rjsmin.jsmin + try: + xrange + except NameError: + xrange = range # pylint: disable = W0622 + + space_chars = r'[\000-\011\013\014\016-\040]' + + line_comment = r'(?://[^\r\n]*)' + space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' + string1 = \ + r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)' + string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")' + strings = r'(?:%s|%s)' % (string1, string2) + + charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])' + nospecial = r'[^/\\\[\r\n]' + regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % ( + nospecial, charclass, nospecial + ) + space = r'(?:%s|%s)' % (space_chars, space_comment) + newline = r'(?:%s?[\r\n])' % line_comment + + def fix_charclass(result): + """ Fixup string of chars to fit into a regex char class """ + pos = result.find('-') + if pos >= 0: + result = r'%s%s-' % (result[:pos], result[pos + 1:]) + + def sequentize(string): + """ + Notate consecutive characters as sequence + + (1-4 instead of 1234) + """ + first, last, result = None, None, [] + for char in map(ord, string): + if last is None: + first = last = char + elif last + 1 == char: + last = char + else: + result.append((first, last)) + first = last = char + if last is not None: + result.append((first, last)) + return ''.join(['%s%s%s' % ( + chr(first), + last > first + 1 and '-' or '', + last != first and chr(last) or '' + ) for first, last in result]) + + return _re.sub(r'([\000-\040\047])', # for better portability + lambda m: '\\%03o' % ord(m.group(1)), (sequentize(result) + .replace('\\', '\\\\') + .replace('[', '\\[') + .replace(']', '\\]') + ) + ) + + def id_literal_(what): + """ Make id_literal like char class """ + match = _re.compile(what).match + result = ''.join([ + chr(c) for c in xrange(127) if not match(chr(c)) + ]) + return '[^%s]' % fix_charclass(result) + + def not_id_literal_(keep): + """ Make negated id_literal like char class """ + match = _re.compile(id_literal_(keep)).match + result = ''.join([ + chr(c) for c in xrange(127) if not match(chr(c)) + ]) + return r'[%s]' % fix_charclass(result) + + not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]') + preregex1 = r'[(,=:\[!&|?{};\r\n]' + preregex2 = r'%(not_id_literal)sreturn' % locals() + + id_literal = id_literal_(r'[a-zA-Z0-9_$]') + id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(+-]') + id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]') + + space_sub = _re.compile(( + r'([^\047"/\000-\040]+)' + r'|(%(strings)s[^\047"/\000-\040]*)' + r'|(?:(?<=%(preregex1)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))' + r'|(?:(?<=%(preregex2)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))' + r'|(?<=%(id_literal_close)s)' + r'%(space)s*(?:(%(newline)s)%(space)s*)+' + r'(?=%(id_literal_open)s)' + r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)' + r'|(?<=\+)(%(space)s)+(?=\+\+)' + r'|(?<=-)(%(space)s)+(?=--)' + r'|%(space)s+' + r'|(?:%(newline)s%(space)s*)+' + ) % locals()).sub + #print space_sub.__self__.pattern + + def space_subber(match): + """ Substitution callback """ + # pylint: disable = C0321, R0911 + groups = match.groups() + if groups[0]: return groups[0] + elif groups[1]: return groups[1] + elif groups[2]: return groups[2] + elif groups[3]: return groups[3] + elif groups[4]: return '\n' + elif groups[5] or groups[6] or groups[7]: return ' ' + else: return '' + + def jsmin(script): # pylint: disable = W0621 + r""" + Minify javascript based on `jsmin.c by Douglas Crockford`_\. + + Instead of parsing the stream char by char, it uses a regular + expression approach which minifies the whole script with one big + substitution regex. + + .. _jsmin.c by Douglas Crockford: + http://www.crockford.com/javascript/jsmin.c + + :Parameters: + `script` : ``str`` + Script to minify + + :Return: Minified script + :Rtype: ``str`` + """ + return space_sub(space_subber, '\n%s\n' % script).strip() + + return jsmin + +jsmin = _make_jsmin() + + +def jsmin_for_posers(script): + r""" + Minify javascript based on `jsmin.c by Douglas Crockford`_\. + + Instead of parsing the stream char by char, it uses a regular + expression approach which minifies the whole script with one big + substitution regex. + + .. _jsmin.c by Douglas Crockford: + http://www.crockford.com/javascript/jsmin.c + + :Warning: This function is the digest of a _make_jsmin() call. It just + utilizes the resulting regex. It's just for fun here and may + vanish any time. Use the `jsmin` function instead. + + :Parameters: + `script` : ``str`` + Script to minify + + :Return: Minified script + :Rtype: ``str`` + """ + def subber(match): + """ Substitution callback """ + groups = match.groups() + return ( + groups[0] or + groups[1] or + groups[2] or + groups[3] or + (groups[4] and '\n') or + (groups[5] and ' ') or + (groups[6] and ' ') or + (groups[7] and ' ') or + '' + ) + + return _re.sub( + r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?' + r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|' + r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?:(?<=[(,=:\[!&|?{};\r\n]' + r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/' + r'))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*' + r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*' + r'))|(?:(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\01' + r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*((?:/(?![\r\n/*])[^/' + r'\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]' + r'*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*))|(?<=[^\000-!#%&(*,./' + r':-@\[\\^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/' + r'*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\01' + r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-#%-\04' + r'7)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011' + r'\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-' + r'#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|(?:/' + r'\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+\+)|(?<=-)((?:[\000-\011\013' + r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=--)|(?:[\00' + r'0-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:(' + r'?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]' + r'*\*+(?:[^/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script + ).strip() + + +if __name__ == '__main__': + import sys as _sys + _sys.stdout.write(jsmin(_sys.stdin.read())) diff --git a/venv/lib/python2.7/site-packages/ckan/lib/__init__.py b/venv/lib/python2.7/site-packages/ckan/lib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/venv/lib/python2.7/site-packages/ckan/lib/activity_streams.py b/venv/lib/python2.7/site-packages/ckan/lib/activity_streams.py new file mode 100644 index 00000000..70c17730 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/activity_streams.py @@ -0,0 +1,262 @@ +# encoding: utf-8 + +import re + +from webhelpers.html import literal + +import ckan.lib.helpers as h +import ckan.lib.base as base +import ckan.logic as logic + +from ckan.common import _, is_flask_request + +# get_snippet_*() functions replace placeholders like {user}, {dataset}, etc. +# in activity strings with HTML representations of particular users, datasets, +# etc. + +def get_snippet_actor(activity, detail): + return literal('''%s''' + % (h.linked_user(activity['user_id'], 0, 30)) + ) + +def get_snippet_user(activity, detail): + return literal('''%s''' + % (h.linked_user(activity['object_id'], 0, 20)) + ) + +def get_snippet_dataset(activity, detail): + data = activity['data'] + pkg_dict = data.get('package') or data.get('dataset') + link = h.dataset_link(pkg_dict) if pkg_dict else '' + return literal('''%s''' + % (link) + ) + +def get_snippet_tag(activity, detail): + return h.tag_link(detail['data']['tag']) + +def get_snippet_group(activity, detail): + link = h.group_link(activity['data']['group']) + return literal('''%s''' + % (link) + ) + +def get_snippet_organization(activity, detail): + return h.organization_link(activity['data']['group']) + +def get_snippet_extra(activity, detail): + return '"%s"' % detail['data']['package_extra']['key'] + +def get_snippet_resource(activity, detail): + return h.resource_link(detail['data']['resource'], + activity['data']['package']['id']) + +# activity_stream_string_*() functions return translatable string +# representations of activity types, the strings contain placeholders like +# {user}, {dataset} etc. to be replaced with snippets from the get_snippet_*() +# functions above. + +def activity_stream_string_added_tag(context, activity): + return _("{actor} added the tag {tag} to the dataset {dataset}") + +def activity_stream_string_changed_group(context, activity): + return _("{actor} updated the group {group}") + +def activity_stream_string_changed_organization(context, activity): + return _("{actor} updated the organization {organization}") + +def activity_stream_string_changed_package(context, activity): + return _("{actor} updated the dataset {dataset}") + +def activity_stream_string_changed_package_extra(context, activity): + return _("{actor} changed the extra {extra} of the dataset {dataset}") + +def activity_stream_string_changed_resource(context, activity): + return _("{actor} updated the resource {resource} in the dataset {dataset}") + +def activity_stream_string_changed_user(context, activity): + return _("{actor} updated their profile") + +def activity_stream_string_deleted_group(context, activity): + return _("{actor} deleted the group {group}") + +def activity_stream_string_deleted_organization(context, activity): + return _("{actor} deleted the organization {organization}") + +def activity_stream_string_deleted_package(context, activity): + return _("{actor} deleted the dataset {dataset}") + +def activity_stream_string_deleted_package_extra(context, activity): + return _("{actor} deleted the extra {extra} from the dataset {dataset}") + +def activity_stream_string_deleted_resource(context, activity): + return _("{actor} deleted the resource {resource} from the dataset " + "{dataset}") + +def activity_stream_string_new_group(context, activity): + return _("{actor} created the group {group}") + +def activity_stream_string_new_organization(context, activity): + return _("{actor} created the organization {organization}") + +def activity_stream_string_new_package(context, activity): + return _("{actor} created the dataset {dataset}") + +def activity_stream_string_new_package_extra(context, activity): + return _("{actor} added the extra {extra} to the dataset {dataset}") + +def activity_stream_string_new_resource(context, activity): + return _("{actor} added the resource {resource} to the dataset {dataset}") + +def activity_stream_string_new_user(context, activity): + return _("{actor} signed up") + +def activity_stream_string_removed_tag(context, activity): + return _("{actor} removed the tag {tag} from the dataset {dataset}") + +def activity_stream_string_follow_dataset(context, activity): + return _("{actor} started following {dataset}") + +def activity_stream_string_follow_user(context, activity): + return _("{actor} started following {user}") + +def activity_stream_string_follow_group(context, activity): + return _("{actor} started following {group}") + +# A dictionary mapping activity snippets to functions that expand the snippets. +activity_snippet_functions = { + 'actor': get_snippet_actor, + 'user': get_snippet_user, + 'dataset': get_snippet_dataset, + 'tag': get_snippet_tag, + 'group': get_snippet_group, + 'organization': get_snippet_organization, + 'extra': get_snippet_extra, + 'resource': get_snippet_resource, +} + +# A dictionary mapping activity types to functions that return translatable +# string descriptions of the activity types. +activity_stream_string_functions = { + 'added tag': activity_stream_string_added_tag, + 'changed group': activity_stream_string_changed_group, + 'changed organization': activity_stream_string_changed_organization, + 'changed package': activity_stream_string_changed_package, + 'changed package_extra': activity_stream_string_changed_package_extra, + 'changed resource': activity_stream_string_changed_resource, + 'changed user': activity_stream_string_changed_user, + 'deleted group': activity_stream_string_deleted_group, + 'deleted organization': activity_stream_string_deleted_organization, + 'deleted package': activity_stream_string_deleted_package, + 'deleted package_extra': activity_stream_string_deleted_package_extra, + 'deleted resource': activity_stream_string_deleted_resource, + 'new group': activity_stream_string_new_group, + 'new organization': activity_stream_string_new_organization, + 'new package': activity_stream_string_new_package, + 'new package_extra': activity_stream_string_new_package_extra, + 'new resource': activity_stream_string_new_resource, + 'new user': activity_stream_string_new_user, + 'removed tag': activity_stream_string_removed_tag, + 'follow dataset': activity_stream_string_follow_dataset, + 'follow user': activity_stream_string_follow_user, + 'follow group': activity_stream_string_follow_group, +} + +# A dictionary mapping activity types to the icons associated to them +activity_stream_string_icons = { + 'added tag': 'tag', + 'changed group': 'users', + 'changed package': 'sitemap', + 'changed package_extra': 'pencil-square-o', + 'changed resource': 'file', + 'changed user': 'user', + 'deleted group': 'users', + 'deleted package': 'sitemap', + 'deleted package_extra': 'pencil-square-o', + 'deleted resource': 'file', + 'new group': 'users', + 'new package': 'sitemap', + 'new package_extra': 'pencil-square-o', + 'new resource': 'file', + 'new user': 'user', + 'removed tag': 'tag', + 'follow dataset': 'sitemap', + 'follow user': 'user', + 'follow group': 'users', + 'changed organization': 'briefcase', + 'deleted organization': 'briefcase', + 'new organization': 'briefcase', + 'undefined': 'certificate', # This is when no activity icon can be found +} + +# A list of activity types that may have details +activity_stream_actions_with_detail = ['changed package'] + +def activity_list_to_html(context, activity_stream, extra_vars): + '''Return the given activity stream as a snippet of HTML. + + :param activity_stream: the activity stream to render + :type activity_stream: list of activity dictionaries + :param extra_vars: extra variables to pass to the activity stream items + template when rendering it + :type extra_vars: dictionary + + :rtype: HTML-formatted string + + ''' + activity_list = [] # These are the activity stream messages. + for activity in activity_stream: + detail = None + activity_type = activity['activity_type'] + # Some activity types may have details. + if activity_type in activity_stream_actions_with_detail: + details = logic.get_action('activity_detail_list')(context=context, + data_dict={'id': activity['id']}) + # If an activity has just one activity detail then render the + # detail instead of the activity. + if len(details) == 1: + detail = details[0] + object_type = detail['object_type'] + + if object_type == 'PackageExtra': + object_type = 'package_extra' + + new_activity_type = '%s %s' % (detail['activity_type'], + object_type.lower()) + if new_activity_type in activity_stream_string_functions: + activity_type = new_activity_type + + if not activity_type in activity_stream_string_functions: + raise NotImplementedError("No activity renderer for activity " + "type '%s'" % activity_type) + + if activity_type in activity_stream_string_icons: + activity_icon = activity_stream_string_icons[activity_type] + else: + activity_icon = activity_stream_string_icons['undefined'] + + activity_msg = activity_stream_string_functions[activity_type](context, + activity) + + # Get the data needed to render the message. + matches = re.findall('\{([^}]*)\}', activity_msg) + data = {} + for match in matches: + snippet = activity_snippet_functions[match](activity, detail) + data[str(match)] = snippet + + activity_list.append({'msg': activity_msg, + 'type': activity_type.replace(' ', '-').lower(), + 'icon': activity_icon, + 'data': data, + 'timestamp': activity['timestamp'], + 'is_new': activity.get('is_new', False)}) + extra_vars['activities'] = activity_list + + # TODO: Do this properly without having to check if it's Flask or not + if is_flask_request(): + return base.render('activity_streams/activity_stream_items.html', + extra_vars=extra_vars) + else: + return literal(base.render('activity_streams/activity_stream_items.html', + extra_vars=extra_vars)) diff --git a/venv/lib/python2.7/site-packages/ckan/lib/activity_streams_session_extension.py b/venv/lib/python2.7/site-packages/ckan/lib/activity_streams_session_extension.py new file mode 100644 index 00000000..85cf677f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/activity_streams_session_extension.py @@ -0,0 +1,153 @@ +# encoding: utf-8 + +from ckan.common import config +from sqlalchemy.orm.session import SessionExtension +from paste.deploy.converters import asbool +import logging + +log = logging.getLogger(__name__) + + +def activity_stream_item(obj, activity_type, revision, user_id): + method = getattr(obj, "activity_stream_item", None) + if callable(method): + return method(activity_type, revision, user_id) + else: + # Object did not have a suitable activity_stream_item() method; it must + # not be a package + return None + + +def activity_stream_detail(obj, activity_id, activity_type): + method = getattr(obj, "activity_stream_detail", None) + if callable(method): + return method(activity_id, activity_type) + else: + # Object did not have a suitable activity_stream_detail() method + return None + + +class DatasetActivitySessionExtension(SessionExtension): + """Session extension that emits activity stream activities for packages + and related objects. + + An SQLAlchemy SessionExtension that watches for new, changed or deleted + Packages or objects with related packages (Resources, PackageExtras..) + being committed to the SQLAlchemy session and creates Activity and + ActivityDetail objects for these activities. + + For most types of activity the Activity and ActivityDetail objects are + created in the relevant ckan/logic/action/ functions, but for Packages and + objects with related packages they are created by this class instead. + + """ + def before_commit(self, session): + if not asbool(config.get('ckan.activity_streams_enabled', 'true')): + return + + session.flush() + + try: + object_cache = session._object_cache + revision = session.revision + except AttributeError: + # session had no _object_cache or no revision; skipping this commit + return + + if revision.user: + user_id = revision.user.id + else: + # If the user is not logged in then revision.user is None and + # revision.author is their IP address. Just log them as 'not logged + # in' rather than logging their IP address. + user_id = 'not logged in' + + # The top-level objects that we will append to the activity table. The + # keys here are package IDs, and the values are model.activity:Activity + # objects. + activities = {} + + # The second-level objects that we will append to the activity_detail + # table. Each row in the activity table has zero or more related rows + # in the activity_detail table. The keys here are activity IDs, and the + # values are lists of model.activity:ActivityDetail objects. + activity_details = {} + + # Log new packages first to prevent them from getting incorrectly + # logged as changed packages. + # Looking for new packages... + for obj in object_cache['new']: + activity = activity_stream_item(obj, 'new', revision, user_id) + if activity is None: + continue + # The object returns an activity stream item, so we know that the + # object is a package. + + # Don't create activities for private datasets. + if obj.private: + continue + + activities[obj.id] = activity + + activity_detail = activity_stream_detail(obj, activity.id, "new") + if activity_detail is not None: + activity_details[activity.id] = [activity_detail] + + # Now process other objects. + for activity_type in ('new', 'changed', 'deleted'): + objects = object_cache[activity_type] + for obj in objects: + + if not hasattr(obj, "id"): + # Object has no id; skipping + continue + + if (activity_type in ('new', 'changed') and + obj.id in activities): + # This object was already logged as a new package + continue + + try: + related_packages = obj.related_packages() + except (AttributeError, TypeError): + # Object did not have a suitable related_packages() method; + # skipping it + continue + + for package in related_packages: + if package is None: + continue + + # Don't create activities for private datasets. + if package.private: + continue + + if package.id in activities: + activity = activities[package.id] + else: + activity = activity_stream_item( + package, "changed", revision, user_id) + if activity is None: + continue + + activity_detail = activity_stream_detail( + obj, activity.id, activity_type) + if activity_detail is not None: + if not package.id in activities: + activities[package.id] = activity + if activity_details.has_key(activity.id): + activity_details[activity.id].append( + activity_detail) + else: + activity_details[activity.id] = [activity_detail] + + for key, activity in activities.items(): + # Emitting activity + session.add(activity) + + for key, activity_detail_list in activity_details.items(): + for activity_detail_obj in activity_detail_list: + # Emitting activity detail + session.add(activity_detail_obj) + + session.flush() diff --git a/venv/lib/python2.7/site-packages/ckan/lib/alphabet_paginate.py b/venv/lib/python2.7/site-packages/ckan/lib/alphabet_paginate.py new file mode 100644 index 00000000..b04b7aa4 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/alphabet_paginate.py @@ -0,0 +1,159 @@ +# encoding: utf-8 + +''' +Based on webhelpers.paginator, but: + * each page is for items beginning with a particular letter + * output is suitable for Bootstrap + + Example: + c.page = h.Page( + collection=query, + page=request.params.get('page', 'A'), + ) + Template: + ${c.page.pager()} + ${package_list(c.page.items)} + ${c.page.pager()} +''' +from itertools import dropwhile +import re + +from six import text_type +from sqlalchemy import __version__ as sqav +from sqlalchemy.orm.query import Query +from webhelpers.html.builder import HTML +from ckan.lib.helpers import url_for + + +class AlphaPage(object): + def __init__(self, collection, alpha_attribute, page, other_text, paging_threshold=50, + controller_name='tag'): + ''' + @param collection - sqlalchemy query of all the items to paginate + @param alpha_attribute - name of the attribute (on each item of the + collection) which has the string to paginate by + @param page - the page identifier - the start character or other_text + @param other_text - the (i18n-ized) string for items with + non-alphabetic first character. + @param paging_threshold - the minimum number of items required to + start paginating them. + @param controller_name - The name of the controller that will be linked to, + which defaults to tag. The controller name should be the + same as the route so for some this will be the full + controller name such as 'A.B.controllers.C:ClassName' + ''' + self.collection = collection + self.alpha_attribute = alpha_attribute + self.page = page + self.other_text = other_text + self.paging_threshold = paging_threshold + self.controller_name = controller_name + + self.letters = [char for char in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'] + [self.other_text] + + # Work out which alphabet letters are 'available' i.e. have some results + # because we grey-out those which aren't. + self.available = dict( (c,0,) for c in self.letters ) + for c in self.collection: + if isinstance(c, text_type): + x = c[0] + elif isinstance(c, dict): + x = c[self.alpha_attribute][0] + else: + x = getattr(c, self.alpha_attribute)[0] + x = x.upper() + if x not in self.letters: + x = self.other_text + self.available[x] = self.available.get(x, 0) + 1 + + def pager(self, q=None): + '''Returns pager html - for navigating between the pages. + e.g. Something like this: + + ''' + if self.item_count < self.paging_threshold: + return '' + pages = [] + page = q or self.page + for letter in self.letters: + href = url_for(controller=self.controller_name, action='index', page=letter) + link = HTML.a(href=href, c=letter) + if letter != page: + if self.available.get(letter, 0): + li_class = '' + else: + li_class = 'disabled' + else: + li_class = 'active' + attributes = {'class_': li_class} if li_class else {} + page_element = HTML.li(link, **attributes) + pages.append(page_element) + ul = HTML.tag('ul', *pages) + div = HTML.div(ul, class_='pagination pagination-alphabet') + return div + + + @property + def items(self): + '''Returns items on the current page.''' + if isinstance(self.collection, Query): + query = self.collection + if sqav.startswith("0.4"): + attribute = getattr(query.table.c, + self.alpha_attribute) + elif sqav.startswith("0.5"): + attribute = getattr(query._entity_zero().selectable.c, + self.alpha_attribute) + else: + entity = getattr(query.column_descriptions[0]['expr'], + self.alpha_attribute) + query = query.add_columns(entity) + column = dropwhile(lambda x: x['name'] != \ + self.alpha_attribute, + query.column_descriptions) + attribute = column.next()['expr'] + if self.item_count >= self.paging_threshold: + if self.page != self.other_text: + query = query.filter(attribute.ilike(u'%s%%' % self.page)) + else: + # regexp search + query = query.filter(attribute.op('~')(u'^[^a-zA-Z].*')) + query.order_by(attribute) + return query.all() + elif isinstance(self.collection,list): + if self.item_count >= self.paging_threshold: + if self.page != self.other_text: + if isinstance(self.collection[0], dict): + items = [x for x in self.collection if x[self.alpha_attribute][0:1].lower() == self.page.lower()] + elif isinstance(self.collection[0], text_type): + items = [x for x in self.collection if x[0:1].lower() == self.page.lower()] + else: + items = [x for x in self.collection if getattr(x,self.alpha_attribute)[0:1].lower() == self.page.lower()] + else: + # regexp search + if isinstance(self.collection[0], dict): + items = [x for x in self.collection if re.match('^[^a-zA-Z].*',x[self.alpha_attribute])] + else: + items = [x for x in self.collection if re.match('^[^a-zA-Z].*',x)] + items.sort() + else: + items = self.collection + return items + else: + raise NotImplementedError + + @property + def item_count(self): + if isinstance(self.collection, Query): + return self.collection.count() + elif isinstance(self.collection,list): + return len(self.collection) + else: + raise NotImplementedError diff --git a/venv/lib/python2.7/site-packages/ckan/lib/app_globals.py b/venv/lib/python2.7/site-packages/ckan/lib/app_globals.py new file mode 100644 index 00000000..0b7a5889 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/app_globals.py @@ -0,0 +1,225 @@ +# encoding: utf-8 + +''' The application's Globals object ''' + +import logging +import time +from threading import Lock +import re + +from paste.deploy.converters import asbool +from ckan.common import config + +import ckan +import ckan.model as model +import ckan.logic as logic +from logic.schema import update_configuration_schema + + +log = logging.getLogger(__name__) + + +# mappings translate between config settings and globals because our naming +# conventions are not well defined and/or implemented +mappings = { +# 'config_key': 'globals_key', +} + + +# This mapping is only used to define the configuration options (from the +# `config` object) that should be copied to the `app_globals` (`g`) object. +app_globals_from_config_details = { + 'ckan.site_title': {}, + 'ckan.site_logo': {}, + 'ckan.site_url': {}, + 'ckan.site_description': {}, + 'ckan.site_about': {}, + 'ckan.site_intro_text': {}, + 'ckan.site_custom_css': {}, + 'ckan.favicon': {}, # default gets set in config.environment.py + 'ckan.template_head_end': {}, + 'ckan.template_footer_end': {}, + # has been setup in load_environment(): + 'ckan.site_id': {}, + 'ckan.recaptcha.publickey': {'name': 'recaptcha_publickey'}, + 'ckan.template_title_deliminater': {'default': '-'}, + 'ckan.template_head_end': {}, + 'ckan.template_footer_end': {}, + 'ckan.dumps_url': {}, + 'ckan.dumps_format': {}, + 'ofs.impl': {'name': 'ofs_impl'}, + 'ckan.homepage_style': {'default': '1'}, + + # split string + 'search.facets': {'default': 'organization groups tags res_format license_id', + 'type': 'split', + 'name': 'facets'}, + 'package_hide_extras': {'type': 'split'}, + 'ckan.plugins': {'type': 'split'}, + + # bool + 'debug': {'default': 'false', 'type' : 'bool'}, + 'ckan.debug_supress_header' : {'default': 'false', 'type' : 'bool'}, + 'ckan.legacy_templates' : {'default': 'false', 'type' : 'bool'}, + 'ckan.tracking_enabled' : {'default': 'false', 'type' : 'bool'}, + + # int + 'ckan.datasets_per_page': {'default': '20', 'type': 'int'}, + 'ckan.activity_list_limit': {'default': '30', 'type': 'int'}, + 'ckan.user_list_limit': {'default': '20', 'type': 'int'}, + 'search.facets.default': {'default': '10', 'type': 'int', + 'name': 'facets_default_number'}, +} + + +# A place to store the origional config options of we override them +_CONFIG_CACHE = {} + +def set_main_css(css_file): + ''' Sets the main_css. The css_file must be of the form file.css ''' + assert css_file.endswith('.css') + new_css = css_file + # FIXME we should check the css file exists + app_globals.main_css = str(new_css) + + +def set_app_global(key, value): + ''' + Set a new key on the app_globals (g) object + + It will process the value according to the options on + app_globals_from_config_details (if any) + ''' + key, value = process_app_global(key, value) + setattr(app_globals, key, value) + + +def process_app_global(key, value): + ''' + Tweak a key, value pair meant to be set on the app_globals (g) object + + According to the options on app_globals_from_config_details (if any) + ''' + options = app_globals_from_config_details.get(key) + key = get_globals_key(key) + if options: + if 'name' in options: + key = options['name'] + value = value or options.get('default', '') + + data_type = options.get('type') + if data_type == 'bool': + value = asbool(value) + elif data_type == 'int': + value = int(value) + elif data_type == 'split': + value = value.split() + + return key, value + + +def get_globals_key(key): + # create our globals key + # these can be specified in mappings or else we remove + # the `ckan.` part this is to keep the existing namings + # set the value + if key in mappings: + return mappings[key] + elif key.startswith('ckan.'): + return key[5:] + else: + return key + + +def reset(): + ''' set updatable values from config ''' + def get_config_value(key, default=''): + if model.meta.engine.has_table('system_info'): + value = model.get_system_info(key) + else: + value = None + config_value = config.get(key) + # sort encodeings if needed + if isinstance(config_value, str): + try: + config_value = config_value.decode('utf-8') + except UnicodeDecodeError: + config_value = config_value.decode('latin-1') + # we want to store the config the first time we get here so we can + # reset them if needed + if key not in _CONFIG_CACHE: + _CONFIG_CACHE[key] = config_value + if value is not None: + log.debug('config `%s` set to `%s` from db' % (key, value)) + else: + value = _CONFIG_CACHE[key] + if value: + log.debug('config `%s` set to `%s` from config' % (key, value)) + else: + value = default + + set_app_global(key, value) + + # update the config + config[key] = value + + return value + + # update the config settings in auto update + schema = update_configuration_schema() + for key in schema.keys(): + get_config_value(key) + + # custom styling + main_css = get_config_value('ckan.main_css', '/base/css/main.css') + set_main_css(main_css) + + if app_globals.site_logo: + app_globals.header_class = 'header-image' + elif not app_globals.site_description: + app_globals.header_class = 'header-text-logo' + else: + app_globals.header_class = 'header-text-logo-tagline' + + +class _Globals(object): + + ''' Globals acts as a container for objects available throughout the + life of the application. ''' + + def __init__(self): + '''One instance of Globals is created during application + initialization and is available during requests via the + 'app_globals' variable + ''' + self._init() + self._config_update = None + self._mutex = Lock() + + def _check_uptodate(self): + ''' check the config is uptodate needed when several instances are + running ''' + value = model.get_system_info('ckan.config_update') + if self._config_update != value: + if self._mutex.acquire(False): + reset() + self._config_update = value + self._mutex.release() + + def _init(self): + + self.ckan_version = ckan.__version__ + self.ckan_base_version = re.sub('[^0-9\.]', '', self.ckan_version) + if self.ckan_base_version == self.ckan_version: + self.ckan_doc_version = self.ckan_version[:3] + else: + self.ckan_doc_version = 'latest' + + # process the config details to set globals + for key in app_globals_from_config_details.keys(): + new_key, value = process_app_global(key, config.get(key) or '') + setattr(self, new_key, value) + + +app_globals = _Globals() +del _Globals diff --git a/venv/lib/python2.7/site-packages/ckan/lib/auth_tkt.py b/venv/lib/python2.7/site-packages/ckan/lib/auth_tkt.py new file mode 100644 index 00000000..36faea56 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/auth_tkt.py @@ -0,0 +1,87 @@ +# encoding: utf-8 + +import logging +import math +import os + +from ckan.common import config +from repoze.who.plugins import auth_tkt as repoze_auth_tkt + +_bool = repoze_auth_tkt._bool + +log = logging.getLogger(__name__) + + +class CkanAuthTktCookiePlugin(repoze_auth_tkt.AuthTktCookiePlugin): + + def __init__(self, httponly, *args, **kwargs): + super(CkanAuthTktCookiePlugin, self).__init__(*args, **kwargs) + self.httponly = httponly + + def _get_cookies(self, *args, **kwargs): + ''' + Override method in superclass to ensure HttpOnly is set appropriately. + ''' + super_cookies = super(CkanAuthTktCookiePlugin, self). \ + _get_cookies(*args, **kwargs) + + cookies = [] + for k, v in super_cookies: + replace_with = '; HttpOnly' if self.httponly else '' + v = v.replace('; HttpOnly', '') + replace_with + cookies.append((k, v)) + + return cookies + + +def make_plugin(secret=None, + secretfile=None, + cookie_name='auth_tkt', + secure=False, + include_ip=False, + timeout=None, + reissue_time=None, + userid_checker=None): + from repoze.who.utils import resolveDotted + + # ckan specifics: + # Get secret from beaker setting if necessary + if secret is None or secret == 'somesecret': + secret = config['beaker.session.secret'] + # Session timeout and reissue time for auth cookie + if timeout is None and config.get('who.timeout'): + timeout = config.get('who.timeout') + if reissue_time is None and config.get('who.reissue_time'): + reissue_time = config.get('who.reissue_time') + if timeout is not None and reissue_time is None: + reissue_time = int(math.ceil(int(timeout) * 0.1)) + # Set httponly based on config value. Default is True + httponly = config.get('who.httponly', True) + # Set secure based on config value. Default is False + secure = config.get('who.secure', False) + + # back to repoze boilerplate + if (secret is None and secretfile is None): + raise ValueError("One of 'secret' or 'secretfile' must not be None.") + if (secret is not None and secretfile is not None): + raise ValueError("Specify only one of 'secret' or 'secretfile'.") + if secretfile: + secretfile = os.path.abspath(os.path.expanduser(secretfile)) + if not os.path.exists(secretfile): + raise ValueError("No such 'secretfile': %s" % secretfile) + secret = open(secretfile).read().strip() + if timeout: + timeout = int(timeout) + if reissue_time: + reissue_time = int(reissue_time) + if userid_checker is not None: + userid_checker = resolveDotted(userid_checker) + plugin = CkanAuthTktCookiePlugin(_bool(httponly), + secret, + cookie_name, + _bool(secure), + _bool(include_ip), + timeout, + reissue_time, + userid_checker) + return plugin diff --git a/venv/lib/python2.7/site-packages/ckan/lib/authenticator.py b/venv/lib/python2.7/site-packages/ckan/lib/authenticator.py new file mode 100644 index 00000000..05d469fb --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/authenticator.py @@ -0,0 +1,32 @@ +# encoding: utf-8 + +import logging + +from zope.interface import implements +from repoze.who.interfaces import IAuthenticator + +from ckan.model import User + +log = logging.getLogger(__name__) + + +class UsernamePasswordAuthenticator(object): + implements(IAuthenticator) + + def authenticate(self, environ, identity): + if not ('login' in identity and 'password' in identity): + return None + + login = identity['login'] + user = User.by_name(login) + + if user is None: + log.debug('Login failed - username %r not found', login) + elif not user.is_active(): + log.debug('Login as %r failed - user isn\'t active', login) + elif not user.validate_password(identity['password']): + log.debug('Login as %r failed - password not valid', login) + else: + return user.name + + return None diff --git a/venv/lib/python2.7/site-packages/ckan/lib/base.py b/venv/lib/python2.7/site-packages/ckan/lib/base.py new file mode 100644 index 00000000..62fb6076 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/base.py @@ -0,0 +1,259 @@ +# encoding: utf-8 + +"""The base Controller API + +Provides the BaseController class for subclassing. +""" +import logging +import time +import inspect +import sys + +from pylons import cache +from pylons.controllers import WSGIController +from pylons.controllers.util import abort as _abort +from pylons.decorators import jsonify +from pylons.templating import cached_template, pylons_globals +from webhelpers.html import literal + +from flask import ( + render_template as flask_render_template, + abort as flask_abort +) +import ckan.exceptions +import ckan +import ckan.lib.i18n as i18n +import ckan.lib.render as render_ +import ckan.lib.helpers as h +import ckan.lib.app_globals as app_globals +import ckan.plugins as p +import ckan.model as model + +from ckan.views import (identify_user, + set_cors_headers_for_response, + check_session_cookie, + ) + +# These imports are for legacy usages and will be removed soon these should +# be imported directly from ckan.common for internal ckan code and via the +# plugins.toolkit for extensions. +from ckan.common import (json, _, ungettext, c, request, response, config, + session, is_flask_request) + +log = logging.getLogger(__name__) + +APIKEY_HEADER_NAME_KEY = 'apikey_header_name' +APIKEY_HEADER_NAME_DEFAULT = 'X-CKAN-API-Key' + + +def abort(status_code=None, detail='', headers=None, comment=None): + '''Abort the current request immediately by returning an HTTP exception. + + This is a wrapper for :py:func:`pylons.controllers.util.abort` that adds + some CKAN custom behavior, including allowing + :py:class:`~ckan.plugins.interfaces.IAuthenticator` plugins to alter the + abort response, and showing flash messages in the web interface. + + ''' + if status_code == 403: + # Allow IAuthenticator plugins to alter the abort + for item in p.PluginImplementations(p.IAuthenticator): + result = item.abort(status_code, detail, headers, comment) + (status_code, detail, headers, comment) = result + + if detail and status_code != 503: + h.flash_error(detail) + # #1267 Convert detail to plain text, since WebOb 0.9.7.1 (which comes + # with Lucid) causes an exception when unicode is received. + detail = detail.encode('utf8') + if is_flask_request(): + flask_abort(status_code, detail) + + return _abort(status_code=status_code, + detail=detail, + headers=headers, + comment=comment) + + +def render_snippet(template_name, **kw): + ''' Helper function for rendering snippets. Rendered html has + comment tags added to show the template used. NOTE: unlike other + render functions this takes a list of keywords instead of a dict for + the extra template variables. ''' + + output = render(template_name, extra_vars=kw) + if config.get('debug'): + output = ('\n\n%s\n\n' + % (template_name, output, template_name)) + return literal(output) + + +def render_jinja2(template_name, extra_vars): + env = config['pylons.app_globals'].jinja_env + template = env.get_template(template_name) + return template.render(**extra_vars) + + +def render(template_name, extra_vars=None, *pargs, **kwargs): + '''Render a template and return the output. + + This is CKAN's main template rendering function. + + :params template_name: relative path to template inside registered tpl_dir + :type template_name: str + :params extra_vars: additional variables available in template + :type extra_vars: dict + :params pargs: DEPRECATED + :type pargs: tuple + :params kwargs: DEPRECATED + :type kwargs: dict + + ''' + if pargs or kwargs: + tb = inspect.getframeinfo(sys._getframe(1)) + log.warning( + 'Extra arguments to `base.render` are deprecated: ' + + '<{0.filename}:{0.lineno}>'.format(tb) + ) + + if extra_vars is None: + extra_vars = {} + + if not is_flask_request(): + renderer = _pylons_prepare_renderer(template_name, extra_vars, + *pargs, **kwargs) + return cached_template(template_name, renderer) + + return flask_render_template(template_name, **extra_vars) + + +def _pylons_prepare_renderer(template_name, extra_vars, cache_key=None, + cache_type=None, cache_expire=None, + cache_force=None, renderer=None): + def render_template(): + globs = extra_vars or {} + globs.update(pylons_globals()) + + # Using pylons.url() directly destroys the localisation stuff so + # we remove it so any bad templates crash and burn + del globs['url'] + + try: + template_path, template_type = render_.template_info(template_name) + except render_.TemplateNotFound: + raise + + log.debug('rendering %s [%s]' % (template_path, template_type)) + if config.get('debug'): + context_vars = globs.get('c') + if context_vars: + context_vars = dir(context_vars) + debug_info = {'template_name': template_name, + 'template_path': template_path, + 'template_type': template_type, + 'vars': globs, + 'c_vars': context_vars, + 'renderer': renderer} + if 'CKAN_DEBUG_INFO' not in request.environ: + request.environ['CKAN_DEBUG_INFO'] = [] + request.environ['CKAN_DEBUG_INFO'].append(debug_info) + + del globs['config'] + return render_jinja2(template_name, globs) + + def set_pylons_response_headers(allow_cache): + if 'Pragma' in response.headers: + del response.headers["Pragma"] + if allow_cache: + response.headers["Cache-Control"] = "public" + try: + cache_expire = int(config.get('ckan.cache_expires', 0)) + response.headers["Cache-Control"] += \ + ", max-age=%s, must-revalidate" % cache_expire + except ValueError: + pass + else: + # We do not want caching. + response.headers["Cache-Control"] = "private" + + # Caching Logic + + allow_cache = True + # Force cache or not if explicit. + if cache_force is not None: + allow_cache = cache_force + # Do not allow caching of pages for logged in users/flash messages etc. + elif session.last_accessed: + allow_cache = False + # Tests etc. + elif 'REMOTE_USER' in request.environ: + allow_cache = False + # Don't cache if based on a non-cachable template used in this. + elif request.environ.get('__no_cache__'): + allow_cache = False + # Don't cache if we have set the __no_cache__ param in the query string. + elif request.params.get('__no_cache__'): + allow_cache = False + # Don't cache if we have extra vars containing data. + elif extra_vars: + for k, v in extra_vars.iteritems(): + allow_cache = False + break + + # TODO: replicate this logic in Flask once we start looking at the + # rendering for the frontend controllers + set_pylons_response_headers(allow_cache) + + if not allow_cache: + # Prevent any further rendering from being cached. + request.environ['__no_cache__'] = True + + return render_template + + +class ValidationException(Exception): + pass + + +class BaseController(WSGIController): + '''Base class for CKAN controller classes to inherit from. + + ''' + repo = model.repo + log = logging.getLogger(__name__) + + def __before__(self, action, **params): + c.__timer = time.time() + app_globals.app_globals._check_uptodate() + + identify_user() + + i18n.handle_request(request, c) + + def __call__(self, environ, start_response): + """Invoke the Controller""" + # WSGIController.__call__ dispatches to the Controller method + # the request is routed to. This routing information is + # available in environ['pylons.routes_dict'] + + try: + res = WSGIController.__call__(self, environ, start_response) + finally: + model.Session.remove() + + check_session_cookie(response) + + return res + + def __after__(self, action, **params): + + set_cors_headers_for_response(response) + + r_time = time.time() - c.__timer + url = request.environ['CKAN_CURRENT_URL'].split('?')[0] + log.info(' %s render time %.3f seconds' % (url, r_time)) + + +# Include the '_' function in the public names +__all__ = [__name for __name in locals().keys() if not __name.startswith('_') + or __name == '_'] diff --git a/venv/lib/python2.7/site-packages/ckan/lib/captcha.py b/venv/lib/python2.7/site-packages/ckan/lib/captcha.py new file mode 100644 index 00000000..9a138dff --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/captcha.py @@ -0,0 +1,41 @@ +# encoding: utf-8 + +from ckan.common import config + +import urllib +import urllib2 +import json + +def check_recaptcha(request): + '''Check a user\'s recaptcha submission is valid, and raise CaptchaError + on failure.''' + recaptcha_private_key = config.get('ckan.recaptcha.privatekey', '') + if not recaptcha_private_key: + # Recaptcha not enabled + return + + client_ip_address = request.environ.get('REMOTE_ADDR', 'Unknown IP Address') + + # reCAPTCHA v2 + recaptcha_response_field = request.form.get('g-recaptcha-response', '') + recaptcha_server_name = 'https://www.google.com/recaptcha/api/siteverify' + + # recaptcha_response_field will be unicode if there are foreign chars in + # the user input. So we need to encode it as utf8 before urlencoding or + # we get an exception (#1431). + params = urllib.urlencode(dict(secret=recaptcha_private_key, + remoteip=client_ip_address, + response=recaptcha_response_field.encode('utf8'))) + f = urllib2.urlopen(recaptcha_server_name, params) + data = json.load(f) + f.close() + + try: + if not data['success']: + raise CaptchaError() + except IndexError: + # Something weird with recaptcha response + raise CaptchaError() + +class CaptchaError(ValueError): + pass diff --git a/venv/lib/python2.7/site-packages/ckan/lib/cli.py b/venv/lib/python2.7/site-packages/ckan/lib/cli.py new file mode 100644 index 00000000..7135a156 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/cli.py @@ -0,0 +1,2649 @@ +# encoding: utf-8 + +from __future__ import print_function + +import collections +import csv +import multiprocessing as mp +import os +import datetime +import sys +from pprint import pprint +import re +import itertools +import json +import logging +from optparse import OptionConflictError +import traceback + +from six import text_type +from six.moves import input, xrange +from six.moves.urllib.error import HTTPError +from six.moves.urllib.parse import urljoin, urlparse +from six.moves.urllib.request import urlopen + +import sqlalchemy as sa +import routes +import paste.script +from paste.registry import Registry +from paste.script.util.logging_config import fileConfig +import click + +from ckan.config.middleware import make_app +import ckan.logic as logic +import ckan.model as model +import ckan.include.rjsmin as rjsmin +import ckan.include.rcssmin as rcssmin +import ckan.plugins as p +from ckan.common import config + +# This is a test Flask request context to be used internally. +# Do not use it! +_cli_test_request_context = None + + +# NB No CKAN imports are allowed until after the config file is loaded. +# i.e. do the imports in methods, after _load_config is called. +# Otherwise loggers get disabled. + + +def deprecation_warning(message=None): + ''' + Print a deprecation warning to STDERR. + + If ``message`` is given it is also printed to STDERR. + ''' + sys.stderr.write(u'WARNING: This function is deprecated.') + if message: + sys.stderr.write(u' ' + message.strip()) + sys.stderr.write(u'\n') + + +def error(msg): + ''' + Print an error message to STDOUT and exit with return code 1. + ''' + sys.stderr.write(msg) + if not msg.endswith('\n'): + sys.stderr.write('\n') + sys.exit(1) + + +def parse_db_config(config_key='sqlalchemy.url'): + ''' Takes a config key for a database connection url and parses it into + a dictionary. Expects a url like: + + 'postgres://tester:pass@localhost/ckantest3' + ''' + from ckan.common import config + url = config[config_key] + regex = [ + '^\s*(?P\w*)', + '://', + '(?P[^:]*)', + ':?', + '(?P[^@]*)', + '@', + '(?P[^/:]*)', + ':?', + '(?P[^/]*)', + '/', + '(?P[\w.-]*)' + ] + db_details_match = re.match(''.join(regex), url) + if not db_details_match: + raise Exception('Could not extract db details from url: %r' % url) + db_details = db_details_match.groupdict() + return db_details + + +def user_add(args): + '''Add new user if we use paster sysadmin add + or paster user add + ''' + if len(args) < 1: + error('Error: you need to specify the user name.') + username = args[0] + + # parse args into data_dict + data_dict = {'name': username} + for arg in args[1:]: + try: + field, value = arg.split('=', 1) + data_dict[field] = value + except ValueError: + raise ValueError( + 'Could not parse arg: %r (expected "
.+)\]') + + +class ConfigToolError(Exception): + pass diff --git a/venv/lib/python2.7/site-packages/ckan/lib/create_test_data.py b/venv/lib/python2.7/site-packages/ckan/lib/create_test_data.py new file mode 100644 index 00000000..f6929c6e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/create_test_data.py @@ -0,0 +1,933 @@ +# encoding: utf-8 + +import logging +from collections import defaultdict +import datetime + +from six import string_types, text_type + +import ckan.model as model + +log = logging.getLogger(__name__) + +class CreateTestData(object): + # keep track of the objects created by this class so that + # tests can easy call delete() method to delete them all again. + pkg_names = [] + tag_names = [] + group_names = set() + user_refs = [] + + author = u'tester' + + pkg_core_fields = ['name', 'title', 'version', 'url', 'notes', + 'author', 'author_email', + 'maintainer', 'maintainer_email', + 'private', + ] + @classmethod + def create_basic_test_data(cls): + cls.create() + + @classmethod + def create_search_test_data(cls): + cls.create_arbitrary(search_items) + + @classmethod + def create_gov_test_data(cls, extra_users=[]): + cls.create_arbitrary(gov_items, extra_user_names=extra_users) + + @classmethod + def create_family_test_data(cls, extra_users=[]): + cls.create_arbitrary(family_items, + relationships=family_relationships, + extra_user_names=extra_users) + + @classmethod + def create_group_hierarchy_test_data(cls, extra_users=[]): + cls.create_users(group_hierarchy_users) + cls.create_groups(group_hierarchy_groups) + cls.create_arbitrary(group_hierarchy_datasets) + + @classmethod + def create_test_user(cls): + tester = model.User.by_name(u'tester') + if tester is None: + tester = model.User(name=u'tester', apikey=u'tester', + password=u'tester') + model.Session.add(tester) + model.Session.commit() + model.Session.remove() + cls.user_refs.append(u'tester') + + @classmethod + + def create_translations_test_data(cls): + import ckan.model + CreateTestData.create() + rev = ckan.model.repo.new_revision() + rev.author = CreateTestData.author + rev.message = u'Creating test translations.' + + sysadmin_user = ckan.model.User.get('testsysadmin') + package = ckan.model.Package.get('annakarenina') + + # Add some new tags to the package. + # These tags are codes that are meant to be always translated before + # display, if not into the user's current language then into the + # fallback language. + package.add_tags([ckan.model.Tag('123'), ckan.model.Tag('456'), + ckan.model.Tag('789')]) + + # Add the above translations to CKAN. + for (lang_code, translations) in (('de', german_translations), + ('fr', french_translations), ('en', english_translations)): + for term in terms: + if term in translations: + data_dict = { + 'term': term, + 'term_translation': translations[term], + 'lang_code': lang_code, + } + context = { + 'model': ckan.model, + 'session': ckan.model.Session, + 'user': sysadmin_user.name, + } + ckan.logic.action.update.term_translation_update(context, + data_dict) + + ckan.model.Session.commit() + + def create_vocabs_test_data(cls): + import ckan.model + CreateTestData.create() + sysadmin_user = ckan.model.User.get('testsysadmin') + annakarenina = ckan.model.Package.get('annakarenina') + warandpeace = ckan.model.Package.get('warandpeace') + + # Create a couple of vocabularies. + context = { + 'model': ckan.model, + 'session': ckan.model.Session, + 'user': sysadmin_user.name + } + data_dict = { + 'name': 'Genre', + 'tags': [{'name': 'Drama'}, {'name': 'Sci-Fi'}, + {'name': 'Mystery'}], + } + ckan.logic.action.create.vocabulary_create(context, data_dict) + + data_dict = { + 'name': 'Actors', + 'tags': [{'name': 'keira-knightley'}, {'name': 'jude-law'}, + {'name': 'alessio-boni'}], + } + ckan.logic.action.create.vocabulary_create(context, data_dict) + + # Add some vocab tags to some packages. + genre_vocab = ckan.model.Vocabulary.get('Genre') + actors_vocab = ckan.model.Vocabulary.get('Actors') + annakarenina.add_tag_by_name('Drama', vocab=genre_vocab) + annakarenina.add_tag_by_name('keira-knightley', vocab=actors_vocab) + annakarenina.add_tag_by_name('jude-law', vocab=actors_vocab) + warandpeace.add_tag_by_name('Drama', vocab=genre_vocab) + warandpeace.add_tag_by_name('alessio-boni', vocab=actors_vocab) + + @classmethod + def create_arbitrary(cls, package_dicts, relationships=[], + extra_user_names=[], extra_group_names=[]): + '''Creates packages and a few extra objects as well at the + same time if required. + @param package_dicts - a list of dictionaries with the package + properties. + Extra keys allowed: + @param extra_group_names - a list of group names to create. No + properties get set though. + ''' + assert isinstance(relationships, (list, tuple)) + assert isinstance(extra_user_names, (list, tuple)) + assert isinstance(extra_group_names, (list, tuple)) + model.Session.remove() + new_user_names = extra_user_names + new_group_names = set() + new_groups = {} + + if package_dicts: + if isinstance(package_dicts, dict): + package_dicts = [package_dicts] + for item in package_dicts: + rev = model.repo.new_revision() + rev.author = cls.author + rev.message = u'Creating test packages.' + pkg_dict = {} + for field in cls.pkg_core_fields: + if item.has_key(field): + pkg_dict[field] = text_type(item[field]) + if model.Package.by_name(pkg_dict['name']): + log.warning('Cannot create package "%s" as it already exists.' % \ + (pkg_dict['name'])) + continue + pkg = model.Package(**pkg_dict) + model.Session.add(pkg) + for attr, val in item.items(): + if isinstance(val, str): + val = text_type(val) + if attr=='name': + continue + if attr in cls.pkg_core_fields: + pass + elif attr == 'download_url': + pkg.add_resource(text_type(val)) + elif attr == 'resources': + assert isinstance(val, (list, tuple)) + for res_dict in val: + non_extras = {} + for k, v in res_dict.items(): + if k != 'extras': + if not isinstance(v, datetime.datetime): + v = text_type(v) + non_extras[str(k)] = v + extras = {str(k): text_type(v) for k, v in res_dict.get('extras', {}).items()} + pkg.add_resource(extras=extras, **non_extras) + elif attr == 'tags': + if isinstance(val, string_types): + tags = val.split() + elif isinstance(val, list): + tags = val + else: + raise NotImplementedError + for tag_name in tags: + tag_name = text_type(tag_name) + tag = model.Tag.by_name(tag_name) + if not tag: + tag = model.Tag(name=tag_name) + cls.tag_names.append(tag_name) + model.Session.add(tag) + pkg.add_tag(tag) + model.Session.flush() + elif attr == 'groups': + model.Session.flush() + if isinstance(val, string_types): + group_names = val.split() + elif isinstance(val, list): + group_names = val + else: + raise NotImplementedError + for group_name in group_names: + group = model.Group.by_name(text_type(group_name)) + if not group: + if not group_name in new_groups: + group = model.Group(name= + text_type(group_name)) + model.Session.add(group) + new_group_names.add(group_name) + new_groups[group_name] = group + else: + # If adding multiple packages with the same + # group name, model.Group.by_name will not + # find the group as the session has not yet + # been committed at this point. Fetch from + # the new_groups dict instead. + group = new_groups[group_name] + capacity = 'organization' if group.is_organization\ + else 'public' + member = model.Member(group=group, table_id=pkg.id, + table_name='package', + capacity=capacity) + model.Session.add(member) + if group.is_organization: + pkg.owner_org = group.id + elif attr == 'license': + pkg.license_id = val + elif attr == 'license_id': + pkg.license_id = val + elif attr == 'extras': + pkg.extras = val + elif attr == 'admins': + assert 0, 'Deprecated param "admins"' + else: + raise NotImplementedError(attr) + cls.pkg_names.append(item['name']) + model.repo.commit_and_remove() + + needs_commit = False + + rev = model.repo.new_revision() + for group_name in extra_group_names: + group = model.Group(name=text_type(group_name)) + model.Session.add(group) + new_group_names.add(group_name) + needs_commit = True + + if needs_commit: + model.repo.commit_and_remove() + needs_commit = False + + # create users that have been identified as being needed + for user_name in new_user_names: + if not model.User.by_name(text_type(user_name)): + user = model.User(name=text_type(user_name)) + model.Session.add(user) + cls.user_refs.append(user_name) + needs_commit = True + + if needs_commit: + model.repo.commit_and_remove() + needs_commit = False + + # setup authz for groups just created + for group_name in new_group_names: + group = model.Group.by_name(text_type(group_name)) + cls.group_names.add(group_name) + needs_commit = True + + if needs_commit: + model.repo.commit_and_remove() + needs_commit = False + + if relationships: + rev = model.repo.new_revision() + rev.author = cls.author + rev.message = u'Creating package relationships.' + + def pkg(pkg_name): + return model.Package.by_name(text_type(pkg_name)) + for subject_name, relationship, object_name in relationships: + pkg(subject_name).add_relationship( + text_type(relationship), pkg(object_name)) + needs_commit = True + + model.repo.commit_and_remove() + + + @classmethod + def create_groups(cls, group_dicts, admin_user_name=None, auth_profile=""): + '''A more featured interface for creating groups. + All group fields can be filled, packages added, can have + an admin user and be a member of other groups.''' + rev = model.repo.new_revision() + rev.author = cls.author + if admin_user_name: + admin_users = [model.User.by_name(admin_user_name)] + else: + admin_users = [] + assert isinstance(group_dicts, (list, tuple)) + group_attributes = set(('name', 'title', 'description', 'parent_id', + 'type', 'is_organization')) + for group_dict in group_dicts: + if model.Group.by_name(text_type(group_dict['name'])): + log.warning('Cannot create group "%s" as it already exists.' % + group_dict['name']) + continue + pkg_names = group_dict.pop('packages', []) + group = model.Group(name=text_type(group_dict['name'])) + group.type = auth_profile or 'group' + for key in group_dict: + if key in group_attributes: + setattr(group, key, group_dict[key]) + elif key not in ('admins', 'editors', 'parent'): + group.extras[key] = group_dict[key] + assert isinstance(pkg_names, (list, tuple)) + for pkg_name in pkg_names: + pkg = model.Package.by_name(text_type(pkg_name)) + assert pkg, pkg_name + member = model.Member(group=group, table_id=pkg.id, + table_name='package') + model.Session.add(member) + model.Session.add(group) + admins = [model.User.by_name(user_name) + for user_name in group_dict.get('admins', [])] + \ + admin_users + for admin in admins: + member = model.Member(group=group, table_id=admin.id, + table_name='user', capacity='admin') + model.Session.add(member) + editors = [model.User.by_name(user_name) + for user_name in group_dict.get('editors', [])] + for editor in editors: + member = model.Member(group=group, table_id=editor.id, + table_name='user', capacity='editor') + model.Session.add(member) + # Need to commit the current Group for two reasons: + # 1. It might have a parent, and the Member will need the Group.id + # value allocated on commit. + # 2. The next Group created may have this Group as a parent so + # creation of the Member needs to refer to this one. + model.Session.commit() + rev = model.repo.new_revision() + rev.author = cls.author + # add it to a parent's group + if 'parent' in group_dict: + parent = model.Group.by_name(text_type(group_dict['parent'])) + assert parent, group_dict['parent'] + member = model.Member(group=group, table_id=parent.id, + table_name='group', capacity='parent') + model.Session.add(member) + cls.group_names.add(group_dict['name']) + model.repo.commit_and_remove() + + @classmethod + def create(cls, auth_profile="", package_type=None): + model.Session.remove() + rev = model.repo.new_revision() + # same name as user we create below + rev.author = cls.author + rev.message = u'''Creating test data. + * Package: annakarenina + * Package: warandpeace + * Associated tags, etc etc +''' + if auth_profile == "publisher": + organization_group = model.Group(name=u"organization_group", + type="organization") + + cls.pkg_names = [u'annakarenina', u'warandpeace'] + pkg1 = model.Package(name=cls.pkg_names[0], type=package_type) + if auth_profile == "publisher": + pkg1.group = organization_group + model.Session.add(pkg1) + pkg1.title = u'A Novel By Tolstoy' + pkg1.version = u'0.7a' + pkg1.url = u'http://datahub.io' + # put an & in the url string to test escaping + if 'alt_url' in model.Resource.get_extra_columns(): + configured_extras = ({'alt_url': u'alt123'}, + {'alt_url': u'alt345'}) + else: + configured_extras = ({}, {}) + pr1 = model.Resource( + url=u'http://datahub.io/download/x=1&y=2', + format=u'plain text', + description=u'Full text. Needs escaping: " Umlaut: \xfc', + hash=u'abc123', + extras={'size_extra': u'123'}, + **configured_extras[0] + ) + pr2 = model.Resource( + url=u'http://datahub.io/index.json', + format=u'JSON', + description=u'Index of the novel', + hash=u'def456', + extras={'size_extra': u'345'}, + **configured_extras[1] + ) + model.Session.add(pr1) + model.Session.add(pr2) + pkg1.resources_all.append(pr1) + pkg1.resources_all.append(pr2) + pkg1.notes = u'''Some test notes + +### A 3rd level heading + +**Some bolded text.** + +*Some italicized text.* + +Foreign characters: +u with umlaut \xfc +66-style quote \u201c +foreign word: th\xfcmb + +Needs escaping: +left arrow < + + + +''' + pkg2 = model.Package(name=cls.pkg_names[1], type=package_type) + tag1 = model.Tag(name=u'russian') + tag2 = model.Tag(name=u'tolstoy') + + if auth_profile == "publisher": + pkg2.group = organization_group + + # Flexible tag, allows spaces, upper-case, + # and all punctuation except commas + tag3 = model.Tag(name=u'Flexible \u30a1') + + for obj in [pkg2, tag1, tag2, tag3]: + model.Session.add(obj) + pkg1.add_tags([tag1, tag2, tag3]) + pkg2.add_tags([ tag1, tag3 ]) + cls.tag_names = [ t.name for t in (tag1, tag2, tag3) ] + pkg1.license_id = u'other-open' + pkg2.license_id = u'cc-nc' # closed license + pkg2.title = u'A Wonderful Story' + pkg1.extras = {u'genre':'romantic novel', + u'original media':'book'} + # group + david = model.Group(name=u'david', + title=u'Dave\'s books', + description=u'These are books that David likes.', + type=auth_profile or 'group') + roger = model.Group(name=u'roger', + title=u'Roger\'s books', + description=u'Roger likes these books.', + type=auth_profile or 'group') + + for obj in [david, roger]: + model.Session.add(obj) + + cls.group_names.add(u'david') + cls.group_names.add(u'roger') + + model.Session.flush() + + model.Session.add(model.Member(table_id=pkg1.id, table_name='package', group=david)) + model.Session.add(model.Member(table_id=pkg2.id, table_name='package', group=david)) + model.Session.add(model.Member(table_id=pkg1.id, table_name='package', group=roger)) + # authz + sysadmin = model.User(name=u'testsysadmin', password=u'testsysadmin') + sysadmin.sysadmin = True + model.Session.add_all([ + model.User(name=u'tester', apikey=u'tester', password=u'tester'), + model.User(name=u'joeadmin', password=u'joeadmin'), + model.User(name=u'annafan', about=u'I love reading Annakarenina. My site: http://datahub.io', password=u'annafan'), + model.User(name=u'russianfan', password=u'russianfan'), + sysadmin, + ]) + cls.user_refs.extend([u'tester', u'joeadmin', u'annafan', u'russianfan', u'testsysadmin']) + model.repo.commit_and_remove() + + # method used in DGU and all good tests elsewhere + @classmethod + def create_users(cls, user_dicts): + needs_commit = False + for user_dict in user_dicts: + user = cls._create_user_without_commit(**user_dict) + if user: + needs_commit = True + if needs_commit: + model.repo.commit_and_remove() + + @classmethod + def _create_user_without_commit(cls, name='', **user_dict): + if model.User.by_name(name): + log.warning('Cannot create user "%s" as it already exists.' % + name or user_dict['name']) + return + # User objects are not revisioned so no need to create a revision + user_ref = name + assert user_ref + for k, v in user_dict.items(): + if v: + # avoid unicode warnings + user_dict[k] = text_type(v) + user = model.User(name=text_type(name), **user_dict) + model.Session.add(user) + cls.user_refs.append(user_ref) + return user + + @classmethod + def create_user(cls, name='', **kwargs): + user = cls._create_user_without_commit(name, **kwargs) + model.Session.commit() + return user + + @classmethod + def flag_for_deletion(cls, pkg_names=[], tag_names=[], group_names=[], + user_names=[]): + '''If you create a domain object manually in your test then you + can name it here (flag it up) and it will be deleted when you next + call CreateTestData.delete().''' + if isinstance(pkg_names, string_types): + pkg_names = [pkg_names] + cls.pkg_names.extend(pkg_names) + cls.tag_names.extend(tag_names) + cls.group_names = cls.group_names.union(set(group_names)) + cls.user_refs.extend(user_names) + + @classmethod + def delete(cls): + '''Purges packages etc. that were created by this class.''' + for pkg_name in cls.pkg_names: + model.Session().autoflush = False + pkg = model.Package.by_name(text_type(pkg_name)) + if pkg: + pkg.purge() + for tag_name in cls.tag_names: + tag = model.Tag.by_name(text_type(tag_name)) + if tag: + tag.purge() + for group_name in cls.group_names: + group = model.Group.by_name(text_type(group_name)) + if group: + model.Session.delete(group) + revs = model.Session.query(model.Revision).filter_by(author=cls.author) + for rev in revs: + for pkg in rev.packages: + pkg.purge() + for grp in rev.groups: + grp.purge() + model.Session.commit() + model.Session.delete(rev) + for user_name in cls.user_refs: + user = model.User.get(text_type(user_name)) + if user: + user.purge() + model.Session.commit() + model.Session.remove() + cls.reset() + + @classmethod + def reset(cls): + cls.pkg_names = [] + cls.group_names = set() + cls.tag_names = [] + cls.user_refs = [] + + @classmethod + def get_all_data(cls): + return cls.pkg_names + list(cls.group_names) + cls.tag_names + cls.user_refs + + @classmethod + def make_some_vocab_tags(cls): + model.repo.new_revision() + + # Create a couple of vocabularies. + genre_vocab = model.Vocabulary(u'genre') + model.Session.add(genre_vocab) + composers_vocab = model.Vocabulary(u'composers') + model.Session.add(composers_vocab) + + # Create some additional free tags for tag search tests. + tolkien_tag = model.Tag(name="tolkien") + model.Session.add(tolkien_tag) + toledo_tag = model.Tag(name="toledo") + model.Session.add(toledo_tag) + tolerance_tag = model.Tag(name="tolerance") + model.Session.add(tolerance_tag) + tollbooth_tag = model.Tag(name="tollbooth") + model.Session.add(tollbooth_tag) + # We have to add free tags to a package or they won't show up in tag results. + model.Package.get('warandpeace').add_tags((tolkien_tag, toledo_tag, + tolerance_tag, tollbooth_tag)) + + # Create some tags that belong to vocabularies. + sonata_tag = model.Tag(name=u'sonata', vocabulary_id=genre_vocab.id) + model.Session.add(sonata_tag) + + bach_tag = model.Tag(name=u'Bach', vocabulary_id=composers_vocab.id) + model.Session.add(bach_tag) + + neoclassical_tag = model.Tag(name='neoclassical', + vocabulary_id=genre_vocab.id) + model.Session.add(neoclassical_tag) + + neofolk_tag = model.Tag(name='neofolk', vocabulary_id=genre_vocab.id) + model.Session.add(neofolk_tag) + + neomedieval_tag = model.Tag(name='neomedieval', + vocabulary_id=genre_vocab.id) + model.Session.add(neomedieval_tag) + + neoprog_tag = model.Tag(name='neoprog', + vocabulary_id=genre_vocab.id) + model.Session.add(neoprog_tag) + + neopsychedelia_tag = model.Tag(name='neopsychedelia', + vocabulary_id=genre_vocab.id) + model.Session.add(neopsychedelia_tag) + + neosoul_tag = model.Tag(name='neosoul', vocabulary_id=genre_vocab.id) + model.Session.add(neosoul_tag) + + nerdcore_tag = model.Tag(name='nerdcore', vocabulary_id=genre_vocab.id) + model.Session.add(nerdcore_tag) + + model.Package.get('warandpeace').add_tag(bach_tag) + model.Package.get('annakarenina').add_tag(sonata_tag) + + model.Session.commit() + + + +search_items = [{'name':'gils', + 'title':'Government Information Locator Service', + 'url':'', + 'tags':'registry,country-usa,government,federal,gov,workshop-20081101,penguin'.split(','), + 'resources':[{'url':'http://www.dcsf.gov.uk/rsgateway/DB/SFR/s000859/SFR17_2009_tables.xls', + 'format':'XLS', + 'last_modified': datetime.datetime(2005, 10, 1), + 'description':'December 2009 | http://www.statistics.gov.uk/hub/id/119-36345'}, + {'url':'http://www.dcsf.gov.uk/rsgateway/DB/SFR/s000860/SFR17_2009_key.doc', + 'format':'DOC', + 'description':'http://www.statistics.gov.uk/hub/id/119-34565'}], + 'groups':'ukgov test1 test2 penguin', + 'license':'odc-by', + 'notes':u'''From + +> The Government Information Locator Service (GILS) is an effort to identify, locate, and describe publicly available Federal +> Because this collection is decentralized, the GPO + +Foreign word: +u with umlaut th\xfcmb +''', + 'extras':{'date_released':'2008'}, + }, + {'name':'us-gov-images', + 'title':'U.S. Government Photos and Graphics', + 'url':'http://www.usa.gov/Topics/Graphics.shtml', + 'download_url':'http://www.usa.gov/Topics/Graphics.shtml', + 'tags':'images,graphics,photographs,photos,pictures,us,usa,america,history,wildlife,nature,war,military,todo split,gov,penguin'.split(','), + 'groups':'ukgov test1 penguin', + 'license':'other-open', + 'notes':'''## About + +Collection of links to different US image collections in the public domain. + +## Openness + +> Most of these images and graphics are available for use in the public domain, and''', + 'extras':{'date_released':'2009'}, + }, + {'name':'usa-courts-gov', + 'title':'Text of US Federal Cases', + 'url':'http://bulk.resource.org/courts.gov/', + 'download_url':'http://bulk.resource.org/courts.gov/', + 'tags':'us,courts,case-law,us,courts,case-law,gov,legal,law,access-bulk,penguins,penguin'.split(','), + 'groups':'ukgov test2 penguin', + 'license':'cc-zero', + 'notes':'''### Description + +1.8 million pages of U.S. case law available with no restrictions. From the [README](http://bulk.resource.org/courts.gov/0_README.html): + +> This file is http://bulk.resource.org/courts.gov/0_README.html and was last revised. + +penguin +''', + 'extras':{'date_released':'2007-06'}, + }, + {'name':'uk-government-expenditure', + 'title':'UK Government Expenditure', + 'tags':'workshop-20081101,uk,gov,expenditure,finance,public,funding,penguin'.split(','), + 'groups':'ukgov penguin', + 'notes':'''Discussed at [Workshop on Public Information, 2008-11-02](http://okfn.org/wiki/PublicInformation). + +Overview is available in Red Book, or Financial Statement and Budget Report (FSBR), [published by the Treasury](http://www.hm-treasury.gov.uk/budget.htm).''', + 'extras':{'date_released':'2007-10'}, + }, + {'name':'se-publications', + 'title':'Sweden - Government Offices of Sweden - Publications', + 'url':'http://www.sweden.gov.se/sb/d/574', + 'groups':'penguin', + 'tags':u'country-sweden,format-pdf,access-www,documents,publications,government,eutransparency,penguin,CAPITALS,surprise.,greek omega \u03a9,japanese katakana \u30a1'.split(','), + 'license':'', + 'notes':'''### About + +Official documents including "government bills and reports, information material and other publications". + +### Reuse + +Not clear.''', + 'extras':{'date_released':'2009-10-27'}, + }, + {'name':'se-opengov', + 'title':'Opengov.se', + 'groups':'penguin', + 'url':'http://www.opengov.se/', + 'download_url':'http://www.opengov.se/data/open/', + 'tags':'country-sweden,government,data,penguin'.split(','), + 'license':'cc-by-sa', + 'notes':'''### About + +From [website](http://www.opengov.se/sidor/english/): + +> Opengov.se is an initiative to highlight available public datasets in Sweden. It contains a commentable catalog of government datasets, their formats and usage restrictions. + +> The goal is to highlight the benefits of open access to government data and explain how this is done in practice. + +### Openness + +It appears that the website is under a CC-BY-SA license. Legal status of the data varies. Data that is fully open can be viewed at: + + * ''' + }, + ] + +family_items = [{'name':u'abraham', 'title':u'Abraham'}, + {'name':u'homer', 'title':u'Homer'}, + {'name':u'homer_derived', 'title':u'Homer Derived'}, + {'name':u'beer', 'title':u'Beer'}, + {'name':u'bart', 'title':u'Bart'}, + {'name':u'lisa', 'title':u'Lisa'}, + {'name':u'marge', 'title':u'Marge'}, + ] +family_relationships = [('abraham', 'parent_of', 'homer'), + ('homer', 'parent_of', 'bart'), + ('homer', 'parent_of', 'lisa'), + ('marge', 'parent_of', 'lisa'), + ('marge', 'parent_of', 'bart'), + ('homer_derived', 'derives_from', 'homer'), + ('homer', 'depends_on', 'beer'), + ] + +gov_items = [ + {'name':'private-fostering-england-2009', + 'title':'Private Fostering', + 'notes':'Figures on children cared for and accommodated in private fostering arrangements, England, Year ending 31 March 2009', + 'resources':[{'url':'http://www.dcsf.gov.uk/rsgateway/DB/SFR/s000859/SFR17_2009_tables.xls', + 'format':'XLS', + 'description':'December 2009 | http://www.statistics.gov.uk/hub/id/119-36345'}, + {'url':'http://www.dcsf.gov.uk/rsgateway/DB/SFR/s000860/SFR17_2009_key.doc', + 'format':'DOC', + 'description':'http://www.statistics.gov.uk/hub/id/119-34565'}], + 'url':'http://www.dcsf.gov.uk/rsgateway/DB/SFR/s000859/index.shtml', + 'author':'DCSF Data Services Group', + 'author_email':'statistics@dcsf.gsi.gov.uk', + 'license':'ukcrown', + 'tags':'children fostering', + 'extras':{ + 'external_reference':'DCSF-DCSF-0024', + 'date_released':'2009-07-30', + 'date_updated':'2009-07-30', + 'update_frequency':'annually', + 'geographic_granularity':'regional', + 'geographic_coverage':'100000: England', + 'department':'Department for Education', + 'published_by':'Department for Education [3]', + 'published_via':'', + 'temporal_granularity':'years', + 'temporal_coverage-from':'2008-6', + 'temporal_coverage-to':'2009-6', + 'mandate':'', + 'national_statistic':'yes', + 'precision':'Numbers to nearest 10, percentage to nearest whole number', + 'taxonomy_url':'', + 'agency':'', + 'import_source':'ONS-Jan-09', + } + }, + {'name':'weekly-fuel-prices', + 'title':'Weekly fuel prices', + 'notes':'Latest price as at start of week of unleaded petrol and diesel.', + 'resources':[{'url':'http://www.decc.gov.uk/assets/decc/statistics/source/prices/qep211.xls', 'format':'XLS', 'description':'Quarterly 23/2/12'}], + 'url':'http://www.decc.gov.uk/en/content/cms/statistics/source/prices/prices.aspx', + 'author':'DECC Energy Statistics Team', + 'author_email':'energy.stats@decc.gsi.gov.uk', + 'license':'ukcrown', + 'tags':'fuel prices', + 'extras':{ + 'external_reference':'DECC-DECC-0001', + 'date_released':'2009-11-24', + 'date_updated':'2009-11-24', + 'update_frequency':'weekly', + 'geographic_granularity':'national', + 'geographic_coverage':'111100: United Kingdom (England, Scotland, Wales, Northern Ireland)', + 'department':'Department of Energy and Climate Change', + 'published_by':'Department of Energy and Climate Change [4]', + 'published_via':'', + 'mandate':'', + 'temporal_granularity':'weeks', + 'temporal_coverage-from':'2008-11-24', + 'temporal_coverage-to':'2009-11-24', + 'national_statistic':'no', + 'import_source':'DECC-Jan-09', + } + } + ] + +group_hierarchy_groups = [ + {'name': 'department-of-health', + 'title': 'Department of Health', + 'contact-email': 'contact@doh.gov.uk', + 'type': 'organization', + 'is_organization': True + }, + {'name': 'food-standards-agency', + 'title': 'Food Standards Agency', + 'contact-email': 'contact@fsa.gov.uk', + 'parent': 'department-of-health', + 'type': 'organization', + 'is_organization': True}, + {'name': 'national-health-service', + 'title': 'National Health Service', + 'contact-email': 'contact@nhs.gov.uk', + 'parent': 'department-of-health', + 'type': 'organization', + 'is_organization': True, + 'editors': ['nhseditor'], + 'admins': ['nhsadmin']}, + {'name': 'nhs-wirral-ccg', + 'title': 'NHS Wirral CCG', + 'contact-email': 'contact@wirral.nhs.gov.uk', + 'parent': 'national-health-service', + 'type': 'organization', + 'is_organization': True, + 'editors': ['wirraleditor'], + 'admins': ['wirraladmin']}, + {'name': 'nhs-southwark-ccg', + 'title': 'NHS Southwark CCG', + 'contact-email': 'contact@southwark.nhs.gov.uk', + 'parent': 'national-health-service', + 'type': 'organization', + 'is_organization': True}, + {'name': 'cabinet-office', + 'title': 'Cabinet Office', + 'contact-email': 'contact@cabinet-office.gov.uk', + 'type': 'organization', + 'is_organization': True}, + ] + +group_hierarchy_datasets = [ + {'name': 'doh-spend', 'title': 'Department of Health Spend Data', + 'groups': ['department-of-health']}, + {'name': 'nhs-spend', 'title': 'NHS Spend Data', + 'groups': ['national-health-service']}, + {'name': 'wirral-spend', 'title': 'Wirral Spend Data', + 'groups': ['nhs-wirral-ccg']}, + {'name': 'southwark-spend', 'title': 'Southwark Spend Data', + 'groups': ['nhs-southwark-ccg']}, + ] + +group_hierarchy_users = [{'name': 'nhsadmin', 'password': 'pass'}, + {'name': 'nhseditor', 'password': 'pass'}, + {'name': 'wirraladmin', 'password': 'pass'}, + {'name': 'wirraleditor', 'password': 'pass'}, + ] + +# Some test terms and translations. +terms = ('A Novel By Tolstoy', + 'Index of the novel', + 'russian', + 'tolstoy', + "Dave's books", + "Roger's books", + 'romantic novel', + 'book', + '123', + '456', + '789', + 'plain text', + 'Roger likes these books.', +) +english_translations = { + '123': 'jealousy', + '456': 'realism', + '789': 'hypocrisy', +} +german_translations = { + 'A Novel By Tolstoy': 'Roman von Tolstoi', + 'Index of the novel': 'Index des Romans', + 'russian': 'Russisch', + 'tolstoy': 'Tolstoi', + "Dave's books": 'Daves Bucher', + "Roger's books": 'Rogers Bucher', + 'romantic novel': 'Liebesroman', + 'book': 'Buch', + '456': 'Realismus', + '789': 'Heuchelei', + 'plain text': 'Klartext', + 'Roger likes these books.': 'Roger mag diese Bucher.' +} +french_translations = { + 'A Novel By Tolstoy': 'A Novel par Tolstoi', + 'Index of the novel': 'Indice du roman', + 'russian': 'russe', + 'romantic novel': 'roman romantique', + 'book': 'livre', + '123': 'jalousie', + '789': 'hypocrisie', +} diff --git a/venv/lib/python2.7/site-packages/ckan/lib/datapreview.py b/venv/lib/python2.7/site-packages/ckan/lib/datapreview.py new file mode 100644 index 00000000..cb995de2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/datapreview.py @@ -0,0 +1,306 @@ +# encoding: utf-8 + +"""Data previewer functions + +Functions and data structures that are needed for the ckan data preview. +""" + +import urlparse +import logging + +from ckan.common import config + +import ckan.plugins as p +from ckan import logic +from ckan.common import _ + + +log = logging.getLogger(__name__) + + +DEFAULT_RESOURCE_VIEW_TYPES = ['image_view', 'recline_view'] + + +def res_format(resource): + ''' The assumed resource format in lower case. ''' + if not resource['url']: + return None + return (resource['format'] or resource['url'].split('.')[-1]).lower() + + +def compare_domains(urls): + ''' Return True if the domains of the provided urls are the same. + ''' + first_domain = None + for url in urls: + # all urls are interpreted as absolute urls, + # except for urls that start with a / + try: + if not urlparse.urlparse(url).scheme and not url.startswith('/'): + url = '//' + url + parsed = urlparse.urlparse(url.lower(), 'http') + domain = (parsed.scheme, parsed.hostname, parsed.port) + except ValueError: + # URL is so messed up that even urlparse can't stand it + return False + + if not first_domain: + first_domain = domain + continue + if first_domain != domain: + return False + return True + + +def on_same_domain(data_dict): + # compare CKAN domain and resource URL + ckan_url = config.get('ckan.site_url', '//localhost:5000') + resource_url = data_dict['resource']['url'] + + return compare_domains([ckan_url, resource_url]) + + +def get_preview_plugin(data_dict, return_first=False): + '''Determines whether there is an extension that can preview the resource. + + :param data_dict: contains a resource and package dict. + The resource dict has to have a value for ``on_same_domain`` + :type data_dict: dictionary + + :param return_first: If True return the first plugin that can preview + :type return_first: bool + + Returns a dict of plugins that can preview or ones that are fixable''' + + data_dict['resource']['on_same_domain'] = on_same_domain(data_dict) + + plugins_that_can_preview = [] + plugins_fixable = [] + for plugin in p.PluginImplementations(p.IResourcePreview): + p_info = {'plugin': plugin, 'quality': 1} + data = plugin.can_preview(data_dict) + # old school plugins return true/False + if isinstance(data, bool): + p_info['can_preview'] = data + else: + # new school provide a dict + p_info.update(data) + # if we can preview + if p_info['can_preview']: + if return_first: + plugin + plugins_that_can_preview.append(p_info) + elif p_info.get('fixable'): + plugins_fixable.append(p_info) + + num_plugins = len(plugins_that_can_preview) + if num_plugins == 0: + # we didn't find any. see if any could be made to work + for plug in plugins_fixable: + log.info('%s would allow previews. To fix: %s' % ( + plug['plugin'], plug['fixable'])) + preview_plugin = None + elif num_plugins == 1: + # just one available + preview_plugin = plugins_that_can_preview[0]['plugin'] + else: + # multiple plugins so get the best one + plugs = [pl['plugin'] for pl in plugins_that_can_preview] + log.warn('Multiple previews are possible. {0}'.format(plugs)) + preview_plugin = max(plugins_that_can_preview, + key=lambda x: x['quality'])['plugin'] + return preview_plugin + + +def get_view_plugin(view_type): + ''' + Returns the IResourceView plugin associated with the given view_type. + ''' + for plugin in p.PluginImplementations(p.IResourceView): + info = plugin.info() + name = info.get('name') + if name == view_type: + return plugin + + +def get_view_plugins(view_types): + ''' + Returns a list of the view plugins associated with the given view_types. + ''' + view_plugins = [] + for view_type in view_types: + view_plugin = get_view_plugin(view_type) + + if view_plugin: + view_plugins.append(view_plugin) + return view_plugins + + +def get_allowed_view_plugins(data_dict): + ''' + Returns a list of view plugins that work against the resource provided + + The ``data_dict`` contains: ``resource`` and ``package``. + ''' + can_view = [] + for plugin in p.PluginImplementations(p.IResourceView): + + plugin_info = plugin.info() + + if (plugin_info.get('always_available', False) or + plugin.can_view(data_dict)): + can_view.append(plugin) + return can_view + + +def get_default_view_plugins(get_datastore_views=False): + ''' + Returns the list of view plugins to be created by default on new resources + + The default view types are defined via the `ckan.views.default_views` + configuration option. If this is not set (as opposed to empty, which means + no default views), the value of DEFAULT_RESOURCE_VIEW_TYPES is used to + look up the plugins. + + If get_datastore_views is False, only the ones not requiring data to be in + the DataStore are returned, and if True, only the ones requiring it are. + + To flag a view plugin as requiring the DataStore, it must have the + `requires_datastore` key set to True in the dict returned by its `info()` + method. + + Returns a list of IResourceView plugins + ''' + + if config.get('ckan.views.default_views') is None: + default_view_types = DEFAULT_RESOURCE_VIEW_TYPES + else: + default_view_types = config.get('ckan.views.default_views').split() + + default_view_plugins = [] + for view_type in default_view_types: + + view_plugin = get_view_plugin(view_type) + + if not view_plugin: + log.warn('Plugin for view {0} could not be found' + .format(view_type)) + # We should probably check on startup if the default + # view types exist + continue + + info = view_plugin.info() + + plugin_requires_datastore = info.get('requires_datastore', False) + + if plugin_requires_datastore == get_datastore_views: + default_view_plugins.append(view_plugin) + + return default_view_plugins + + +def add_views_to_resource(context, + resource_dict, + dataset_dict=None, + view_types=[], + create_datastore_views=False): + ''' + Creates the provided views (if necessary) on the provided resource + + Views to create are provided as a list of ``view_types``. If no types are + provided, the default views defined in the ``ckan.views.default_views`` + will be created. + + The function will get the plugins for the default views defined in + the configuration, and if some were found the `can_view` method of + each one of them will be called to determine if a resource view should + be created. + + Resource views extensions get the resource dict and the parent dataset + dict. If the latter is not provided, `package_show` is called to get it. + + By default only view plugins that don't require the resource data to be in + the DataStore are called. This is only relevant when the default view + plugins are used, not when explicitly passing view types. See + :py:func:`ckan.logic.action.create.package_create_default_resource_views.`` + for details on the ``create_datastore_views`` parameter. + + Returns a list of resource views created (empty if none were created) + ''' + if not dataset_dict: + dataset_dict = logic.get_action('package_show')( + context, {'id': resource_dict['package_id']}) + + if not view_types: + view_plugins = get_default_view_plugins(create_datastore_views) + else: + view_plugins = get_view_plugins(view_types) + + if not view_plugins: + return [] + + existing_views = p.toolkit.get_action('resource_view_list')( + context, {'id': resource_dict['id']}) + + existing_view_types = ([v['view_type'] for v in existing_views] + if existing_views + else []) + + created_views = [] + for view_plugin in view_plugins: + + view_info = view_plugin.info() + + # Check if a view of this type already exists + if view_info['name'] in existing_view_types: + continue + + # Check if a view of this type can preview this resource + if view_plugin.can_view({ + 'resource': resource_dict, + 'package': dataset_dict + }): + view = {'resource_id': resource_dict['id'], + 'view_type': view_info['name'], + 'title': view_info.get('default_title', _('View')), + 'description': view_info.get('default_description', '')} + + view_dict = p.toolkit.get_action('resource_view_create')(context, + view) + created_views.append(view_dict) + + return created_views + + +def add_views_to_dataset_resources(context, + dataset_dict, + view_types=[], + create_datastore_views=False): + ''' + Creates the provided views on all resources of the provided dataset + + Views to create are provided as a list of ``view_types``. If no types are + provided, the default views defined in the ``ckan.views.default_views`` + will be created. Note that in both cases only these views that can render + the resource will be created (ie its view plugin ``can_view`` method + returns True. + + By default only view plugins that don't require the resource data to be in + the DataStore are called. This is only relevant when the default view + plugins are used, not when explicitly passing view types. See + :py:func:`ckan.logic.action.create.package_create_default_resource_views.`` + for details on the ``create_datastore_views`` parameter. + + Returns a list of resource views created (empty if none were created) + ''' + + created_views = [] + for resource_dict in dataset_dict.get('resources', []): + new_views = add_views_to_resource(context, + resource_dict, + dataset_dict, + view_types, + create_datastore_views) + created_views.extend(new_views) + + return created_views diff --git a/venv/lib/python2.7/site-packages/ckan/lib/dictization/__init__.py b/venv/lib/python2.7/site-packages/ckan/lib/dictization/__init__.py new file mode 100644 index 00000000..3d8e1576 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/dictization/__init__.py @@ -0,0 +1,156 @@ +# encoding: utf-8 + +import datetime +from sqlalchemy.orm import class_mapper +import sqlalchemy +from six import text_type +from ckan.common import config +from ckan.model.core import State + +try: + RowProxy = sqlalchemy.engine.result.RowProxy +except AttributeError: + RowProxy = sqlalchemy.engine.base.RowProxy + +try: + long # Python 2 +except NameError: + long = int # Python 3 + + +# NOTE +# The functions in this file contain very generic methods for dictizing objects +# and saving dictized objects. If a specialised use is needed please do NOT extend +# these functions. Copy code from here as needed. + + +def table_dictize(obj, context, **kw): + '''Get any model object and represent it as a dict''' + + result_dict = {} + + model = context["model"] + session = model.Session + + if isinstance(obj, RowProxy): + fields = obj.keys() + else: + ModelClass = obj.__class__ + table = class_mapper(ModelClass).mapped_table + fields = [field.name for field in table.c] + + for field in fields: + name = field + if name in ('current', 'expired_timestamp', 'expired_id'): + continue + if name == 'continuity_id': + continue + value = getattr(obj, name) + if value is None: + result_dict[name] = value + elif isinstance(value, dict): + result_dict[name] = value + elif isinstance(value, int): + result_dict[name] = value + elif isinstance(value, long): + result_dict[name] = value + elif isinstance(value, datetime.datetime): + result_dict[name] = value.isoformat() + elif isinstance(value, list): + result_dict[name] = value + else: + result_dict[name] = text_type(value) + + result_dict.update(kw) + + ##HACK For optimisation to get metadata_modified created faster. + + context['metadata_modified'] = max(result_dict.get('revision_timestamp', ''), + context.get('metadata_modified', '')) + + return result_dict + + +def obj_list_dictize(obj_list, context, sort_key=lambda x:x): + '''Get a list of model object and represent it as a list of dicts''' + + result_list = [] + active = context.get('active', True) + + for obj in obj_list: + if context.get('with_capacity'): + obj, capacity = obj + dictized = table_dictize(obj, context, capacity=capacity) + else: + dictized = table_dictize(obj, context) + if active and obj.state != 'active': + continue + result_list.append(dictized) + + return sorted(result_list, key=sort_key) + +def obj_dict_dictize(obj_dict, context, sort_key=lambda x:x): + '''Get a dict whose values are model objects + and represent it as a list of dicts''' + + result_list = [] + + for key, obj in obj_dict.items(): + result_list.append(table_dictize(obj, context)) + + return sorted(result_list, key=sort_key) + + +def get_unique_constraints(table, context): + '''Get a list of unique constraints for a sqlalchemy table''' + + list_of_constraints = [] + + for contraint in table.constraints: + if isinstance(contraint, sqlalchemy.UniqueConstraint): + columns = [column.name for column in contraint.columns] + list_of_constraints.append(columns) + + return list_of_constraints + +def table_dict_save(table_dict, ModelClass, context): + '''Given a dict and a model class, update or create a sqlalchemy object. + This will use an existing object if "id" is supplied OR if any unique + constraints are met. e.g supplying just a tag name will get out that tag obj. + ''' + + model = context["model"] + session = context["session"] + + table = class_mapper(ModelClass).mapped_table + + obj = None + + id = table_dict.get("id") + + if id: + obj = session.query(ModelClass).get(id) + + if not obj: + unique_constraints = get_unique_constraints(table, context) + for constraint in unique_constraints: + params = dict((key, table_dict.get(key)) for key in constraint) + obj = session.query(ModelClass).filter_by(**params).first() + if obj: + if 'name' in params and getattr(obj, 'state', None) == State.DELETED: + obj.name = obj.id + obj = None + else: + break + + if not obj: + obj = ModelClass() + + for key, value in table_dict.iteritems(): + if isinstance(value, list): + continue + setattr(obj, key, value) + + session.add(obj) + + return obj diff --git a/venv/lib/python2.7/site-packages/ckan/lib/dictization/model_dictize.py b/venv/lib/python2.7/site-packages/ckan/lib/dictization/model_dictize.py new file mode 100644 index 00000000..9acb1509 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/dictization/model_dictize.py @@ -0,0 +1,768 @@ +# encoding: utf-8 + +''' +These dictize functions generally take a domain object (such as Package) and +convert it to a dictionary, including related objects (e.g. for Package it +includes PackageTags, PackageExtras, PackageGroup etc). + +The basic recipe is to call: + + dictized = ckan.lib.dictization.table_dictize(domain_object) + +which builds the dictionary by iterating over the table columns. +''' +import datetime +import urlparse + +from ckan.common import config +from sqlalchemy.sql import select + +import ckan.logic as logic +import ckan.plugins as plugins +import ckan.lib.helpers as h +import ckan.lib.dictization as d +import ckan.authz as authz +import ckan.lib.search as search +import ckan.lib.munge as munge + +## package save + +def group_list_dictize(obj_list, context, + sort_key=lambda x: x['display_name'], reverse=False, + with_package_counts=True, + include_groups=False, + include_tags=False, + include_extras=False): + + group_dictize_context = dict(context.items()[:]) + # Set options to avoid any SOLR queries for each group, which would + # slow things further. + group_dictize_options = { + 'packages_field': 'dataset_count' if with_package_counts else None, + # don't allow packages_field='datasets' as it is too slow + 'include_groups': include_groups, + 'include_tags': include_tags, + 'include_extras': include_extras, + 'include_users': False, # too slow - don't allow + } + if with_package_counts and 'dataset_counts' not in group_dictize_context: + # 'dataset_counts' will already be in the context in the case that + # group_list_dictize recurses via group_dictize (groups in groups) + group_dictize_context['dataset_counts'] = get_group_dataset_counts() + if context.get('with_capacity'): + group_list = [group_dictize(group, group_dictize_context, + capacity=capacity, **group_dictize_options) + for group, capacity in obj_list] + else: + group_list = [group_dictize(group, group_dictize_context, + **group_dictize_options) + for group in obj_list] + + return sorted(group_list, key=sort_key, reverse=reverse) + +def resource_list_dictize(res_list, context): + + active = context.get('active', True) + result_list = [] + for res in res_list: + resource_dict = resource_dictize(res, context) + if active and res.state != 'active': + continue + + result_list.append(resource_dict) + + return sorted(result_list, key=lambda x: x["position"]) + +def extras_dict_dictize(extras_dict, context): + result_list = [] + for name, extra in extras_dict.iteritems(): + dictized = d.table_dictize(extra, context) + if not extra.state == 'active': + continue + value = dictized["value"] + result_list.append(dictized) + + return sorted(result_list, key=lambda x: x["key"]) + +def extras_list_dictize(extras_list, context): + result_list = [] + active = context.get('active', True) + for extra in extras_list: + dictized = d.table_dictize(extra, context) + if active and extra.state != 'active': + continue + value = dictized["value"] + result_list.append(dictized) + + return sorted(result_list, key=lambda x: x["key"]) + + +def resource_dictize(res, context): + model = context['model'] + resource = d.table_dictize(res, context) + extras = resource.pop("extras", None) + if extras: + resource.update(extras) + # some urls do not have the protocol this adds http:// to these + url = resource['url'] + ## for_edit is only called at the times when the dataset is to be edited + ## in the frontend. Without for_edit the whole qualified url is returned. + if resource.get('url_type') == 'upload' and not context.get('for_edit'): + url = url.rsplit('/')[-1] + cleaned_name = munge.munge_filename(url) + resource['url'] = h.url_for(controller='package', + action='resource_download', + id=resource['package_id'], + resource_id=res.id, + filename=cleaned_name, + qualified=True) + elif resource['url'] and not urlparse.urlsplit(url).scheme and not context.get('for_edit'): + resource['url'] = u'http://' + url.lstrip('/') + return resource + + +def _execute(q, table, context): + ''' + Takes an SqlAlchemy query (q) that is (at its base) a Select on an + object table (table), and it returns the object. + + Analogous with _execute_with_revision, so takes the same params, even + though it doesn't need the table. + ''' + model = context['model'] + session = model.Session + return session.execute(q) + + +def _execute_with_revision(q, rev_table, context): + ''' + Takes an SqlAlchemy query (q) that is (at its base) a Select on an object + revision table (rev_table), and you provide revision_id or revision_date in + the context and it will filter the object revision(s) to an earlier time. + + Raises NotFound if context['revision_id'] is provided, but the revision + ID does not exist. + + Returns [] if there are no results. + + ''' + model = context['model'] + session = model.Session + revision_id = context.get('revision_id') + revision_date = context.get('revision_date') + + if revision_id: + revision = session.query(context['model'].Revision).filter_by( + id=revision_id).first() + if not revision: + raise logic.NotFound + revision_date = revision.timestamp + + q = q.where(rev_table.c.revision_timestamp <= revision_date) + q = q.where(rev_table.c.expired_timestamp > revision_date) + + return session.execute(q) + + +def package_dictize(pkg, context): + ''' + Given a Package object, returns an equivalent dictionary. + + Normally this is the most recent version, but you can provide revision_id + or revision_date in the context and it will filter to an earlier time. + + May raise NotFound if: + * the specified revision_id doesn't exist + * the specified revision_date was before the package was created + ''' + model = context['model'] + is_latest_revision = not(context.get('revision_id') or + context.get('revision_date')) + execute = _execute if is_latest_revision else _execute_with_revision + #package + if is_latest_revision: + if isinstance(pkg, model.PackageRevision): + pkg = model.Package.get(pkg.id) + result = pkg + else: + package_rev = model.package_revision_table + q = select([package_rev]).where(package_rev.c.id == pkg.id) + result = execute(q, package_rev, context).first() + if not result: + raise logic.NotFound + result_dict = d.table_dictize(result, context) + #strip whitespace from title + if result_dict.get('title'): + result_dict['title'] = result_dict['title'].strip() + + #resources + if is_latest_revision: + res = model.resource_table + else: + res = model.resource_revision_table + q = select([res]).where(res.c.package_id == pkg.id) + result = execute(q, res, context) + result_dict["resources"] = resource_list_dictize(result, context) + result_dict['num_resources'] = len(result_dict.get('resources', [])) + + #tags + tag = model.tag_table + if is_latest_revision: + pkg_tag = model.package_tag_table + else: + pkg_tag = model.package_tag_revision_table + q = select([tag, pkg_tag.c.state], + from_obj=pkg_tag.join(tag, tag.c.id == pkg_tag.c.tag_id) + ).where(pkg_tag.c.package_id == pkg.id) + result = execute(q, pkg_tag, context) + result_dict["tags"] = d.obj_list_dictize(result, context, + lambda x: x["name"]) + result_dict['num_tags'] = len(result_dict.get('tags', [])) + + # Add display_names to tags. At first a tag's display_name is just the + # same as its name, but the display_name might get changed later (e.g. + # translated into another language by the multilingual extension). + for tag in result_dict['tags']: + assert not 'display_name' in tag + tag['display_name'] = tag['name'] + + #extras + if is_latest_revision: + extra = model.package_extra_table + else: + extra = model.extra_revision_table + q = select([extra]).where(extra.c.package_id == pkg.id) + result = execute(q, extra, context) + result_dict["extras"] = extras_list_dictize(result, context) + + #groups + if is_latest_revision: + member = model.member_table + else: + member = model.member_revision_table + group = model.group_table + q = select([group, member.c.capacity], + from_obj=member.join(group, group.c.id == member.c.group_id) + ).where(member.c.table_id == pkg.id)\ + .where(member.c.state == 'active') \ + .where(group.c.is_organization == False) + result = execute(q, member, context) + context['with_capacity'] = False + ## no package counts as cannot fetch from search index at the same + ## time as indexing to it. + ## tags, extras and sub-groups are not included for speed + result_dict["groups"] = group_list_dictize(result, context, + with_package_counts=False) + + #owning organization + if is_latest_revision: + group = model.group_table + else: + group = model.group_revision_table + q = select([group] + ).where(group.c.id == pkg.owner_org) \ + .where(group.c.state == 'active') + result = execute(q, group, context) + organizations = d.obj_list_dictize(result, context) + if organizations: + result_dict["organization"] = organizations[0] + else: + result_dict["organization"] = None + + #relations + if is_latest_revision: + rel = model.package_relationship_table + else: + rel = model.package_relationship_revision_table + q = select([rel]).where(rel.c.subject_package_id == pkg.id) + result = execute(q, rel, context) + result_dict["relationships_as_subject"] = \ + d.obj_list_dictize(result, context) + q = select([rel]).where(rel.c.object_package_id == pkg.id) + result = execute(q, rel, context) + result_dict["relationships_as_object"] = \ + d.obj_list_dictize(result, context) + + # Extra properties from the domain object + # We need an actual Package object for this, not a PackageRevision + if isinstance(pkg, model.PackageRevision): + pkg = model.Package.get(pkg.id) + + # isopen + result_dict['isopen'] = pkg.isopen if isinstance(pkg.isopen, bool) \ + else pkg.isopen() + + # type + # if null assign the default value to make searching easier + result_dict['type'] = pkg.type or u'dataset' + + # license + if pkg.license and pkg.license.url: + result_dict['license_url'] = pkg.license.url + result_dict['license_title'] = pkg.license.title.split('::')[-1] + elif pkg.license: + result_dict['license_title'] = pkg.license.title + else: + result_dict['license_title'] = pkg.license_id + + # creation and modification date + result_dict['metadata_modified'] = pkg.metadata_modified.isoformat() + result_dict['metadata_created'] = pkg.metadata_created.isoformat() \ + if pkg.metadata_created else None + + return result_dict + + +def _get_members(context, group, member_type): + + model = context['model'] + Entity = getattr(model, member_type[:-1].capitalize()) + q = model.Session.query(Entity, model.Member.capacity).\ + join(model.Member, model.Member.table_id == Entity.id).\ + filter(model.Member.group_id == group.id).\ + filter(model.Member.state == 'active').\ + filter(model.Member.table_name == member_type[:-1]) + if member_type == 'packages': + q = q.filter(Entity.private==False) + if 'limits' in context and member_type in context['limits']: + return q[:context['limits'][member_type]] + return q.all() + + +def get_group_dataset_counts(): + '''For all public groups, return their dataset counts, as a SOLR facet''' + query = search.PackageSearchQuery() + q = {'q': '+capacity:public', + 'fl': 'groups', 'facet.field': ['groups', 'owner_org'], + 'facet.limit': -1, 'rows': 1} + query.run(q) + return query.facets + + +def group_dictize(group, context, + include_groups=True, + include_tags=True, + include_users=True, + include_extras=True, + packages_field='datasets', + **kw): + ''' + Turns a Group object and related into a dictionary. The related objects + like tags are included unless you specify it in the params. + + :param packages_field: determines the format of the `packages` field - can + be `datasets`, `dataset_count` or None. + ''' + assert packages_field in ('datasets', 'dataset_count', None) + if packages_field == 'dataset_count': + dataset_counts = context.get('dataset_counts', None) + + result_dict = d.table_dictize(group, context) + result_dict.update(kw) + + result_dict['display_name'] = group.title or group.name + + if include_extras: + result_dict['extras'] = extras_dict_dictize( + group._extras, context) + + context['with_capacity'] = True + + if packages_field: + def get_packages_for_this_group(group_, just_the_count=False): + # Ask SOLR for the list of packages for this org/group + q = { + 'facet': 'false', + 'rows': 0, + } + + if group_.is_organization: + q['fq'] = 'owner_org:"{0}"'.format(group_.id) + else: + q['fq'] = 'groups:"{0}"'.format(group_.name) + + # Allow members of organizations to see private datasets. + if group_.is_organization: + is_group_member = (context.get('user') and + authz.has_user_permission_for_group_or_org( + group_.id, context.get('user'), 'read')) + if is_group_member: + q['include_private'] = True + + if not just_the_count: + # Is there a packages limit in the context? + try: + packages_limit = context['limits']['packages'] + except KeyError: + q['rows'] = 1000 # Only the first 1000 datasets are returned + else: + q['rows'] = packages_limit + + search_context = dict((k, v) for (k, v) in context.items() + if k != 'schema') + search_results = logic.get_action('package_search')(search_context, + q) + return search_results['count'], search_results['results'] + + if packages_field == 'datasets': + package_count, packages = get_packages_for_this_group(group) + result_dict['packages'] = packages + else: + if dataset_counts is None: + package_count, packages = get_packages_for_this_group( + group, just_the_count=True) + else: + # Use the pre-calculated package_counts passed in. + facets = dataset_counts + if group.is_organization: + package_count = facets['owner_org'].get(group.id, 0) + else: + package_count = facets['groups'].get(group.name, 0) + + result_dict['package_count'] = package_count + + if include_tags: + # group tags are not creatable via the API yet, but that was(/is) a + # future intention (see kindly's commit 5c8df894 on 2011/12/23) + result_dict['tags'] = tag_list_dictize( + _get_members(context, group, 'tags'), + context) + + if include_groups: + # these sub-groups won't have tags or extras for speed + result_dict['groups'] = group_list_dictize( + _get_members(context, group, 'groups'), + context, include_groups=True) + + if include_users: + result_dict['users'] = user_list_dictize( + _get_members(context, group, 'users'), + context) + + context['with_capacity'] = False + + if context.get('for_view'): + if result_dict['is_organization']: + plugin = plugins.IOrganizationController + else: + plugin = plugins.IGroupController + for item in plugins.PluginImplementations(plugin): + result_dict = item.before_view(result_dict) + + image_url = result_dict.get('image_url') + result_dict['image_display_url'] = image_url + if image_url and not image_url.startswith('http'): + #munge here should not have an effect only doing it incase + #of potential vulnerability of dodgy api input + image_url = munge.munge_filename_legacy(image_url) + result_dict['image_display_url'] = h.url_for_static( + 'uploads/group/%s' % result_dict.get('image_url'), + qualified=True + ) + return result_dict + +def tag_list_dictize(tag_list, context): + + result_list = [] + for tag in tag_list: + if context.get('with_capacity'): + tag, capacity = tag + dictized = d.table_dictize(tag, context, capacity=capacity) + else: + dictized = d.table_dictize(tag, context) + + # Add display_names to tag dicts. At first a tag's display_name is just + # the same as its name, but the display_name might get changed later + # (e.g. translated into another language by the multilingual + # extension). + assert not dictized.has_key('display_name') + dictized['display_name'] = dictized['name'] + + if context.get('for_view'): + for item in plugins.PluginImplementations( + plugins.ITagController): + dictized = item.before_view(dictized) + + result_list.append(dictized) + + return result_list + +def tag_dictize(tag, context, include_datasets=True): + tag_dict = d.table_dictize(tag, context) + + if include_datasets: + query = search.PackageSearchQuery() + + tag_query = u'+capacity:public ' + vocab_id = tag_dict.get('vocabulary_id') + + if vocab_id: + model = context['model'] + vocab = model.Vocabulary.get(vocab_id) + tag_query += u'+vocab_{0}:"{1}"'.format(vocab.name, tag.name) + else: + tag_query += u'+tags:"{0}"'.format(tag.name) + + q = {'q': tag_query, 'fl': 'data_dict', 'wt': 'json', 'rows': 1000} + + package_dicts = [h.json.loads(result['data_dict']) + for result in query.run(q)['results']] + + # Add display_names to tags. At first a tag's display_name is just the + # same as its name, but the display_name might get changed later (e.g. + # translated into another language by the multilingual extension). + assert 'display_name' not in tag_dict + tag_dict['display_name'] = tag_dict['name'] + + if context.get('for_view'): + for item in plugins.PluginImplementations(plugins.ITagController): + tag_dict = item.before_view(tag_dict) + + if include_datasets: + tag_dict['packages'] = [] + for package_dict in package_dicts: + for item in plugins.PluginImplementations(plugins.IPackageController): + package_dict = item.before_view(package_dict) + tag_dict['packages'].append(package_dict) + else: + if include_datasets: + tag_dict['packages'] = package_dicts + + return tag_dict + +def user_list_dictize(obj_list, context, + sort_key=lambda x:x['name'], reverse=False): + + result_list = [] + + for obj in obj_list: + user_dict = user_dictize(obj, context) + user_dict.pop('reset_key', None) + user_dict.pop('apikey', None) + user_dict.pop('email', None) + result_list.append(user_dict) + return sorted(result_list, key=sort_key, reverse=reverse) + +def member_dictize(member, context): + return d.table_dictize(member, context) + +def user_dictize(user, context, include_password_hash=False): + + if context.get('with_capacity'): + user, capacity = user + result_dict = d.table_dictize(user, context, capacity=capacity) + else: + result_dict = d.table_dictize(user, context) + + password_hash = result_dict.pop('password') + del result_dict['reset_key'] + + result_dict['display_name'] = user.display_name + result_dict['email_hash'] = user.email_hash + result_dict['number_of_edits'] = user.number_of_edits() + result_dict['number_created_packages'] = user.number_created_packages( + include_private_and_draft=context.get( + 'count_private_and_draft_datasets', False)) + + requester = context.get('user') + + reset_key = result_dict.pop('reset_key', None) + apikey = result_dict.pop('apikey', None) + email = result_dict.pop('email', None) + + if context.get('keep_email', False): + result_dict['email'] = email + + if context.get('keep_apikey', False): + result_dict['apikey'] = apikey + + if requester == user.name: + result_dict['apikey'] = apikey + result_dict['email'] = email + + if authz.is_sysadmin(requester): + result_dict['apikey'] = apikey + result_dict['email'] = email + + if include_password_hash: + result_dict['password_hash'] = password_hash + + model = context['model'] + session = model.Session + + return result_dict + +def task_status_dictize(task_status, context): + return d.table_dictize(task_status, context) + +## conversion to api + +def group_to_api(group, context): + api_version = context.get('api_version') + assert api_version, 'No api_version supplied in context' + dictized = group_dictize(group, context) + dictized["extras"] = dict((extra["key"], extra["value"]) + for extra in dictized["extras"]) + if api_version == 1: + dictized["packages"] = sorted([pkg["name"] for pkg in dictized["packages"]]) + else: + dictized["packages"] = sorted([pkg["id"] for pkg in dictized["packages"]]) + return dictized + +def tag_to_api(tag, context): + api_version = context.get('api_version') + assert api_version, 'No api_version supplied in context' + dictized = tag_dictize(tag, context) + if api_version == 1: + return sorted([package["name"] for package in dictized["packages"]]) + else: + return sorted([package["id"] for package in dictized["packages"]]) + + +def resource_dict_to_api(res_dict, package_id, context): + res_dict.pop("revision_id") + res_dict.pop("state") + res_dict["package_id"] = package_id + + +def package_to_api(pkg, context): + api_version = context.get('api_version') + assert api_version, 'No api_version supplied in context' + dictized = package_dictize(pkg, context) + + dictized["tags"] = [tag["name"] for tag in dictized["tags"] \ + if not tag.get('vocabulary_id')] + dictized["extras"] = dict((extra["key"], extra["value"]) + for extra in dictized["extras"]) + dictized['license'] = pkg.license.title if pkg.license else None + dictized['ratings_average'] = pkg.get_average_rating() + dictized['ratings_count'] = len(pkg.ratings) + dictized['notes_rendered'] = h.render_markdown(pkg.notes) + + site_url = config.get('ckan.site_url', None) + if site_url: + dictized['ckan_url'] = '%s/dataset/%s' % (site_url, pkg.name) + + for resource in dictized["resources"]: + resource_dict_to_api(resource, pkg.id, context) + + def make_api_1(package_id): + return pkg.get(package_id).name + + def make_api_2(package_id): + return package_id + + if api_version == 1: + api_fn = make_api_1 + dictized["groups"] = [group["name"] for group in dictized["groups"]] + # FIXME why is this just for version 1? + if pkg.resources: + dictized['download_url'] = pkg.resources[0].url + else: + api_fn = make_api_2 + dictized["groups"] = [group["id"] for group in dictized["groups"]] + + subjects = dictized.pop("relationships_as_subject") + objects = dictized.pop("relationships_as_object") + + relationships = [] + for rel in objects: + model = context['model'] + swap_types = model.PackageRelationship.forward_to_reverse_type + type = swap_types(rel['type']) + relationships.append({'subject': api_fn(rel['object_package_id']), + 'type': type, + 'object': api_fn(rel['subject_package_id']), + 'comment': rel["comment"]}) + for rel in subjects: + relationships.append({'subject': api_fn(rel['subject_package_id']), + 'type': rel['type'], + 'object': api_fn(rel['object_package_id']), + 'comment': rel["comment"]}) + + dictized['relationships'] = relationships + + return dictized + +def vocabulary_dictize(vocabulary, context, include_datasets=False): + vocabulary_dict = d.table_dictize(vocabulary, context) + assert not vocabulary_dict.has_key('tags') + + vocabulary_dict['tags'] = [tag_dictize(tag, context, include_datasets) + for tag in vocabulary.tags] + return vocabulary_dict + +def vocabulary_list_dictize(vocabulary_list, context): + return [vocabulary_dictize(vocabulary, context) + for vocabulary in vocabulary_list] + +def activity_dictize(activity, context): + activity_dict = d.table_dictize(activity, context) + return activity_dict + +def activity_list_dictize(activity_list, context): + return [activity_dictize(activity, context) for activity in activity_list] + +def activity_detail_dictize(activity_detail, context): + return d.table_dictize(activity_detail, context) + +def activity_detail_list_dictize(activity_detail_list, context): + return [activity_detail_dictize(activity_detail, context) + for activity_detail in activity_detail_list] + + +def package_to_api1(pkg, context): + # DEPRICIATED set api_version in context and use package_to_api() + context['api_version'] = 1 + return package_to_api(pkg, context) + +def package_to_api2(pkg, context): + # DEPRICIATED set api_version in context and use package_to_api() + context['api_version'] = 2 + return package_to_api(pkg, context) + +def group_to_api1(group, context): + # DEPRICIATED set api_version in context and use group_to_api() + context['api_version'] = 1 + return group_to_api(group, context) + +def group_to_api2(group, context): + # DEPRICIATED set api_version in context and use group_to_api() + context['api_version'] = 2 + return group_to_api(group, context) + +def tag_to_api1(tag, context): + # DEPRICIATED set api_version in context and use tag_to_api() + context['api_version'] = 1 + return tag_to_api(tag, context) + +def tag_to_api2(tag, context): + # DEPRICIATED set api_version in context and use tag_to_api() + context['api_version'] = 2 + return tag_to_api(tag, context) + +def user_following_user_dictize(follower, context): + return d.table_dictize(follower, context) + +def user_following_dataset_dictize(follower, context): + return d.table_dictize(follower, context) + +def user_following_group_dictize(follower, context): + return d.table_dictize(follower, context) + +def resource_view_dictize(resource_view, context): + dictized = d.table_dictize(resource_view, context) + dictized.pop('order') + config = dictized.pop('config', {}) + dictized.update(config) + resource = context['model'].Resource.get(resource_view.resource_id) + package_id = resource.package_id + dictized['package_id'] = package_id + return dictized + +def resource_view_list_dictize(resource_views, context): + resource_view_dicts = [] + for view in resource_views: + resource_view_dicts.append(resource_view_dictize(view, context)) + return resource_view_dicts + diff --git a/venv/lib/python2.7/site-packages/ckan/lib/dictization/model_save.py b/venv/lib/python2.7/site-packages/ckan/lib/dictization/model_save.py new file mode 100644 index 00000000..454159f4 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/dictization/model_save.py @@ -0,0 +1,614 @@ +# encoding: utf-8 + +import datetime +import uuid +import logging + +from sqlalchemy.orm import class_mapper +from six import string_types + +import ckan.lib.dictization as d +import ckan.lib.helpers as h +import ckan.authz as authz + +log = logging.getLogger(__name__) + + +def resource_dict_save(res_dict, context): + + model = context["model"] + session = context["session"] + + id = res_dict.get("id") + obj = None + if id: + obj = session.query(model.Resource).get(id) + if not obj: + new = True + obj = model.Resource() + else: + new = False + + table = class_mapper(model.Resource).mapped_table + fields = [field.name for field in table.c] + + # Strip the full url for resources of type 'upload' + if res_dict.get('url') and res_dict.get('url_type') == u'upload': + res_dict['url'] = res_dict['url'].rsplit('/')[-1] + + # Resource extras not submitted will be removed from the existing extras + # dict + new_extras = {} + for key, value in res_dict.iteritems(): + if isinstance(value, list): + continue + if key in ('extras', 'revision_timestamp', 'tracking_summary'): + continue + if key in fields: + if isinstance(getattr(obj, key), datetime.datetime): + if getattr(obj, key).isoformat() == value: + continue + if key == 'last_modified' and not new: + obj.url_changed = True + if key == 'url' and not new and obj.url != value: + obj.url_changed = True + setattr(obj, key, value) + else: + # resources save extras directly onto the object, instead + # of in a separate extras field like packages and groups + new_extras[key] = value + + obj.state = u'active' + obj.extras = new_extras + + session.add(obj) + return obj + +def package_resource_list_save(res_dicts, package, context): + allow_partial_update = context.get("allow_partial_update", False) + if res_dicts is None and allow_partial_update: + return + + resource_list = package.resources_all + old_list = package.resources_all[:] + + obj_list = [] + for res_dict in res_dicts or []: + if not u'package_id' in res_dict or not res_dict[u'package_id']: + res_dict[u'package_id'] = package.id + obj = resource_dict_save(res_dict, context) + obj_list.append(obj) + + # Set the package's resources. resource_list is an ORM relation - the + # package's resources. If we didn't have the slice operator "[:]" then it + # would reassign the variable "resource_list" to be the obj_list. But with + # the slice operator it changes the contents of the relation, setting the + # package's resources. + # At the table level, for each resource in the obj_list, its + # resource.package_id is changed to this package (which is needed for new + # resources), and every resource.position is set to ascending integers, + # according to their ordering in the obj_list. + resource_list[:] = obj_list + + # Mark any left-over resources as deleted + for resource in set(old_list) - set(obj_list): + resource.state = 'deleted' + resource_list.append(resource) + + +def package_extras_save(extra_dicts, obj, context): + allow_partial_update = context.get("allow_partial_update", False) + if extra_dicts is None and allow_partial_update: + return + + model = context["model"] + session = context["session"] + + extras_list = obj.extras_list + old_extras = dict((extra.key, extra) for extra in extras_list) + + new_extras = {} + for extra_dict in extra_dicts or []: + if extra_dict.get("deleted"): + continue + + if extra_dict['value'] is None: + pass + else: + new_extras[extra_dict["key"]] = extra_dict["value"] + #new + for key in set(new_extras.keys()) - set(old_extras.keys()): + state = 'active' + extra = model.PackageExtra(state=state, key=key, value=new_extras[key]) + session.add(extra) + extras_list.append(extra) + #changed + for key in set(new_extras.keys()) & set(old_extras.keys()): + extra = old_extras[key] + if new_extras[key] == extra.value and extra.state != 'deleted': + continue + state = 'active' + extra.value = new_extras[key] + extra.state = state + session.add(extra) + #deleted + for key in set(old_extras.keys()) - set(new_extras.keys()): + extra = old_extras[key] + if extra.state == 'deleted': + continue + state = 'deleted' + extra.state = state + +def package_tag_list_save(tag_dicts, package, context): + allow_partial_update = context.get("allow_partial_update", False) + if tag_dicts is None and allow_partial_update: + return + + model = context["model"] + session = context["session"] + + tag_package_tag = dict((package_tag.tag, package_tag) + for package_tag in + package.package_tag_all) + + tag_package_tag_inactive = {tag: pt for tag,pt in tag_package_tag.items() if + pt.state in ['deleted']} + + tag_name_vocab = set() + tags = set() + for tag_dict in tag_dicts or []: + if (tag_dict.get('name'), tag_dict.get('vocabulary_id')) not in tag_name_vocab: + tag_obj = d.table_dict_save(tag_dict, model.Tag, context) + tags.add(tag_obj) + tag_name_vocab.add((tag_obj.name, tag_obj.vocabulary_id)) + + # 3 cases + # case 1: currently active but not in new list + for tag in set(tag_package_tag.keys()) - tags: + package_tag = tag_package_tag[tag] + package_tag.state = 'deleted' + + # case 2: in new list but never used before + for tag in tags - set(tag_package_tag.keys()): + state = 'active' + package_tag_obj = model.PackageTag(package, tag, state) + session.add(package_tag_obj) + tag_package_tag[tag] = package_tag_obj + + # case 3: in new list and already used but in deleted state + for tag in tags.intersection(set(tag_package_tag_inactive.keys())): + state = 'active' + package_tag = tag_package_tag[tag] + package_tag.state = state + + package.package_tag_all[:] = tag_package_tag.values() + +def package_membership_list_save(group_dicts, package, context): + + allow_partial_update = context.get("allow_partial_update", False) + if group_dicts is None and allow_partial_update: + return + + capacity = 'public' + model = context["model"] + session = context["session"] + user = context.get('user') + + members = session.query(model.Member) \ + .filter(model.Member.table_id == package.id) \ + .filter(model.Member.capacity != 'organization') + + group_member = dict((member.group, member) + for member in + members) + groups = set() + for group_dict in group_dicts or []: + id = group_dict.get("id") + name = group_dict.get("name") + capacity = group_dict.get("capacity", "public") + if capacity == 'organization': + continue + if id: + group = session.query(model.Group).get(id) + else: + group = session.query(model.Group).filter_by(name=name).first() + if group: + groups.add(group) + + ## need to flush so we can get out the package id + model.Session.flush() + + # Remove any groups we are no longer in + for group in set(group_member.keys()) - groups: + member_obj = group_member[group] + if member_obj and member_obj.state == 'deleted': + continue + if authz.has_user_permission_for_group_or_org( + member_obj.group_id, user, 'read'): + member_obj.capacity = capacity + member_obj.state = 'deleted' + session.add(member_obj) + + # Add any new groups + for group in groups: + member_obj = group_member.get(group) + if member_obj and member_obj.state == 'active': + continue + if authz.has_user_permission_for_group_or_org( + group.id, user, 'read'): + member_obj = group_member.get(group) + if member_obj: + member_obj.capacity = capacity + member_obj.state = 'active' + else: + member_obj = model.Member(table_id=package.id, + table_name='package', + group=group, + capacity=capacity, + group_id=group.id, + state = 'active') + session.add(member_obj) + + +def relationship_list_save(relationship_dicts, package, attr, context): + + allow_partial_update = context.get("allow_partial_update", False) + if relationship_dicts is None and allow_partial_update: + return + + model = context["model"] + session = context["session"] + + relationship_list = getattr(package, attr) + old_list = relationship_list[:] + + relationships = [] + for relationship_dict in relationship_dicts or []: + obj = d.table_dict_save(relationship_dict, + model.PackageRelationship, context) + relationships.append(obj) + + relationship_list[:] = relationships + + for relationship in set(old_list) - set(relationship_list): + relationship.state = 'deleted' + relationship_list.append(relationship) + +def package_dict_save(pkg_dict, context): + model = context["model"] + package = context.get("package") + allow_partial_update = context.get("allow_partial_update", False) + if package: + pkg_dict["id"] = package.id + Package = model.Package + + if 'metadata_created' in pkg_dict: + del pkg_dict['metadata_created'] + if 'metadata_modified' in pkg_dict: + del pkg_dict['metadata_modified'] + + pkg = d.table_dict_save(pkg_dict, Package, context) + + if not pkg.id: + pkg.id = str(uuid.uuid4()) + + package_resource_list_save(pkg_dict.get("resources"), pkg, context) + package_tag_list_save(pkg_dict.get("tags"), pkg, context) + package_membership_list_save(pkg_dict.get("groups"), pkg, context) + + # relationships are not considered 'part' of the package, so only + # process this if the key is provided + if 'relationships_as_subject' in pkg_dict: + subjects = pkg_dict.get('relationships_as_subject') + relationship_list_save(subjects, pkg, 'relationships_as_subject', context) + if 'relationships_as_object' in pkg_dict: + objects = pkg_dict.get('relationships_as_object') + relationship_list_save(objects, pkg, 'relationships_as_object', context) + + extras = package_extras_save(pkg_dict.get("extras"), pkg, context) + + return pkg + +def group_member_save(context, group_dict, member_table_name): + model = context["model"] + session = context["session"] + group = context['group'] + entity_list = group_dict.get(member_table_name, None) + + if entity_list is None: + if context.get('allow_partial_update', False): + return {'added': [], 'removed': []} + else: + entity_list = [] + + entities = {} + Member = model.Member + + classname = member_table_name[:-1].capitalize() + if classname == 'Organization': + # Organizations use the model.Group class + classname = 'Group' + ModelClass = getattr(model, classname) + + for entity_dict in entity_list: + name_or_id = entity_dict.get('id') or entity_dict.get('name') + obj = ModelClass.get(name_or_id) + if obj and obj not in entities.values(): + entities[(obj.id, entity_dict.get('capacity', 'public'))] = obj + + members = session.query(Member).filter_by( + table_name=member_table_name[:-1], + group_id=group.id, + ).all() + + processed = { + 'added': [], + 'removed': [] + } + + entity_member = dict(((member.table_id, member.capacity), member) for member in members) + for entity_id in set(entity_member.keys()) - set(entities.keys()): + if entity_member[entity_id].state != 'deleted': + processed['removed'].append(entity_id[0]) + entity_member[entity_id].state = 'deleted' + session.add(entity_member[entity_id]) + + for entity_id in set(entity_member.keys()) & set(entities.keys()): + if entity_member[entity_id].state != 'active': + processed['added'].append(entity_id[0]) + entity_member[entity_id].state = 'active' + session.add(entity_member[entity_id]) + + for entity_id in set(entities.keys()) - set(entity_member.keys()): + member = Member(group=group, group_id=group.id, table_id=entity_id[0], + table_name=member_table_name[:-1], + capacity=entity_id[1]) + processed['added'].append(entity_id[0]) + session.add(member) + + return processed + + +def group_dict_save(group_dict, context, prevent_packages_update=False): + from ckan.lib.search import rebuild + + model = context["model"] + session = context["session"] + group = context.get("group") + allow_partial_update = context.get("allow_partial_update", False) + + Group = model.Group + if group: + group_dict["id"] = group.id + + group = d.table_dict_save(group_dict, Group, context) + if not group.id: + group.id = str(uuid.uuid4()) + + context['group'] = group + + # Under the new org rules we do not want to be able to update datasets + # via group edit so we need a way to prevent this. It may be more + # sensible in future to send a list of allowed/disallowed updates for + # groups, users, tabs etc. + if not prevent_packages_update: + pkgs_edited = group_member_save(context, group_dict, 'packages') + else: + pkgs_edited = { + 'added': [], + 'removed': [] + } + group_users_changed = group_member_save(context, group_dict, 'users') + group_groups_changed = group_member_save(context, group_dict, 'groups') + group_tags_changed = group_member_save(context, group_dict, 'tags') + log.debug('Group save membership changes - Packages: %r Users: %r ' + 'Groups: %r Tags: %r', pkgs_edited, group_users_changed, + group_groups_changed, group_tags_changed) + + extras = group_dict.get("extras", []) + new_extras = {i['key'] for i in extras} + if extras: + old_extras = group.extras + for key in set(old_extras) - new_extras: + del group.extras[key] + for x in extras: + if 'deleted' in x and x['key'] in old_extras: + del group.extras[x['key']] + continue + group.extras[x['key']] = x['value'] + + # We will get a list of packages that we have either added or + # removed from the group, and trigger a re-index. + package_ids = pkgs_edited['removed'] + package_ids.extend( pkgs_edited['added'] ) + if package_ids: + session.commit() + map( rebuild, package_ids ) + + return group + + +def user_dict_save(user_dict, context): + + model = context['model'] + session = context['session'] + user = context.get('user_obj') + + User = model.User + if user: + user_dict['id'] = user.id + + if 'password' in user_dict and not len(user_dict['password']): + del user_dict['password'] + + user = d.table_dict_save(user_dict, User, context) + + return user + + +def package_api_to_dict(api1_dict, context): + + package = context.get("package") + api_version = context.get('api_version') + assert api_version, 'No api_version supplied in context' + + dictized = {} + + for key, value in api1_dict.iteritems(): + new_value = value + if key == 'tags': + if isinstance(value, string_types): + new_value = [{"name": item} for item in value.split()] + else: + new_value = [{"name": item} for item in value] + if key == 'extras': + updated_extras = {} + if package: + updated_extras.update(package.extras) + updated_extras.update(value) + + new_value = [] + + for extras_key, extras_value in updated_extras.iteritems(): + new_value.append({"key": extras_key, + "value": extras_value}) + + if key == 'groups' and len(value): + if api_version == 1: + new_value = [{'name': item} for item in value] + else: + new_value = [{'id': item} for item in value] + + dictized[key] = new_value + + download_url = dictized.pop('download_url', None) + if download_url and not dictized.get('resources'): + dictized["resources"] = [{'url': download_url}] + + download_url = dictized.pop('download_url', None) + + return dictized + +def group_api_to_dict(api1_dict, context): + + dictized = {} + + for key, value in api1_dict.iteritems(): + new_value = value + if key == 'packages': + new_value = [{"id": item} for item in value] + if key == 'extras': + new_value = [{"key": extra_key, "value": value[extra_key]} + for extra_key in value] + dictized[key] = new_value + + return dictized + +def task_status_dict_save(task_status_dict, context): + model = context["model"] + task_status = context.get("task_status") + allow_partial_update = context.get("allow_partial_update", False) + if task_status: + task_status_dict["id"] = task_status.id + + task_status = d.table_dict_save(task_status_dict, model.TaskStatus, context) + return task_status + +def activity_dict_save(activity_dict, context): + + model = context['model'] + session = context['session'] + user_id = activity_dict['user_id'] + object_id = activity_dict['object_id'] + revision_id = activity_dict['revision_id'] + activity_type = activity_dict['activity_type'] + if activity_dict.has_key('data'): + data = activity_dict['data'] + else: + data = None + activity_obj = model.Activity(user_id, object_id, revision_id, + activity_type, data) + session.add(activity_obj) + + # TODO: Handle activity details. + + return activity_obj + +def vocabulary_tag_list_save(new_tag_dicts, vocabulary_obj, context): + model = context['model'] + session = context['session'] + + # First delete any tags not in new_tag_dicts. + for tag in vocabulary_obj.tags: + if tag.name not in [t['name'] for t in new_tag_dicts]: + tag.delete() + # Now add any new tags. + for tag_dict in new_tag_dicts: + current_tag_names = [tag.name for tag in vocabulary_obj.tags] + if tag_dict['name'] not in current_tag_names: + # Make sure the tag belongs to this vocab.. + tag_dict['vocabulary_id'] = vocabulary_obj.id + # then add it. + tag_dict_save(tag_dict, {'model': model, 'session': session}) + +def vocabulary_dict_save(vocabulary_dict, context): + model = context['model'] + session = context['session'] + vocabulary_name = vocabulary_dict['name'] + + vocabulary_obj = model.Vocabulary(vocabulary_name) + session.add(vocabulary_obj) + + if vocabulary_dict.has_key('tags'): + vocabulary_tag_list_save(vocabulary_dict['tags'], vocabulary_obj, + context) + + return vocabulary_obj + +def vocabulary_dict_update(vocabulary_dict, context): + + model = context['model'] + session = context['session'] + + vocabulary_obj = model.vocabulary.Vocabulary.get(vocabulary_dict['id']) + + if vocabulary_dict.has_key('name'): + vocabulary_obj.name = vocabulary_dict['name'] + + if vocabulary_dict.has_key('tags'): + vocabulary_tag_list_save(vocabulary_dict['tags'], vocabulary_obj, + context) + + return vocabulary_obj + +def tag_dict_save(tag_dict, context): + model = context['model'] + tag = context.get('tag') + if tag: + tag_dict['id'] = tag.id + tag = d.table_dict_save(tag_dict, model.Tag, context) + return tag + +def follower_dict_save(data_dict, context, FollowerClass): + model = context['model'] + session = context['session'] + follower_obj = FollowerClass( + follower_id=model.User.get(context['user']).id, + object_id=data_dict['id']) + session.add(follower_obj) + return follower_obj + + +def resource_view_dict_save(data_dict, context): + model = context['model'] + resource_view = context.get('resource_view') + if resource_view: + data_dict['id'] = resource_view.id + config = {} + for key, value in data_dict.iteritems(): + if key not in model.ResourceView.get_columns(): + config[key] = value + data_dict['config'] = config + + + return d.table_dict_save(data_dict, model.ResourceView, context) diff --git a/venv/lib/python2.7/site-packages/ckan/lib/email_notifications.py b/venv/lib/python2.7/site-packages/ckan/lib/email_notifications.py new file mode 100644 index 00000000..0552e797 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/email_notifications.py @@ -0,0 +1,226 @@ +# encoding: utf-8 + +''' +Code for generating email notifications for users (e.g. email notifications for +new activities in your dashboard activity stream) and emailing them to the +users. + +''' +import datetime +import re + +import ckan.model as model +import ckan.logic as logic +import ckan.lib.base as base + +from ckan.common import ungettext, config + + +def string_to_timedelta(s): + '''Parse a string s and return a standard datetime.timedelta object. + + Handles days, hours, minutes, seconds, and microseconds. + + Accepts strings in these formats: + + 2 days + 14 days + 4:35:00 (hours, minutes and seconds) + 4:35:12.087465 (hours, minutes, seconds and microseconds) + 7 days, 3:23:34 + 7 days, 3:23:34.087465 + .087465 (microseconds only) + + :raises ckan.logic.ValidationError: if the given string does not match any + of the recognised formats + + ''' + patterns = [] + days_only_pattern = '(?P\d+)\s+day(s)?' + patterns.append(days_only_pattern) + hms_only_pattern = '(?P\d?\d):(?P\d\d):(?P\d\d)' + patterns.append(hms_only_pattern) + ms_only_pattern = '.(?P\d\d\d)(?P\d\d\d)' + patterns.append(ms_only_pattern) + hms_and_ms_pattern = hms_only_pattern + ms_only_pattern + patterns.append(hms_and_ms_pattern) + days_and_hms_pattern = '{0},\s+{1}'.format(days_only_pattern, + hms_only_pattern) + patterns.append(days_and_hms_pattern) + days_and_hms_and_ms_pattern = days_and_hms_pattern + ms_only_pattern + patterns.append(days_and_hms_and_ms_pattern) + + for pattern in patterns: + match = re.match('^{0}$'.format(pattern), s) + if match: + break + + if not match: + raise logic.ValidationError('Not a valid time: {0}'.format(s)) + + gd = match.groupdict() + days = int(gd.get('days', '0')) + hours = int(gd.get('hours', '0')) + minutes = int(gd.get('minutes', '0')) + seconds = int(gd.get('seconds', '0')) + milliseconds = int(gd.get('milliseconds', '0')) + microseconds = int(gd.get('microseconds', '0')) + delta = datetime.timedelta(days=days, hours=hours, minutes=minutes, + seconds=seconds, milliseconds=milliseconds, + microseconds=microseconds) + return delta + + +def _notifications_for_activities(activities, user_dict): + '''Return one or more email notifications covering the given activities. + + This function handles grouping multiple activities into a single digest + email. + + :param activities: the activities to consider + :type activities: list of activity dicts like those returned by + ckan.logic.action.get.dashboard_activity_list() + + :returns: a list of email notifications + :rtype: list of dicts each with keys 'subject' and 'body' + + ''' + if not activities: + return [] + + if not user_dict.get('activity_streams_email_notifications'): + return [] + + # We just group all activities into a single "new activity" email that + # doesn't say anything about _what_ new activities they are. + # TODO: Here we could generate some smarter content for the emails e.g. + # say something about the contents of the activities, or single out + # certain types of activity to be sent in their own individual emails, + # etc. + subject = ungettext( + "{n} new activity from {site_title}", + "{n} new activities from {site_title}", + len(activities)).format( + site_title=config.get('ckan.site_title'), + n=len(activities)) + body = base.render( + 'activity_streams/activity_stream_email_notifications.text', + extra_vars={'activities': activities}) + notifications = [{ + 'subject': subject, + 'body': body + }] + + return notifications + + +def _notifications_from_dashboard_activity_list(user_dict, since): + '''Return any email notifications from the given user's dashboard activity + list since `since`. + + ''' + # Get the user's dashboard activity stream. + context = {'model': model, 'session': model.Session, + 'user': user_dict['id']} + activity_list = logic.get_action('dashboard_activity_list')(context, {}) + + # Filter out the user's own activities., so they don't get an email every + # time they themselves do something (we are not Trac). + activity_list = [activity for activity in activity_list + if activity['user_id'] != user_dict['id']] + + # Filter out the old activities. + strptime = datetime.datetime.strptime + fmt = '%Y-%m-%dT%H:%M:%S.%f' + activity_list = [activity for activity in activity_list + if strptime(activity['timestamp'], fmt) > since] + + return _notifications_for_activities(activity_list, user_dict) + + +# A list of functions that provide email notifications for users from different +# sources. Add to this list if you want to implement a new source of email +# notifications. +_notifications_functions = [ + _notifications_from_dashboard_activity_list, + ] + + +def get_notifications(user_dict, since): + '''Return any email notifications for the given user since `since`. + + For example email notifications about activity streams will be returned for + any activities the occurred since `since`. + + :param user_dict: a dictionary representing the user, should contain 'id' + and 'name' + :type user_dict: dictionary + + :param since: datetime after which to return notifications from + :rtype since: datetime.datetime + + :returns: a list of email notifications + :rtype: list of dicts with keys 'subject' and 'body' + + ''' + notifications = [] + for function in _notifications_functions: + notifications.extend(function(user_dict, since)) + return notifications + + +def send_notification(user, email_dict): + '''Email `email_dict` to `user`.''' + import ckan.lib.mailer + + if not user.get('email'): + # FIXME: Raise an exception. + return + + try: + ckan.lib.mailer.mail_recipient(user['display_name'], user['email'], + email_dict['subject'], email_dict['body']) + except ckan.lib.mailer.MailerException: + raise + + +def get_and_send_notifications_for_user(user): + + # Parse the email_notifications_since config setting, email notifications + # from longer ago than this time will not be sent. + email_notifications_since = config.get( + 'ckan.email_notifications_since', '2 days') + email_notifications_since = string_to_timedelta( + email_notifications_since) + email_notifications_since = (datetime.datetime.utcnow() + - email_notifications_since) + + # FIXME: We are accessing model from lib here but I'm not sure what + # else to do unless we add a get_email_last_sent() logic function which + # would only be needed by this lib. + email_last_sent = model.Dashboard.get(user['id']).email_last_sent + activity_stream_last_viewed = ( + model.Dashboard.get(user['id']).activity_stream_last_viewed) + + since = max(email_notifications_since, email_last_sent, + activity_stream_last_viewed) + + notifications = get_notifications(user, since) + # TODO: Handle failures from send_email_notification. + for notification in notifications: + send_notification(user, notification) + + # FIXME: We are accessing model from lib here but I'm not sure what + # else to do unless we add a update_email_last_sent() + # logic function which would only be needed by this lib. + dash = model.Dashboard.get(user['id']) + dash.email_last_sent = datetime.datetime.utcnow() + model.repo.commit() + + +def get_and_send_notifications_for_all_users(): + context = {'model': model, 'session': model.Session, 'ignore_auth': True, + 'keep_email': True} + users = logic.get_action('user_list')(context, {}) + for user in users: + get_and_send_notifications_for_user(user) diff --git a/venv/lib/python2.7/site-packages/ckan/lib/extract.py b/venv/lib/python2.7/site-packages/ckan/lib/extract.py new file mode 100644 index 00000000..6a57ce66 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/extract.py @@ -0,0 +1,42 @@ +# encoding: utf-8 + +import re +from jinja2.ext import babel_extract as extract_jinja2 +import lib.jinja_extensions +from six import string_types + +jinja_extensions = ''' + jinja2.ext.do, jinja2.ext.with_, + ckan.lib.jinja_extensions.SnippetExtension, + ckan.lib.jinja_extensions.CkanExtend, + ckan.lib.jinja_extensions.LinkForExtension, + ckan.lib.jinja_extensions.ResourceExtension, + ckan.lib.jinja_extensions.UrlForStaticExtension, + ckan.lib.jinja_extensions.UrlForExtension + ''' + +def jinja2_cleaner(fileobj, *args, **kw): + # We want to format the messages correctly and intercepting here seems + # the best location + # add our custom tags + kw['options']['extensions'] = jinja_extensions + + raw_extract = extract_jinja2(fileobj, *args, **kw) + + for lineno, func, message, finder in raw_extract: + + if isinstance(message, string_types): + message = lib.jinja_extensions.regularise_html(message) + elif message is not None: + message = (lib.jinja_extensions.regularise_html(message[0]) + ,lib.jinja_extensions.regularise_html(message[1])) + + yield lineno, func, message, finder + + +def extract_ckan(fileobj, *args, **kw): + source = fileobj.read() + output = jinja2_cleaner(fileobj, *args, **kw) + # we've eaten the file so we need to get back to the start + fileobj.seek(0) + return output diff --git a/venv/lib/python2.7/site-packages/ckan/lib/fanstatic_extensions.py b/venv/lib/python2.7/site-packages/ckan/lib/fanstatic_extensions.py new file mode 100644 index 00000000..28ad0e2c --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/lib/fanstatic_extensions.py @@ -0,0 +1,116 @@ +# encoding: utf-8 + +import fanstatic.core as core + + +class CkanCustomRenderer(object): + ''' Allows for in-line js and IE conditionals via fanstatic. ''' + def __init__(self, script=None, renderer=None, condition=None, + other_browsers=False): + self.script = script + self.other_browsers = other_browsers + self.renderer = renderer + start = '' + end = '' + # IE conditionals + if condition: + start = '' + if other_browsers: + start += '' + end = ' [1, 6, 1] ''' + import re + v_str = re.sub(r'[^0-9.]', '', v_str) + return [int(part) for part in v_str.split('.')] + + @classmethod + def _check_ckan_version(cls, min_version=None, max_version=None): + '''Return ``True`` if the CKAN version is greater than or equal to + ``min_version`` and less than or equal to ``max_version``, + return ``False`` otherwise. + + If no ``min_version`` is given, just check whether the CKAN version is + less than or equal to ``max_version``. + + If no ``max_version`` is given, just check whether the CKAN version is + greater than or equal to ``min_version``. + + :param min_version: the minimum acceptable CKAN version, + eg. ``'2.1'`` + :type min_version: string + + :param max_version: the maximum acceptable CKAN version, + eg. ``'2.3'`` + :type max_version: string + + ''' + current = cls._version_str_2_list(cls.ckan.__version__) + + if min_version: + min_required = cls._version_str_2_list(min_version) + if current < min_required: + return False + if max_version: + max_required = cls._version_str_2_list(max_version) + if current > max_required: + return False + return True + + @classmethod + def _requires_ckan_version(cls, min_version, max_version=None): + '''Raise :py:exc:`~ckan.plugins.toolkit.CkanVersionException` if the + CKAN version is not greater than or equal to ``min_version`` and + less then or equal to ``max_version``. + + If no ``max_version`` is given, just check whether the CKAN version is + greater than or equal to ``min_version``. + + Plugins can call this function if they require a certain CKAN version, + other versions of CKAN will crash if a user tries to use the plugin + with them. + + :param min_version: the minimum acceptable CKAN version, + eg. ``'2.1'`` + :type min_version: string + + :param max_version: the maximum acceptable CKAN version, + eg. ``'2.3'`` + :type max_version: string + + ''' + from ckan.exceptions import CkanVersionException + if not cls._check_ckan_version(min_version=min_version, + max_version=max_version): + if not max_version: + error = 'Requires ckan version %s or higher' % min_version + else: + error = 'Requires ckan version between {0} and {1}'.format( + min_version, + max_version + ) + raise CkanVersionException(error) + + @classmethod + def _get_endpoint(cls): + """Returns tuple in format: (controller|blueprint, action|view). + """ + import ckan.common as common + try: + # CKAN >= 2.8 + endpoint = tuple(common.request.endpoint.split('.')) + except AttributeError: + try: + return common.c.controller, common.c.action + except AttributeError: + return (None, None) + # there are some routes('hello_world') that are not using blueprint + # For such case, let's assume that view function is a controller + # itself and action is None. + if len(endpoint) is 1: + return endpoint + (None,) + return endpoint + + def __getattr__(self, name): + ''' return the function/object requested ''' + if not self._toolkit: + self._initialize() + if name in self._toolkit: + return self._toolkit[name] + else: + if name == '__bases__': + return self.__class__.__bases__ + raise AttributeError('`%s` not found in plugins toolkit' % name) + + def __dir__(self): + if not self._toolkit: + self._initialize() + return sorted(self._toolkit.keys()) + + +# https://mail.python.org/pipermail/python-ideas/2012-May/014969.html +sys.modules[__name__] = _Toolkit() diff --git a/venv/lib/python2.7/site-packages/ckan/plugins/toolkit_sphinx_extension.py b/venv/lib/python2.7/site-packages/ckan/plugins/toolkit_sphinx_extension.py new file mode 100644 index 00000000..6ab963d0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/plugins/toolkit_sphinx_extension.py @@ -0,0 +1,187 @@ +# encoding: utf-8 + +'''A Sphinx extension to automatically document CKAN's crazy plugins toolkit, +autodoc-style. + +Sphinx's autodoc extension can document modules or classes, but although it +masquerades as a module CKAN's plugins toolkit is actually neither a module nor +a class, it's an object-instance of a class, and it's an object with weird +__getattr__ behavior too. Autodoc can't handle it, so we have this custom +Sphinx extension to automate documenting it instead. + +This extension plugs into the reading phase of the Sphinx build. It intercepts +the 'toolkit' document (extensions/plugins-toolkit.rst) after Sphinx has read +the reStructuredText source from file. It modifies the source, adding in Sphinx +directives for everything in the plugins toolkit, and then the Sphinx build +continues as normal (just as if the generated reStructuredText had been entered +into plugins-toolkit.rst manually before running Sphinx). + +''' +import inspect +import types + +import ckan.plugins.toolkit as toolkit + + +def setup(app): + '''Setup this Sphinx extension. Called once when initializing Sphinx. + + ''' + # Connect to Sphinx's source-read event, the callback function will be + # called after each source file is read. + app.connect('source-read', source_read) + + +def format_function(name, function, docstring=None): + '''Return a Sphinx .. function:: directive for the given function. + + The directive includes the function's docstring if it has one. + + :param name: the name to give to the function in the directive, + eg. 'get_converter' + :type name: string + + :param function: the function itself + :type function: function + + :param docstring: if given, use this instead of introspecting the function + to find its actual docstring + :type docstring: string + + :returns: a Sphinx .. function:: directive for the function + :rtype: string + + ''' + # The template we'll use to render the Sphinx function directive. + template = ('.. py:function:: ckan.plugins.toolkit.{function}{args}\n' + '\n' + '{docstring}\n' + '\n') + + # Get the arguments of the function, as a string like: + # "(foo, bar=None, ...)" + argstring = inspect.formatargspec(*inspect.getargspec(function)) + + docstring = docstring or inspect.getdoc(function) + if docstring is None: + docstring = '' + else: + # Indent the docstring by 3 spaces, as needed for the Sphinx directive. + docstring = '\n'.join([' ' + line for line in docstring.split('\n')]) + + return template.format(function=name, args=argstring, docstring=docstring) + + +def format_class(name, class_, docstring=None): + '''Return a Sphinx .. class:: directive for the given class. + + The directive includes the class's docstring if it has one. + + :param name: the name to give to the class in the directive, + eg. 'DefaultDatasetForm' + :type name: string + + :param class_: the class itself + :type class_: class + + :param docstring: if given, use this instead of introspecting the class + to find its actual docstring + :type docstring: string + + :returns: a Sphinx .. class:: directive for the class + :rtype: string + + ''' + # The template we'll use to render the Sphinx class directive. + template = ('.. py:class:: ckan.plugins.toolkit.{cls}\n' + '\n' + '{docstring}\n' + '\n') + + docstring = docstring or inspect.getdoc(class_) + if docstring is None: + docstring = '' + else: + # Indent the docstring by 3 spaces, as needed for the Sphinx directive. + docstring = '\n'.join([' ' + line for line in docstring.split('\n')]) + + return template.format(cls=name, docstring=docstring) + + +def format_object(name, object_, docstring=None): + '''Return a Sphinx .. attribute:: directive for the given object. + + The directive includes the object's class's docstring if it has one. + + :param name: the name to give to the object in the directive, + eg. 'request' + :type name: string + + :param object_: the object itself + :type object_: object + + :param docstring: if given, use this instead of introspecting the object + to find its actual docstring + :type docstring: string + + :returns: a Sphinx .. attribute:: directive for the object + :rtype: string + + ''' + # The template we'll use to render the Sphinx attribute directive. + template = ('.. py:attribute:: ckan.plugins.toolkit.{obj}\n' + '\n' + '{docstring}\n' + '\n') + + docstring = docstring or inspect.getdoc(object_) + if docstring is None: + docstring = '' + else: + # Indent the docstring by 3 spaces, as needed for the Sphinx directive. + docstring = '\n'.join([' ' + line for line in docstring.split('\n')]) + + return template.format(obj=name, docstring=docstring) + + +def source_read(app, docname, source): + '''Transform the contents of plugins-toolkit.rst to contain reference docs. + + ''' + # We're only interested in the 'plugins-toolkit' doc (plugins-toolkit.rst). + if docname != 'extensions/plugins-toolkit': + return + + source_ = '' + for name, thing in inspect.getmembers(toolkit): + + # The plugins toolkit can override the docstrings of some of its + # members (e.g. things that are imported from third-party libraries) + # by putting custom docstrings in this docstring_overrides dict. + custom_docstring = toolkit.docstring_overrides.get(name) + + if inspect.isfunction(thing): + source_ += format_function(name, thing, docstring=custom_docstring) + elif inspect.ismethod(thing): + # We document plugins toolkit methods as if they're functions. This + # is correct because the class ckan.plugins.toolkit._Toolkit + # actually masquerades as a module ckan.plugins.toolkit, and you + # call its methods as if they were functions. + source_ += format_function(name, thing, docstring=custom_docstring) + elif inspect.isclass(thing): + source_ += format_class(name, thing, docstring=custom_docstring) + elif isinstance(thing, types.ObjectType): + source_ += format_object(name, thing, docstring=custom_docstring) + + else: + assert False, ("Someone added {name}:{thing} to the plugins " + "toolkit and this Sphinx extension doesn't know " + "how to document that yet. If you're that someone, " + "you need to add a new format_*() function for it " + "here or the docs won't build.".format( + name=name, thing=thing)) + + source[0] += source_ + + # This is useful for debugging the generated RST. + # open('/tmp/source', 'w').write(source[0]) diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/activity-stream.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/activity-stream.js new file mode 100644 index 00000000..16314f5a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/activity-stream.js @@ -0,0 +1,119 @@ +/* Activity stream + * Handle the loading more of activity items within actiivity streams + * + * Options + * - more: are there more items to load + * - context: what's the context for the ajax calls + * - id: what's the id of the context? + * - offset: what's the current offset? + */ +this.ckan.module('activity-stream', function($) { + return { + /* options object can be extended using data-module-* attributes */ + options : { + more: null, + id: null, + context: null, + offset: null, + loading: false + }, + + /* Initialises the module setting up elements and event listeners. + * + * Returns nothing. + */ + initialize: function () { + $.proxyAll(this, /_on/); + var options = this.options; + options.more = (options.more == 'True'); + this._onBuildLoadMore(); + $(window).on('scroll', this._onScrollIntoView); + this._onScrollIntoView(); + }, + + /* Function that tells if el is within the window viewpost + * + * Returns boolean + */ + elementInViewport: function(el) { + var top = el.offsetTop; + var left = el.offsetLeft; + var width = el.offsetWidth; + var height = el.offsetHeight; + while(el.offsetParent) { + el = el.offsetParent; + top += el.offsetTop; + left += el.offsetLeft; + } + return ( + top < (window.pageYOffset + window.innerHeight) && + left < (window.pageXOffset + window.innerWidth) && + (top + height) > window.pageYOffset && + (left + width) > window.pageXOffset + ); + }, + + /* Whenever the window scrolls check if the load more button + * exists, if it's in the view and we're not already loading. + * If all conditions are satisfied... fire a click event on + * the load more button. + * + * Returns nothing + */ + _onScrollIntoView: function() { + var el = $('.load-more a', this.el); + if (el.length == 1) { + var in_viewport = this.elementInViewport(el[0]); + if (in_viewport && !this.options.loading) { + el.trigger('click'); + } + } + }, + + /* If we are able to load more... then attach the ajax request + * to the load more button. + * + * Returns nothing + */ + _onBuildLoadMore: function() { + var options = this.options; + if (options.more) { + $('.load-more', this.el).on('click', 'a', this._onLoadMoreClick); + options.offset = $('.item', this.el).length; + } + }, + + /* Fires when someone clicks the load more button + * ... and if not loading then make the API call to load + * more activities + * + * Returns nothing + */ + _onLoadMoreClick: function (event) { + event.preventDefault(); + var options = this.options; + if (!options.loading) { + options.loading = true; + $('.load-more a', this.el).html(this._('Loading...')).addClass('disabled'); + this.sandbox.client.call('GET', options.context+'_activity_list_html', '?id='+options.id+'&offset='+options.offset, this._onActivitiesLoaded); + } + }, + + /* Callback for after the API call + * + * Returns nothing + */ + _onActivitiesLoaded: function(json) { + var options = this.options; + var result = $(json.result); + options.more = ( result.data('module-more') == 'True' ); + options.offset += 30; + $('.load-less', result).remove(); + $('.load-more', this.el).remove(); + $('li', result).appendTo(this.el); + this._onBuildLoadMore(); + options.loading = false; + } + + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/activity-stream.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/activity-stream.min.js new file mode 100644 index 00000000..3fbe3c04 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/activity-stream.min.js @@ -0,0 +1,2 @@ +this.ckan.module('activity-stream',function($){return{options:{more:null,id:null,context:null,offset:null,loading:false},initialize:function(){$.proxyAll(this,/_on/);var options=this.options;options.more=(options.more=='True');this._onBuildLoadMore();$(window).on('scroll',this._onScrollIntoView);this._onScrollIntoView();},elementInViewport:function(el){var top=el.offsetTop;var left=el.offsetLeft;var width=el.offsetWidth;var height=el.offsetHeight;while(el.offsetParent){el=el.offsetParent;top+=el.offsetTop;left+=el.offsetLeft;} +return(top<(window.pageYOffset+window.innerHeight)&&left<(window.pageXOffset+window.innerWidth)&&(top+height)>window.pageYOffset&&(left+width)>window.pageXOffset);},_onScrollIntoView:function(){var el=$('.load-more a',this.el);if(el.length==1){var in_viewport=this.elementInViewport(el[0]);if(in_viewport&&!this.options.loading){el.trigger('click');}}},_onBuildLoadMore:function(){var options=this.options;if(options.more){$('.load-more',this.el).on('click','a',this._onLoadMoreClick);options.offset=$('.item',this.el).length;}},_onLoadMoreClick:function(event){event.preventDefault();var options=this.options;if(!options.loading){options.loading=true;$('.load-more a',this.el).html(this._('Loading...')).addClass('disabled');this.sandbox.client.call('GET',options.context+'_activity_list_html','?id='+options.id+'&offset='+options.offset,this._onActivitiesLoaded);}},_onActivitiesLoaded:function(json){var options=this.options;var result=$(json.result);options.more=(result.data('module-more')=='True');options.offset+=30;$('.load-less',result).remove();$('.load-more',this.el).remove();$('li',result).appendTo(this.el);this._onBuildLoadMore();options.loading=false;}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/api-info.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/api-info.js new file mode 100644 index 00000000..0271e30e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/api-info.js @@ -0,0 +1,127 @@ +/* Loads the API Info snippet into a modal dialog. Retrieves the snippet + * url from the data-snippet-url on the module element. + * + * template - The url to the template to display in a modal. + * + * Examples + * + * API + * + */ +this.ckan.module('api-info', function (jQuery) { + return { + + /* holds the loaded lightbox */ + modal: null, + + options: { + template: null + }, + + /* Sets up the API info module. + * + * Returns nothing. + */ + initialize: function () { + jQuery.proxyAll(this, /_on/); + + this.el.on('click', this._onClick); + this.el.button(); + }, + + /* Displays a loading message in the button. If false is provided as an + * argument the message is reset. + * + * loading - Resets the message if false. + * + * Examples + * + * module.loading(); // Show + * module.loading(false); // Hide + * + * Returns nothing. + */ + loading: function (loading) { + this.el.button(loading !== false ? 'loading' : 'reset'); + }, + + /* Displays the API info box. + * + * Examples + * + * module.show() + * + * Returns nothing. + */ + show: function () { + var sandbox = this.sandbox, + module = this; + + if (this.modal) { + return this.modal.modal('show'); + } + + this.loadTemplate().done(function (html) { + module.modal = jQuery(html); + module.modal.find('.modal-header :header').append(''); + module.modal.modal().appendTo(sandbox.body); + }); + }, + + /* Hides the modal. + * + * Examples + * + * module.hide(); + * + * Returns nothing. + */ + hide: function () { + if (this.modal) { + this.modal.modal('hide'); + } + }, + + /* Loads the template and returns a promise that on complete will + * receive the html content for the modal. + * + * Examples + * + * module.loadTemplate().then(onSuccess, onError); + * + * Returns a promise instance. + */ + loadTemplate: function () { + if (!this.options.template) { + this.sandbox.notify(this._('There is no API data to load for this resource')); + return jQuery.Deferred().reject().promise(); + } + + if (!this.promise) { + this.loading(); + + // This should use sandbox.client! + this.promise = jQuery.get(this.options.template); + this.promise.then(this._onTemplateSuccess, this._onTemplateError); + } + return this.promise; + }, + + /* Event handler for clicking on the element */ + _onClick: function (event) { + event.preventDefault(); + this.show(); + }, + + /* Success handler for when the template is loaded */ + _onTemplateSuccess: function () { + this.loading(false); + }, + + /* error handler when the template fails to load */ + _onTemplateError: function () { + this.loading(false); + this.sandbox.notify(this._('Failed to load data API information')); + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/api-info.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/api-info.min.js new file mode 100644 index 00000000..87472dd6 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/api-info.min.js @@ -0,0 +1,4 @@ +this.ckan.module('api-info',function(jQuery){return{modal:null,options:{template:null},initialize:function(){jQuery.proxyAll(this,/_on/);this.el.on('click',this._onClick);this.el.button();},loading:function(loading){this.el.button(loading!==false?'loading':'reset');},show:function(){var sandbox=this.sandbox,module=this;if(this.modal){return this.modal.modal('show');} +this.loadTemplate().done(function(html){module.modal=jQuery(html);module.modal.find('.modal-header :header').append('');module.modal.modal().appendTo(sandbox.body);});},hide:function(){if(this.modal){this.modal.modal('hide');}},loadTemplate:function(){if(!this.options.template){this.sandbox.notify(this._('There is no API data to load for this resource'));return jQuery.Deferred().reject().promise();} +if(!this.promise){this.loading();this.promise=jQuery.get(this.options.template);this.promise.then(this._onTemplateSuccess,this._onTemplateError);} +return this.promise;},_onClick:function(event){event.preventDefault();this.show();},_onTemplateSuccess:function(){this.loading(false);},_onTemplateError:function(){this.loading(false);this.sandbox.notify(this._('Failed to load data API information'));}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/autocomplete.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/autocomplete.js new file mode 100644 index 00000000..769f06e6 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/autocomplete.js @@ -0,0 +1,275 @@ +/* An auto-complete module for select and input elements that can pull in + * a list of terms from an API endpoint (provided using data-module-source). + * + * source - A url pointing to an API autocomplete endpoint. + * interval - The interval between requests in milliseconds (default: 1000). + * items - The max number of items to display (default: 10) + * tags - Boolean attribute if true will create a tag input. + * key - A string of the key you want to be the form value to end up on + * from the ajax returned results + * label - A string of the label you want to appear within the dropdown for + * returned results + * + * Examples + * + * // + * + */ +this.ckan.module('autocomplete', function (jQuery) { + return { + /* Options for the module */ + options: { + tags: false, + key: false, + label: false, + items: 10, + source: null, + interval: 300, + dropdownClass: '', + containerClass: '' + }, + + /* Sets up the module, binding methods, creating elements etc. Called + * internally by ckan.module.initialize(); + * + * Returns nothing. + */ + initialize: function () { + jQuery.proxyAll(this, /_on/, /format/); + this.setupAutoComplete(); + }, + + /* Sets up the auto complete plugin. + * + * Returns nothing. + */ + setupAutoComplete: function () { + var settings = { + width: 'resolve', + formatResult: this.formatResult, + formatNoMatches: this.formatNoMatches, + formatInputTooShort: this.formatInputTooShort, + dropdownCssClass: this.options.dropdownClass, + containerCssClass: this.options.containerClass + }; + + // Different keys are required depending on whether the select is + // tags or generic completion. + if (!this.el.is('select')) { + if (this.options.tags) { + settings.tags = this._onQuery; + } else { + settings.query = this._onQuery; + settings.createSearchChoice = this.formatTerm; + } + settings.initSelection = this.formatInitialValue; + } + else { + if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) { + var ieversion=new Number(RegExp.$1); + if (ieversion<=7) {return} + } + } + + var select2 = this.el.select2(settings).data('select2'); + + if (this.options.tags && select2 && select2.search) { + // find the "fake" input created by select2 and add the keypress event. + // This is not part of the plugins API and so may break at any time. + select2.search.on('keydown', this._onKeydown); + } + + // This prevents Internet Explorer from causing a window.onbeforeunload + // even from firing unnecessarily + $('.select2-choice', select2.container).on('click', function() { + return false; + }); + + this._select2 = select2; + }, + + /* Looks up the completions for the current search term and passes them + * into the provided callback function. + * + * The results are formatted for use in the select2 autocomplete plugin. + * + * string - The term to search for. + * fn - A callback function. + * + * Examples + * + * module.getCompletions('cake', function (results) { + * results === {results: []} + * }); + * + * Returns a jqXHR promise. + */ + getCompletions: function (string, fn) { + var parts = this.options.source.split('?'); + var end = parts.pop(); + var source = parts.join('?') + encodeURIComponent(string) + end; + var client = this.sandbox.client; + var options = { + format: function(data) { + var completion_options = jQuery.extend(options, {objects: true}); + return { + results: client.parseCompletions(data, completion_options) + } + }, + key: this.options.key, + label: this.options.label + }; + + return client.getCompletions(source, options, fn); + }, + + /* Looks up the completions for the provided text but also provides a few + * optimisations. If there is no search term it will automatically set + * an empty array. Ajax requests will also be debounced to ensure that + * the server is not overloaded. + * + * string - The term to search for. + * fn - A callback function. + * + * Returns nothing. + */ + lookup: function (string, fn) { + var module = this; + + // Cache the last searched term otherwise we'll end up searching for + // old data. + this._lastTerm = string; + + // Kills previous timeout + clearTimeout(this._debounced); + + // OK, wipe the dropdown before we start ajaxing the completions + fn({results:[]}); + + if (string) { + // Set a timer to prevent the search lookup occurring too often. + this._debounced = setTimeout(function () { + var term = module._lastTerm; + + // Cancel the previous request if it hasn't yet completed. + if (module._last && typeof module._last.abort == 'function') { + module._last.abort(); + } + + module._last = module.getCompletions(term, fn); + }, this.options.interval); + + // This forces the ajax throbber to appear, because we've called the + // callback already and that hides the throbber + $('.select2-search input', this._select2.dropdown).addClass('select2-active'); + } + }, + + /* Formatter for the select2 plugin that returns a string for use in the + * results list with the current term emboldened. + * + * state - The current object that is being rendered. + * container - The element the content will be added to (added in 3.0) + * query - The query object (added in select2 3.0). + * + * + * Returns a text string. + */ + formatResult: function (state, container, query) { + var term = this._lastTerm || null; // same as query.term + + if (container) { + // Append the select id to the element for styling. + container.attr('data-value', state.id); + } + + return state.text.split(term).join(term && term.bold()); + }, + + /* Formatter for the select2 plugin that returns a string used when + * the filter has no matches. + * + * Returns a text string. + */ + formatNoMatches: function (term) { + return !term ? this._('Start typing…') : this._('No matches found'); + }, + + /* Formatter used by the select2 plugin that returns a string when the + * input is too short. + * + * Returns a string. + */ + formatInputTooShort: function (term, min) { + return this.ngettext( + 'Input is too short, must be at least one character', + 'Input is too short, must be at least %(num)d characters', + min + ); + }, + + /* Takes a string and converts it into an object used by the select2 plugin. + * + * term - The term to convert. + * + * Returns an object for use in select2. + */ + formatTerm: function (term) { + term = jQuery.trim(term || ''); + + // Need to replace comma with a unicode character to trick the plugin + // as it won't split this into multiple items. + return {id: term.replace(/,/g, '\u002C'), text: term}; + }, + + /* Callback function that parses the initial field value. + * + * element - The initialized input element wrapped in jQuery. + * callback - A callback to run once the formatting is complete. + * + * Returns a term object or an array depending on the type. + */ + formatInitialValue: function (element, callback) { + var value = jQuery.trim(element.val() || ''); + var formatted; + + if (this.options.tags) { + formatted = jQuery.map(value.split(","), this.formatTerm); + } else { + formatted = this.formatTerm(value); + } + + // Select2 v3.0 supports a callback for async calls. + if (typeof callback === 'function') { + callback(formatted); + } + + return formatted; + }, + + /* Callback triggered when the select2 plugin needs to make a request. + * + * Returns nothing. + */ + _onQuery: function (options) { + if (options) { + this.lookup(options.term, options.callback); + } + }, + + /* Called when a key is pressed. If the key is a comma we block it and + * then simulate pressing return. + * + * Returns nothing. + */ + _onKeydown: function (event) { + if (typeof event.key !== 'undefined' ? event.key === ',' : event.which === 188) { + event.preventDefault(); + setTimeout(function () { + var e = jQuery.Event("keydown", { which: 13 }); + jQuery(event.target).trigger(e); + }, 10); + } + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/autocomplete.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/autocomplete.min.js new file mode 100644 index 00000000..06219fb5 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/autocomplete.min.js @@ -0,0 +1,9 @@ +this.ckan.module('autocomplete',function(jQuery){return{options:{tags:false,key:false,label:false,items:10,source:null,interval:300,dropdownClass:'',containerClass:''},initialize:function(){jQuery.proxyAll(this,/_on/,/format/);this.setupAutoComplete();},setupAutoComplete:function(){var settings={width:'resolve',formatResult:this.formatResult,formatNoMatches:this.formatNoMatches,formatInputTooShort:this.formatInputTooShort,dropdownCssClass:this.options.dropdownClass,containerCssClass:this.options.containerClass};if(!this.el.is('select')){if(this.options.tags){settings.tags=this._onQuery;}else{settings.query=this._onQuery;settings.createSearchChoice=this.formatTerm;} +settings.initSelection=this.formatInitialValue;} +else{if(/MSIE (\d+\.\d+);/.test(navigator.userAgent)){var ieversion=new Number(RegExp.$1);if(ieversion<=7){return}}} +var select2=this.el.select2(settings).data('select2');if(this.options.tags&&select2&&select2.search){select2.search.on('keydown',this._onKeydown);} +$('.select2-choice',select2.container).on('click',function(){return false;});this._select2=select2;},getCompletions:function(string,fn){var parts=this.options.source.split('?');var end=parts.pop();var source=parts.join('?')+encodeURIComponent(string)+end;var client=this.sandbox.client;var options={format:function(data){var completion_options=jQuery.extend(options,{objects:true});return{results:client.parseCompletions(data,completion_options)}},key:this.options.key,label:this.options.label};return client.getCompletions(source,options,fn);},lookup:function(string,fn){var module=this;this._lastTerm=string;clearTimeout(this._debounced);fn({results:[]});if(string){this._debounced=setTimeout(function(){var term=module._lastTerm;if(module._last&&typeof module._last.abort=='function'){module._last.abort();} +module._last=module.getCompletions(term,fn);},this.options.interval);$('.select2-search input',this._select2.dropdown).addClass('select2-active');}},formatResult:function(state,container,query){var term=this._lastTerm||null;if(container){container.attr('data-value',state.id);} +return state.text.split(term).join(term&&term.bold());},formatNoMatches:function(term){return!term?this._('Start typing…'):this._('No matches found');},formatInputTooShort:function(term,min){return this.ngettext('Input is too short, must be at least one character','Input is too short, must be at least %(num)d characters',min);},formatTerm:function(term){term=jQuery.trim(term||'');return{id:term.replace(/,/g,'\u002C'),text:term};},formatInitialValue:function(element,callback){var value=jQuery.trim(element.val()||'');var formatted;if(this.options.tags){formatted=jQuery.map(value.split(","),this.formatTerm);}else{formatted=this.formatTerm(value);} +if(typeof callback==='function'){callback(formatted);} +return formatted;},_onQuery:function(options){if(options){this.lookup(options.term,options.callback);}},_onKeydown:function(event){if(typeof event.key!=='undefined'?event.key===',':event.which===188){event.preventDefault();setTimeout(function(){var e=jQuery.Event("keydown",{which:13});jQuery(event.target).trigger(e);},10);}}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/basic-form.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/basic-form.js new file mode 100644 index 00000000..37915a61 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/basic-form.js @@ -0,0 +1,24 @@ +this.ckan.module('basic-form', function (jQuery) { + return { + initialize: function () { + var message = this._('There are unsaved modifications to this form'); + + $.proxyAll(this, /_on/); + + this.el.incompleteFormWarning(message); + + // Disable the submit button on form submit, to prevent multiple + // consecutive form submissions. + this.el.on('submit', this._onSubmit); + }, + _onSubmit: function () { + + // The button is not disabled immediately so that its value can be sent + // the first time the form is submitted, because the "save" field is + // used in the backend. + setTimeout(function() { + this.el.find('button[name="save"]').attr('disabled', true); + }.bind(this), 0); + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/basic-form.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/basic-form.min.js new file mode 100644 index 00000000..0f9142f4 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/basic-form.min.js @@ -0,0 +1 @@ +this.ckan.module('basic-form',function(jQuery){return{initialize:function(){var message=this._('There are unsaved modifications to this form');$.proxyAll(this,/_on/);this.el.incompleteFormWarning(message);this.el.on('submit',this._onSubmit);},_onSubmit:function(){setTimeout(function(){this.el.find('button[name="save"]').attr('disabled',true);}.bind(this),0);}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/confirm-action.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/confirm-action.js new file mode 100644 index 00000000..8afd65a8 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/confirm-action.js @@ -0,0 +1,128 @@ +this.ckan.module('confirm-action', function (jQuery) { + return { + /* An object of module options */ + options: { + /* Content can be overriden by setting data-module-content to a + * *translated* string inside the template, e.g. + * + * + * {{ _('Delete') }} + * + */ + content: '', + + /* This is part of the old i18n system and is kept for backwards- + * compatibility for templates which set the content via the + * `i18n.content` attribute instead of via the `content` attribute + * as described above. + */ + i18n: { + content: '', + }, + + template: [ + '' + ].join('\n') + }, + + /* Sets up the event listeners for the object. Called internally by + * module.createInstance(). + * + * Returns nothing. + */ + initialize: function () { + jQuery.proxyAll(this, /_on/); + this.el.on('click', this._onClick); + }, + + /* Presents the user with a confirm dialogue to ensure that they wish to + * continue with the current action. + * + * Examples + * + * jQuery('.delete').click(function () { + * module.confirm(); + * }); + * + * Returns nothing. + */ + confirm: function () { + this.sandbox.body.append(this.createModal()); + this.modal.modal('show'); + + // Center the modal in the middle of the screen. + this.modal.css({ + 'margin-top': this.modal.height() * -0.5, + 'top': '50%' + }); + }, + + /* Performs the action for the current item. + * + * Returns nothing. + */ + performAction: function () { + // create a form and submit it to confirm the deletion + var form = jQuery('
', { + action: this.el.attr('href'), + method: 'POST' + }); + form.appendTo('body').submit(); + }, + + /* Creates the modal dialog, attaches event listeners and localised + * strings. + * + * Returns the newly created element. + */ + createModal: function () { + if (!this.modal) { + var element = this.modal = jQuery(this.options.template); + element.on('click', '.btn-primary', this._onConfirmSuccess); + element.on('click', '.btn-cancel', this._onConfirmCancel); + element.modal({show: false}); + + element.find('.modal-title').text(this._('Please Confirm Action')); + var content = this.options.content || + this.options.i18n.content || /* Backwards-compatibility */ + this._('Are you sure you want to perform this action?'); + element.find('.modal-body').text(content); + element.find('.btn-primary').text(this._('Confirm')); + element.find('.btn-cancel').text(this._('Cancel')); + } + return this.modal; + }, + + /* Event handler that displays the confirm dialog */ + _onClick: function (event) { + event.preventDefault(); + this.confirm(); + }, + + /* Event handler for the success event */ + _onConfirmSuccess: function (event) { + this.performAction(); + }, + + /* Event handler for the cancel event */ + _onConfirmCancel: function (event) { + this.modal.modal('hide'); + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/confirm-action.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/confirm-action.min.js new file mode 100644 index 00000000..900e8762 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/confirm-action.min.js @@ -0,0 +1,2 @@ +this.ckan.module('confirm-action',function(jQuery){return{options:{content:'',i18n:{content:'',},template:[''].join('\n')},initialize:function(){jQuery.proxyAll(this,/_on/);this.el.on('click',this._onClick);},confirm:function(){this.sandbox.body.append(this.createModal());this.modal.modal('show');this.modal.css({'margin-top':this.modal.height()*-0.5,'top':'50%'});},performAction:function(){var form=jQuery('',{action:this.el.attr('href'),method:'POST'});form.appendTo('body').submit();},createModal:function(){if(!this.modal){var element=this.modal=jQuery(this.options.template);element.on('click','.btn-primary',this._onConfirmSuccess);element.on('click','.btn-cancel',this._onConfirmCancel);element.modal({show:false});element.find('.modal-title').text(this._('Please Confirm Action'));var content=this.options.content||this.options.i18n.content||this._('Are you sure you want to perform this action?');element.find('.modal-body').text(content);element.find('.btn-primary').text(this._('Confirm'));element.find('.btn-cancel').text(this._('Cancel'));} +return this.modal;},_onClick:function(event){event.preventDefault();this.confirm();},_onConfirmSuccess:function(event){this.performAction();},_onConfirmCancel:function(event){this.modal.modal('hide');}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/custom-fields.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/custom-fields.js new file mode 100644 index 00000000..189b0031 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/custom-fields.js @@ -0,0 +1,99 @@ +/* Module for working with multiple custom field inputs. This will create + * a new field when the user enters text into the last field key. It also + * gives a visual indicator when fields are removed by disabling them. + * + * See the snippets/custom_form_fields.html for an example. + */ +this.ckan.module('custom-fields', function (jQuery) { + return { + options: { + /* The selector used for each custom field wrapper */ + fieldSelector: '.control-custom' + }, + + /* Initializes the module and attaches custom event listeners. This + * is called internally by ckan.module.initialize(). + * + * Returns nothing. + */ + initialize: function () { + jQuery.proxyAll(this, /_on/); + + var delegated = this.options.fieldSelector + ':last input:first'; + this.el.on('change', delegated, this._onChange); + this.el.on('change', ':checkbox', this._onRemove); + }, + + /* Creates a new field and appends it to the list. This currently works by + * cloning and erasing an existing input rather than using a template. In + * future using a template might be more appropriate. + * + * element - Another custom field element to wrap. + * + * Returns nothing. + */ + newField: function (element) { + this.el.append(this.cloneField(element)); + }, + + /* Clones the provided element, wipes it's content and increments it's + * for, id and name fields (if possible). + * + * current - A custom field to clone. + * + * Returns a newly created custom field element. + */ + cloneField: function (current) { + return this.resetField(jQuery(current).clone()); + }, + + /* Wipes the contents of the field provided and increments it's name, id + * and for attributes. + * + * field - A custom field to wipe. + * + * Returns the wiped element. + */ + resetField: function (field) { + function increment(index, string) { + return (string || '').replace(/\d+/, function (int) { return 1 + parseInt(int, 10); }); + } + + var input = field.find(':input'); + input.val('').attr('id', increment).attr('name', increment); + + var label = field.find('label'); + label.text(increment).attr('for', increment); + + return field; + }, + + /* Disables the provided field and input elements. Can be re-enabled by + * passing false as the second argument. + * + * field - The field to disable. + * disable - If false re-enables the element. + * + * Returns nothing. + */ + disableField: function (field, disable) { + field.toggleClass('disabled', disable !== false); + }, + + /* Event handler that fires when the last key in the custom field block + * changes. + */ + _onChange: function (event) { + if (event.target.value !== '') { + var parent = jQuery(event.target).parents(this.options.fieldSelector); + this.newField(parent); + } + }, + + /* Event handler called when the remove checkbox is checked */ + _onRemove: function (event) { + var parent = jQuery(event.target).parents(this.options.fieldSelector); + this.disableField(parent, event.target.checked); + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/custom-fields.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/custom-fields.min.js new file mode 100644 index 00000000..9dc4ac8b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/custom-fields.min.js @@ -0,0 +1,2 @@ +this.ckan.module('custom-fields',function(jQuery){return{options:{fieldSelector:'.control-custom'},initialize:function(){jQuery.proxyAll(this,/_on/);var delegated=this.options.fieldSelector+':last input:first';this.el.on('change',delegated,this._onChange);this.el.on('change',':checkbox',this._onRemove);},newField:function(element){this.el.append(this.cloneField(element));},cloneField:function(current){return this.resetField(jQuery(current).clone());},resetField:function(field){function increment(index,string){return(string||'').replace(/\d+/,function(int){return 1+parseInt(int,10);});} +var input=field.find(':input');input.val('').attr('id',increment).attr('name',increment);var label=field.find('label');label.text(increment).attr('for',increment);return field;},disableField:function(field,disable){field.toggleClass('disabled',disable!==false);},_onChange:function(event){if(event.target.value!==''){var parent=jQuery(event.target).parents(this.options.fieldSelector);this.newField(parent);}},_onRemove:function(event){var parent=jQuery(event.target).parents(this.options.fieldSelector);this.disableField(parent,event.target.checked);}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dashboard.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dashboard.js new file mode 100644 index 00000000..965340cb --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dashboard.js @@ -0,0 +1,86 @@ +/* User Dashboard + * Handles the filter dropdown menu and the reduction of the notifications number + * within the header to zero + * + * Examples + * + *
+ * + */ +this.ckan.module('dashboard', function ($) { + return { + button: null, + popover: null, + searchTimeout: null, + + /* Initialises the module setting up elements and event listeners. + * + * Returns nothing. + */ + initialize: function () { + $.proxyAll(this, /_on/); + this.button = $('#followee-filter .btn'). + on('click', this._onShowFolloweeDropdown); + var title = this.button.prop('title'); + this.button.popover({ + placement: 'bottom', + title: 'Filter', + html: true, + content: $('#followee-popover').html() + }); + this.button.prop('title', title); + this.popover = this.button.data('bs.popover').tip().addClass('popover-followee'); + }, + + /* Handles click event on the 'show me:' dropdown button + * + * Returns nothing. + */ + _onShowFolloweeDropdown: function() { + this.button.toggleClass('active'); + if (this.button.hasClass('active')) { + setTimeout(this._onInitSearch, 100); + } + return false; + }, + + /* Handles focusing on the input and making sure that the keyup + * even is applied to the input + * + * Returns nothing. + */ + _onInitSearch: function() { + var input = $('input', this.popover); + if (!input.hasClass('inited')) { + input. + on('keyup', this._onSearchKeyUp). + addClass('inited'); + } + input.focus(); + }, + + /* Handles the keyup event + * + * Returns nothing. + */ + _onSearchKeyUp: function() { + clearTimeout(this.searchTimeout); + this.searchTimeout = setTimeout(this._onSearchKeyUpTimeout, 300); + }, + + /* Handles the actual filtering of search results + * + * Returns nothing. + */ + _onSearchKeyUpTimeout: function() { + var input = $('input', this.popover); + var q = input.val().toLowerCase(); + if (q) { + $('li', this.popover).hide(); + $('li.everything, [data-search^="' + q + '"]', this.popover).show(); + } else { + $('li', this.popover).show(); + } + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dashboard.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dashboard.min.js new file mode 100644 index 00000000..502ffdb3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dashboard.min.js @@ -0,0 +1,3 @@ +this.ckan.module('dashboard',function($){return{button:null,popover:null,searchTimeout:null,initialize:function(){$.proxyAll(this,/_on/);this.button=$('#followee-filter .btn').on('click',this._onShowFolloweeDropdown);var title=this.button.prop('title');this.button.popover({placement:'bottom',title:'Filter',html:true,content:$('#followee-popover').html()});this.button.prop('title',title);this.popover=this.button.data('bs.popover').tip().addClass('popover-followee');},_onShowFolloweeDropdown:function(){this.button.toggleClass('active');if(this.button.hasClass('active')){setTimeout(this._onInitSearch,100);} +return false;},_onInitSearch:function(){var input=$('input',this.popover);if(!input.hasClass('inited')){input.on('keyup',this._onSearchKeyUp).addClass('inited');} +input.focus();},_onSearchKeyUp:function(){clearTimeout(this.searchTimeout);this.searchTimeout=setTimeout(this._onSearchKeyUpTimeout,300);},_onSearchKeyUpTimeout:function(){var input=$('input',this.popover);var q=input.val().toLowerCase();if(q){$('li',this.popover).hide();$('li.everything, [data-search^="'+q+'"]',this.popover).show();}else{$('li',this.popover).show();}}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/data-viewer.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/data-viewer.js new file mode 100644 index 00000000..5b9cbafc --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/data-viewer.js @@ -0,0 +1,54 @@ +// data viewer module +// resizes the iframe when the content is loaded +this.ckan.module('data-viewer', function (jQuery) { + return { + options: { + timeout: 200, + minHeight: 400, + padding: 30 + }, + + initialize: function () { + jQuery.proxyAll(this, /_on/); + this.el.on('load', this._onLoad); + this._FirefoxFix(); + this.sandbox.subscribe('data-viewer-error', this._onDataViewerError); + }, + + _onDataViewerError: function(message) { + var parent = this.el.parent(); + $('.data-viewer-error .collapse', parent).html(message); + $('.data-viewer-error', parent).removeClass('js-hide'); + this.el.hide(); + }, + + _onLoad: function() { + var self = this; + var loc = $('body').data('site-root'); + // see if page is in part of the same domain + if (this.el.attr('src').substring(0, loc.length) === loc) { + this._recalibrate(); + setInterval(function() { + self._recalibrate(); + }, this.options.timeout); + } else { + this.el.css('height', 600); + } + }, + + _recalibrate: function() { + var height = this.el.contents().find('body').outerHeight(true); + height = Math.max(height, this.options.minHeight); + this.el.css('height', height + this.options.padding); + }, + + // firefox caches iframes so force it to get fresh content + _FirefoxFix: function() { + if(/#$/.test(this.el.src)) { + this.el.src = this.el.src.substr(0, this.src.length - 1); + } else { + this.el.src = this.el.src + '#'; + } + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/data-viewer.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/data-viewer.min.js new file mode 100644 index 00000000..0ea13506 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/data-viewer.min.js @@ -0,0 +1 @@ +this.ckan.module('data-viewer',function(jQuery){return{options:{timeout:200,minHeight:400,padding:30},initialize:function(){jQuery.proxyAll(this,/_on/);this.el.on('load',this._onLoad);this._FirefoxFix();this.sandbox.subscribe('data-viewer-error',this._onDataViewerError);},_onDataViewerError:function(message){var parent=this.el.parent();$('.data-viewer-error .collapse',parent).html(message);$('.data-viewer-error',parent).removeClass('js-hide');this.el.hide();},_onLoad:function(){var self=this;var loc=$('body').data('site-root');if(this.el.attr('src').substring(0,loc.length)===loc){this._recalibrate();setInterval(function(){self._recalibrate();},this.options.timeout);}else{this.el.css('height',600);}},_recalibrate:function(){var height=this.el.contents().find('body').outerHeight(true);height=Math.max(height,this.options.minHeight);this.el.css('height',height+this.options.padding);},_FirefoxFix:function(){if(/#$/.test(this.el.src)){this.el.src=this.el.src.substr(0,this.src.length-1);}else{this.el.src=this.el.src+'#';}}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dataset-visibility.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dataset-visibility.js new file mode 100644 index 00000000..4d9148f2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dataset-visibility.js @@ -0,0 +1,32 @@ +/* Dataset visibility toggler + * When no organization is selected in the org dropdown then set visibility to + * public always and disable dropdown + */ +this.ckan.module('dataset-visibility', function ($) { + return { + currentValue: false, + options: { + organizations: $('#field-organizations'), + visibility: $('#field-private'), + currentValue: null + }, + initialize: function() { + $.proxyAll(this, /_on/); + this.options.currentValue = this.options.visibility.val(); + this.options.organizations.on('change', this._onOrganizationChange); + this._onOrganizationChange(); + }, + _onOrganizationChange: function() { + var value = this.options.organizations.val(); + if (value) { + this.options.visibility + .prop('disabled', false) + .val(this.options.currentValue); + } else { + this.options.visibility + .prop('disabled', true) + .val('False'); + } + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dataset-visibility.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dataset-visibility.min.js new file mode 100644 index 00000000..5534893d --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/dataset-visibility.min.js @@ -0,0 +1 @@ +this.ckan.module('dataset-visibility',function($){return{currentValue:false,options:{organizations:$('#field-organizations'),visibility:$('#field-private'),currentValue:null},initialize:function(){$.proxyAll(this,/_on/);this.options.currentValue=this.options.visibility.val();this.options.organizations.on('change',this._onOrganizationChange);this._onOrganizationChange();},_onOrganizationChange:function(){var value=this.options.organizations.val();if(value){this.options.visibility.prop('disabled',false).val(this.options.currentValue);}else{this.options.visibility.prop('disabled',true).val('False');}}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/follow.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/follow.js new file mode 100644 index 00000000..40438f30 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/follow.js @@ -0,0 +1,78 @@ +/* Follow buttons + * Handles calling the API to follow the current user + * + * action - This being the action that the button should perform. Currently: "follow" or "unfollow" + * type - The being the type of object the user is trying to support. Currently: "user", "group" or "dataset" + * id - id of the objec the user is trying to follow + * loading - State management helper + * + * Examples + * + * Follow User + * + */ +this.ckan.module('follow', function($) { + return { + /* options object can be extended using data-module-* attributes */ + options : { + action: null, + type: null, + id: null, + loading: false + }, + + /* Initialises the module setting up elements and event listeners. + * + * Returns nothing. + */ + initialize: function () { + $.proxyAll(this, /_on/); + this.el.on('click', this._onClick); + }, + + /* Handles the clicking of the follow button + * + * event - An event object. + * + * Returns nothing. + */ + _onClick: function(event) { + var options = this.options; + if ( + options.action + && options.type + && options.id + && !options.loading + ) { + event.preventDefault(); + var client = this.sandbox.client; + var path = options.action + '_' + options.type; + options.loading = true; + this.el.addClass('disabled'); + client.call('POST', path, { id : options.id }, this._onClickLoaded); + } + }, + + /* Fired after the call to the API to either follow or unfollow + * + * json - The return json from the follow / unfollow API call + * + * Returns nothing. + */ + _onClickLoaded: function(json) { + var options = this.options; + var sandbox = this.sandbox; + var oldAction = options.action; + options.loading = false; + this.el.removeClass('disabled'); + if (options.action == 'follow') { + options.action = 'unfollow'; + this.el.html(' ' + this._('Unfollow')).removeClass('btn-success').addClass('btn-danger'); + } else { + options.action = 'follow'; + this.el.html(' ' + this._('Follow')).removeClass('btn-danger').addClass('btn-success'); + } + sandbox.publish('follow-' + oldAction + '-' + options.id); + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/follow.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/follow.min.js new file mode 100644 index 00000000..092aa29f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/follow.min.js @@ -0,0 +1,2 @@ +this.ckan.module('follow',function($){return{options:{action:null,type:null,id:null,loading:false},initialize:function(){$.proxyAll(this,/_on/);this.el.on('click',this._onClick);},_onClick:function(event){var options=this.options;if(options.action&&options.type&&options.id&&!options.loading){event.preventDefault();var client=this.sandbox.client;var path=options.action+'_'+options.type;options.loading=true;this.el.addClass('disabled');client.call('POST',path,{id:options.id},this._onClickLoaded);}},_onClickLoaded:function(json){var options=this.options;var sandbox=this.sandbox;var oldAction=options.action;options.loading=false;this.el.removeClass('disabled');if(options.action=='follow'){options.action='unfollow';this.el.html(' '+this._('Unfollow')).removeClass('btn-success').addClass('btn-danger');}else{options.action='follow';this.el.html(' '+this._('Follow')).removeClass('btn-danger').addClass('btn-success');} +sandbox.publish('follow-'+oldAction+'-'+options.id);}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/followers-counter.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/followers-counter.js new file mode 100644 index 00000000..b6441f62 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/followers-counter.js @@ -0,0 +1,93 @@ +/* Updates the Followers counter in the UI when the Follow/Unfollow button +* is clicked. +* +* id - id of the object the user is trying to follow/unfollow. +* num_followers - Number of followers the object has. +* +* Example +* +*
+* 6 +*
+* +*/ +this.ckan.module('followers-counter', function($) { + 'use strict'; + + return { + options: { + id: null, + num_followers: 0 + }, + + /* Subscribe to events when the Follow/Unfollow button is clicked. + * + * Returns nothing. + */ + initialize: function() { + $.proxyAll(this, /_on/); + + this.counterEl = this.$('span'); + this.objId = this.options.id; + + this.sandbox.subscribe('follow-follow-' + this.objId, this._onFollow); + this.sandbox.subscribe('follow-unfollow-' + this.objId, this._onUnfollow); + }, + + /* Calls a function to update the counter when the Follow button is clicked. + * + * Returns nothing. + */ + _onFollow: function() { + this._updateCounter({action: 'follow'}); + }, + + /* Calls a function to update the counter when the Unfollow button is clicked. + * + * Returns nothing. + */ + _onUnfollow: function() { + this._updateCounter({action: 'unfollow'}); + }, + + /* Handles updating the UI for Followers counter. + * + * Returns nothing. + */ + _updateCounter: function(options) { + var locale = $('html').attr('lang'); + var action = options.action; + var incrementedFollowers; + + locale = locale ? locale.replace('_', '-') : locale; + + if (action === 'follow') { + incrementedFollowers = (++this.options.num_followers).toLocaleString(locale); + } else if (action === 'unfollow') { + incrementedFollowers = (--this.options.num_followers).toLocaleString(locale); + } + + // Only update the value if it's less than 1000, because for larger + // numbers the change won't be noticeable since the value is converted + // to SI number abbreviated with "k", "m" and so on. + if (this.options.num_followers < 1000) { + this.counterEl.text(incrementedFollowers); + this.counterEl.removeAttr('title'); + } else { + this.counterEl.attr('title', incrementedFollowers); + } + }, + + /* Remove any subscriptions to prevent memory leaks. This function is + * called when a module element is removed from the page. + * + * Returns nothing. + */ + teardown: function() { + this.sandbox.unsubscribe('follow-follow-' + this.objId, this._onFollow); + this.sandbox.unsubscribe('follow-unfollow-' + this.objId, this._onUnfollow); + } + } +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/followers-counter.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/followers-counter.min.js new file mode 100644 index 00000000..31d39796 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/followers-counter.min.js @@ -0,0 +1,2 @@ +this.ckan.module('followers-counter',function($){'use strict';return{options:{id:null,num_followers:0},initialize:function(){$.proxyAll(this,/_on/);this.counterEl=this.$('span');this.objId=this.options.id;this.sandbox.subscribe('follow-follow-'+this.objId,this._onFollow);this.sandbox.subscribe('follow-unfollow-'+this.objId,this._onUnfollow);},_onFollow:function(){this._updateCounter({action:'follow'});},_onUnfollow:function(){this._updateCounter({action:'unfollow'});},_updateCounter:function(options){var locale=$('html').attr('lang');var action=options.action;var incrementedFollowers;locale=locale?locale.replace('_','-'):locale;if(action==='follow'){incrementedFollowers=(++this.options.num_followers).toLocaleString(locale);}else if(action==='unfollow'){incrementedFollowers=(--this.options.num_followers).toLocaleString(locale);} +if(this.options.num_followers<1000){this.counterEl.text(incrementedFollowers);this.counterEl.removeAttr('title');}else{this.counterEl.attr('title',incrementedFollowers);}},teardown:function(){this.sandbox.unsubscribe('follow-follow-'+this.objId,this._onFollow);this.sandbox.unsubscribe('follow-unfollow-'+this.objId,this._onUnfollow);}}}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/image-upload.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/image-upload.js new file mode 100644 index 00000000..49255b71 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/image-upload.js @@ -0,0 +1,280 @@ + /* Image Upload + * + */ +this.ckan.module('image-upload', function($) { + return { + /* options object can be extended using data-module-* attributes */ + options: { + is_url: false, + is_upload: false, + field_upload: 'image_upload', + field_url: 'image_url', + field_clear: 'clear_upload', + field_name: 'name', + upload_label: '' + }, + + /* Should be changed to true if user modifies resource's name + * + * @type {Boolean} + */ + _nameIsDirty: false, + + /* Initialises the module setting up elements and event listeners. + * + * Returns nothing. + */ + initialize: function () { + $.proxyAll(this, /_on/); + var options = this.options; + + // firstly setup the fields + var field_upload = 'input[name="' + options.field_upload + '"]'; + var field_url = 'input[name="' + options.field_url + '"]'; + var field_clear = 'input[name="' + options.field_clear + '"]'; + var field_name = 'input[name="' + options.field_name + '"]'; + + this.input = $(field_upload, this.el); + this.field_url = $(field_url, this.el).parents('.form-group'); + this.field_image = this.input.parents('.form-group'); + this.field_url_input = $('input', this.field_url); + this.field_name = this.el.parents('form').find(field_name); + // this is the location for the upload/link data/image label + this.label_location = $('label[for="field-image-url"]'); + // determines if the resource is a data resource + this.is_data_resource = (this.options.field_url === 'url') && (this.options.field_upload === 'upload'); + + // Is there a clear checkbox on the form already? + var checkbox = $(field_clear, this.el); + if (checkbox.length > 0) { + checkbox.parents('.form-group').remove(); + } + + // Adds the hidden clear input to the form + this.field_clear = $('') + .appendTo(this.el); + + // Button to set the field to be a URL + this.button_url = $('' + + '' + + this._('Link') + '') + .prop('title', this._('Link to a URL on the internet (you can also link to an API)')) + .on('click', this._onFromWeb) + .insertAfter(this.input); + + // Button to attach local file to the form + this.button_upload = $('' + + '' + + this._('Upload') + '') + .insertAfter(this.input); + + // Button for resetting the form when there is a URL set + var removeText = this._('Remove'); + $('' + + removeText + '') + .prop('title', removeText) + .on('click', this._onRemove) + .insertBefore(this.field_url_input); + + // Update the main label (this is displayed when no data/image has been uploaded/linked) + $('label[for="field-image-upload"]').text(options.upload_label || this._('Image')); + + // Setup the file input + this.input + .on('mouseover', this._onInputMouseOver) + .on('mouseout', this._onInputMouseOut) + .on('change', this._onInputChange) + .prop('title', this._('Upload a file on your computer')) + .css('width', this.button_upload.outerWidth()); + + // Fields storage. Used in this.changeState + this.fields = $('') + .add(this.button_upload) + .add(this.button_url) + .add(this.input) + .add(this.field_url) + .add(this.field_image); + + // Disables autoName if user modifies name field + this.field_name + .on('change', this._onModifyName); + // Disables autoName if resource name already has value, + // i.e. we on edit page + if (this.field_name.val()){ + this._nameIsDirty = true; + } + + if (options.is_url) { + this._showOnlyFieldUrl(); + + this._updateUrlLabel(this._('URL')); + } else if (options.is_upload) { + this._showOnlyFieldUrl(); + + this.field_url_input.prop('readonly', true); + // If the data is an uploaded file, the filename will display rather than whole url of the site + var filename = this._fileNameFromUpload(this.field_url_input.val()); + this.field_url_input.val(filename); + + this._updateUrlLabel(this._('File')); + } else { + this._showOnlyButtons(); + } + }, + + /* Quick way of getting just the filename from the uri of the resource data + * + * url - The url of the uploaded data file + * + * Returns String. + */ + _fileNameFromUpload: function(url) { + // If it's a local CKAN image return the entire URL. + if (/^\/base\/images/.test(url)) { + return url; + } + + // remove fragment (#) + url = url.substring(0, (url.indexOf("#") === -1) ? url.length : url.indexOf("#")); + // remove query string + url = url.substring(0, (url.indexOf("?") === -1) ? url.length : url.indexOf("?")); + // extract the filename + url = url.substring(url.lastIndexOf("/") + 1, url.length); + + return url; // filename + }, + + /* Update the `this.label_location` text + * + * If the upload/link is for a data resource, rather than an image, + * the text for label[for="field-image-url"] will be updated. + * + * label_text - The text for the label of an uploaded/linked resource + * + * Returns nothing. + */ + _updateUrlLabel: function(label_text) { + if (! this.is_data_resource) { + return; + } + + this.label_location.text(label_text); + }, + + /* Event listener for when someone sets the field to URL mode + * + * Returns nothing. + */ + _onFromWeb: function() { + this._showOnlyFieldUrl(); + + this.field_url_input.focus() + .on('blur', this._onFromWebBlur); + + if (this.options.is_upload) { + this.field_clear.val('true'); + } + + this._updateUrlLabel(this._('URL')); + }, + + /* Event listener for resetting the field back to the blank state + * + * Returns nothing. + */ + _onRemove: function() { + this._showOnlyButtons(); + + this.field_url_input.val(''); + this.field_url_input.prop('readonly', false); + + this.field_clear.val('true'); + }, + + /* Event listener for when someone chooses a file to upload + * + * Returns nothing. + */ + _onInputChange: function() { + var file_name = this.input.val().split(/^C:\\fakepath\\/).pop(); + this.field_url_input.val(file_name); + this.field_url_input.prop('readonly', true); + + this.field_clear.val(''); + + this._showOnlyFieldUrl(); + + this._autoName(file_name); + + this._updateUrlLabel(this._('File')); + }, + + /* Show only the buttons, hiding all others + * + * Returns nothing. + */ + _showOnlyButtons: function() { + this.fields.hide(); + this.button_upload + .add(this.field_image) + .add(this.button_url) + .add(this.input) + .show(); + }, + + /* Show only the URL field, hiding all others + * + * Returns nothing. + */ + _showOnlyFieldUrl: function() { + this.fields.hide(); + this.field_url.show(); + }, + + /* Event listener for when a user mouseovers the hidden file input + * + * Returns nothing. + */ + _onInputMouseOver: function() { + this.button_upload.addClass('hover'); + }, + + /* Event listener for when a user mouseouts the hidden file input + * + * Returns nothing. + */ + _onInputMouseOut: function() { + this.button_upload.removeClass('hover'); + }, + + /* Event listener for changes in resource's name by direct input from user + * + * Returns nothing + */ + _onModifyName: function() { + this._nameIsDirty = true; + }, + + /* Event listener for when someone loses focus of URL field + * + * Returns nothing + */ + _onFromWebBlur: function() { + var url = this.field_url_input.val().match(/([^\/]+)\/?$/) + if (url) { + this._autoName(url.pop()); + } + }, + + /* Automatically add file name into field Name + * + * Select by attribute [name] to be on the safe side and allow to change field id + * Returns nothing + */ + _autoName: function(name) { + if (!this._nameIsDirty){ + this.field_name.val(name); + } + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/image-upload.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/image-upload.min.js new file mode 100644 index 00000000..1bce1609 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/image-upload.min.js @@ -0,0 +1,10 @@ +this.ckan.module('image-upload',function($){return{options:{is_url:false,is_upload:false,field_upload:'image_upload',field_url:'image_url',field_clear:'clear_upload',field_name:'name',upload_label:''},_nameIsDirty:false,initialize:function(){$.proxyAll(this,/_on/);var options=this.options;var field_upload='input[name="'+options.field_upload+'"]';var field_url='input[name="'+options.field_url+'"]';var field_clear='input[name="'+options.field_clear+'"]';var field_name='input[name="'+options.field_name+'"]';this.input=$(field_upload,this.el);this.field_url=$(field_url,this.el).parents('.form-group');this.field_image=this.input.parents('.form-group');this.field_url_input=$('input',this.field_url);this.field_name=this.el.parents('form').find(field_name);this.label_location=$('label[for="field-image-url"]');this.is_data_resource=(this.options.field_url==='url')&&(this.options.field_upload==='upload');var checkbox=$(field_clear,this.el);if(checkbox.length>0){checkbox.parents('.form-group').remove();} +this.field_clear=$('').appendTo(this.el);this.button_url=$(''+''+ +this._('Link')+'').prop('title',this._('Link to a URL on the internet (you can also link to an API)')).on('click',this._onFromWeb).insertAfter(this.input);this.button_upload=$(''+''+ +this._('Upload')+'').insertAfter(this.input);var removeText=this._('Remove');$('' ++removeText+'').prop('title',removeText).on('click',this._onRemove).insertBefore(this.field_url_input);$('label[for="field-image-upload"]').text(options.upload_label||this._('Image'));this.input.on('mouseover',this._onInputMouseOver).on('mouseout',this._onInputMouseOut).on('change',this._onInputChange).prop('title',this._('Upload a file on your computer')).css('width',this.button_upload.outerWidth());this.fields=$('').add(this.button_upload).add(this.button_url).add(this.input).add(this.field_url).add(this.field_image);this.field_name.on('change',this._onModifyName);if(this.field_name.val()){this._nameIsDirty=true;} +if(options.is_url){this._showOnlyFieldUrl();this._updateUrlLabel(this._('URL'));}else if(options.is_upload){this._showOnlyFieldUrl();this.field_url_input.prop('readonly',true);var filename=this._fileNameFromUpload(this.field_url_input.val());this.field_url_input.val(filename);this._updateUrlLabel(this._('File'));}else{this._showOnlyButtons();}},_fileNameFromUpload:function(url){if(/^\/base\/images/.test(url)){return url;} +url=url.substring(0,(url.indexOf("#")===-1)?url.length:url.indexOf("#"));url=url.substring(0,(url.indexOf("?")===-1)?url.length:url.indexOf("?"));url=url.substring(url.lastIndexOf("/")+1,url.length);return url;},_updateUrlLabel:function(label_text){if(!this.is_data_resource){return;} +this.label_location.text(label_text);},_onFromWeb:function(){this._showOnlyFieldUrl();this.field_url_input.focus().on('blur',this._onFromWebBlur);if(this.options.is_upload){this.field_clear.val('true');} +this._updateUrlLabel(this._('URL'));},_onRemove:function(){this._showOnlyButtons();this.field_url_input.val('');this.field_url_input.prop('readonly',false);this.field_clear.val('true');},_onInputChange:function(){var file_name=this.input.val().split(/^C:\\fakepath\\/).pop();this.field_url_input.val(file_name);this.field_url_input.prop('readonly',true);this.field_clear.val('');this._showOnlyFieldUrl();this._autoName(file_name);this._updateUrlLabel(this._('File'));},_showOnlyButtons:function(){this.fields.hide();this.button_upload.add(this.field_image).add(this.button_url).add(this.input).show();},_showOnlyFieldUrl:function(){this.fields.hide();this.field_url.show();},_onInputMouseOver:function(){this.button_upload.addClass('hover');},_onInputMouseOut:function(){this.button_upload.removeClass('hover');},_onModifyName:function(){this._nameIsDirty=true;},_onFromWebBlur:function(){var url=this.field_url_input.val().match(/([^\/]+)\/?$/) +if(url){this._autoName(url.pop());}},_autoName:function(name){if(!this._nameIsDirty){this.field_name.val(name);}}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/media-grid.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/media-grid.js new file mode 100644 index 00000000..a6e269f5 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/media-grid.js @@ -0,0 +1,16 @@ +/* Media Grid + * Super simple plugin that waits for all the images to be loaded in the media + * grid and then applies the jQuery.masonry to then + */ +this.ckan.module('media-grid', function ($) { + return { + initialize: function () { + var wrapper = this.el; + wrapper.imagesLoaded(function() { + wrapper.masonry({ + itemSelector: '.media-item' + }); + }); + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/media-grid.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/media-grid.min.js new file mode 100644 index 00000000..2199fd43 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/media-grid.min.js @@ -0,0 +1 @@ +this.ckan.module('media-grid',function($){return{initialize:function(){var wrapper=this.el;wrapper.imagesLoaded(function(){wrapper.masonry({itemSelector:'.media-item'});});}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/popover-context.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/popover-context.js new file mode 100644 index 00000000..236969f0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/popover-context.js @@ -0,0 +1,257 @@ +/* Popover context + * These appear when someone hovers over a context item in a activity stream to + * give the user more context into that particular item. It also allows for + * people to follow and unfollow quickly from within the popover + * + * id - The user_id of user + * context - The type of this popover: currently supports user & package + + * url - The URL of the profile for that user + * loading - Loading state helper + * authed - Is the current user authed ... if so what's their user_id + * template - Simple string-replace template for content of popover + * + * Examples + * + * A user + * + */ + +// Global dictionary and render store for items +window.popover_context = { + dict: { + user: {}, + dataset: {}, + group: {} + }, + render: { + user: {}, + dataset: {}, + group: {} + } +}; + +this.ckan.module('popover-context', function($) { + return { + + /* options object can be extended using data-module-* attributes */ + options : { + id: null, + loading: false, + error: false, + authed: false, + throbber: '' + }, + + /* Initialises the module setting up elements and event listeners. + * + * Returns nothing. + */ + initialize: function() { + if ( + this.options.id != true + && this.options.id != null + ) { + $.proxyAll(this, /_on/); + if ($('.account').hasClass('authed')) { + this.options.authed = $('.account').data('me'); + } + this.el.popover({ + animation: false, + html: true, + content: this.options.throbber.replace('{SITE_ROOT}', ckan.SITE_ROOT) + this._('Loading...'), + placement: 'bottom' + }); + this.el.on('mouseover', this._onMouseOver); + $(document).on('mouseup', this._onDocumentMouseUp); + this.sandbox.subscribe('follow-follow-' + this.options.id, this._onHandleFollow); + this.sandbox.subscribe('follow-unfollow-' + this.options.id, this._onHandleFollow); + } + }, + + /* Get's called on document click in order to hide popover on not hit + * + * Returns nothing. + */ + _onDocumentMouseUp: function(event) { + var popover = this.el.data('popover'); + if (typeof popover.$tip != 'undefined') { + if (popover.$tip.has(event.target).length === 0) { + this.el.popover('hide'); + } + } + }, + + /* Helper that changes the loading state of the active popover + * + * Returns nothing. + */ + loadingHelper: function(loading) { + this.options.loading = loading; + var popover = this.el.data('popover'); + if (typeof popover.$tip != 'undefined') { + if (loading) { + popover.$tip.addClass('popover-context-loading'); + } else { + popover.$tip.removeClass('popover-context-loading'); + } + } + }, + + /* Handles the showing of the popover on hover (also hides other active + * popovers) + * + * Returns nothing. + */ + _onMouseOver: function() { + $('[data-module="popover-context"]').popover('hide'); + this.el.popover('show'); + this.getData(); + }, + + /* Get's the data from the ckan api + * + * Returns nothing. + */ + getData: function() { + if (!this.options.loading) { + this.loadingHelper(true); + var id = this.options.id; + var type = this.options.type; + if (typeof window.popover_context.dict[type][id] == 'undefined') { + var client = this.sandbox.client; + var endpoint = type + '_show'; + if (type == 'dataset') { + endpoint = 'package_show'; + } + client.call('GET', endpoint, '?id=' + id, this._onHandleData, this._onHandleError); + } else { + this._onHandleData(window.popover_context.dict[type][id]); + } + } + }, + + /* Handle's a error on the call api + * + * Returns nothing. + */ + _onHandleError: function(error) { + $('[data-module="popover-context"][data-module-type="'+this.options.type+'"][data-module-id="'+this.options.id+'"]').popover('destroy'); + }, + + /* Callback from getting the endpoint from the ckan api + * + * Returns nothing. + */ + _onHandleData: function(json) { + if (json.success) { + var id = this.options.id; + var type = this.options.type; + var client = this.sandbox.client; + // set the dictionary + window.popover_context.dict[type][id] = json; + + // has this been rendered before? + if (typeof window.popover_context.render[type][id] == 'undefined') { + var params = this.sanitiseParams(json.result); + client.getTemplate('popover_context_' + type + '.html', params, this._onRenderPopover); + } else { + this._onRenderPopover(window.popover_context.render[type][id]); + } + } + }, + + /* Used to break down a raw object into something a little more + * passable into a GET request + * + * Returns object. + */ + sanitiseParams: function(raw) { + var type = this.options.type; + var params = {}; + if (type == 'user') { + params.id = raw.id; + params.name = raw.name; + params.about = raw.about; + params.display_name = raw.display_name; + params.num_followers = raw.num_followers; + params.number_administered_packages = raw.number_administered_packages; + params.number_of_edits = raw.number_of_edits; + params.is_me = ( raw.id == this.options.authed ); + } else if (type == 'dataset') { + params.id = raw.id; + params.title = raw.title; + params.name = raw.name; + params.notes = raw.notes; + params.num_resources = raw.num_resources; + params.num_tags = raw.num_tags; + } else if (type == 'group') { + params.id = raw.id; + params.title = raw.title; + params.name = raw.name; + params.description = raw.description; + params.package_count = raw.package_count; + params.num_followers = raw.num_followers; + } + return params; + }, + + /* Renders the contents of the popover + * + * Returns nothing. + */ + _onRenderPopover: function(html) { + var id = this.options.id; + var type = this.options.type; + var dict = window.popover_context.dict[type][id].result; + var popover = this.el.data('popover'); + if (typeof popover.$tip != 'undefined') { + var tip = popover.$tip; + var title = ( type == 'user' ) ? dict.display_name : dict.title; + $('.popover-title', tip).html('×' + title); + $('.popover-content', tip).html(html); + $('.popover-close', tip).on('click', this._onClickPopoverClose); + var follow_check = this.getFollowButton(); + if (follow_check) { + ckan.module.initializeElement(follow_check[0]); + } + this.loadingHelper(false); + } + // set the global + window.popover_context.render[type][id] = html; + }, + + /* Handles closing the currently open popover + * + * Returns nothing. + */ + _onClickPopoverClose: function() { + this.el.popover('hide'); + }, + + /* Handles getting the follow button form within a popover + * + * Returns jQuery collection || false. + */ + getFollowButton: function() { + var popover = this.el.data('popover'); + if (typeof popover.$tip != 'undefined') { + var button = $('[data-module="follow"]', popover.$tip); + if (button.length > 0) { + return button; + } + } + return false; + }, + + /* Callback from when you follow/unfollow a specified item... this is + * used to ensure all popovers associated to that user get re-populated + * + * Returns nothing. + */ + _onHandleFollow: function() { + delete window.popover_context.render[this.options.type][this.options.id]; + } + + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/popover-context.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/popover-context.min.js new file mode 100644 index 00000000..6ca2b3c1 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/popover-context.min.js @@ -0,0 +1,7 @@ +window.popover_context={dict:{user:{},dataset:{},group:{}},render:{user:{},dataset:{},group:{}}};this.ckan.module('popover-context',function($){return{options:{id:null,loading:false,error:false,authed:false,throbber:''},initialize:function(){if(this.options.id!=true&&this.options.id!=null){$.proxyAll(this,/_on/);if($('.account').hasClass('authed')){this.options.authed=$('.account').data('me');} +this.el.popover({animation:false,html:true,content:this.options.throbber.replace('{SITE_ROOT}',ckan.SITE_ROOT)+this._('Loading...'),placement:'bottom'});this.el.on('mouseover',this._onMouseOver);$(document).on('mouseup',this._onDocumentMouseUp);this.sandbox.subscribe('follow-follow-'+this.options.id,this._onHandleFollow);this.sandbox.subscribe('follow-unfollow-'+this.options.id,this._onHandleFollow);}},_onDocumentMouseUp:function(event){var popover=this.el.data('popover');if(typeof popover.$tip!='undefined'){if(popover.$tip.has(event.target).length===0){this.el.popover('hide');}}},loadingHelper:function(loading){this.options.loading=loading;var popover=this.el.data('popover');if(typeof popover.$tip!='undefined'){if(loading){popover.$tip.addClass('popover-context-loading');}else{popover.$tip.removeClass('popover-context-loading');}}},_onMouseOver:function(){$('[data-module="popover-context"]').popover('hide');this.el.popover('show');this.getData();},getData:function(){if(!this.options.loading){this.loadingHelper(true);var id=this.options.id;var type=this.options.type;if(typeof window.popover_context.dict[type][id]=='undefined'){var client=this.sandbox.client;var endpoint=type+'_show';if(type=='dataset'){endpoint='package_show';} +client.call('GET',endpoint,'?id='+id,this._onHandleData,this._onHandleError);}else{this._onHandleData(window.popover_context.dict[type][id]);}}},_onHandleError:function(error){$('[data-module="popover-context"][data-module-type="'+this.options.type+'"][data-module-id="'+this.options.id+'"]').popover('destroy');},_onHandleData:function(json){if(json.success){var id=this.options.id;var type=this.options.type;var client=this.sandbox.client;window.popover_context.dict[type][id]=json;if(typeof window.popover_context.render[type][id]=='undefined'){var params=this.sanitiseParams(json.result);client.getTemplate('popover_context_'+type+'.html',params,this._onRenderPopover);}else{this._onRenderPopover(window.popover_context.render[type][id]);}}},sanitiseParams:function(raw){var type=this.options.type;var params={};if(type=='user'){params.id=raw.id;params.name=raw.name;params.about=raw.about;params.display_name=raw.display_name;params.num_followers=raw.num_followers;params.number_administered_packages=raw.number_administered_packages;params.number_of_edits=raw.number_of_edits;params.is_me=(raw.id==this.options.authed);}else if(type=='dataset'){params.id=raw.id;params.title=raw.title;params.name=raw.name;params.notes=raw.notes;params.num_resources=raw.num_resources;params.num_tags=raw.num_tags;}else if(type=='group'){params.id=raw.id;params.title=raw.title;params.name=raw.name;params.description=raw.description;params.package_count=raw.package_count;params.num_followers=raw.num_followers;} +return params;},_onRenderPopover:function(html){var id=this.options.id;var type=this.options.type;var dict=window.popover_context.dict[type][id].result;var popover=this.el.data('popover');if(typeof popover.$tip!='undefined'){var tip=popover.$tip;var title=(type=='user')?dict.display_name:dict.title;$('.popover-title',tip).html('×'+title);$('.popover-content',tip).html(html);$('.popover-close',tip).on('click',this._onClickPopoverClose);var follow_check=this.getFollowButton();if(follow_check){ckan.module.initializeElement(follow_check[0]);} +this.loadingHelper(false);} +window.popover_context.render[type][id]=html;},_onClickPopoverClose:function(){this.el.popover('hide');},getFollowButton:function(){var popover=this.el.data('popover');if(typeof popover.$tip!='undefined'){var button=$('[data-module="follow"]',popover.$tip);if(button.length>0){return button;}} +return false;},_onHandleFollow:function(){delete window.popover_context.render[this.options.type][this.options.id];}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-form.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-form.js new file mode 100644 index 00000000..f8bd136f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-form.js @@ -0,0 +1,55 @@ +/* Module for the resource form. Handles validation and updating the form + * with external data such as from a file upload. + */ +this.ckan.module('resource-form', function (jQuery) { + return { + /* Called by the ckan core if a corresponding element is found on the page. + * Handles setting up event listeners, adding elements to the page etc. + * + * Returns nothing. + */ + initialize: function () { + jQuery.proxyAll(this, /_on/); + this.sandbox.subscribe('resource:uploaded', this._onResourceUploaded); + }, + + /* Remove any subscriptions to prevent memory leaks. This function is + * called when a module element is removed from the page. + * + * Returns nothing.. + */ + teardown: function () { + this.sandbox.unsubscribe('resource:uploaded', this._onResourceUploaded); + }, + + /* Callback function that loads a newly uploaded resource into the form. + * Handles updating the various types of form fields. + * + * resource - A resource data object. + * + * Examples + * + * this.sandbox.subscribe('resource:uploaded', this._onResourceUploaded); + * + * Returns nothing. + */ + _onResourceUploaded: function (resource) { + var key; + var field; + + for (key in resource) { + if (resource.hasOwnProperty(key)) { + field = this.$('[name="' + key + '"]'); + + if (field.is(':checkbox, :radio')) { + this.$('[value="' + resource[key] + '"]').prop('checked', true); + } else if (field.is('select')) { + field.prop('selected', resource[key]); + } else { + field.val(resource[key]); + } + } + } + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-form.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-form.min.js new file mode 100644 index 00000000..2cdb05a7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-form.min.js @@ -0,0 +1 @@ +this.ckan.module('resource-form',function(jQuery){return{initialize:function(){jQuery.proxyAll(this,/_on/);this.sandbox.subscribe('resource:uploaded',this._onResourceUploaded);},teardown:function(){this.sandbox.unsubscribe('resource:uploaded',this._onResourceUploaded);},_onResourceUploaded:function(resource){var key;var field;for(key in resource){if(resource.hasOwnProperty(key)){field=this.$('[name="'+key+'"]');if(field.is(':checkbox, :radio')){this.$('[value="'+resource[key]+'"]').prop('checked',true);}else if(field.is('select')){field.prop('selected',resource[key]);}else{field.val(resource[key]);}}}}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-reorder.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-reorder.js new file mode 100644 index 00000000..3489742a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-reorder.js @@ -0,0 +1,151 @@ +/* Module for reordering resources + */ +this.ckan.module('resource-reorder', function($) { + return { + options: { + id: false + }, + template: { + title: '

', + help_text: '

', + button: [ + '', + '', + '', + '' + ].join('\n'), + form_actions: [ + '
', + '', + '', + '
' + ].join('\n'), + handle: [ + '', + '', + '' + ].join('\n'), + saving: [ + '', + '', + '', + '' + ].join('\n') + }, + is_reordering: false, + cache: false, + + initialize: function() { + jQuery.proxyAll(this, /_on/); + + var labelText = this._('Reorder resources'); + var helpText = this._('You can rearrange the resources by dragging them using the arrow icon. Drag the resource ' + + 'to the right and place it to the desired location on the list. When you are done, click the "Save order" -button.'); + + this.html_title = $(this.template.title) + .text(labelText) + .insertBefore(this.el) + .hide(); + + this.html_help_text = $(this.template.help_text) + .text(helpText) + .insertBefore(this.el) + .hide(); + + var button = $(this.template.button) + .on('click', this._onHandleStartReorder) + .appendTo('.page_primary_action'); + $('span', button).text(labelText); + + this.html_form_actions = $(this.template.form_actions) + .hide() + .insertAfter(this.el); + $('.save', this.html_form_actions) + .text(this._('Save order')) + .on('click', this._onHandleSave); + $('.cancel', this.html_form_actions) + .text(this._('Cancel')) + .on('click', this._onHandleCancel); + + this.html_handles = $(this.template.handle) + .hide() + .appendTo($('.resource-item', this.el)); + + this.html_saving = $(this.template.saving) + .hide() + .insertBefore($('.save', this.html_form_actions)); + $('span', this.html_saving).text(this._('Saving...')); + + this.cache = this.el.html(); + + this.el + .sortable() + .sortable('disable'); + + }, + + _onHandleStartReorder: function() { + if (!this.is_reordering) { + this.html_form_actions + .add(this.html_handles) + .add(this.html_title) + .add(this.html_help_text) + .show(); + this.el + .addClass('reordering') + .sortable('enable'); + $('.page_primary_action').hide(); + this.is_reordering = true; + } + }, + + _onHandleCancel: function() { + if ( + this.is_reordering + && !$('.cancel', this.html_form_actions).hasClass('disabled') + ) { + this.reset(); + this.is_reordering = false; + this.el.html(this.cache) + .sortable() + .sortable('disable'); + this.html_handles = $('.handle', this.el); + } + }, + + _onHandleSave: function() { + if (!$('.save', this.html_form_actions).hasClass('disabled')) { + var module = this; + module.html_saving.show(); + $('.save, .cancel', module.html_form_actions).addClass('disabled'); + var order = []; + $('.resource-item', module.el).each(function() { + order.push($(this).data('id')); + }); + module.sandbox.client.call('POST', 'package_resource_reorder', { + id: module.options.id, + order: order + }, function() { + module.html_saving.hide(); + $('.save, .cancel', module.html_form_actions).removeClass('disabled'); + module.cache = module.el.html(); + module.reset(); + module.is_reordering = false; + }); + } + }, + + reset: function() { + this.html_form_actions + .add(this.html_handles) + .add(this.html_title) + .add(this.html_help_text) + .hide(); + this.el + .removeClass('reordering') + .sortable('disable'); + $('.page_primary_action').show(); + } + + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-reorder.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-reorder.min.js new file mode 100644 index 00000000..21570394 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-reorder.min.js @@ -0,0 +1 @@ +this.ckan.module('resource-reorder',function($){return{options:{id:false},template:{title:'

',help_text:'

',button:['','','',''].join('\n'),form_actions:['
','','','
'].join('\n'),handle:['','',''].join('\n'),saving:['','','',''].join('\n')},is_reordering:false,cache:false,initialize:function(){jQuery.proxyAll(this,/_on/);var labelText=this._('Reorder resources');var helpText=this._('You can rearrange the resources by dragging them using the arrow icon. Drag the resource '+'to the right and place it to the desired location on the list. When you are done, click the "Save order" -button.');this.html_title=$(this.template.title).text(labelText).insertBefore(this.el).hide();this.html_help_text=$(this.template.help_text).text(helpText).insertBefore(this.el).hide();var button=$(this.template.button).on('click',this._onHandleStartReorder).appendTo('.page_primary_action');$('span',button).text(labelText);this.html_form_actions=$(this.template.form_actions).hide().insertAfter(this.el);$('.save',this.html_form_actions).text(this._('Save order')).on('click',this._onHandleSave);$('.cancel',this.html_form_actions).text(this._('Cancel')).on('click',this._onHandleCancel);this.html_handles=$(this.template.handle).hide().appendTo($('.resource-item',this.el));this.html_saving=$(this.template.saving).hide().insertBefore($('.save',this.html_form_actions));$('span',this.html_saving).text(this._('Saving...'));this.cache=this.el.html();this.el.sortable().sortable('disable');},_onHandleStartReorder:function(){if(!this.is_reordering){this.html_form_actions.add(this.html_handles).add(this.html_title).add(this.html_help_text).show();this.el.addClass('reordering').sortable('enable');$('.page_primary_action').hide();this.is_reordering=true;}},_onHandleCancel:function(){if(this.is_reordering&&!$('.cancel',this.html_form_actions).hasClass('disabled')){this.reset();this.is_reordering=false;this.el.html(this.cache).sortable().sortable('disable');this.html_handles=$('.handle',this.el);}},_onHandleSave:function(){if(!$('.save',this.html_form_actions).hasClass('disabled')){var module=this;module.html_saving.show();$('.save, .cancel',module.html_form_actions).addClass('disabled');var order=[];$('.resource-item',module.el).each(function(){order.push($(this).data('id'));});module.sandbox.client.call('POST','package_resource_reorder',{id:module.options.id,order:order},function(){module.html_saving.hide();$('.save, .cancel',module.html_form_actions).removeClass('disabled');module.cache=module.el.html();module.reset();module.is_reordering=false;});}},reset:function(){this.html_form_actions.add(this.html_handles).add(this.html_title).add(this.html_help_text).hide();this.el.removeClass('reordering').sortable('disable');$('.page_primary_action').show();}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-upload-field.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-upload-field.js new file mode 100644 index 00000000..354491f8 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-upload-field.js @@ -0,0 +1,283 @@ +/* This module creates a new resource_type field for an uploaded file and + * appends file input into the page. + * + * Events: + * + * Publishes the 'resource:uploaded' event when a file is successfully + * uploaded. An callbacks receive an object of resource data. + * + * See: http://docs.ckan.org/en/latest/filestore.html + * + * options - form: General form overrides for the upload. + * template: Optional template can be provided. + * + */ +this.ckan.module('resource-upload-field', function (jQuery) { + return { + /* Default options for the module */ + options: { + form: { + method: 'POST', + file: 'file', + params: [] + }, + template: [ + '', + '', + '', + '', + '', + '' + ].join('\n') + }, + + /* Initializes the module, creates new elements and registers event + * listeners etc. This method is called by ckan.initialize() if there + * is a corresponding element on the page. + * + * Returns nothing. + */ + initialize: function () { + jQuery.proxyAll(this, /_on/); + + this.upload = jQuery(this.options.template); + this.setupFileUpload(); + this.el.append(this.upload); + + jQuery(window).on('beforeunload', this._onWindowUpload); + }, + + /* Sets up the jQuery.fileUpload() plugin with the provided options. + * + * Returns nothing. + */ + setupFileUpload: function () { + var options = this.options; + + this.upload.find('label').text(this._('Upload a file')); + this.upload.find('input[type=file]').fileupload({ + type: options.form.method, + paramName: options.form.file, + forceIframeTransport: true, // Required for XDomain request. + replaceFileInput: true, + autoUpload: false, + add: this._onUploadAdd, + send: this._onUploadSend, + done: this._onUploadDone, + fail: this._onUploadFail, + always: this._onUploadComplete + }); + }, + + /* Displays a loading spinner next to the input while uploading. This + * can be cancelled by recalling the method passing false as the first + * argument. + * + * show - If false hides the spinner (default: true). + * + * Examples + * + * module.loading(); // Show spinner + * + * module.loading(false); // Hide spinner. + * + * Returns nothing. + */ + loading: function (show) { + this.upload.toggleClass('loading', show); + }, + + /* Requests Authentication for the upload from CKAN. Uses the + * _onAuthSuccess/_onAuthError callbacks. + * + * key - A unique key for the file that is to be uploaded. + * data - The file data object from the jQuery.fileUpload() plugin. + * + * Examples + * + * onFileAdd: function (event, data) { + * this.authenticate('my-file', data); + * } + * + * Returns an jqXHR promise. + */ + authenticate: function (key, data) { + data.key = key; + + var request = this.sandbox.client.getStorageAuth(key); + var onSuccess = jQuery.proxy(this._onAuthSuccess, this, data); + return request.then(onSuccess, this._onAuthError); + }, + + /* Requests file metadata for the uploaded file and calls the + * _onMetadataSuccess/_onMetadataError callbacks. + * + * key - A unique key for the file that is to be uploaded. + * data - The file data object from the jQuery.fileUpload() plugin. + * + * Examples + * + * onFileUploaded: function (event, data) { + * this.lookupMetadata('my-file', data); + * } + * + * Returns an jqXHR promise. + */ + lookupMetadata: function (key, data) { + var request = this.sandbox.client.getStorageMetadata(key); + var onSuccess = jQuery.proxy(this._onMetadataSuccess, this, data); + return request.then(onSuccess, this._onMetadataError); + }, + + /* Displays a global notification for the upload status. + * + * message - A message string to display. + * type - The type of message eg. error/info/warning + * + * Examples + * + * module.notify('Upload failed', 'error'); + * + * Returns nothing. + */ + notify: function (message, type) { + var title = this._('An Error Occurred'); + this.sandbox.notify(title, message, type); + }, + + /* Creates a unique key for the filename provided. This is a url + * safe string with a timestamp prepended. + * + * filename - The filename for the upload. + * + * Examples + * + * module.generateKey('my file'); + * // => '2012-06-05T12:00:00.000Z/my-file' + * + * Returns a unique string. + */ + generateKey: function (filename) { + var parts = filename.split('.'); + var extension = jQuery.url.slugify(parts.pop()); + + // Clean up the filename hopefully leaving the extension intact. + filename = jQuery.url.slugify(parts.join('.')) + '.' + extension; + return jQuery.date.toISOString() + '/' + filename; + }, + + /* Attaches the beforeunload event to window to prevent away navigation + * whilst a upload is happening + * + * is_uploading: Boolean of whether we're uploading right now + * + * Returns nothing + */ + uploading: function(is_uploading) { + var method = is_uploading ? 'on' : 'off'; + jQuery(window)[method]('beforeunload', this._onWindowBeforeUnload); + }, + + /* Callback called when the jQuery file upload plugin receives a file. + * + * event - The jQuery event object. + * data - An object of file data. + * + * Returns nothing. + */ + _onUploadAdd: function (event, data) { + this.uploading(true); + if (data.files && data.files.length) { + for (var i = 0; i < data.files.length; i++) { + data.files[i].name = data.files[i].name.split('/').pop(); + } + var key = this.generateKey(data.files[0].name); + + this.authenticate(key, data); + } + }, + + /* Callback called when the jQuery file upload plugin fails to upload + * a file. + */ + _onUploadFail: function () { + this.sandbox.notify(this._('Unable to upload file')); + }, + + /* Callback called when jQuery file upload plugin sends a file */ + _onUploadSend: function () { + this.loading(); + }, + + /* Callback called when jQuery file upload plugin successfully uploads a file */ + _onUploadDone: function (event, data) { + // Need to check for a result key. A Google upload can return a 404 if + // the bucket does not exist, this is still treated as a success by the + // form upload plugin. + var result = data.result; + if (result && !(jQuery.isPlainObject(result) && result.error)) { + this.lookupMetadata(data.key, data); + } else { + this._onUploadFail(event, data); + } + }, + + /* Callback called when jQuery file upload plugin completes a request + * regardless of it's success/failure. + */ + _onUploadComplete: function () { + this.loading(false); + this.uploading(false); + }, + + /* Callback function for a successful Auth request. This cannot be + * used straight up but requires the data object to be passed in + * as the first argument. + * + * data - The data object for the current upload. + * response - The auth response object. + * + * Examples + * + * var onSuccess = jQuery.proxy(this._onAuthSuccess, this, data); + * sandbox.client.getStorageAuth(key).done(onSuccess); + * + * Returns nothing. + */ + _onAuthSuccess: function (data, response) { + data.url = response.action; + data.formData = this.options.form.params.concat(response.fields); + data.submit(); + }, + + /* Called when the request for auth credentials fails. */ + _onAuthError: function (event, data) { + this.sandbox.notify(this._('Unable to authenticate upload')); + this._onUploadComplete(); + }, + + /* Called when the request for file metadata succeeds */ + _onMetadataSuccess: function (data, response) { + var resource = this.sandbox.client.convertStorageMetadataToResource(response); + + this.sandbox.notify(this._('Resource uploaded'), '', 'success'); + this.sandbox.publish('resource:uploaded', resource); + }, + + /* Called when the request for file metadata fails */ + _onMetadataError: function () { + this.sandbox.notify(this._('Unable to get data for uploaded file')); + this._onUploadComplete(); + }, + + /* Called before the window unloads whilst uploading */ + _onWindowBeforeUnload: function(event) { + var message = this._('You are uploading a file. Are you sure you ' + + 'want to navigate away and stop this upload?'); + if (event.originalEvent.returnValue) { + event.originalEvent.returnValue = message; + } + return message; + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-upload-field.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-upload-field.min.js new file mode 100644 index 00000000..1d6c5b33 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-upload-field.min.js @@ -0,0 +1,3 @@ +this.ckan.module('resource-upload-field',function(jQuery){return{options:{form:{method:'POST',file:'file',params:[]},template:['','','','','',''].join('\n')},initialize:function(){jQuery.proxyAll(this,/_on/);this.upload=jQuery(this.options.template);this.setupFileUpload();this.el.append(this.upload);jQuery(window).on('beforeunload',this._onWindowUpload);},setupFileUpload:function(){var options=this.options;this.upload.find('label').text(this._('Upload a file'));this.upload.find('input[type=file]').fileupload({type:options.form.method,paramName:options.form.file,forceIframeTransport:true,replaceFileInput:true,autoUpload:false,add:this._onUploadAdd,send:this._onUploadSend,done:this._onUploadDone,fail:this._onUploadFail,always:this._onUploadComplete});},loading:function(show){this.upload.toggleClass('loading',show);},authenticate:function(key,data){data.key=key;var request=this.sandbox.client.getStorageAuth(key);var onSuccess=jQuery.proxy(this._onAuthSuccess,this,data);return request.then(onSuccess,this._onAuthError);},lookupMetadata:function(key,data){var request=this.sandbox.client.getStorageMetadata(key);var onSuccess=jQuery.proxy(this._onMetadataSuccess,this,data);return request.then(onSuccess,this._onMetadataError);},notify:function(message,type){var title=this._('An Error Occurred');this.sandbox.notify(title,message,type);},generateKey:function(filename){var parts=filename.split('.');var extension=jQuery.url.slugify(parts.pop());filename=jQuery.url.slugify(parts.join('.'))+'.'+extension;return jQuery.date.toISOString()+'/'+filename;},uploading:function(is_uploading){var method=is_uploading?'on':'off';jQuery(window)[method]('beforeunload',this._onWindowBeforeUnload);},_onUploadAdd:function(event,data){this.uploading(true);if(data.files&&data.files.length){for(var i=0;i'; + } + + return { + initialize: initialize, + options: { + id: 0, + url: '#', + width: 700, + height: 400 + } + } +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-embed.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-embed.min.js new file mode 100644 index 00000000..4a2ebca3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-embed.min.js @@ -0,0 +1,9 @@ +this.ckan.module('resource-view-embed',function($){var modal;var self;function initialize(){self=this;modal=$('#embed-'+this.options.id) +$('body').append(modal);this.el.on('click',_onClick);$('textarea',modal).on('focus',_selectAllCode).on('mouseup',_preventClick);$('input',modal).on('keyup change',_updateValues);_updateEmbedCode();} +function _onClick(event){event.preventDefault();modal.modal('show');} +function _selectAllCode(){$('textarea',modal).select();} +function _updateValues(){self.options.width=$('[name="width"]',modal).val();self.options.height=$('[name="height"]',modal).val();_updateEmbedCode();} +function _updateEmbedCode(){$('[name="code"]',modal).val(_embedCode());} +function _preventClick(event){event.preventDefault();} +function _embedCode(){return'';} +return{initialize:initialize,options:{id:0,url:'#',width:700,height:400}}}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters-form.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters-form.js new file mode 100644 index 00000000..8dd976ce --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters-form.js @@ -0,0 +1,104 @@ +ckan.module('resource-view-filters-form', function (jQuery) { + 'use strict'; + + function applyDropdown(selectField, resourceId) { + var inputField = selectField.parent().find('input'), + filterName = selectField.val(), + queryLimit = 20; + + inputField.select2({ + width: 'resolve', + minimumInputLength: 0, + ajax: { + url: ckan.url('/api/3/action/datastore_search'), + datatype: 'json', + quietMillis: 200, + cache: true, + data: function (term, page) { + var offset = (page - 1) * queryLimit, + query; + + query = { + resource_id: resourceId, + limit: queryLimit, + offset: offset, + fields: filterName, + distinct: true, + sort: filterName, + include_total: false + }; + + if (term !== '') { + var q = {}; + if (term.indexOf(' ') == -1) { + term = term + ':*'; + query.plain = false; + } + q[filterName] = term; + query.q = JSON.stringify(q); + } + + return query; + }, + results: function (data, page) { + var records = data.result.records, + hasMore = (records.length == queryLimit), + results; + + results = $.map(records, function (record) { + return { id: record[filterName], text: String(record[filterName]) }; + }); + + return { results: results, more: hasMore }; + }, + }, + initSelection: function (element, callback) { + var data = {id: element.val(), text: element.val()}; + callback(data); + }, + }); + } + + function initialize() { + var self = this, + resourceId = self.options.resourceId, + templateFilterInputs = self.options.templateFilterInputs, + inputFieldTemplateEl = $(templateFilterInputs).find('input[type="text"][name]'), + filtersDiv = self.el.find(self.options.filtersSelector), + addFilterEl = self.el.find(self.options.addFilterSelector), + removeFilterSelector = self.options.removeFilterSelector; + + var selects = filtersDiv.find('select'); + selects.each(function (i, select) { + applyDropdown($(select), resourceId); + }); + + addFilterEl.click(function (evt) { + var selectField; + evt.preventDefault(); + filtersDiv.append(templateFilterInputs); + selectField = filtersDiv.children().last().find('select'); + applyDropdown(selectField, resourceId); + }); + + filtersDiv.on('click', removeFilterSelector, function (evt) { + evt.preventDefault(); + $(this).parent().remove(); + }); + + filtersDiv.on('change', 'select', function (evt) { + var el = $(this), + parentEl = el.parent(), + inputField = parentEl.find('input'), + select2Container = parentEl.find('.select2-container'); + evt.preventDefault(); + select2Container.remove(); + inputField.replaceWith(inputFieldTemplateEl.clone()); + applyDropdown(el, resourceId); + }); + } + + return { + initialize: initialize + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters-form.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters-form.min.js new file mode 100644 index 00000000..7f9dadb7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters-form.min.js @@ -0,0 +1,5 @@ +ckan.module('resource-view-filters-form',function(jQuery){'use strict';function applyDropdown(selectField,resourceId){var inputField=selectField.parent().find('input'),filterName=selectField.val(),queryLimit=20;inputField.select2({width:'resolve',minimumInputLength:0,ajax:{url:ckan.url('/api/3/action/datastore_search'),datatype:'json',quietMillis:200,cache:true,data:function(term,page){var offset=(page-1)*queryLimit,query;query={resource_id:resourceId,limit:queryLimit,offset:offset,fields:filterName,distinct:true,sort:filterName,include_total:false};if(term!==''){var q={};if(term.indexOf(' ')==-1){term=term+':*';query.plain=false;} +q[filterName]=term;query.q=JSON.stringify(q);} +return query;},results:function(data,page){var records=data.result.records,hasMore=(records.length==queryLimit),results;results=$.map(records,function(record){return{id:record[filterName],text:String(record[filterName])};});return{results:results,more:hasMore};},},initSelection:function(element,callback){var data={id:element.val(),text:element.val()};callback(data);},});} +function initialize(){var self=this,resourceId=self.options.resourceId,templateFilterInputs=self.options.templateFilterInputs,inputFieldTemplateEl=$(templateFilterInputs).find('input[type="text"][name]'),filtersDiv=self.el.find(self.options.filtersSelector),addFilterEl=self.el.find(self.options.addFilterSelector),removeFilterSelector=self.options.removeFilterSelector;var selects=filtersDiv.find('select');selects.each(function(i,select){applyDropdown($(select),resourceId);});addFilterEl.click(function(evt){var selectField;evt.preventDefault();filtersDiv.append(templateFilterInputs);selectField=filtersDiv.children().last().find('select');applyDropdown(selectField,resourceId);});filtersDiv.on('click',removeFilterSelector,function(evt){evt.preventDefault();$(this).parent().remove();});filtersDiv.on('change','select',function(evt){var el=$(this),parentEl=el.parent(),inputField=parentEl.find('input'),select2Container=parentEl.find('.select2-container');evt.preventDefault();select2Container.remove();inputField.replaceWith(inputFieldTemplateEl.clone());applyDropdown(el,resourceId);});} +return{initialize:initialize};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters.js new file mode 100644 index 00000000..5a9ffaec --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters.js @@ -0,0 +1,186 @@ +this.ckan.module('resource-view-filters', function (jQuery) { + 'use strict'; + + function initialize() { + var self = this, + resourceId = self.options.resourceId, + fields = self.options.fields, + dropdownTemplate = self.options.dropdownTemplate, + addFilterTemplate = '' + self._('Add Filter') + '', + filtersDiv = $('
'); + + var filters = ckan.views.filters.get(); + _appendDropdowns(filtersDiv, resourceId, dropdownTemplate, fields, filters); + var addFilterButton = _buildAddFilterButton(self, filtersDiv, addFilterTemplate, + fields, filters, function (evt) { + // Build filters object with this element's val as key and a placeholder + // value so _appendDropdowns() will create its dropdown + var filters = {}; + filters[evt.val] = []; + + $(this).select2('destroy'); + _appendDropdowns(filtersDiv, resourceId, dropdownTemplate, fields, filters); + evt.preventDefault(); + }); + self.el.append(filtersDiv); + self.el.append(addFilterButton); + } + + function _buildAddFilterButton(self, el, template, fields, filters, onChangeCallback) { + var addFilterButton = $(template), + currentFilters = Object.keys(filters), + fieldsNotFiltered = $.grep(fields, function (field) { + return !filters.hasOwnProperty(field); + }), + data = $.map(fieldsNotFiltered, function (d) { + return { id: d, text: d }; + }); + + if (data.length === 0) { + return ''; + } + + addFilterButton.click(function (evt) { + // FIXME: Move this class name to some external variable to keep it DRY + var addFilterDiv = $('
'), + addFilterInput = addFilterDiv.find('input'); + el.append(addFilterDiv); + + // TODO: Remove element from "data" when some select selects it. + addFilterInput.select2({ + data: data, + placeholder: self._('Select a field'), + width: 'resolve', + }).on('change', onChangeCallback); + + evt.preventDefault(); + }); + + return addFilterButton; + } + + function _appendDropdowns(dropdowns, resourceId, template, fields, filters) { + $.each(fields, function (i, field) { + if (filters.hasOwnProperty(field)) { + dropdowns.append(_buildDropdown(self.el, template, field)); + } + }); + + return dropdowns; + + function _buildDropdown(el, template, filterName) { + var theseFilters = filters[filterName] || []; + template = $(template.replace(/{filter}/g, filterName)); + // FIXME: Get the CSS class from some external variable + var dropdowns = template.find('.resource-view-filter-values'); + + // Can't use push because we need to create a new array, as we're + // modifying it. + theseFilters = theseFilters.concat([undefined]); + theseFilters.forEach(function (value, i) { + var dropdown = $(''); + + if (value !== undefined) { + dropdown.val(value); + } + + dropdowns.append(dropdown); + }); + + var queryLimit = 20; + dropdowns.find('input').select2({ + allowClear: true, + placeholder: ' ', // select2 needs a placeholder to allow clearing + width: 'resolve', + minimumInputLength: 0, + ajax: { + url: ckan.url('/api/3/action/datastore_search'), + datatype: 'json', + quietMillis: 200, + cache: true, + data: function (term, page) { + var offset = (page - 1) * queryLimit, + query; + + query = { + resource_id: resourceId, + limit: queryLimit, + offset: offset, + fields: filterName, + distinct: true, + sort: filterName, + include_total: false + }; + + if (term !== '') { + var q = {}; + if (term.indexOf(' ') == -1) { + term = term + ':*'; + query.plain = false; + } + q[filterName] = term; + query.q = JSON.stringify(q); + } + + return query; + }, + results: function (data, page) { + var records = data.result.records, + hasMore = (records.length == queryLimit), + results; + + results = $.map(records, function (record) { + return { id: record[filterName], text: String(record[filterName]) }; + }); + + return { results: results, more: hasMore }; + } + }, + initSelection: function (element, callback) { + var data = {id: element.val(), text: element.val()}; + callback(data); + }, + }).on('change', _onChange); + + return template; + } + } + + function _onChange(evt) { + var filterName = evt.currentTarget.name, + filterValue = evt.val, + currentFilters = ckan.views.filters.get(filterName) || [], + addToIndex = currentFilters.length; + + // Make sure we're not editing the original array, but a copy. + currentFilters = currentFilters.slice(); + + if (evt.removed) { + addToIndex = currentFilters.indexOf(evt.removed.id); + if (addToIndex !== -1) { + currentFilters.splice(addToIndex, 1); + } + } + if (evt.added) { + currentFilters.splice(addToIndex, 0, filterValue); + } + + if (currentFilters.length > 0) { + ckan.views.filters.set(filterName, currentFilters); + } else { + ckan.views.filters.unset(filterName); + } + } + + return { + initialize: initialize, + options: { + dropdownTemplate: [ + '
', + ' {filter}:', + '
', + '
', + ].join('\n') + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters.min.js new file mode 100644 index 00000000..9938d529 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-filters.min.js @@ -0,0 +1,11 @@ +this.ckan.module('resource-view-filters',function(jQuery){'use strict';function initialize(){var self=this,resourceId=self.options.resourceId,fields=self.options.fields,dropdownTemplate=self.options.dropdownTemplate,addFilterTemplate=''+self._('Add Filter')+'',filtersDiv=$('
');var filters=ckan.views.filters.get();_appendDropdowns(filtersDiv,resourceId,dropdownTemplate,fields,filters);var addFilterButton=_buildAddFilterButton(self,filtersDiv,addFilterTemplate,fields,filters,function(evt){var filters={};filters[evt.val]=[];$(this).select2('destroy');_appendDropdowns(filtersDiv,resourceId,dropdownTemplate,fields,filters);evt.preventDefault();});self.el.append(filtersDiv);self.el.append(addFilterButton);} +function _buildAddFilterButton(self,el,template,fields,filters,onChangeCallback){var addFilterButton=$(template),currentFilters=Object.keys(filters),fieldsNotFiltered=$.grep(fields,function(field){return!filters.hasOwnProperty(field);}),data=$.map(fieldsNotFiltered,function(d){return{id:d,text:d};});if(data.length===0){return'';} +addFilterButton.click(function(evt){var addFilterDiv=$('
'),addFilterInput=addFilterDiv.find('input');el.append(addFilterDiv);addFilterInput.select2({data:data,placeholder:self._('Select a field'),width:'resolve',}).on('change',onChangeCallback);evt.preventDefault();});return addFilterButton;} +function _appendDropdowns(dropdowns,resourceId,template,fields,filters){$.each(fields,function(i,field){if(filters.hasOwnProperty(field)){dropdowns.append(_buildDropdown(self.el,template,field));}});return dropdowns;function _buildDropdown(el,template,filterName){var theseFilters=filters[filterName]||[];template=$(template.replace(/{filter}/g,filterName));var dropdowns=template.find('.resource-view-filter-values');theseFilters=theseFilters.concat([undefined]);theseFilters.forEach(function(value,i){var dropdown=$('');if(value!==undefined){dropdown.val(value);} +dropdowns.append(dropdown);});var queryLimit=20;dropdowns.find('input').select2({allowClear:true,placeholder:' ',width:'resolve',minimumInputLength:0,ajax:{url:ckan.url('/api/3/action/datastore_search'),datatype:'json',quietMillis:200,cache:true,data:function(term,page){var offset=(page-1)*queryLimit,query;query={resource_id:resourceId,limit:queryLimit,offset:offset,fields:filterName,distinct:true,sort:filterName,include_total:false};if(term!==''){var q={};if(term.indexOf(' ')==-1){term=term+':*';query.plain=false;} +q[filterName]=term;query.q=JSON.stringify(q);} +return query;},results:function(data,page){var records=data.result.records,hasMore=(records.length==queryLimit),results;results=$.map(records,function(record){return{id:record[filterName],text:String(record[filterName])};});return{results:results,more:hasMore};}},initSelection:function(element,callback){var data={id:element.val(),text:element.val()};callback(data);},}).on('change',_onChange);return template;}} +function _onChange(evt){var filterName=evt.currentTarget.name,filterValue=evt.val,currentFilters=ckan.views.filters.get(filterName)||[],addToIndex=currentFilters.length;currentFilters=currentFilters.slice();if(evt.removed){addToIndex=currentFilters.indexOf(evt.removed.id);if(addToIndex!==-1){currentFilters.splice(addToIndex,1);}} +if(evt.added){currentFilters.splice(addToIndex,0,filterValue);} +if(currentFilters.length>0){ckan.views.filters.set(filterName,currentFilters);}else{ckan.views.filters.unset(filterName);}} +return{initialize:initialize,options:{dropdownTemplate:['
',' {filter}:','
','
',].join('\n')}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-reorder.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-reorder.js new file mode 100644 index 00000000..b4e75cb9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-reorder.js @@ -0,0 +1,140 @@ +/* Module for reordering resource views + */ +this.ckan.module('resource-view-reorder', function($) { + return { + options: { + id: false, + labelText: 'Reorder resource view' + }, + template: { + title: '

', + button: [ + '', + '', + '', + '' + ].join('\n'), + form_actions: [ + '
', + '', + '', + '
' + ].join('\n'), + handle: [ + '', + '', + '' + ].join('\n'), + saving: [ + '', + '', + '', + '' + ].join('\n') + }, + is_reordering: false, + cache: false, + + initialize: function() { + jQuery.proxyAll(this, /_on/); + + var labelText = this._(this.options.labelText); + this.html_title = $(this.template.title) + .text(labelText) + .insertBefore(this.el) + .hide(); + var button = $(this.template.button) + .on('click', this._onHandleStartReorder) + .appendTo('.page_primary_action'); + $('span', button).text(labelText); + + this.html_form_actions = $(this.template.form_actions) + .hide() + .insertAfter(this.el); + $('.save', this.html_form_actions) + .text(this._('Save order')) + .on('click', this._onHandleSave); + $('.cancel', this.html_form_actions) + .text(this._('Cancel')) + .on('click', this._onHandleCancel); + + this.html_handles = $(this.template.handle) + .hide() + .appendTo($('li', this.el)); + + this.html_saving = $(this.template.saving) + .hide() + .insertBefore($('.save', this.html_form_actions)); + $('span', this.html_saving).text(this._('Saving...')); + + this.cache = this.el.html(); + + this.el + .sortable() + .sortable('disable'); + + }, + + _onHandleStartReorder: function() { + if (!this.is_reordering) { + this.html_form_actions + .add(this.html_handles) + .add(this.html_title) + .show(); + this.el + .addClass('reordering') + .sortable('enable'); + $('.page_primary_action').hide(); + this.is_reordering = true; + } + }, + + _onHandleCancel: function() { + if ( + this.is_reordering + && !$('.cancel', this.html_form_actions).hasClass('disabled') + ) { + this.reset(); + this.is_reordering = false; + this.el.html(this.cache) + .sortable() + .sortable('disable'); + this.html_handles = $('.handle', this.el); + } + }, + + _onHandleSave: function() { + if (!$('.save', this.html_form_actions).hasClass('disabled')) { + var module = this; + module.html_saving.show(); + $('.save, .cancel', module.html_form_actions).addClass('disabled'); + var order = []; + $('li', module.el).each(function() { + order.push($(this).data('id')); + }); + module.sandbox.client.call('POST', 'resource_view_reorder', { + id: module.options.id, + order: order + }, function() { + module.html_saving.hide(); + $('.save, .cancel', module.html_form_actions).removeClass('disabled'); + module.cache = module.el.html(); + module.reset(); + module.is_reordering = false; + }); + } + }, + + reset: function() { + this.html_form_actions + .add(this.html_handles) + .add(this.html_title) + .hide(); + this.el + .removeClass('reordering') + .sortable('disable'); + $('.page_primary_action').show(); + } + + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-reorder.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-reorder.min.js new file mode 100644 index 00000000..6fcae188 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/resource-view-reorder.min.js @@ -0,0 +1 @@ +this.ckan.module('resource-view-reorder',function($){return{options:{id:false,labelText:'Reorder resource view'},template:{title:'

',button:['','','',''].join('\n'),form_actions:['
','','','
'].join('\n'),handle:['','',''].join('\n'),saving:['','','',''].join('\n')},is_reordering:false,cache:false,initialize:function(){jQuery.proxyAll(this,/_on/);var labelText=this._(this.options.labelText);this.html_title=$(this.template.title).text(labelText).insertBefore(this.el).hide();var button=$(this.template.button).on('click',this._onHandleStartReorder).appendTo('.page_primary_action');$('span',button).text(labelText);this.html_form_actions=$(this.template.form_actions).hide().insertAfter(this.el);$('.save',this.html_form_actions).text(this._('Save order')).on('click',this._onHandleSave);$('.cancel',this.html_form_actions).text(this._('Cancel')).on('click',this._onHandleCancel);this.html_handles=$(this.template.handle).hide().appendTo($('li',this.el));this.html_saving=$(this.template.saving).hide().insertBefore($('.save',this.html_form_actions));$('span',this.html_saving).text(this._('Saving...'));this.cache=this.el.html();this.el.sortable().sortable('disable');},_onHandleStartReorder:function(){if(!this.is_reordering){this.html_form_actions.add(this.html_handles).add(this.html_title).show();this.el.addClass('reordering').sortable('enable');$('.page_primary_action').hide();this.is_reordering=true;}},_onHandleCancel:function(){if(this.is_reordering&&!$('.cancel',this.html_form_actions).hasClass('disabled')){this.reset();this.is_reordering=false;this.el.html(this.cache).sortable().sortable('disable');this.html_handles=$('.handle',this.el);}},_onHandleSave:function(){if(!$('.save',this.html_form_actions).hasClass('disabled')){var module=this;module.html_saving.show();$('.save, .cancel',module.html_form_actions).addClass('disabled');var order=[];$('li',module.el).each(function(){order.push($(this).data('id'));});module.sandbox.client.call('POST','resource_view_reorder',{id:module.options.id,order:order},function(){module.html_saving.hide();$('.save, .cancel',module.html_form_actions).removeClass('disabled');module.cache=module.el.html();module.reset();module.is_reordering=false;});}},reset:function(){this.html_form_actions.add(this.html_handles).add(this.html_title).hide();this.el.removeClass('reordering').sortable('disable');$('.page_primary_action').show();}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/select-switch.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/select-switch.js new file mode 100644 index 00000000..145b33a4 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/select-switch.js @@ -0,0 +1,31 @@ +/* Finds the nearest select box in a form and watches it for changes. When + * a change occurs it submits the form. It can also hide the submit button if + * required. + * + * target - A selector to watch for changes (default: select) + * button - A selector for the button to hide in the form. + * + * Examples + * + * + * + * + * + * + * + * Returns . + */ +this.ckan.module('select-switch', { + + options: { + target: 'select' + }, + + initialize: function () { + var _this = this; + + this.el.on('change', this.options.target, function () { + _this.el.submit(); + }); + } +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/select-switch.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/select-switch.min.js new file mode 100644 index 00000000..b0247c1f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/select-switch.min.js @@ -0,0 +1 @@ +this.ckan.module('select-switch',{options:{target:'select'},initialize:function(){var _this=this;this.el.on('change',this.options.target,function(){_this.el.submit();});}}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/slug-preview.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/slug-preview.js new file mode 100644 index 00000000..7036b15f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/slug-preview.js @@ -0,0 +1,79 @@ +this.ckan.module('slug-preview-target', { + initialize: function () { + var sandbox = this.sandbox; + var options = this.options; + var el = this.el; + + sandbox.subscribe('slug-preview-created', function (preview) { + // Append the preview string after the target input. + el.after(preview); + }); + + // Make sure there isn't a value in the field already... + if (el.val() == '') { + // Once the preview box is modified stop watching it. + sandbox.subscribe('slug-preview-modified', function () { + el.off('.slug-preview'); + }); + + // Watch for updates to the target field and update the hidden slug field + // triggering the "change" event manually. + el.on('keyup.slug-preview input.slug-preview', function (event) { + sandbox.publish('slug-target-changed', this.value); + //slug.val(this.value).trigger('change'); + }); + } + } +}); + +this.ckan.module('slug-preview-slug', function (jQuery) { + return { + options: { + prefix: '', + placeholder: '' + }, + + initialize: function () { + var sandbox = this.sandbox; + var options = this.options; + var el = this.el; + var _ = sandbox.translate; + + var slug = el.slug(); + var parent = slug.parents('.form-group'); + var preview; + + if (!(parent.length)) { + return; + } + + // Leave the slug field visible + if (!parent.hasClass('error')) { + preview = parent.slugPreview({ + prefix: options.prefix, + placeholder: options.placeholder, + i18n: { + 'URL': this._('URL'), + 'Edit': this._('Edit') + } + }); + + // If the user manually enters text into the input we cancel the slug + // listeners so that we don't clobber the slug when the title next changes. + slug.keypress(function () { + if (event.charCode) { + sandbox.publish('slug-preview-modified', preview[0]); + } + }); + + sandbox.publish('slug-preview-created', preview[0]); + } + + // Watch for updates to the target field and update the hidden slug field + // triggering the "change" event manually. + sandbox.subscribe('slug-target-changed', function (value) { + slug.val(value).trigger('change'); + }); + } + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/slug-preview.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/slug-preview.min.js new file mode 100644 index 00000000..777fb3b2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/slug-preview.min.js @@ -0,0 +1,3 @@ +this.ckan.module('slug-preview-target',{initialize:function(){var sandbox=this.sandbox;var options=this.options;var el=this.el;sandbox.subscribe('slug-preview-created',function(preview){el.after(preview);});if(el.val()==''){sandbox.subscribe('slug-preview-modified',function(){el.off('.slug-preview');});el.on('keyup.slug-preview input.slug-preview',function(event){sandbox.publish('slug-target-changed',this.value);});}}});this.ckan.module('slug-preview-slug',function(jQuery){return{options:{prefix:'',placeholder:''},initialize:function(){var sandbox=this.sandbox;var options=this.options;var el=this.el;var _=sandbox.translate;var slug=el.slug();var parent=slug.parents('.form-group');var preview;if(!(parent.length)){return;} +if(!parent.hasClass('error')){preview=parent.slugPreview({prefix:options.prefix,placeholder:options.placeholder,i18n:{'URL':this._('URL'),'Edit':this._('Edit')}});slug.keypress(function(){if(event.charCode){sandbox.publish('slug-preview-modified',preview[0]);}});sandbox.publish('slug-preview-created',preview[0]);} +sandbox.subscribe('slug-target-changed',function(value){slug.val(value).trigger('change');});}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-selectable-rows.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-selectable-rows.js new file mode 100644 index 00000000..4a1965ad --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-selectable-rows.js @@ -0,0 +1,95 @@ +/* Table Selectable Rows + * Put's a select box in the of a and makes all rows + * selectable. + * + * Examples + * + *
...
+ * + */ +this.ckan.module('table-selectable-rows', function($) { + return { + + // Store for jQuery object for the select all checkbox + select_all: null, + // Total number of checkboxes in the table (used for checking later) + total_checkboxes: 0, + // Store for jQuery object of all table header buttons + buttons: null, + + /* Initialises the module setting up elements and event listeners. + * + * Returns nothing. + */ + initialize: function() { + $.proxyAll(this, /_on/); + this.total_checkboxes = $('input[type="checkbox"]', this.el).length; + this.select_all = $('') + .data('select-all', true) + .appendTo($('thead th:first-child', this.el)); + this.el.on('change', 'input[type="checkbox"]', this._onHandleCheckboxToggle); + this.buttons = $('th.actions .btn', this.el).addClass('disabled').prop('disabled', true); + }, + + /* Gets called whenever a user changes the :checked state on a checkbox + * within the table + * + * $e - jQuery event object + * + * Returns nothing. + */ + _onHandleCheckboxToggle: function($e) { + var checkbox = $($e.target); + if (checkbox.data('select-all')) { + this.handleSelectAll(checkbox, checkbox.is(':checked')); + } else { + this.handleSelectOne(checkbox, checkbox.is(':checked')); + } + }, + + /* Handles the checking of all row + * + * $target - jQuery checkbox object + * $checked - Boolean of whether $target is checked + * + * Returns nothing. + */ + handleSelectAll: function($target, $checked) { + $('input[type="checkbox"]', this.el).prop('checked', $checked); + if ($checked) { + $('tbody tr', this.el).addClass('table-selected'); + this.buttons.removeClass('disabled').prop('disabled', false); + } else { + $('tbody tr', this.el).removeClass('table-selected'); + this.buttons.addClass('disabled').prop('disabled', true); + } + }, + + /* Handles the checking of a single row + * + * $target - jQuery checkbox object + * $checked - Boolean of whether $target is checked + * + * Returns nothing. + */ + handleSelectOne: function($target, $checked) { + if ($checked) { + $target.parents('tr').addClass('table-selected'); + } else { + $target.parents('tr').removeClass('table-selected'); + } + var checked = $('tbody input[type="checkbox"]:checked', this.el).length; + if (checked >= this.total_checkboxes) { + this.select_all.prop('checked', true); + } else { + this.select_all.prop('checked', false); + } + if (checked > 0) { + this.buttons.removeClass('disabled').prop('disabled', false); + } else { + this.buttons.addClass('disabled').prop('disabled', true); + } + } + + }; +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-selectable-rows.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-selectable-rows.min.js new file mode 100644 index 00000000..765fb096 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-selectable-rows.min.js @@ -0,0 +1,3 @@ +this.ckan.module('table-selectable-rows',function($){return{select_all:null,total_checkboxes:0,buttons:null,initialize:function(){$.proxyAll(this,/_on/);this.total_checkboxes=$('input[type="checkbox"]',this.el).length;this.select_all=$('').data('select-all',true).appendTo($('thead th:first-child',this.el));this.el.on('change','input[type="checkbox"]',this._onHandleCheckboxToggle);this.buttons=$('th.actions .btn',this.el).addClass('disabled').prop('disabled',true);},_onHandleCheckboxToggle:function($e){var checkbox=$($e.target);if(checkbox.data('select-all')){this.handleSelectAll(checkbox,checkbox.is(':checked'));}else{this.handleSelectOne(checkbox,checkbox.is(':checked'));}},handleSelectAll:function($target,$checked){$('input[type="checkbox"]',this.el).prop('checked',$checked);if($checked){$('tbody tr',this.el).addClass('table-selected');this.buttons.removeClass('disabled').prop('disabled',false);}else{$('tbody tr',this.el).removeClass('table-selected');this.buttons.addClass('disabled').prop('disabled',true);}},handleSelectOne:function($target,$checked){if($checked){$target.parents('tr').addClass('table-selected');}else{$target.parents('tr').removeClass('table-selected');} +var checked=$('tbody input[type="checkbox"]:checked',this.el).length;if(checked>=this.total_checkboxes){this.select_all.prop('checked',true);}else{this.select_all.prop('checked',false);} +if(checked>0){this.buttons.removeClass('disabled').prop('disabled',false);}else{this.buttons.addClass('disabled').prop('disabled',true);}}};}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-toggle-more.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-toggle-more.js new file mode 100644 index 00000000..7a5c49c3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-toggle-more.js @@ -0,0 +1,61 @@ +/* Table toggle more + * When a table has more things to it that need to be hidden and then shown more + */ +this.ckan.module('table-toggle-more', function($) { + return { + /* options object can be extended using data-module-* attributes */ + options: {}, + + /* Initialises the module setting up elements and event listeners. + * + * Returns nothing. + */ + initialize: function () { + $.proxyAll(this, /_on/); + this.el.addClass('table-toggle-more'); + // Do we actually want this table to expand? + var rows = $('.toggle-more', this.el).length; + if (rows) { + // How much is the colspan? + var cols = $('thead tr th', this.el).length; + var template_more = [ + '', + '', + '', + '' + this._('Show more') + '', + '' + this._('Hide') + '', + '', + '', + '' + ].join('\n'); + var template_seperator = [ + '', + '', + '', + '' + ].join('\n'); + + var seperator = $(template_seperator).insertAfter($('.toggle-more:last-child', this.el)); + $(template_more).insertAfter(seperator); + + $('.show-more', this.el).on('click', this._onShowMore); + $('.show-less', this.el).on('click', this._onShowLess); + } + }, + + _onShowMore: function($e) { + $e.preventDefault(); + this.el + .removeClass('table-toggle-more') + .addClass('table-toggle-less'); + }, + + _onShowLess: function($e) { + $e.preventDefault(); + this.el + .removeClass('table-toggle-less') + .addClass('table-toggle-more'); + } + + } +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-toggle-more.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-toggle-more.min.js new file mode 100644 index 00000000..e846d3f3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/modules/table-toggle-more.min.js @@ -0,0 +1 @@ +this.ckan.module('table-toggle-more',function($){return{options:{},initialize:function(){$.proxyAll(this,/_on/);this.el.addClass('table-toggle-more');var rows=$('.toggle-more',this.el).length;if(rows){var cols=$('thead tr th',this.el).length;var template_more=['','','',''+this._('Show more')+'',''+this._('Hide')+'','','',''].join('\n');var template_seperator=['','','',''].join('\n');var seperator=$(template_seperator).insertAfter($('.toggle-more:last-child',this.el));$(template_more).insertAfter(seperator);$('.show-more',this.el).on('click',this._onShowMore);$('.show-less',this.el).on('click',this._onShowLess);}},_onShowMore:function($e){$e.preventDefault();this.el.removeClass('table-toggle-more').addClass('table-toggle-less');},_onShowLess:function($e){$e.preventDefault();this.el.removeClass('table-toggle-less').addClass('table-toggle-more');}}}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.date-helpers.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.date-helpers.js new file mode 100644 index 00000000..45d48a52 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.date-helpers.js @@ -0,0 +1,82 @@ +this.jQuery.date = { + /* A map of date methods to text strings. */ + METHODS: { + "yyyy": "getUTCFullYear", + "MM": "getUTCMonth", + "dd": "getUTCDate", + "HH": "getUTCHours", + "mm": "getUTCMinutes", + "ss": "getUTCSeconds", + "fff": "getUTCMilliseconds" + }, + + /* Formatting of an ISO8601 compatible date */ + ISO8601: "yyyy-MM-ddTHH:mm:ss.fffZ", + + /* Formatting of a CKAN compatible ISO string. See helpers.py */ + CKAN8601: "yyyy-MM-ddTHH:mm:ss", + + /* Returns a date string for the format provided. + * + * format - A format string in the form "yyyy-MM-dd" + * date - A date object to output. + * + * Returns a formatted date string. + */ + format: function (format, date) { + var map = this.METHODS; + + date = date || new Date(); + + function pad(str, exp) { + str = "" + str; + exp = exp.replace(/[a-z]/ig, '0'); + return str.length !== exp.length ? exp.slice(str.length) + str : str; + } + + return format.replace(/([a-zA-Z])\1+/g, function (_, $1) { + if (map[_]) { + var value = date[map[_]](); + if (_ === 'MM') { + value += 1; + } + return pad(value, _); + } + return _; + }); + }, + + /* Generates a CKAN friendly ISO8601 timestamp. + * + * date - A date object to convert. + * + * Examples + * + * var timestamp = jQuery.date.toCKANString(new Date()); + * + * Returns a timestamp string. + */ + toCKANString: function (date) { + return this.format(this.CKAN8601, date); + }, + + /* Generates a ISO8601 timestamp. Uses the native methods if available. + * + * date - A date object to convert. + * + * Examples + * + * var timestamp = jQuery.date.toISOString(new Date()); + * + * Returns a timestamp string. + */ + toISOString: function (date) { + date = date || new Date(); + + if (date.toISOString) { + return date.toISOString(); + } else { + return this.format(this.ISO8601, date); + } + } +}; diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.date-helpers.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.date-helpers.min.js new file mode 100644 index 00000000..aede7abe --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.date-helpers.min.js @@ -0,0 +1,4 @@ +this.jQuery.date={METHODS:{"yyyy":"getUTCFullYear","MM":"getUTCMonth","dd":"getUTCDate","HH":"getUTCHours","mm":"getUTCMinutes","ss":"getUTCSeconds","fff":"getUTCMilliseconds"},ISO8601:"yyyy-MM-ddTHH:mm:ss.fffZ",CKAN8601:"yyyy-MM-ddTHH:mm:ss",format:function(format,date){var map=this.METHODS;date=date||new Date();function pad(str,exp){str=""+str;exp=exp.replace(/[a-z]/ig,'0');return str.length!==exp.length?exp.slice(str.length)+str:str;} +return format.replace(/([a-zA-Z])\1+/g,function(_,$1){if(map[_]){var value=date[map[_]]();if(_==='MM'){value+=1;} +return pad(value,_);} +return _;});},toCKANString:function(date){return this.format(this.CKAN8601,date);},toISOString:function(date){date=date||new Date();if(date.toISOString){return date.toISOString();}else{return this.format(this.ISO8601,date);}}}; \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.form-warning.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.form-warning.js new file mode 100644 index 00000000..11fc993a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.form-warning.js @@ -0,0 +1,41 @@ +(function (jQuery) { + /* Accepts a form element and once changed binds an event handler to the + * window "beforeunload" event that warns a user that the form has unsaved + * changes. The notice is only displayed if the user does not submit the + * form. + * + * message - A message to display to the user (browser support dependant). + * + * Examples + * + * jQuery('form').incompleteFormWarning('Form has modified fields'); + * + * Returns the jQuery collection. + */ + jQuery.fn.incompleteFormWarning = function (message) { + return this.each(function () { + var form = jQuery(this); + var state = form.serialize(); + + function onWindowUnload(event) { + if (event.originalEvent.returnValue) { + event.originalEvent.returnValue = message; + } + return message; + } + + form.on({ + change: function () { + // See if the form has changed, if so add an event listener otherwise + // remove it. + var method = form.serialize() === state ? 'off' : 'on'; + jQuery(window)[method]('beforeunload', onWindowUnload); + }, + submit: function () { + // Allow the form to be submitted. + jQuery(window).off('beforeunload', onWindowUnload); + } + }); + }); + }; +})(this.jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.form-warning.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.form-warning.min.js new file mode 100644 index 00000000..13d95e27 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.form-warning.min.js @@ -0,0 +1,3 @@ +(function(jQuery){jQuery.fn.incompleteFormWarning=function(message){return this.each(function(){var form=jQuery(this);var state=form.serialize();function onWindowUnload(event){if(event.originalEvent.returnValue){event.originalEvent.returnValue=message;} +return message;} +form.on({change:function(){var method=form.serialize()===state?'off':'on';jQuery(window)[method]('beforeunload',onWindowUnload);},submit:function(){jQuery(window).off('beforeunload',onWindowUnload);}});});};})(this.jQuery); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.images-loaded.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.images-loaded.js new file mode 100644 index 00000000..f6ac95c2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.images-loaded.js @@ -0,0 +1,496 @@ +/*! + * imagesLoaded PACKAGED v4.1.4 + * JavaScript is all like "You images are done yet or what?" + * MIT License + */ + +/** + * EvEmitter v1.1.0 + * Lil' event emitter + * MIT License + */ + +/* jshint unused: true, undef: true, strict: true */ + +( function( global, factory ) { + // universal module definition + /* jshint strict: false */ /* globals define, module, window */ + if ( typeof define == 'function' && define.amd ) { + // AMD - RequireJS + define( 'ev-emitter/ev-emitter',factory ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS - Browserify, Webpack + module.exports = factory(); + } else { + // Browser globals + global.EvEmitter = factory(); + } + +}( typeof window != 'undefined' ? window : this, function() { + + + +function EvEmitter() {} + +var proto = EvEmitter.prototype; + +proto.on = function( eventName, listener ) { + if ( !eventName || !listener ) { + return; + } + // set events hash + var events = this._events = this._events || {}; + // set listeners array + var listeners = events[ eventName ] = events[ eventName ] || []; + // only add once + if ( listeners.indexOf( listener ) == -1 ) { + listeners.push( listener ); + } + + return this; +}; + +proto.once = function( eventName, listener ) { + if ( !eventName || !listener ) { + return; + } + // add event + this.on( eventName, listener ); + // set once flag + // set onceEvents hash + var onceEvents = this._onceEvents = this._onceEvents || {}; + // set onceListeners object + var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {}; + // set flag + onceListeners[ listener ] = true; + + return this; +}; + +proto.off = function( eventName, listener ) { + var listeners = this._events && this._events[ eventName ]; + if ( !listeners || !listeners.length ) { + return; + } + var index = listeners.indexOf( listener ); + if ( index != -1 ) { + listeners.splice( index, 1 ); + } + + return this; +}; + +proto.emitEvent = function( eventName, args ) { + var listeners = this._events && this._events[ eventName ]; + if ( !listeners || !listeners.length ) { + return; + } + // copy over to avoid interference if .off() in listener + listeners = listeners.slice(0); + args = args || []; + // once stuff + var onceListeners = this._onceEvents && this._onceEvents[ eventName ]; + + for ( var i=0; i < listeners.length; i++ ) { + var listener = listeners[i] + var isOnce = onceListeners && onceListeners[ listener ]; + if ( isOnce ) { + // remove listener + // remove before trigger to prevent recursion + this.off( eventName, listener ); + // unset once flag + delete onceListeners[ listener ]; + } + // trigger listener + listener.apply( this, args ); + } + + return this; +}; + +proto.allOff = function() { + delete this._events; + delete this._onceEvents; +}; + +return EvEmitter; + +})); + +/*! + * imagesLoaded v4.1.4 + * JavaScript is all like "You images are done yet or what?" + * MIT License + */ + +( function( window, factory ) { 'use strict'; + // universal module definition + + /*global define: false, module: false, require: false */ + + if ( typeof define == 'function' && define.amd ) { + // AMD + define( [ + 'ev-emitter/ev-emitter' + ], function( EvEmitter ) { + return factory( window, EvEmitter ); + }); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory( + window, + require('ev-emitter') + ); + } else { + // browser global + window.imagesLoaded = factory( + window, + window.EvEmitter + ); + } + +})( typeof window !== 'undefined' ? window : this, + +// -------------------------- factory -------------------------- // + +function factory( window, EvEmitter ) { + + + +var $ = window.jQuery; +var console = window.console; + +// -------------------------- helpers -------------------------- // + +// extend objects +function extend( a, b ) { + for ( var prop in b ) { + a[ prop ] = b[ prop ]; + } + return a; +} + +var arraySlice = Array.prototype.slice; + +// turn element or nodeList into an array +function makeArray( obj ) { + if ( Array.isArray( obj ) ) { + // use object if already an array + return obj; + } + + var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number'; + if ( isArrayLike ) { + // convert nodeList to array + return arraySlice.call( obj ); + } + + // array of single index + return [ obj ]; +} + +// -------------------------- imagesLoaded -------------------------- // + +/** + * @param {Array, Element, NodeList, String} elem + * @param {Object or Function} options - if function, use as callback + * @param {Function} onAlways - callback function + */ +function ImagesLoaded( elem, options, onAlways ) { + // coerce ImagesLoaded() without new, to be new ImagesLoaded() + if ( !( this instanceof ImagesLoaded ) ) { + return new ImagesLoaded( elem, options, onAlways ); + } + // use elem as selector string + var queryElem = elem; + if ( typeof elem == 'string' ) { + queryElem = document.querySelectorAll( elem ); + } + // bail if bad element + if ( !queryElem ) { + console.error( 'Bad element for imagesLoaded ' + ( queryElem || elem ) ); + return; + } + + this.elements = makeArray( queryElem ); + this.options = extend( {}, this.options ); + // shift arguments if no options set + if ( typeof options == 'function' ) { + onAlways = options; + } else { + extend( this.options, options ); + } + + if ( onAlways ) { + this.on( 'always', onAlways ); + } + + this.getImages(); + + if ( $ ) { + // add jQuery Deferred object + this.jqDeferred = new $.Deferred(); + } + + // HACK check async to allow time to bind listeners + setTimeout( this.check.bind( this ) ); +} + +ImagesLoaded.prototype = Object.create( EvEmitter.prototype ); + +ImagesLoaded.prototype.options = {}; + +ImagesLoaded.prototype.getImages = function() { + this.images = []; + + // filter & find items if we have an item selector + this.elements.forEach( this.addElementImages, this ); +}; + +/** + * @param {Node} element + */ +ImagesLoaded.prototype.addElementImages = function( elem ) { + // filter siblings + if ( elem.nodeName == 'IMG' ) { + this.addImage( elem ); + } + // get background image on element + if ( this.options.background === true ) { + this.addElementBackgroundImages( elem ); + } + + // find children + // no non-element nodes, #143 + var nodeType = elem.nodeType; + if ( !nodeType || !elementNodeTypes[ nodeType ] ) { + return; + } + var childImgs = elem.querySelectorAll('img'); + // concat childElems to filterFound array + for ( var i=0; i < childImgs.length; i++ ) { + var img = childImgs[i]; + this.addImage( img ); + } + + // get child background images + if ( typeof this.options.background == 'string' ) { + var children = elem.querySelectorAll( this.options.background ); + for ( i=0; i < children.length; i++ ) { + var child = children[i]; + this.addElementBackgroundImages( child ); + } + } +}; + +var elementNodeTypes = { + 1: true, + 9: true, + 11: true +}; + +ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) { + var style = getComputedStyle( elem ); + if ( !style ) { + // Firefox returns null if in a hidden iframe https://bugzil.la/548397 + return; + } + // get url inside url("...") + var reURL = /url\((['"])?(.*?)\1\)/gi; + var matches = reURL.exec( style.backgroundImage ); + while ( matches !== null ) { + var url = matches && matches[2]; + if ( url ) { + this.addBackground( url, elem ); + } + matches = reURL.exec( style.backgroundImage ); + } +}; + +/** + * @param {Image} img + */ +ImagesLoaded.prototype.addImage = function( img ) { + var loadingImage = new LoadingImage( img ); + this.images.push( loadingImage ); +}; + +ImagesLoaded.prototype.addBackground = function( url, elem ) { + var background = new Background( url, elem ); + this.images.push( background ); +}; + +ImagesLoaded.prototype.check = function() { + var _this = this; + this.progressedCount = 0; + this.hasAnyBroken = false; + // complete if no images + if ( !this.images.length ) { + this.complete(); + return; + } + + function onProgress( image, elem, message ) { + // HACK - Chrome triggers event before object properties have changed. #83 + setTimeout( function() { + _this.progress( image, elem, message ); + }); + } + + this.images.forEach( function( loadingImage ) { + loadingImage.once( 'progress', onProgress ); + loadingImage.check(); + }); +}; + +ImagesLoaded.prototype.progress = function( image, elem, message ) { + this.progressedCount++; + this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded; + // progress event + this.emitEvent( 'progress', [ this, image, elem ] ); + if ( this.jqDeferred && this.jqDeferred.notify ) { + this.jqDeferred.notify( this, image ); + } + // check if completed + if ( this.progressedCount == this.images.length ) { + this.complete(); + } + + if ( this.options.debug && console ) { + console.log( 'progress: ' + message, image, elem ); + } +}; + +ImagesLoaded.prototype.complete = function() { + var eventName = this.hasAnyBroken ? 'fail' : 'done'; + this.isComplete = true; + this.emitEvent( eventName, [ this ] ); + this.emitEvent( 'always', [ this ] ); + if ( this.jqDeferred ) { + var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve'; + this.jqDeferred[ jqMethod ]( this ); + } +}; + +// -------------------------- -------------------------- // + +function LoadingImage( img ) { + this.img = img; +} + +LoadingImage.prototype = Object.create( EvEmitter.prototype ); + +LoadingImage.prototype.check = function() { + // If complete is true and browser supports natural sizes, + // try to check for image status manually. + var isComplete = this.getIsImageComplete(); + if ( isComplete ) { + // report based on naturalWidth + this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' ); + return; + } + + // If none of the checks above matched, simulate loading on detached element. + this.proxyImage = new Image(); + this.proxyImage.addEventListener( 'load', this ); + this.proxyImage.addEventListener( 'error', this ); + // bind to image as well for Firefox. #191 + this.img.addEventListener( 'load', this ); + this.img.addEventListener( 'error', this ); + this.proxyImage.src = this.img.src; +}; + +LoadingImage.prototype.getIsImageComplete = function() { + // check for non-zero, non-undefined naturalWidth + // fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671 + return this.img.complete && this.img.naturalWidth; +}; + +LoadingImage.prototype.confirm = function( isLoaded, message ) { + this.isLoaded = isLoaded; + this.emitEvent( 'progress', [ this, this.img, message ] ); +}; + +// ----- events ----- // + +// trigger specified handler for event type +LoadingImage.prototype.handleEvent = function( event ) { + var method = 'on' + event.type; + if ( this[ method ] ) { + this[ method ]( event ); + } +}; + +LoadingImage.prototype.onload = function() { + this.confirm( true, 'onload' ); + this.unbindEvents(); +}; + +LoadingImage.prototype.onerror = function() { + this.confirm( false, 'onerror' ); + this.unbindEvents(); +}; + +LoadingImage.prototype.unbindEvents = function() { + this.proxyImage.removeEventListener( 'load', this ); + this.proxyImage.removeEventListener( 'error', this ); + this.img.removeEventListener( 'load', this ); + this.img.removeEventListener( 'error', this ); +}; + +// -------------------------- Background -------------------------- // + +function Background( url, element ) { + this.url = url; + this.element = element; + this.img = new Image(); +} + +// inherit LoadingImage prototype +Background.prototype = Object.create( LoadingImage.prototype ); + +Background.prototype.check = function() { + this.img.addEventListener( 'load', this ); + this.img.addEventListener( 'error', this ); + this.img.src = this.url; + // check if image is already complete + var isComplete = this.getIsImageComplete(); + if ( isComplete ) { + this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' ); + this.unbindEvents(); + } +}; + +Background.prototype.unbindEvents = function() { + this.img.removeEventListener( 'load', this ); + this.img.removeEventListener( 'error', this ); +}; + +Background.prototype.confirm = function( isLoaded, message ) { + this.isLoaded = isLoaded; + this.emitEvent( 'progress', [ this, this.element, message ] ); +}; + +// -------------------------- jQuery -------------------------- // + +ImagesLoaded.makeJQueryPlugin = function( jQuery ) { + jQuery = jQuery || window.jQuery; + if ( !jQuery ) { + return; + } + // set local variable + $ = jQuery; + // $().imagesLoaded() + $.fn.imagesLoaded = function( options, callback ) { + var instance = new ImagesLoaded( this, options, callback ); + return instance.jqDeferred.promise( $(this) ); + }; +}; +// try making plugin +ImagesLoaded.makeJQueryPlugin(); + +// -------------------------- -------------------------- // + +return ImagesLoaded; + +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.images-loaded.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.images-loaded.min.js new file mode 100644 index 00000000..dd524bed --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.images-loaded.min.js @@ -0,0 +1,37 @@ +(function(global,factory){if(typeof define=='function'&&define.amd){define('ev-emitter/ev-emitter',factory);}else if(typeof module=='object'&&module.exports){module.exports=factory();}else{global.EvEmitter=factory();}}(typeof window!='undefined'?window:this,function(){function EvEmitter(){} +var proto=EvEmitter.prototype;proto.on=function(eventName,listener){if(!eventName||!listener){return;} +var events=this._events=this._events||{};var listeners=events[eventName]=events[eventName]||[];if(listeners.indexOf(listener)==-1){listeners.push(listener);} +return this;};proto.once=function(eventName,listener){if(!eventName||!listener){return;} +this.on(eventName,listener);var onceEvents=this._onceEvents=this._onceEvents||{};var onceListeners=onceEvents[eventName]=onceEvents[eventName]||{};onceListeners[listener]=true;return this;};proto.off=function(eventName,listener){var listeners=this._events&&this._events[eventName];if(!listeners||!listeners.length){return;} +var index=listeners.indexOf(listener);if(index!=-1){listeners.splice(index,1);} +return this;};proto.emitEvent=function(eventName,args){var listeners=this._events&&this._events[eventName];if(!listeners||!listeners.length){return;} +listeners=listeners.slice(0);args=args||[];var onceListeners=this._onceEvents&&this._onceEvents[eventName];for(var i=0;i $().plugin('option', {...}) + if ( !PluginClass.prototype.option ) { + // option setter + PluginClass.prototype.option = function( opts ) { + // bail out if not an object + if ( !$.isPlainObject( opts ) ){ + return; + } + this.options = $.extend( true, this.options, opts ); + }; + } + + // make jQuery plugin + $.fn[ namespace ] = function( arg0 /*, arg1 */ ) { + if ( typeof arg0 == 'string' ) { + // method call $().plugin( 'methodName', { options } ) + // shift arguments by 1 + var args = arraySlice.call( arguments, 1 ); + return methodCall( this, arg0, args ); + } + // just $().plugin({ options }) + plainCall( this, arg0 ); + return this; + }; + + // $().plugin('methodName') + function methodCall( $elems, methodName, args ) { + var returnValue; + var pluginMethodStr = '$().' + namespace + '("' + methodName + '")'; + + $elems.each( function( i, elem ) { + // get instance + var instance = $.data( elem, namespace ); + if ( !instance ) { + logError( namespace + ' not initialized. Cannot call methods, i.e. ' + + pluginMethodStr ); + return; + } + + var method = instance[ methodName ]; + if ( !method || methodName.charAt(0) == '_' ) { + logError( pluginMethodStr + ' is not a valid method' ); + return; + } + + // apply method, get return value + var value = method.apply( instance, args ); + // set return value if value is returned, use only first value + returnValue = returnValue === undefined ? value : returnValue; + }); + + return returnValue !== undefined ? returnValue : $elems; + } + + function plainCall( $elems, options ) { + $elems.each( function( i, elem ) { + var instance = $.data( elem, namespace ); + if ( instance ) { + // set options & init + instance.option( options ); + instance._init(); + } else { + // initialize new instance + instance = new PluginClass( elem, options ); + $.data( elem, namespace, instance ); + } + }); + } + + updateJQuery( $ ); + +} + +// ----- updateJQuery ----- // + +// set $.bridget for v1 backwards compatibility +function updateJQuery( $ ) { + if ( !$ || ( $ && $.bridget ) ) { + return; + } + $.bridget = jQueryBridget; +} + +updateJQuery( jQuery || window.jQuery ); + +// ----- ----- // + +return jQueryBridget; + +})); + +/** + * EvEmitter v1.1.0 + * Lil' event emitter + * MIT License + */ + +/* jshint unused: true, undef: true, strict: true */ + +( function( global, factory ) { + // universal module definition + /* jshint strict: false */ /* globals define, module, window */ + if ( typeof define == 'function' && define.amd ) { + // AMD - RequireJS + define( 'ev-emitter/ev-emitter',factory ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS - Browserify, Webpack + module.exports = factory(); + } else { + // Browser globals + global.EvEmitter = factory(); + } + +}( typeof window != 'undefined' ? window : this, function() { + + + +function EvEmitter() {} + +var proto = EvEmitter.prototype; + +proto.on = function( eventName, listener ) { + if ( !eventName || !listener ) { + return; + } + // set events hash + var events = this._events = this._events || {}; + // set listeners array + var listeners = events[ eventName ] = events[ eventName ] || []; + // only add once + if ( listeners.indexOf( listener ) == -1 ) { + listeners.push( listener ); + } + + return this; +}; + +proto.once = function( eventName, listener ) { + if ( !eventName || !listener ) { + return; + } + // add event + this.on( eventName, listener ); + // set once flag + // set onceEvents hash + var onceEvents = this._onceEvents = this._onceEvents || {}; + // set onceListeners object + var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {}; + // set flag + onceListeners[ listener ] = true; + + return this; +}; + +proto.off = function( eventName, listener ) { + var listeners = this._events && this._events[ eventName ]; + if ( !listeners || !listeners.length ) { + return; + } + var index = listeners.indexOf( listener ); + if ( index != -1 ) { + listeners.splice( index, 1 ); + } + + return this; +}; + +proto.emitEvent = function( eventName, args ) { + var listeners = this._events && this._events[ eventName ]; + if ( !listeners || !listeners.length ) { + return; + } + // copy over to avoid interference if .off() in listener + listeners = listeners.slice(0); + args = args || []; + // once stuff + var onceListeners = this._onceEvents && this._onceEvents[ eventName ]; + + for ( var i=0; i < listeners.length; i++ ) { + var listener = listeners[i] + var isOnce = onceListeners && onceListeners[ listener ]; + if ( isOnce ) { + // remove listener + // remove before trigger to prevent recursion + this.off( eventName, listener ); + // unset once flag + delete onceListeners[ listener ]; + } + // trigger listener + listener.apply( this, args ); + } + + return this; +}; + +proto.allOff = function() { + delete this._events; + delete this._onceEvents; +}; + +return EvEmitter; + +})); + +/*! + * getSize v2.0.2 + * measure size of elements + * MIT license + */ + +/*jshint browser: true, strict: true, undef: true, unused: true */ +/*global define: false, module: false, console: false */ + +( function( window, factory ) { + 'use strict'; + + if ( typeof define == 'function' && define.amd ) { + // AMD + define( 'get-size/get-size',[],function() { + return factory(); + }); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory(); + } else { + // browser global + window.getSize = factory(); + } + +})( window, function factory() { +'use strict'; + +// -------------------------- helpers -------------------------- // + +// get a number from a string, not a percentage +function getStyleSize( value ) { + var num = parseFloat( value ); + // not a percent like '100%', and a number + var isValid = value.indexOf('%') == -1 && !isNaN( num ); + return isValid && num; +} + +function noop() {} + +var logError = typeof console == 'undefined' ? noop : + function( message ) { + console.error( message ); + }; + +// -------------------------- measurements -------------------------- // + +var measurements = [ + 'paddingLeft', + 'paddingRight', + 'paddingTop', + 'paddingBottom', + 'marginLeft', + 'marginRight', + 'marginTop', + 'marginBottom', + 'borderLeftWidth', + 'borderRightWidth', + 'borderTopWidth', + 'borderBottomWidth' +]; + +var measurementsLength = measurements.length; + +function getZeroSize() { + var size = { + width: 0, + height: 0, + innerWidth: 0, + innerHeight: 0, + outerWidth: 0, + outerHeight: 0 + }; + for ( var i=0; i < measurementsLength; i++ ) { + var measurement = measurements[i]; + size[ measurement ] = 0; + } + return size; +} + +// -------------------------- getStyle -------------------------- // + +/** + * getStyle, get style of element, check for Firefox bug + * https://bugzilla.mozilla.org/show_bug.cgi?id=548397 + */ +function getStyle( elem ) { + var style = getComputedStyle( elem ); + if ( !style ) { + logError( 'Style returned ' + style + + '. Are you running this code in a hidden iframe on Firefox? ' + + 'See http://bit.ly/getsizebug1' ); + } + return style; +} + +// -------------------------- setup -------------------------- // + +var isSetup = false; + +var isBoxSizeOuter; + +/** + * setup + * check isBoxSizerOuter + * do on first getSize() rather than on page load for Firefox bug + */ +function setup() { + // setup once + if ( isSetup ) { + return; + } + isSetup = true; + + // -------------------------- box sizing -------------------------- // + + /** + * WebKit measures the outer-width on style.width on border-box elems + * IE & Firefox<29 measures the inner-width + */ + var div = document.createElement('div'); + div.style.width = '200px'; + div.style.padding = '1px 2px 3px 4px'; + div.style.borderStyle = 'solid'; + div.style.borderWidth = '1px 2px 3px 4px'; + div.style.boxSizing = 'border-box'; + + var body = document.body || document.documentElement; + body.appendChild( div ); + var style = getStyle( div ); + + getSize.isBoxSizeOuter = isBoxSizeOuter = getStyleSize( style.width ) == 200; + body.removeChild( div ); + +} + +// -------------------------- getSize -------------------------- // + +function getSize( elem ) { + setup(); + + // use querySeletor if elem is string + if ( typeof elem == 'string' ) { + elem = document.querySelector( elem ); + } + + // do not proceed on non-objects + if ( !elem || typeof elem != 'object' || !elem.nodeType ) { + return; + } + + var style = getStyle( elem ); + + // if hidden, everything is 0 + if ( style.display == 'none' ) { + return getZeroSize(); + } + + var size = {}; + size.width = elem.offsetWidth; + size.height = elem.offsetHeight; + + var isBorderBox = size.isBorderBox = style.boxSizing == 'border-box'; + + // get all measurements + for ( var i=0; i < measurementsLength; i++ ) { + var measurement = measurements[i]; + var value = style[ measurement ]; + var num = parseFloat( value ); + // any 'auto', 'medium' value will be 0 + size[ measurement ] = !isNaN( num ) ? num : 0; + } + + var paddingWidth = size.paddingLeft + size.paddingRight; + var paddingHeight = size.paddingTop + size.paddingBottom; + var marginWidth = size.marginLeft + size.marginRight; + var marginHeight = size.marginTop + size.marginBottom; + var borderWidth = size.borderLeftWidth + size.borderRightWidth; + var borderHeight = size.borderTopWidth + size.borderBottomWidth; + + var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter; + + // overwrite width and height if we can get it from style + var styleWidth = getStyleSize( style.width ); + if ( styleWidth !== false ) { + size.width = styleWidth + + // add padding and border unless it's already including it + ( isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth ); + } + + var styleHeight = getStyleSize( style.height ); + if ( styleHeight !== false ) { + size.height = styleHeight + + // add padding and border unless it's already including it + ( isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight ); + } + + size.innerWidth = size.width - ( paddingWidth + borderWidth ); + size.innerHeight = size.height - ( paddingHeight + borderHeight ); + + size.outerWidth = size.width + marginWidth; + size.outerHeight = size.height + marginHeight; + + return size; +} + +return getSize; + +}); + +/** + * matchesSelector v2.0.2 + * matchesSelector( element, '.selector' ) + * MIT license + */ + +/*jshint browser: true, strict: true, undef: true, unused: true */ + +( function( window, factory ) { + /*global define: false, module: false */ + 'use strict'; + // universal module definition + if ( typeof define == 'function' && define.amd ) { + // AMD + define( 'desandro-matches-selector/matches-selector',factory ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory(); + } else { + // browser global + window.matchesSelector = factory(); + } + +}( window, function factory() { + 'use strict'; + + var matchesMethod = ( function() { + var ElemProto = window.Element.prototype; + // check for the standard method name first + if ( ElemProto.matches ) { + return 'matches'; + } + // check un-prefixed + if ( ElemProto.matchesSelector ) { + return 'matchesSelector'; + } + // check vendor prefixes + var prefixes = [ 'webkit', 'moz', 'ms', 'o' ]; + + for ( var i=0; i < prefixes.length; i++ ) { + var prefix = prefixes[i]; + var method = prefix + 'MatchesSelector'; + if ( ElemProto[ method ] ) { + return method; + } + } + })(); + + return function matchesSelector( elem, selector ) { + return elem[ matchesMethod ]( selector ); + }; + +})); + +/** + * Fizzy UI utils v2.0.5 + * MIT license + */ + +/*jshint browser: true, undef: true, unused: true, strict: true */ + +( function( window, factory ) { + // universal module definition + /*jshint strict: false */ /*globals define, module, require */ + + if ( typeof define == 'function' && define.amd ) { + // AMD + define( 'fizzy-ui-utils/utils',[ + 'desandro-matches-selector/matches-selector' + ], function( matchesSelector ) { + return factory( window, matchesSelector ); + }); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory( + window, + require('desandro-matches-selector') + ); + } else { + // browser global + window.fizzyUIUtils = factory( + window, + window.matchesSelector + ); + } + +}( window, function factory( window, matchesSelector ) { + + + +var utils = {}; + +// ----- extend ----- // + +// extends objects +utils.extend = function( a, b ) { + for ( var prop in b ) { + a[ prop ] = b[ prop ]; + } + return a; +}; + +// ----- modulo ----- // + +utils.modulo = function( num, div ) { + return ( ( num % div ) + div ) % div; +}; + +// ----- makeArray ----- // + +// turn element or nodeList into an array +utils.makeArray = function( obj ) { + var ary = []; + if ( Array.isArray( obj ) ) { + // use object if already an array + ary = obj; + } else if ( obj && typeof obj == 'object' && + typeof obj.length == 'number' ) { + // convert nodeList to array + for ( var i=0; i < obj.length; i++ ) { + ary.push( obj[i] ); + } + } else { + // array of single index + ary.push( obj ); + } + return ary; +}; + +// ----- removeFrom ----- // + +utils.removeFrom = function( ary, obj ) { + var index = ary.indexOf( obj ); + if ( index != -1 ) { + ary.splice( index, 1 ); + } +}; + +// ----- getParent ----- // + +utils.getParent = function( elem, selector ) { + while ( elem.parentNode && elem != document.body ) { + elem = elem.parentNode; + if ( matchesSelector( elem, selector ) ) { + return elem; + } + } +}; + +// ----- getQueryElement ----- // + +// use element as selector string +utils.getQueryElement = function( elem ) { + if ( typeof elem == 'string' ) { + return document.querySelector( elem ); + } + return elem; +}; + +// ----- handleEvent ----- // + +// enable .ontype to trigger from .addEventListener( elem, 'type' ) +utils.handleEvent = function( event ) { + var method = 'on' + event.type; + if ( this[ method ] ) { + this[ method ]( event ); + } +}; + +// ----- filterFindElements ----- // + +utils.filterFindElements = function( elems, selector ) { + // make array of elems + elems = utils.makeArray( elems ); + var ffElems = []; + + elems.forEach( function( elem ) { + // check that elem is an actual element + if ( !( elem instanceof HTMLElement ) ) { + return; + } + // add elem if no selector + if ( !selector ) { + ffElems.push( elem ); + return; + } + // filter & find items if we have a selector + // filter + if ( matchesSelector( elem, selector ) ) { + ffElems.push( elem ); + } + // find children + var childElems = elem.querySelectorAll( selector ); + // concat childElems to filterFound array + for ( var i=0; i < childElems.length; i++ ) { + ffElems.push( childElems[i] ); + } + }); + + return ffElems; +}; + +// ----- debounceMethod ----- // + +utils.debounceMethod = function( _class, methodName, threshold ) { + // original method + var method = _class.prototype[ methodName ]; + var timeoutName = methodName + 'Timeout'; + + _class.prototype[ methodName ] = function() { + var timeout = this[ timeoutName ]; + if ( timeout ) { + clearTimeout( timeout ); + } + var args = arguments; + + var _this = this; + this[ timeoutName ] = setTimeout( function() { + method.apply( _this, args ); + delete _this[ timeoutName ]; + }, threshold || 100 ); + }; +}; + +// ----- docReady ----- // + +utils.docReady = function( callback ) { + var readyState = document.readyState; + if ( readyState == 'complete' || readyState == 'interactive' ) { + // do async to allow for other scripts to run. metafizzy/flickity#441 + setTimeout( callback ); + } else { + document.addEventListener( 'DOMContentLoaded', callback ); + } +}; + +// ----- htmlInit ----- // + +// http://jamesroberts.name/blog/2010/02/22/string-functions-for-javascript-trim-to-camel-case-to-dashed-and-to-underscore/ +utils.toDashed = function( str ) { + return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) { + return $1 + '-' + $2; + }).toLowerCase(); +}; + +var console = window.console; +/** + * allow user to initialize classes via [data-namespace] or .js-namespace class + * htmlInit( Widget, 'widgetName' ) + * options are parsed from data-namespace-options + */ +utils.htmlInit = function( WidgetClass, namespace ) { + utils.docReady( function() { + var dashedNamespace = utils.toDashed( namespace ); + var dataAttr = 'data-' + dashedNamespace; + var dataAttrElems = document.querySelectorAll( '[' + dataAttr + ']' ); + var jsDashElems = document.querySelectorAll( '.js-' + dashedNamespace ); + var elems = utils.makeArray( dataAttrElems ) + .concat( utils.makeArray( jsDashElems ) ); + var dataOptionsAttr = dataAttr + '-options'; + var jQuery = window.jQuery; + + elems.forEach( function( elem ) { + var attr = elem.getAttribute( dataAttr ) || + elem.getAttribute( dataOptionsAttr ); + var options; + try { + options = attr && JSON.parse( attr ); + } catch ( error ) { + // log error, do not initialize + if ( console ) { + console.error( 'Error parsing ' + dataAttr + ' on ' + elem.className + + ': ' + error ); + } + return; + } + // initialize + var instance = new WidgetClass( elem, options ); + // make available via $().data('namespace') + if ( jQuery ) { + jQuery.data( elem, namespace, instance ); + } + }); + + }); +}; + +// ----- ----- // + +return utils; + +})); + +/** + * Outlayer Item + */ + +( function( window, factory ) { + // universal module definition + /* jshint strict: false */ /* globals define, module, require */ + if ( typeof define == 'function' && define.amd ) { + // AMD - RequireJS + define( 'outlayer/item',[ + 'ev-emitter/ev-emitter', + 'get-size/get-size' + ], + factory + ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS - Browserify, Webpack + module.exports = factory( + require('ev-emitter'), + require('get-size') + ); + } else { + // browser global + window.Outlayer = {}; + window.Outlayer.Item = factory( + window.EvEmitter, + window.getSize + ); + } + +}( window, function factory( EvEmitter, getSize ) { +'use strict'; + +// ----- helpers ----- // + +function isEmptyObj( obj ) { + for ( var prop in obj ) { + return false; + } + prop = null; + return true; +} + +// -------------------------- CSS3 support -------------------------- // + + +var docElemStyle = document.documentElement.style; + +var transitionProperty = typeof docElemStyle.transition == 'string' ? + 'transition' : 'WebkitTransition'; +var transformProperty = typeof docElemStyle.transform == 'string' ? + 'transform' : 'WebkitTransform'; + +var transitionEndEvent = { + WebkitTransition: 'webkitTransitionEnd', + transition: 'transitionend' +}[ transitionProperty ]; + +// cache all vendor properties that could have vendor prefix +var vendorProperties = { + transform: transformProperty, + transition: transitionProperty, + transitionDuration: transitionProperty + 'Duration', + transitionProperty: transitionProperty + 'Property', + transitionDelay: transitionProperty + 'Delay' +}; + +// -------------------------- Item -------------------------- // + +function Item( element, layout ) { + if ( !element ) { + return; + } + + this.element = element; + // parent layout class, i.e. Masonry, Isotope, or Packery + this.layout = layout; + this.position = { + x: 0, + y: 0 + }; + + this._create(); +} + +// inherit EvEmitter +var proto = Item.prototype = Object.create( EvEmitter.prototype ); +proto.constructor = Item; + +proto._create = function() { + // transition objects + this._transn = { + ingProperties: {}, + clean: {}, + onEnd: {} + }; + + this.css({ + position: 'absolute' + }); +}; + +// trigger specified handler for event type +proto.handleEvent = function( event ) { + var method = 'on' + event.type; + if ( this[ method ] ) { + this[ method ]( event ); + } +}; + +proto.getSize = function() { + this.size = getSize( this.element ); +}; + +/** + * apply CSS styles to element + * @param {Object} style + */ +proto.css = function( style ) { + var elemStyle = this.element.style; + + for ( var prop in style ) { + // use vendor property if available + var supportedProp = vendorProperties[ prop ] || prop; + elemStyle[ supportedProp ] = style[ prop ]; + } +}; + + // measure position, and sets it +proto.getPosition = function() { + var style = getComputedStyle( this.element ); + var isOriginLeft = this.layout._getOption('originLeft'); + var isOriginTop = this.layout._getOption('originTop'); + var xValue = style[ isOriginLeft ? 'left' : 'right' ]; + var yValue = style[ isOriginTop ? 'top' : 'bottom' ]; + // convert percent to pixels + var layoutSize = this.layout.size; + var x = xValue.indexOf('%') != -1 ? + ( parseFloat( xValue ) / 100 ) * layoutSize.width : parseInt( xValue, 10 ); + var y = yValue.indexOf('%') != -1 ? + ( parseFloat( yValue ) / 100 ) * layoutSize.height : parseInt( yValue, 10 ); + + // clean up 'auto' or other non-integer values + x = isNaN( x ) ? 0 : x; + y = isNaN( y ) ? 0 : y; + // remove padding from measurement + x -= isOriginLeft ? layoutSize.paddingLeft : layoutSize.paddingRight; + y -= isOriginTop ? layoutSize.paddingTop : layoutSize.paddingBottom; + + this.position.x = x; + this.position.y = y; +}; + +// set settled position, apply padding +proto.layoutPosition = function() { + var layoutSize = this.layout.size; + var style = {}; + var isOriginLeft = this.layout._getOption('originLeft'); + var isOriginTop = this.layout._getOption('originTop'); + + // x + var xPadding = isOriginLeft ? 'paddingLeft' : 'paddingRight'; + var xProperty = isOriginLeft ? 'left' : 'right'; + var xResetProperty = isOriginLeft ? 'right' : 'left'; + + var x = this.position.x + layoutSize[ xPadding ]; + // set in percentage or pixels + style[ xProperty ] = this.getXValue( x ); + // reset other property + style[ xResetProperty ] = ''; + + // y + var yPadding = isOriginTop ? 'paddingTop' : 'paddingBottom'; + var yProperty = isOriginTop ? 'top' : 'bottom'; + var yResetProperty = isOriginTop ? 'bottom' : 'top'; + + var y = this.position.y + layoutSize[ yPadding ]; + // set in percentage or pixels + style[ yProperty ] = this.getYValue( y ); + // reset other property + style[ yResetProperty ] = ''; + + this.css( style ); + this.emitEvent( 'layout', [ this ] ); +}; + +proto.getXValue = function( x ) { + var isHorizontal = this.layout._getOption('horizontal'); + return this.layout.options.percentPosition && !isHorizontal ? + ( ( x / this.layout.size.width ) * 100 ) + '%' : x + 'px'; +}; + +proto.getYValue = function( y ) { + var isHorizontal = this.layout._getOption('horizontal'); + return this.layout.options.percentPosition && isHorizontal ? + ( ( y / this.layout.size.height ) * 100 ) + '%' : y + 'px'; +}; + +proto._transitionTo = function( x, y ) { + this.getPosition(); + // get current x & y from top/left + var curX = this.position.x; + var curY = this.position.y; + + var compareX = parseInt( x, 10 ); + var compareY = parseInt( y, 10 ); + var didNotMove = compareX === this.position.x && compareY === this.position.y; + + // save end position + this.setPosition( x, y ); + + // if did not move and not transitioning, just go to layout + if ( didNotMove && !this.isTransitioning ) { + this.layoutPosition(); + return; + } + + var transX = x - curX; + var transY = y - curY; + var transitionStyle = {}; + transitionStyle.transform = this.getTranslate( transX, transY ); + + this.transition({ + to: transitionStyle, + onTransitionEnd: { + transform: this.layoutPosition + }, + isCleaning: true + }); +}; + +proto.getTranslate = function( x, y ) { + // flip cooridinates if origin on right or bottom + var isOriginLeft = this.layout._getOption('originLeft'); + var isOriginTop = this.layout._getOption('originTop'); + x = isOriginLeft ? x : -x; + y = isOriginTop ? y : -y; + return 'translate3d(' + x + 'px, ' + y + 'px, 0)'; +}; + +// non transition + transform support +proto.goTo = function( x, y ) { + this.setPosition( x, y ); + this.layoutPosition(); +}; + +proto.moveTo = proto._transitionTo; + +proto.setPosition = function( x, y ) { + this.position.x = parseInt( x, 10 ); + this.position.y = parseInt( y, 10 ); +}; + +// ----- transition ----- // + +/** + * @param {Object} style - CSS + * @param {Function} onTransitionEnd + */ + +// non transition, just trigger callback +proto._nonTransition = function( args ) { + this.css( args.to ); + if ( args.isCleaning ) { + this._removeStyles( args.to ); + } + for ( var prop in args.onTransitionEnd ) { + args.onTransitionEnd[ prop ].call( this ); + } +}; + +/** + * proper transition + * @param {Object} args - arguments + * @param {Object} to - style to transition to + * @param {Object} from - style to start transition from + * @param {Boolean} isCleaning - removes transition styles after transition + * @param {Function} onTransitionEnd - callback + */ +proto.transition = function( args ) { + // redirect to nonTransition if no transition duration + if ( !parseFloat( this.layout.options.transitionDuration ) ) { + this._nonTransition( args ); + return; + } + + var _transition = this._transn; + // keep track of onTransitionEnd callback by css property + for ( var prop in args.onTransitionEnd ) { + _transition.onEnd[ prop ] = args.onTransitionEnd[ prop ]; + } + // keep track of properties that are transitioning + for ( prop in args.to ) { + _transition.ingProperties[ prop ] = true; + // keep track of properties to clean up when transition is done + if ( args.isCleaning ) { + _transition.clean[ prop ] = true; + } + } + + // set from styles + if ( args.from ) { + this.css( args.from ); + // force redraw. http://blog.alexmaccaw.com/css-transitions + var h = this.element.offsetHeight; + // hack for JSHint to hush about unused var + h = null; + } + // enable transition + this.enableTransition( args.to ); + // set styles that are transitioning + this.css( args.to ); + + this.isTransitioning = true; + +}; + +// dash before all cap letters, including first for +// WebkitTransform => -webkit-transform +function toDashedAll( str ) { + return str.replace( /([A-Z])/g, function( $1 ) { + return '-' + $1.toLowerCase(); + }); +} + +var transitionProps = 'opacity,' + toDashedAll( transformProperty ); + +proto.enableTransition = function(/* style */) { + // HACK changing transitionProperty during a transition + // will cause transition to jump + if ( this.isTransitioning ) { + return; + } + + // make `transition: foo, bar, baz` from style object + // HACK un-comment this when enableTransition can work + // while a transition is happening + // var transitionValues = []; + // for ( var prop in style ) { + // // dash-ify camelCased properties like WebkitTransition + // prop = vendorProperties[ prop ] || prop; + // transitionValues.push( toDashedAll( prop ) ); + // } + // munge number to millisecond, to match stagger + var duration = this.layout.options.transitionDuration; + duration = typeof duration == 'number' ? duration + 'ms' : duration; + // enable transition styles + this.css({ + transitionProperty: transitionProps, + transitionDuration: duration, + transitionDelay: this.staggerDelay || 0 + }); + // listen for transition end event + this.element.addEventListener( transitionEndEvent, this, false ); +}; + +// ----- events ----- // + +proto.onwebkitTransitionEnd = function( event ) { + this.ontransitionend( event ); +}; + +proto.onotransitionend = function( event ) { + this.ontransitionend( event ); +}; + +// properties that I munge to make my life easier +var dashedVendorProperties = { + '-webkit-transform': 'transform' +}; + +proto.ontransitionend = function( event ) { + // disregard bubbled events from children + if ( event.target !== this.element ) { + return; + } + var _transition = this._transn; + // get property name of transitioned property, convert to prefix-free + var propertyName = dashedVendorProperties[ event.propertyName ] || event.propertyName; + + // remove property that has completed transitioning + delete _transition.ingProperties[ propertyName ]; + // check if any properties are still transitioning + if ( isEmptyObj( _transition.ingProperties ) ) { + // all properties have completed transitioning + this.disableTransition(); + } + // clean style + if ( propertyName in _transition.clean ) { + // clean up style + this.element.style[ event.propertyName ] = ''; + delete _transition.clean[ propertyName ]; + } + // trigger onTransitionEnd callback + if ( propertyName in _transition.onEnd ) { + var onTransitionEnd = _transition.onEnd[ propertyName ]; + onTransitionEnd.call( this ); + delete _transition.onEnd[ propertyName ]; + } + + this.emitEvent( 'transitionEnd', [ this ] ); +}; + +proto.disableTransition = function() { + this.removeTransitionStyles(); + this.element.removeEventListener( transitionEndEvent, this, false ); + this.isTransitioning = false; +}; + +/** + * removes style property from element + * @param {Object} style +**/ +proto._removeStyles = function( style ) { + // clean up transition styles + var cleanStyle = {}; + for ( var prop in style ) { + cleanStyle[ prop ] = ''; + } + this.css( cleanStyle ); +}; + +var cleanTransitionStyle = { + transitionProperty: '', + transitionDuration: '', + transitionDelay: '' +}; + +proto.removeTransitionStyles = function() { + // remove transition + this.css( cleanTransitionStyle ); +}; + +// ----- stagger ----- // + +proto.stagger = function( delay ) { + delay = isNaN( delay ) ? 0 : delay; + this.staggerDelay = delay + 'ms'; +}; + +// ----- show/hide/remove ----- // + +// remove element from DOM +proto.removeElem = function() { + this.element.parentNode.removeChild( this.element ); + // remove display: none + this.css({ display: '' }); + this.emitEvent( 'remove', [ this ] ); +}; + +proto.remove = function() { + // just remove element if no transition support or no transition + if ( !transitionProperty || !parseFloat( this.layout.options.transitionDuration ) ) { + this.removeElem(); + return; + } + + // start transition + this.once( 'transitionEnd', function() { + this.removeElem(); + }); + this.hide(); +}; + +proto.reveal = function() { + delete this.isHidden; + // remove display: none + this.css({ display: '' }); + + var options = this.layout.options; + + var onTransitionEnd = {}; + var transitionEndProperty = this.getHideRevealTransitionEndProperty('visibleStyle'); + onTransitionEnd[ transitionEndProperty ] = this.onRevealTransitionEnd; + + this.transition({ + from: options.hiddenStyle, + to: options.visibleStyle, + isCleaning: true, + onTransitionEnd: onTransitionEnd + }); +}; + +proto.onRevealTransitionEnd = function() { + // check if still visible + // during transition, item may have been hidden + if ( !this.isHidden ) { + this.emitEvent('reveal'); + } +}; + +/** + * get style property use for hide/reveal transition end + * @param {String} styleProperty - hiddenStyle/visibleStyle + * @returns {String} + */ +proto.getHideRevealTransitionEndProperty = function( styleProperty ) { + var optionStyle = this.layout.options[ styleProperty ]; + // use opacity + if ( optionStyle.opacity ) { + return 'opacity'; + } + // get first property + for ( var prop in optionStyle ) { + return prop; + } +}; + +proto.hide = function() { + // set flag + this.isHidden = true; + // remove display: none + this.css({ display: '' }); + + var options = this.layout.options; + + var onTransitionEnd = {}; + var transitionEndProperty = this.getHideRevealTransitionEndProperty('hiddenStyle'); + onTransitionEnd[ transitionEndProperty ] = this.onHideTransitionEnd; + + this.transition({ + from: options.visibleStyle, + to: options.hiddenStyle, + // keep hidden stuff hidden + isCleaning: true, + onTransitionEnd: onTransitionEnd + }); +}; + +proto.onHideTransitionEnd = function() { + // check if still hidden + // during transition, item may have been un-hidden + if ( this.isHidden ) { + this.css({ display: 'none' }); + this.emitEvent('hide'); + } +}; + +proto.destroy = function() { + this.css({ + position: '', + left: '', + right: '', + top: '', + bottom: '', + transition: '', + transform: '' + }); +}; + +return Item; + +})); + +/*! + * Outlayer v2.1.0 + * the brains and guts of a layout library + * MIT license + */ + +( function( window, factory ) { + 'use strict'; + // universal module definition + /* jshint strict: false */ /* globals define, module, require */ + if ( typeof define == 'function' && define.amd ) { + // AMD - RequireJS + define( 'outlayer/outlayer',[ + 'ev-emitter/ev-emitter', + 'get-size/get-size', + 'fizzy-ui-utils/utils', + './item' + ], + function( EvEmitter, getSize, utils, Item ) { + return factory( window, EvEmitter, getSize, utils, Item); + } + ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS - Browserify, Webpack + module.exports = factory( + window, + require('ev-emitter'), + require('get-size'), + require('fizzy-ui-utils'), + require('./item') + ); + } else { + // browser global + window.Outlayer = factory( + window, + window.EvEmitter, + window.getSize, + window.fizzyUIUtils, + window.Outlayer.Item + ); + } + +}( window, function factory( window, EvEmitter, getSize, utils, Item ) { +'use strict'; + +// ----- vars ----- // + +var console = window.console; +var jQuery = window.jQuery; +var noop = function() {}; + +// -------------------------- Outlayer -------------------------- // + +// globally unique identifiers +var GUID = 0; +// internal store of all Outlayer intances +var instances = {}; + + +/** + * @param {Element, String} element + * @param {Object} options + * @constructor + */ +function Outlayer( element, options ) { + var queryElement = utils.getQueryElement( element ); + if ( !queryElement ) { + if ( console ) { + console.error( 'Bad element for ' + this.constructor.namespace + + ': ' + ( queryElement || element ) ); + } + return; + } + this.element = queryElement; + // add jQuery + if ( jQuery ) { + this.$element = jQuery( this.element ); + } + + // options + this.options = utils.extend( {}, this.constructor.defaults ); + this.option( options ); + + // add id for Outlayer.getFromElement + var id = ++GUID; + this.element.outlayerGUID = id; // expando + instances[ id ] = this; // associate via id + + // kick it off + this._create(); + + var isInitLayout = this._getOption('initLayout'); + if ( isInitLayout ) { + this.layout(); + } +} + +// settings are for internal use only +Outlayer.namespace = 'outlayer'; +Outlayer.Item = Item; + +// default options +Outlayer.defaults = { + containerStyle: { + position: 'relative' + }, + initLayout: true, + originLeft: true, + originTop: true, + resize: true, + resizeContainer: true, + // item options + transitionDuration: '0.4s', + hiddenStyle: { + opacity: 0, + transform: 'scale(0.001)' + }, + visibleStyle: { + opacity: 1, + transform: 'scale(1)' + } +}; + +var proto = Outlayer.prototype; +// inherit EvEmitter +utils.extend( proto, EvEmitter.prototype ); + +/** + * set options + * @param {Object} opts + */ +proto.option = function( opts ) { + utils.extend( this.options, opts ); +}; + +/** + * get backwards compatible option value, check old name + */ +proto._getOption = function( option ) { + var oldOption = this.constructor.compatOptions[ option ]; + return oldOption && this.options[ oldOption ] !== undefined ? + this.options[ oldOption ] : this.options[ option ]; +}; + +Outlayer.compatOptions = { + // currentName: oldName + initLayout: 'isInitLayout', + horizontal: 'isHorizontal', + layoutInstant: 'isLayoutInstant', + originLeft: 'isOriginLeft', + originTop: 'isOriginTop', + resize: 'isResizeBound', + resizeContainer: 'isResizingContainer' +}; + +proto._create = function() { + // get items from children + this.reloadItems(); + // elements that affect layout, but are not laid out + this.stamps = []; + this.stamp( this.options.stamp ); + // set container style + utils.extend( this.element.style, this.options.containerStyle ); + + // bind resize method + var canBindResize = this._getOption('resize'); + if ( canBindResize ) { + this.bindResize(); + } +}; + +// goes through all children again and gets bricks in proper order +proto.reloadItems = function() { + // collection of item elements + this.items = this._itemize( this.element.children ); +}; + + +/** + * turn elements into Outlayer.Items to be used in layout + * @param {Array or NodeList or HTMLElement} elems + * @returns {Array} items - collection of new Outlayer Items + */ +proto._itemize = function( elems ) { + + var itemElems = this._filterFindItemElements( elems ); + var Item = this.constructor.Item; + + // create new Outlayer Items for collection + var items = []; + for ( var i=0; i < itemElems.length; i++ ) { + var elem = itemElems[i]; + var item = new Item( elem, this ); + items.push( item ); + } + + return items; +}; + +/** + * get item elements to be used in layout + * @param {Array or NodeList or HTMLElement} elems + * @returns {Array} items - item elements + */ +proto._filterFindItemElements = function( elems ) { + return utils.filterFindElements( elems, this.options.itemSelector ); +}; + +/** + * getter method for getting item elements + * @returns {Array} elems - collection of item elements + */ +proto.getItemElements = function() { + return this.items.map( function( item ) { + return item.element; + }); +}; + +// ----- init & layout ----- // + +/** + * lays out all items + */ +proto.layout = function() { + this._resetLayout(); + this._manageStamps(); + + // don't animate first layout + var layoutInstant = this._getOption('layoutInstant'); + var isInstant = layoutInstant !== undefined ? + layoutInstant : !this._isLayoutInited; + this.layoutItems( this.items, isInstant ); + + // flag for initalized + this._isLayoutInited = true; +}; + +// _init is alias for layout +proto._init = proto.layout; + +/** + * logic before any new layout + */ +proto._resetLayout = function() { + this.getSize(); +}; + + +proto.getSize = function() { + this.size = getSize( this.element ); +}; + +/** + * get measurement from option, for columnWidth, rowHeight, gutter + * if option is String -> get element from selector string, & get size of element + * if option is Element -> get size of element + * else use option as a number + * + * @param {String} measurement + * @param {String} size - width or height + * @private + */ +proto._getMeasurement = function( measurement, size ) { + var option = this.options[ measurement ]; + var elem; + if ( !option ) { + // default to 0 + this[ measurement ] = 0; + } else { + // use option as an element + if ( typeof option == 'string' ) { + elem = this.element.querySelector( option ); + } else if ( option instanceof HTMLElement ) { + elem = option; + } + // use size of element, if element + this[ measurement ] = elem ? getSize( elem )[ size ] : option; + } +}; + +/** + * layout a collection of item elements + * @api public + */ +proto.layoutItems = function( items, isInstant ) { + items = this._getItemsForLayout( items ); + + this._layoutItems( items, isInstant ); + + this._postLayout(); +}; + +/** + * get the items to be laid out + * you may want to skip over some items + * @param {Array} items + * @returns {Array} items + */ +proto._getItemsForLayout = function( items ) { + return items.filter( function( item ) { + return !item.isIgnored; + }); +}; + +/** + * layout items + * @param {Array} items + * @param {Boolean} isInstant + */ +proto._layoutItems = function( items, isInstant ) { + this._emitCompleteOnItems( 'layout', items ); + + if ( !items || !items.length ) { + // no items, emit event with empty array + return; + } + + var queue = []; + + items.forEach( function( item ) { + // get x/y object from method + var position = this._getItemLayoutPosition( item ); + // enqueue + position.item = item; + position.isInstant = isInstant || item.isLayoutInstant; + queue.push( position ); + }, this ); + + this._processLayoutQueue( queue ); +}; + +/** + * get item layout position + * @param {Outlayer.Item} item + * @returns {Object} x and y position + */ +proto._getItemLayoutPosition = function( /* item */ ) { + return { + x: 0, + y: 0 + }; +}; + +/** + * iterate over array and position each item + * Reason being - separating this logic prevents 'layout invalidation' + * thx @paul_irish + * @param {Array} queue + */ +proto._processLayoutQueue = function( queue ) { + this.updateStagger(); + queue.forEach( function( obj, i ) { + this._positionItem( obj.item, obj.x, obj.y, obj.isInstant, i ); + }, this ); +}; + +// set stagger from option in milliseconds number +proto.updateStagger = function() { + var stagger = this.options.stagger; + if ( stagger === null || stagger === undefined ) { + this.stagger = 0; + return; + } + this.stagger = getMilliseconds( stagger ); + return this.stagger; +}; + +/** + * Sets position of item in DOM + * @param {Outlayer.Item} item + * @param {Number} x - horizontal position + * @param {Number} y - vertical position + * @param {Boolean} isInstant - disables transitions + */ +proto._positionItem = function( item, x, y, isInstant, i ) { + if ( isInstant ) { + // if not transition, just set CSS + item.goTo( x, y ); + } else { + item.stagger( i * this.stagger ); + item.moveTo( x, y ); + } +}; + +/** + * Any logic you want to do after each layout, + * i.e. size the container + */ +proto._postLayout = function() { + this.resizeContainer(); +}; + +proto.resizeContainer = function() { + var isResizingContainer = this._getOption('resizeContainer'); + if ( !isResizingContainer ) { + return; + } + var size = this._getContainerSize(); + if ( size ) { + this._setContainerMeasure( size.width, true ); + this._setContainerMeasure( size.height, false ); + } +}; + +/** + * Sets width or height of container if returned + * @returns {Object} size + * @param {Number} width + * @param {Number} height + */ +proto._getContainerSize = noop; + +/** + * @param {Number} measure - size of width or height + * @param {Boolean} isWidth + */ +proto._setContainerMeasure = function( measure, isWidth ) { + if ( measure === undefined ) { + return; + } + + var elemSize = this.size; + // add padding and border width if border box + if ( elemSize.isBorderBox ) { + measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight + + elemSize.borderLeftWidth + elemSize.borderRightWidth : + elemSize.paddingBottom + elemSize.paddingTop + + elemSize.borderTopWidth + elemSize.borderBottomWidth; + } + + measure = Math.max( measure, 0 ); + this.element.style[ isWidth ? 'width' : 'height' ] = measure + 'px'; +}; + +/** + * emit eventComplete on a collection of items events + * @param {String} eventName + * @param {Array} items - Outlayer.Items + */ +proto._emitCompleteOnItems = function( eventName, items ) { + var _this = this; + function onComplete() { + _this.dispatchEvent( eventName + 'Complete', null, [ items ] ); + } + + var count = items.length; + if ( !items || !count ) { + onComplete(); + return; + } + + var doneCount = 0; + function tick() { + doneCount++; + if ( doneCount == count ) { + onComplete(); + } + } + + // bind callback + items.forEach( function( item ) { + item.once( eventName, tick ); + }); +}; + +/** + * emits events via EvEmitter and jQuery events + * @param {String} type - name of event + * @param {Event} event - original event + * @param {Array} args - extra arguments + */ +proto.dispatchEvent = function( type, event, args ) { + // add original event to arguments + var emitArgs = event ? [ event ].concat( args ) : args; + this.emitEvent( type, emitArgs ); + + if ( jQuery ) { + // set this.$element + this.$element = this.$element || jQuery( this.element ); + if ( event ) { + // create jQuery event + var $event = jQuery.Event( event ); + $event.type = type; + this.$element.trigger( $event, args ); + } else { + // just trigger with type if no event available + this.$element.trigger( type, args ); + } + } +}; + +// -------------------------- ignore & stamps -------------------------- // + + +/** + * keep item in collection, but do not lay it out + * ignored items do not get skipped in layout + * @param {Element} elem + */ +proto.ignore = function( elem ) { + var item = this.getItem( elem ); + if ( item ) { + item.isIgnored = true; + } +}; + +/** + * return item to layout collection + * @param {Element} elem + */ +proto.unignore = function( elem ) { + var item = this.getItem( elem ); + if ( item ) { + delete item.isIgnored; + } +}; + +/** + * adds elements to stamps + * @param {NodeList, Array, Element, or String} elems + */ +proto.stamp = function( elems ) { + elems = this._find( elems ); + if ( !elems ) { + return; + } + + this.stamps = this.stamps.concat( elems ); + // ignore + elems.forEach( this.ignore, this ); +}; + +/** + * removes elements to stamps + * @param {NodeList, Array, or Element} elems + */ +proto.unstamp = function( elems ) { + elems = this._find( elems ); + if ( !elems ){ + return; + } + + elems.forEach( function( elem ) { + // filter out removed stamp elements + utils.removeFrom( this.stamps, elem ); + this.unignore( elem ); + }, this ); +}; + +/** + * finds child elements + * @param {NodeList, Array, Element, or String} elems + * @returns {Array} elems + */ +proto._find = function( elems ) { + if ( !elems ) { + return; + } + // if string, use argument as selector string + if ( typeof elems == 'string' ) { + elems = this.element.querySelectorAll( elems ); + } + elems = utils.makeArray( elems ); + return elems; +}; + +proto._manageStamps = function() { + if ( !this.stamps || !this.stamps.length ) { + return; + } + + this._getBoundingRect(); + + this.stamps.forEach( this._manageStamp, this ); +}; + +// update boundingLeft / Top +proto._getBoundingRect = function() { + // get bounding rect for container element + var boundingRect = this.element.getBoundingClientRect(); + var size = this.size; + this._boundingRect = { + left: boundingRect.left + size.paddingLeft + size.borderLeftWidth, + top: boundingRect.top + size.paddingTop + size.borderTopWidth, + right: boundingRect.right - ( size.paddingRight + size.borderRightWidth ), + bottom: boundingRect.bottom - ( size.paddingBottom + size.borderBottomWidth ) + }; +}; + +/** + * @param {Element} stamp +**/ +proto._manageStamp = noop; + +/** + * get x/y position of element relative to container element + * @param {Element} elem + * @returns {Object} offset - has left, top, right, bottom + */ +proto._getElementOffset = function( elem ) { + var boundingRect = elem.getBoundingClientRect(); + var thisRect = this._boundingRect; + var size = getSize( elem ); + var offset = { + left: boundingRect.left - thisRect.left - size.marginLeft, + top: boundingRect.top - thisRect.top - size.marginTop, + right: thisRect.right - boundingRect.right - size.marginRight, + bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom + }; + return offset; +}; + +// -------------------------- resize -------------------------- // + +// enable event handlers for listeners +// i.e. resize -> onresize +proto.handleEvent = utils.handleEvent; + +/** + * Bind layout to window resizing + */ +proto.bindResize = function() { + window.addEventListener( 'resize', this ); + this.isResizeBound = true; +}; + +/** + * Unbind layout to window resizing + */ +proto.unbindResize = function() { + window.removeEventListener( 'resize', this ); + this.isResizeBound = false; +}; + +proto.onresize = function() { + this.resize(); +}; + +utils.debounceMethod( Outlayer, 'onresize', 100 ); + +proto.resize = function() { + // don't trigger if size did not change + // or if resize was unbound. See #9 + if ( !this.isResizeBound || !this.needsResizeLayout() ) { + return; + } + + this.layout(); +}; + +/** + * check if layout is needed post layout + * @returns Boolean + */ +proto.needsResizeLayout = function() { + var size = getSize( this.element ); + // check that this.size and size are there + // IE8 triggers resize on body size change, so they might not be + var hasSizes = this.size && size; + return hasSizes && size.innerWidth !== this.size.innerWidth; +}; + +// -------------------------- methods -------------------------- // + +/** + * add items to Outlayer instance + * @param {Array or NodeList or Element} elems + * @returns {Array} items - Outlayer.Items +**/ +proto.addItems = function( elems ) { + var items = this._itemize( elems ); + // add items to collection + if ( items.length ) { + this.items = this.items.concat( items ); + } + return items; +}; + +/** + * Layout newly-appended item elements + * @param {Array or NodeList or Element} elems + */ +proto.appended = function( elems ) { + var items = this.addItems( elems ); + if ( !items.length ) { + return; + } + // layout and reveal just the new items + this.layoutItems( items, true ); + this.reveal( items ); +}; + +/** + * Layout prepended elements + * @param {Array or NodeList or Element} elems + */ +proto.prepended = function( elems ) { + var items = this._itemize( elems ); + if ( !items.length ) { + return; + } + // add items to beginning of collection + var previousItems = this.items.slice(0); + this.items = items.concat( previousItems ); + // start new layout + this._resetLayout(); + this._manageStamps(); + // layout new stuff without transition + this.layoutItems( items, true ); + this.reveal( items ); + // layout previous items + this.layoutItems( previousItems ); +}; + +/** + * reveal a collection of items + * @param {Array of Outlayer.Items} items + */ +proto.reveal = function( items ) { + this._emitCompleteOnItems( 'reveal', items ); + if ( !items || !items.length ) { + return; + } + var stagger = this.updateStagger(); + items.forEach( function( item, i ) { + item.stagger( i * stagger ); + item.reveal(); + }); +}; + +/** + * hide a collection of items + * @param {Array of Outlayer.Items} items + */ +proto.hide = function( items ) { + this._emitCompleteOnItems( 'hide', items ); + if ( !items || !items.length ) { + return; + } + var stagger = this.updateStagger(); + items.forEach( function( item, i ) { + item.stagger( i * stagger ); + item.hide(); + }); +}; + +/** + * reveal item elements + * @param {Array}, {Element}, {NodeList} items + */ +proto.revealItemElements = function( elems ) { + var items = this.getItems( elems ); + this.reveal( items ); +}; + +/** + * hide item elements + * @param {Array}, {Element}, {NodeList} items + */ +proto.hideItemElements = function( elems ) { + var items = this.getItems( elems ); + this.hide( items ); +}; + +/** + * get Outlayer.Item, given an Element + * @param {Element} elem + * @param {Function} callback + * @returns {Outlayer.Item} item + */ +proto.getItem = function( elem ) { + // loop through items to get the one that matches + for ( var i=0; i < this.items.length; i++ ) { + var item = this.items[i]; + if ( item.element == elem ) { + // return item + return item; + } + } +}; + +/** + * get collection of Outlayer.Items, given Elements + * @param {Array} elems + * @returns {Array} items - Outlayer.Items + */ +proto.getItems = function( elems ) { + elems = utils.makeArray( elems ); + var items = []; + elems.forEach( function( elem ) { + var item = this.getItem( elem ); + if ( item ) { + items.push( item ); + } + }, this ); + + return items; +}; + +/** + * remove element(s) from instance and DOM + * @param {Array or NodeList or Element} elems + */ +proto.remove = function( elems ) { + var removeItems = this.getItems( elems ); + + this._emitCompleteOnItems( 'remove', removeItems ); + + // bail if no items to remove + if ( !removeItems || !removeItems.length ) { + return; + } + + removeItems.forEach( function( item ) { + item.remove(); + // remove item from collection + utils.removeFrom( this.items, item ); + }, this ); +}; + +// ----- destroy ----- // + +// remove and disable Outlayer instance +proto.destroy = function() { + // clean up dynamic styles + var style = this.element.style; + style.height = ''; + style.position = ''; + style.width = ''; + // destroy items + this.items.forEach( function( item ) { + item.destroy(); + }); + + this.unbindResize(); + + var id = this.element.outlayerGUID; + delete instances[ id ]; // remove reference to instance by id + delete this.element.outlayerGUID; + // remove data for jQuery + if ( jQuery ) { + jQuery.removeData( this.element, this.constructor.namespace ); + } + +}; + +// -------------------------- data -------------------------- // + +/** + * get Outlayer instance from element + * @param {Element} elem + * @returns {Outlayer} + */ +Outlayer.data = function( elem ) { + elem = utils.getQueryElement( elem ); + var id = elem && elem.outlayerGUID; + return id && instances[ id ]; +}; + + +// -------------------------- create Outlayer class -------------------------- // + +/** + * create a layout class + * @param {String} namespace + */ +Outlayer.create = function( namespace, options ) { + // sub-class Outlayer + var Layout = subclass( Outlayer ); + // apply new options and compatOptions + Layout.defaults = utils.extend( {}, Outlayer.defaults ); + utils.extend( Layout.defaults, options ); + Layout.compatOptions = utils.extend( {}, Outlayer.compatOptions ); + + Layout.namespace = namespace; + + Layout.data = Outlayer.data; + + // sub-class Item + Layout.Item = subclass( Item ); + + // -------------------------- declarative -------------------------- // + + utils.htmlInit( Layout, namespace ); + + // -------------------------- jQuery bridge -------------------------- // + + // make into jQuery plugin + if ( jQuery && jQuery.bridget ) { + jQuery.bridget( namespace, Layout ); + } + + return Layout; +}; + +function subclass( Parent ) { + function SubClass() { + Parent.apply( this, arguments ); + } + + SubClass.prototype = Object.create( Parent.prototype ); + SubClass.prototype.constructor = SubClass; + + return SubClass; +} + +// ----- helpers ----- // + +// how many milliseconds are in each unit +var msUnits = { + ms: 1, + s: 1000 +}; + +// munge time-like parameter into millisecond number +// '0.4s' -> 40 +function getMilliseconds( time ) { + if ( typeof time == 'number' ) { + return time; + } + var matches = time.match( /(^\d*\.?\d*)(\w*)/ ); + var num = matches && matches[1]; + var unit = matches && matches[2]; + if ( !num.length ) { + return 0; + } + num = parseFloat( num ); + var mult = msUnits[ unit ] || 1; + return num * mult; +} + +// ----- fin ----- // + +// back in global +Outlayer.Item = Item; + +return Outlayer; + +})); + +/*! + * Masonry v4.2.1 + * Cascading grid layout library + * https://masonry.desandro.com + * MIT License + * by David DeSandro + */ + +( function( window, factory ) { + // universal module definition + /* jshint strict: false */ /*globals define, module, require */ + if ( typeof define == 'function' && define.amd ) { + // AMD + define( [ + 'outlayer/outlayer', + 'get-size/get-size' + ], + factory ); + } else if ( typeof module == 'object' && module.exports ) { + // CommonJS + module.exports = factory( + require('outlayer'), + require('get-size') + ); + } else { + // browser global + window.Masonry = factory( + window.Outlayer, + window.getSize + ); + } + +}( window, function factory( Outlayer, getSize ) { + + + +// -------------------------- masonryDefinition -------------------------- // + + // create an Outlayer layout class + var Masonry = Outlayer.create('masonry'); + // isFitWidth -> fitWidth + Masonry.compatOptions.fitWidth = 'isFitWidth'; + + var proto = Masonry.prototype; + + proto._resetLayout = function() { + this.getSize(); + this._getMeasurement( 'columnWidth', 'outerWidth' ); + this._getMeasurement( 'gutter', 'outerWidth' ); + this.measureColumns(); + + // reset column Y + this.colYs = []; + for ( var i=0; i < this.cols; i++ ) { + this.colYs.push( 0 ); + } + + this.maxY = 0; + this.horizontalColIndex = 0; + }; + + proto.measureColumns = function() { + this.getContainerWidth(); + // if columnWidth is 0, default to outerWidth of first item + if ( !this.columnWidth ) { + var firstItem = this.items[0]; + var firstItemElem = firstItem && firstItem.element; + // columnWidth fall back to item of first element + this.columnWidth = firstItemElem && getSize( firstItemElem ).outerWidth || + // if first elem has no width, default to size of container + this.containerWidth; + } + + var columnWidth = this.columnWidth += this.gutter; + + // calculate columns + var containerWidth = this.containerWidth + this.gutter; + var cols = containerWidth / columnWidth; + // fix rounding errors, typically with gutters + var excess = columnWidth - containerWidth % columnWidth; + // if overshoot is less than a pixel, round up, otherwise floor it + var mathMethod = excess && excess < 1 ? 'round' : 'floor'; + cols = Math[ mathMethod ]( cols ); + this.cols = Math.max( cols, 1 ); + }; + + proto.getContainerWidth = function() { + // container is parent if fit width + var isFitWidth = this._getOption('fitWidth'); + var container = isFitWidth ? this.element.parentNode : this.element; + // check that this.size and size are there + // IE8 triggers resize on body size change, so they might not be + var size = getSize( container ); + this.containerWidth = size && size.innerWidth; + }; + + proto._getItemLayoutPosition = function( item ) { + item.getSize(); + // how many columns does this brick span + var remainder = item.size.outerWidth % this.columnWidth; + var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil'; + // round if off by 1 pixel, otherwise use ceil + var colSpan = Math[ mathMethod ]( item.size.outerWidth / this.columnWidth ); + colSpan = Math.min( colSpan, this.cols ); + // use horizontal or top column position + var colPosMethod = this.options.horizontalOrder ? + '_getHorizontalColPosition' : '_getTopColPosition'; + var colPosition = this[ colPosMethod ]( colSpan, item ); + // position the brick + var position = { + x: this.columnWidth * colPosition.col, + y: colPosition.y + }; + // apply setHeight to necessary columns + var setHeight = colPosition.y + item.size.outerHeight; + var setMax = colSpan + colPosition.col; + for ( var i = colPosition.col; i < setMax; i++ ) { + this.colYs[i] = setHeight; + } + + return position; + }; + + proto._getTopColPosition = function( colSpan ) { + var colGroup = this._getTopColGroup( colSpan ); + // get the minimum Y value from the columns + var minimumY = Math.min.apply( Math, colGroup ); + + return { + col: colGroup.indexOf( minimumY ), + y: minimumY, + }; + }; + + /** + * @param {Number} colSpan - number of columns the element spans + * @returns {Array} colGroup + */ + proto._getTopColGroup = function( colSpan ) { + if ( colSpan < 2 ) { + // if brick spans only one column, use all the column Ys + return this.colYs; + } + + var colGroup = []; + // how many different places could this brick fit horizontally + var groupCount = this.cols + 1 - colSpan; + // for each group potential horizontal position + for ( var i = 0; i < groupCount; i++ ) { + colGroup[i] = this._getColGroupY( i, colSpan ); + } + return colGroup; + }; + + proto._getColGroupY = function( col, colSpan ) { + if ( colSpan < 2 ) { + return this.colYs[ col ]; + } + // make an array of colY values for that one group + var groupColYs = this.colYs.slice( col, col + colSpan ); + // and get the max value of the array + return Math.max.apply( Math, groupColYs ); + }; + + // get column position based on horizontal index. #873 + proto._getHorizontalColPosition = function( colSpan, item ) { + var col = this.horizontalColIndex % this.cols; + var isOver = colSpan > 1 && col + colSpan > this.cols; + // shift to next row if item can't fit on current row + col = isOver ? 0 : col; + // don't let zero-size items take up space + var hasSize = item.size.outerWidth && item.size.outerHeight; + this.horizontalColIndex = hasSize ? col + colSpan : this.horizontalColIndex; + + return { + col: col, + y: this._getColGroupY( col, colSpan ), + }; + }; + + proto._manageStamp = function( stamp ) { + var stampSize = getSize( stamp ); + var offset = this._getElementOffset( stamp ); + // get the columns that this stamp affects + var isOriginLeft = this._getOption('originLeft'); + var firstX = isOriginLeft ? offset.left : offset.right; + var lastX = firstX + stampSize.outerWidth; + var firstCol = Math.floor( firstX / this.columnWidth ); + firstCol = Math.max( 0, firstCol ); + var lastCol = Math.floor( lastX / this.columnWidth ); + // lastCol should not go over if multiple of columnWidth #425 + lastCol -= lastX % this.columnWidth ? 0 : 1; + lastCol = Math.min( this.cols - 1, lastCol ); + // set colYs to bottom of the stamp + + var isOriginTop = this._getOption('originTop'); + var stampMaxY = ( isOriginTop ? offset.top : offset.bottom ) + + stampSize.outerHeight; + for ( var i = firstCol; i <= lastCol; i++ ) { + this.colYs[i] = Math.max( stampMaxY, this.colYs[i] ); + } + }; + + proto._getContainerSize = function() { + this.maxY = Math.max.apply( Math, this.colYs ); + var size = { + height: this.maxY + }; + + if ( this._getOption('fitWidth') ) { + size.width = this._getContainerFitWidth(); + } + + return size; + }; + + proto._getContainerFitWidth = function() { + var unusedCols = 0; + // count unused columns + var i = this.cols; + while ( --i ) { + if ( this.colYs[i] !== 0 ) { + break; + } + unusedCols++; + } + // fit container to columns that have been used + return ( this.cols - unusedCols ) * this.columnWidth - this.gutter; + }; + + proto.needsResizeLayout = function() { + var previousWidth = this.containerWidth; + this.getContainerWidth(); + return previousWidth != this.containerWidth; + }; + + return Masonry; + +})); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.masonry.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.masonry.min.js new file mode 100644 index 00000000..38fff87c --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.masonry.min.js @@ -0,0 +1,114 @@ +(function(window,factory){if(typeof define=='function'&&define.amd){define('jquery-bridget/jquery-bridget',['jquery'],function(jQuery){return factory(window,jQuery);});}else if(typeof module=='object'&&module.exports){module.exports=factory(window,require('jquery'));}else{window.jQueryBridget=factory(window,window.jQuery);}}(window,function factory(window,jQuery){'use strict';var arraySlice=Array.prototype.slice;var console=window.console;var logError=typeof console=='undefined'?function(){}:function(message){console.error(message);};function jQueryBridget(namespace,PluginClass,$){$=$||jQuery||window.jQuery;if(!$){return;} +if(!PluginClass.prototype.option){PluginClass.prototype.option=function(opts){if(!$.isPlainObject(opts)){return;} +this.options=$.extend(true,this.options,opts);};} +$.fn[namespace]=function(arg0){if(typeof arg0=='string'){var args=arraySlice.call(arguments,1);return methodCall(this,arg0,args);} +plainCall(this,arg0);return this;};function methodCall($elems,methodName,args){var returnValue;var pluginMethodStr='$().'+namespace+'("'+methodName+'")';$elems.each(function(i,elem){var instance=$.data(elem,namespace);if(!instance){logError(namespace+' not initialized. Cannot call methods, i.e. '+ +pluginMethodStr);return;} +var method=instance[methodName];if(!method||methodName.charAt(0)=='_'){logError(pluginMethodStr+' is not a valid method');return;} +var value=method.apply(instance,args);returnValue=returnValue===undefined?value:returnValue;});return returnValue!==undefined?returnValue:$elems;} +function plainCall($elems,options){$elems.each(function(i,elem){var instance=$.data(elem,namespace);if(instance){instance.option(options);instance._init();}else{instance=new PluginClass(elem,options);$.data(elem,namespace,instance);}});} +updateJQuery($);} +function updateJQuery($){if(!$||($&&$.bridget)){return;} +$.bridget=jQueryBridget;} +updateJQuery(jQuery||window.jQuery);return jQueryBridget;}));(function(global,factory){if(typeof define=='function'&&define.amd){define('ev-emitter/ev-emitter',factory);}else if(typeof module=='object'&&module.exports){module.exports=factory();}else{global.EvEmitter=factory();}}(typeof window!='undefined'?window:this,function(){function EvEmitter(){} +var proto=EvEmitter.prototype;proto.on=function(eventName,listener){if(!eventName||!listener){return;} +var events=this._events=this._events||{};var listeners=events[eventName]=events[eventName]||[];if(listeners.indexOf(listener)==-1){listeners.push(listener);} +return this;};proto.once=function(eventName,listener){if(!eventName||!listener){return;} +this.on(eventName,listener);var onceEvents=this._onceEvents=this._onceEvents||{};var onceListeners=onceEvents[eventName]=onceEvents[eventName]||{};onceListeners[listener]=true;return this;};proto.off=function(eventName,listener){var listeners=this._events&&this._events[eventName];if(!listeners||!listeners.length){return;} +var index=listeners.indexOf(listener);if(index!=-1){listeners.splice(index,1);} +return this;};proto.emitEvent=function(eventName,args){var listeners=this._events&&this._events[eventName];if(!listeners||!listeners.length){return;} +listeners=listeners.slice(0);args=args||[];var onceListeners=this._onceEvents&&this._onceEvents[eventName];for(var i=0;i
').append($('.control-required', element).clone()).html(); + + function setValue() { + var val = escape(field.val()) || options.placeholder; + value.text(val); + } + + preview.find('strong').html(required + ' ' + options.i18n['URL'] + ':'); + preview.find('.slug-preview-prefix').text(options.prefix); + preview.find('button').text(options.i18n['Edit']).click(function (event) { + event.preventDefault(); + element.show(); + preview.hide(); + }); + + setValue(); + field.on('change', setValue); + + element.after(preview).hide(); + + return preview[0]; + }); + + // Append the new elements to the current jQuery stack so that the caller + // can modify the elements. Then restore the originals by calling .end(). + return this.pushStack(collected); + } + + slugPreview.defaults = { + prefix: '', + placeholder: '', + i18n: { + 'URL': 'URL', + 'Edit': 'Edit' + }, + template: [ + '
', + '', + '', + '', + '
' + ].join('\n') + }; + + $.fn.slugPreview = slugPreview; + +})(this.jQuery, this); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.slug-preview.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.slug-preview.min.js new file mode 100644 index 00000000..e92c6231 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.slug-preview.min.js @@ -0,0 +1,3 @@ +(function($,window){var escape=$.url.escape;function slugPreview(options){options=$.extend(true,slugPreview.defaults,options||{});var collected=this.map(function(){var element=$(this);var field=element.find('input');var preview=$(options.template);var value=preview.find('.slug-preview-value');var required=$('
').append($('.control-required',element).clone()).html();function setValue(){var val=escape(field.val())||options.placeholder;value.text(val);} +preview.find('strong').html(required+' '+options.i18n['URL']+':');preview.find('.slug-preview-prefix').text(options.prefix);preview.find('button').text(options.i18n['Edit']).click(function(event){event.preventDefault();element.show();preview.hide();});setValue();field.on('change',setValue);element.after(preview).hide();return preview[0];});return this.pushStack(collected);} +slugPreview.defaults={prefix:'',placeholder:'',i18n:{'URL':'URL','Edit':'Edit'},template:['
','','','','
'].join('\n')};$.fn.slugPreview=slugPreview;})(this.jQuery,this); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.slug.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.slug.js new file mode 100644 index 00000000..22e17b18 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.slug.js @@ -0,0 +1,83 @@ +/* Restricts the input into the field to just slug safe characters. + * + * The element will also fire the "slugify" event passing in the new and + * previous strings as arguments. + * + * Examples + * + * var slug = jQuery([name=slug]).slug(); + * + * slug.on('slugify', function (event, current, previous) { + * console.log("value was: %s, and is now %s", current, previous); + * }); + * + * Returns the jQuery collection. + */ +(function ($) { + + /* Handles the on change event that "slugifies" the entire string. This + * catches text pasted into the input. + * + * event - the DOM event object. + * + * Returns nothing. + */ + function onChange(event) { + var value = this.value; + var updated = $.url.slugify(value, true); + + if (value !== updated) { + this.value = updated; + $(this).trigger('slugify', [this.value, value]); + } + } + + /* Handles the keypress event that will convert each character as the user + * inputs new text. This will not catch text pasted into the input. + * + * event - the DOM event object. + * + * Returns nothing. + */ + function onKeypress(event) { + if (!event.charCode) { + return; + } + + event.preventDefault(); + + var value = this.value; + var start = this.selectionStart; + var end = this.selectionEnd; + var char = String.fromCharCode(event.charCode); + var updated; + var range; + + if (this.setSelectionRange) { + updated = value.substring(0, start) + char + value.substring(end, value.length); + + this.value = $.url.slugify(updated, false); + this.setSelectionRange(start + 1, start + 1); + } else if (document.selection && document.selection.createRange) { + range = document.selection.createRange(); + range.text = char + range.text; + } + + $(this).trigger('slugify', [this.value, value]); + } + + /* The jQuery plugin for converting an input. + */ + $.fn.slug = function () { + return this.each(function () { + $(this).on({ + 'blur.slug': onChange, + 'change.slug': onChange, + 'keypress.slug': onKeypress + }); + }); + }; + + // Export the methods onto the plugin for testability. + $.extend($.fn.slug, {onChange: onChange, onKeypress: onKeypress}); +})(this.jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.slug.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.slug.min.js new file mode 100644 index 00000000..67b767d0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.slug.min.js @@ -0,0 +1,5 @@ +(function($){function onChange(event){var value=this.value;var updated=$.url.slugify(value,true);if(value!==updated){this.value=updated;$(this).trigger('slugify',[this.value,value]);}} +function onKeypress(event){if(!event.charCode){return;} +event.preventDefault();var value=this.value;var start=this.selectionStart;var end=this.selectionEnd;var char=String.fromCharCode(event.charCode);var updated;var range;if(this.setSelectionRange){updated=value.substring(0,start)+char+value.substring(end,value.length);this.value=$.url.slugify(updated,false);this.setSelectionRange(start+1,start+1);}else if(document.selection&&document.selection.createRange){range=document.selection.createRange();range.text=char+range.text;} +$(this).trigger('slugify',[this.value,value]);} +$.fn.slug=function(){return this.each(function(){$(this).on({'blur.slug':onChange,'change.slug':onChange,'keypress.slug':onKeypress});});};$.extend($.fn.slug,{onChange:onChange,onKeypress:onKeypress});})(this.jQuery); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.truncator.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.truncator.js new file mode 100644 index 00000000..983f45b2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.truncator.js @@ -0,0 +1,145 @@ +// HTML Truncator for jQuery +// by Henrik Nyh 2008-02-28. +// Free to modify and redistribute with credit. + +// EDIT: This plug-in has been modified from the original source to enable +// some additional functionality. +// +// a) We now return the newly created "truncated" elements as a jQuery +// collection. This can be restored as usual using .end(). +// b) We trigger the "expand.truncate" and "collapse.truncate" events when +// the occur. The event object has an additional .relatedTarget property +// which is the original expanded element. +// c) We add an "ellipses" option that places the ellipses outside of the +// expand/collapse links. +// +// We do this because this is the best plug-in I've found that handles +// truncation of elements containing HTML components. Even better would be +// to find one that can also be provided with a number of lines. +// +// Requirements are: +// +// 1. Must truncate the contents of an element keeping elements intact. +// 2. Must be extensible trigger events when expand/collapse occurs. +// 3. Truncate to a set number of lines rather than just characters. +// +(function($) { + + var trailing_whitespace = true; + + $.fn.truncate = function(options) { + + var opts = $.extend({}, $.fn.truncate.defaults, options); + + var collected = this.map(function() { + + var content_length = $.trim(squeeze($(this).text())).length; + if (content_length <= opts.max_length) + return; // bail early if not overlong + + // include more text, link prefix, and link suffix in max length + var actual_max_length = opts.max_length - opts.more.length - opts.link_prefix.length - opts.link_suffix.length; + + var truncated_node = recursivelyTruncate(this, actual_max_length); + var full_node = $(this).hide(); + + truncated_node.insertAfter(full_node); + + findNodeForMore(truncated_node).append(opts.ellipses + opts.link_prefix+''+opts.more+''+opts.link_suffix); + findNodeForLess(full_node).append(opts.link_prefix+''+opts.less+''+opts.link_suffix); + + truncated_node.find('a:last').click(function(event) { + event.preventDefault(); + truncated_node.hide(); full_node.show(); + + // Trigger an event for extensibility. + truncated_node.trigger({ + type: 'expand.truncate', + relatedTarget: full_node[0] + }); + }); + full_node.find('a:last').click(function(event) { + event.preventDefault(); + truncated_node.show(); full_node.hide(); + + // Trigger an event for extensibility. + truncated_node.trigger({ + type: 'collapse.truncate', + relatedTarget: full_node[0] + }); + }); + + // Return our new truncated node. + return truncated_node[0]; + }); + + // Return the newly created elements. + return this.pushStack(collected); + } + + // Note that the " (…more)" bit counts towards the max length – so a max + // length of 10 would truncate "1234567890" to "12 (…more)". + $.fn.truncate.defaults = { + max_length: 100, + more: 'more', + less: 'less', + ellipses: '…', + css_more_class: 'truncator-link truncator-more', + css_less_class: 'truncator-link truncator-less', + link_prefix: ' (', + link_suffix: ')' + }; + + function recursivelyTruncate(node, max_length) { + return (node.nodeType == 3) ? truncateText(node, max_length) : truncateNode(node, max_length); + } + + function truncateNode(node, max_length) { + var node = $(node); + var new_node = node.clone().empty(); + var truncatedChild; + node.contents().each(function() { + var remaining_length = max_length - new_node.text().length; + if (remaining_length == 0) return; // breaks the loop + truncatedChild = recursivelyTruncate(this, remaining_length); + if (truncatedChild) new_node.append(truncatedChild); + }); + return new_node; + } + + function truncateText(node, max_length) { + var text = squeeze(node.data); + if (trailing_whitespace) // remove initial whitespace if last text + text = text.replace(/^ /, ''); // node had trailing whitespace. + trailing_whitespace = !!text.match(/ $/); + var text = text.slice(0, max_length); + // Ensure HTML entities are encoded + // http://debuggable.com/posts/encode-html-entities-with-jquery:480f4dd6-13cc-4ce9-8071-4710cbdd56cb + text = $('
').text(text).html(); + return text; + } + + // Collapses a sequence of whitespace into a single space. + function squeeze(string) { + return string.replace(/\s+/g, ' '); + } + + // Finds the last, innermost block-level element + function findNodeForMore(node) { + var $node = $(node); + var last_child = $node.children(":last"); + if (!last_child) return node; + var display = last_child.css('display'); + if (!display || display=='inline') return $node; + return findNodeForMore(last_child); + }; + + // Finds the last child if it's a p; otherwise the parent + function findNodeForLess(node) { + var $node = $(node); + var last_child = $node.children(":last"); + if (last_child && last_child.is('p')) return last_child; + return node; + }; + +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.truncator.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.truncator.min.js new file mode 100644 index 00000000..be47cdf7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.truncator.min.js @@ -0,0 +1,8 @@ +(function($){var trailing_whitespace=true;$.fn.truncate=function(options){var opts=$.extend({},$.fn.truncate.defaults,options);var collected=this.map(function(){var content_length=$.trim(squeeze($(this).text())).length;if(content_length<=opts.max_length) +return;var actual_max_length=opts.max_length-opts.more.length-opts.link_prefix.length-opts.link_suffix.length;var truncated_node=recursivelyTruncate(this,actual_max_length);var full_node=$(this).hide();truncated_node.insertAfter(full_node);findNodeForMore(truncated_node).append(opts.ellipses+opts.link_prefix+''+opts.more+''+opts.link_suffix);findNodeForLess(full_node).append(opts.link_prefix+''+opts.less+''+opts.link_suffix);truncated_node.find('a:last').click(function(event){event.preventDefault();truncated_node.hide();full_node.show();truncated_node.trigger({type:'expand.truncate',relatedTarget:full_node[0]});});full_node.find('a:last').click(function(event){event.preventDefault();truncated_node.show();full_node.hide();truncated_node.trigger({type:'collapse.truncate',relatedTarget:full_node[0]});});return truncated_node[0];});return this.pushStack(collected);} +$.fn.truncate.defaults={max_length:100,more:'more',less:'less',ellipses:'…',css_more_class:'truncator-link truncator-more',css_less_class:'truncator-link truncator-less',link_prefix:' (',link_suffix:')'};function recursivelyTruncate(node,max_length){return(node.nodeType==3)?truncateText(node,max_length):truncateNode(node,max_length);} +function truncateNode(node,max_length){var node=$(node);var new_node=node.clone().empty();var truncatedChild;node.contents().each(function(){var remaining_length=max_length-new_node.text().length;if(remaining_length==0)return;truncatedChild=recursivelyTruncate(this,remaining_length);if(truncatedChild)new_node.append(truncatedChild);});return new_node;} +function truncateText(node,max_length){var text=squeeze(node.data);if(trailing_whitespace) +text=text.replace(/^ /,'');trailing_whitespace=!!text.match(/ $/);var text=text.slice(0,max_length);text=$('
').text(text).html();return text;} +function squeeze(string){return string.replace(/\s+/g,' ');} +function findNodeForMore(node){var $node=$(node);var last_child=$node.children(":last");if(!last_child)return node;var display=last_child.css('display');if(!display||display=='inline')return $node;return findNodeForMore(last_child);};function findNodeForLess(node){var $node=$(node);var last_child=$node.children(":last");if(last_child&&last_child.is('p'))return last_child;return node;};})(jQuery); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.url-helpers.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.url-helpers.js new file mode 100644 index 00000000..f92d6292 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.url-helpers.js @@ -0,0 +1,167 @@ +/* .slugify() based on jQuery Slugify a string! by Pablo Bandin + * + * See: http://tracehello.wordpress.com/2011/06/15/jquery-real-slugify-plugin/ + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ +(function ($, window) { + $.url = { + /* Escapes a string for use in a url component. All special characters + * are url encoded and spaces are replaced by a plus rather than a %20. + * + * string - A string to convert. + * + * Examples + * + * jQuery.url.escape('apples & pears'); //=> "apples+%26+pears" + * + * Returns the escaped string. + */ + escape: function (string) { + return window.encodeURIComponent(string || '').replace(/%20/g, '+'); + }, + + /* Converts a string into a url compatible slug. Characters that cannot + * be converted will be replaced by hyphens. + * + * string - The string to convert. + * trim - Remove starting, trailing and duplicate hyphens (default: true) + * + * Examples + * + * jQuery.url.slugify('apples & pears'); //=> 'apples-pears' + * + * Returns the new slug. + */ + slugify: function (string, trim) { + var str = ''; + var index = 0; + var length = string.length; + var map = this.map; + + for (;index < length; index += 1) { + str += map[string.charCodeAt(index).toString(16)] || '-'; + } + + str = str.toLowerCase(); + + return trim === false ? str : str.replace(/\-+/g, '-').replace(/^-|-$/g, ''); + } + }; + + // The following takes two sets of characters, the first a set of hexadecimal + // Unicode character points, the second their visually similar counterparts. + // I'm not 100% sure this is the best way to handle such characters but + // it seems to be a common practice. + var unicode = ('20 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 ' + + '47 48 49 50 51 52 53 54 55 56 57 58 59 61 62 63 64 65 66 67 68 69 70 ' + + '71 72 73 74 75 76 77 78 79 100 101 102 103 104 105 106 107 108 109 ' + + '110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 ' + + '126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 ' + + '142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 ' + + '158 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 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 253 ' + + '254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 ' + + '270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 ' + + '286 287 288 289 290 291 292 293 294 295 296 297 298 299 363 364 ' + + '365 366 367 368 369 386 388 389 390 391 392 393 394 395 396 397 ' + + '398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 ' + + '414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 ' + + '430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 ' + + '446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 ' + + '462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 ' + + '478 479 480 481 490 491 492 493 494 495 496 497 498 499 500 501 ' + + '502 503 504 505 506 507 508 509 510 511 512 513 514 515 531 532 ' + + '533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 ' + + '549 550 551 552 553 554 555 556 561 562 563 564 565 566 567 568 ' + + '569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 ' + + '585 586 587 4a 4b 4c 4d 4e 4f 5a 6a 6b 6c 6d 6e 6f 7a a2 a3 a5 a7 ' + + 'a9 aa ae b2 b3 b5 b6 b9 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ' + + 'ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 ' + + 'e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f8 f9 fa ' + + 'fb fc fd ff 10a 10b 10c 10d 10e 10f 11a 11b 11c 11d 11e 11f 12a ' + + '12b 12c 12d 12e 12f 13a 13b 13c 13d 13e 13f 14a 14b 14c 14d 14e ' + + '14f 15a 15b 15c 15d 15e 15f 16a 16b 16c 16d 16e 16f 17a 17b 17c ' + + '17d 17e 17f 18a 18b 18c 18d 18e 18f 19a 19b 19c 19d 19e 19f 1a0 ' + + '1a1 1a2 1a3 1a4 1a5 1a6 1a7 1a8 1a9 1aa 1ab 1ac 1ad 1ae 1af 1b0 ' + + '1b1 1b2 1b3 1b4 1b5 1b6 1b7 1b8 1b9 1ba 1bb 1bc 1bd 1be 1bf 1c4 ' + + '1c5 1c6 1c7 1c8 1c9 1ca 1cb 1cc 1cd 1ce 1cf 1d0 1d1 1d2 1d3 1d4 ' + + '1d5 1d6 1d7 1d8 1d9 1da 1db 1dc 1dd 1de 1df 1e0 1e1 1e2 1e3 1e4 ' + + '1e5 1e6 1e7 1e8 1e9 1ea 1eb 1ec 1ed 1ee 1ef 1f0 1f1 1f2 1f3 1f4 ' + + '1f5 1f6 1f7 1f8 1f9 1fa 1fb 1fc 1fd 1fe 1ff 20a 20b 20c 20d 20e ' + + '20f 21a 21b 21c 21d 21e 21f 22a 22b 22c 22d 22e 22f 23a 23b 23c ' + + '23d 23e 23f 24a 24b 24c 24d 24e 24f 25a 25b 25c 25d 25e 25f 26a ' + + '26b 26c 26d 26e 26f 27a 27b 27c 27d 27e 27f 28a 28b 28c 28d 28e ' + + '28f 29a 29b 29c 29d 29e 29f 2a0 2a1 2a2 2a3 2a4 2a5 2a6 2a7 2a8 ' + + '2a9 2aa 2ab 2ac 2ae 2af 2b0 2b1 2b2 2b3 2b4 2b5 2b6 2b7 2b8 2df ' + + '2e0 2e1 2e2 2e3 2e4 36a 36b 36c 36d 36e 36f 37b 37c 37d 38a 38c ' + + '38e 38f 39a 39b 39c 39d 39e 39f 3a0 3a1 3a3 3a4 3a5 3a6 3a7 3a8 ' + + '3a9 3aa 3ab 3ac 3ad 3ae 3af 3b0 3b1 3b2 3b3 3b4 3b5 3b6 3b7 3b8 ' + + '3b9 3ba 3bb 3bc 3bd 3be 3bf 3c0 3c1 3c2 3c3 3c4 3c5 3c6 3c7 3c8 ' + + '3c9 3ca 3cb 3cc 3cd 3ce 3d0 3d1 3d2 3d3 3d4 3d5 3d6 3d7 3d8 3d9 ' + + '3da 3db 3dc 3dd 3de 3df 3e2 3e3 3e4 3e5 3e6 3e7 3e8 3e9 3ea 3eb ' + + '3ec 3ed 3ee 3ef 3f0 3f1 3f2 3f3 3f4 3f5 3f6 3f7 3f8 3f9 3fa 3fb ' + + '3fc 3fd 3fe 3ff 40a 40b 40c 40d 40e 40f 41a 41b 41c 41d 41e 41f ' + + '42a 42b 42c 42d 42e 42f 43a 43b 43c 43d 43e 43f 44a 44b 44c 44d ' + + '44e 44f 45a 45b 45c 45d 45e 45f 46a 46b 46c 46d 46e 46f 47a 47b ' + + '47c 47d 47e 47f 48a 48b 48c 48d 48e 48f 49a 49b 49c 49d 49e 49f ' + + '4a0 4a1 4a2 4a3 4a4 4a5 4a6 4a7 4a8 4a9 4aa 4ab 4ac 4ad 4ae 4af ' + + '4b0 4b1 4b2 4b3 4b4 4b5 4b6 4b7 4b8 4b9 4ba 4bb 4bc 4bd 4be 4bf ' + + '4c0 4c1 4c2 4c3 4c4 4c5 4c6 4c7 4c8 4c9 4ca 4cb 4cc 4cd 4ce 4cf ' + + '4d0 4d1 4d2 4d3 4d4 4d5 4d6 4d7 4d8 4d9 4da 4db 4dc 4dd 4de 4df ' + + '4e0 4e1 4e2 4e3 4e4 4e5 4e6 4e7 4e8 4e9 4ea 4eb 4ec 4ed 4ee 4ef ' + + '4f0 4f1 4f2 4f3 4f4 4f5 4f6 4f7 4f8 4f9 4fa 4fb 4fc 4fd 4fe 4ff ' + + '50a 50b 50c 50d 50e 50f 51a 51b 51c 51d 53a 53b 53c 53d 53e 53f ' + + '54a 54b 54c 54d 54e 54f 56a 56b 56c 56d 56e 56f 57a 57b 57c 57d ' + + '57e 57f 5f').split(' '); + + var replacement = ('- 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I P Q R S T ' + + 'U V W X Y a b c d e f g h i p q r s t u v w x y A a A a A a C c C c ' + + 'D d E e E e E e E e G g G g H h H h I i I i IJ ij J j K k k L l L l ' + + 'N n N n N n n O o OE oe R r R r R r S s T t T t T t U u U u U u W w ' + + 'Y y Y Z b B b b b b C C c D E F f G Y h i I K k A a A a E e E e I i ' + + 'R r R r U u U u S s n d 8 8 Z z A a E e O o Y y l n t j db qp < ? ? ' + + 'B U A E e J j a a a b c e d d e e g g g Y x u h h i i w m n n N o oe ' + + 'm o r R R S f f f f t t u Z Z 3 3 ? ? 5 C O B a e i o u c d A ' + + 'E H i A B r A E Z H O I E E T r E S I I J jb A B B r D E X 3 N N P ' + + 'C T y O X U h W W a 6 B r d e x 3 N N P C T Y qp x U h W W e e h r ' + + 'e s i i j jb W w Tb tb IC ic A a IA ia Y y O o V v V v Oy oy C c R ' + + 'r F f H h X x 3 3 d d d d R R R R JT JT E e JT jt JX JX U D Q N T ' + + '2 F r p z 2 n x U B j t n C R 8 R O P O S w f q n t q t n p h a n ' + + 'a u j u 2 n 2 n g l uh p o S u J K L M N O Z j k l m n o z c f Y s ' + + 'c a r 2 3 u p 1 A A A A A A AE C E E E E I I I I D N O O O O O X O ' + + 'U U U U Y p b a a a a a a ae c e e e e i i i i o n o o o o o o u u ' + + 'u u y y C c C c D d E e G g G g I i I i I i l L l L l L n n O o O ' + + 'o S s S s S s U u U u U u z Z z Z z f D d d q E e l h w N n O O o ' + + 'P P P p R S s E l t T t T U u U U Y y Z z 3 3 3 3 2 5 5 5 p DZ Dz ' + + 'dz Lj Lj lj NJ Nj nj A a I i O o U u U u U u U u U u e A a A a AE ' + + 'ae G g G g K k Q q Q q 3 3 J dz dZ DZ g G h p N n A a AE ae O o I ' + + 'i O o O o T t 3 3 H h O o O o O o A C c L T s Q q R r Y y e 3 3 3 ' + + '3 j i I I I h w R r R R r r u v A M Y Y B G H j K L q ? c dz d3 dz ' + + 'ts tf tc fn ls lz ww u u h h j r r r R W Y x Y 1 s x c h m r t v x ' + + 'c c c I O Y O K A M N E O TT P E T Y O X Y O I Y a e n i v a b y d ' + + 'e c n 0 1 k j u v c o tt p s o t u q X Y w i u o u w b e Y Y Y O w ' + + 'x Q q C c F f N N W w q q h e S s X x 6 6 t t x e c j O E E p p C ' + + 'M M p C C C Hb Th K N Y U K jI M H O TT b bI b E IO R K JI M H O N ' + + 'b bI b e io r Hb h k n y u mY my Im Im 3 3 O o W w W W H H B b P p ' + + 'K k K k K k K k H h H h Ih ih O o C c T t Y y Y y X x TI ti H h H ' + + 'h H h E e E e I X x K k jt jt H h H h H h M m l A a A a AE ae E e ' + + 'e e E e X X 3 3 3 3 N n N n O o O o O o E e Y y Y y Y y H h R r bI ' + + 'bi F f X x X x H h G g T t Q q W w d r L Iu O y m o N U Y S d h l ' + + 'lu d y w 2 n u y un _').split(' '); + + // Map the Unicode characters to their counterparts in an object. + var map = {}; + for (var index = 0, length = unicode.length; index < length; index += 1) { + map[unicode[index]] = replacement[index]; + } + + $.url.map = map; + +})(this.jQuery, this); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.url-helpers.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.url-helpers.min.js new file mode 100644 index 00000000..904a62a4 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/javascript/plugins/jquery.url-helpers.min.js @@ -0,0 +1,3 @@ +(function($,window){$.url={escape:function(string){return window.encodeURIComponent(string||'').replace(/%20/g,'+');},slugify:function(string,trim){var str='';var index=0;var length=string.length;var map=this.map;for(;indextermites'); + }); + + it('should return the string with each instance of the term wrapped in bold tags', function () { + var target = this.module.formatResult({id: 'we have a termite terminology', text: 'we have a termite terminology'}); + assert.equal(target, 'we have a termite terminology'); + }); + + it('should return the term if there is no last term saved', function () { + delete this.module._lastTerm; + var target = this.module.formatResult({id: 'we have a termite terminology', text: 'we have a termite terminology'}); + assert.equal(target, 'we have a termite terminology'); + }); + }); + + describe('.formatNoMatches(term)', function () { + it('should return the no matches string if there is a term', function () { + var target = this.module.formatNoMatches('term'); + assert.equal(target, 'No matches found'); + }); + + it('should return the empty string if there is no term', function () { + var target = this.module.formatNoMatches(''); + assert.equal(target, 'Start typing…'); + }); + }); + + describe('.formatInputTooShort(term, min)', function () { + it('should return the plural input too short string', function () { + var target = this.module.formatInputTooShort('term', 2); + assert.equal(target, 'Input is too short, must be at least 2 characters'); + }); + + it('should return the singular input too short string', function () { + var target = this.module.formatInputTooShort('term', 1); + assert.equal(target, 'Input is too short, must be at least one character'); + }); + }); + + describe('.formatTerm()', function () { + it('should return an item object with id and text properties', function () { + assert.deepEqual(this.module.formatTerm('test'), {id: 'test', text: 'test'}); + }); + + it('should trim whitespace from the value', function () { + assert.deepEqual(this.module.formatTerm(' test '), {id: 'test', text: 'test'}); + }); + + it('should convert commas in ids into unicode characters', function () { + assert.deepEqual(this.module.formatTerm('test, test'), {id: 'test\u002C test', text: 'test, test'}); + }); + }); + + describe('.formatInitialValue(element, callback)', function () { + beforeEach(function () { + this.callback = sinon.spy(); + }); + + it('should pass an item object with id and text properties into the callback', function () { + var target = jQuery(''); + + this.module.formatInitialValue(target, this.callback); + assert.calledWith(this.callback, {id: 'test', text: 'test'}); + }); + + it('should pass an array of properties into the callback if options.tags is true', function () { + this.module.options.tags = true; + var target = jQuery('', {value: "test, test"}); + + this.module.formatInitialValue(target, this.callback); + assert.calledWith(this.callback, [{id: 'test', text: 'test'}, {id: 'test', text: 'test'}]); + }); + + it('should return the value if no callback is provided (to support select2 v2.1)', function () { + var target = jQuery(''); + + assert.deepEqual(this.module.formatInitialValue(target), {id: 'test', text: 'test'}); + }); + }); + + describe('._onQuery(options)', function () { + it('should lookup the current term with the callback', function () { + var target = sinon.stub(this.module, 'lookup'); + + this.module._onQuery({term: 'term', callback: 'callback'}); + + assert.called(target); + assert.calledWith(target, 'term', 'callback'); + }); + + it('should do nothing if there is no options object', function () { + var target = sinon.stub(this.module, 'lookup'); + this.module._onQuery(); + assert.notCalled(target); + }); + }); + + describe('._onKeydown(event)', function () { + beforeEach(function () { + this.keyDownEvent = jQuery.Event("keydown", { key: ',', which: 188 }); + this.fakeEvent = {}; + this.clock = sinon.useFakeTimers(); + this.jQuery = sinon.stub(jQuery.fn, 'init', jQuery.fn.init); + this.Event = sinon.stub(jQuery, 'Event').returns(this.fakeEvent); + this.trigger = sinon.stub(jQuery.fn, 'trigger'); + }); + + afterEach(function () { + this.clock.restore(); + this.jQuery.restore(); + this.Event.restore(); + this.trigger.restore(); + }); + + it('should trigger fake "return" keypress if a comma is pressed', function () { + this.module._onKeydown(this.keyDownEvent); + + this.clock.tick(100); + + assert.called(this.jQuery); + assert.called(this.Event); + assert.called(this.trigger); + assert.calledWith(this.trigger, this.fakeEvent); + }); + + it('should do nothing if another key is pressed', function () { + this.keyDownEvent.key = '╚'; + this.keyDownEvent.which = 200; + + this.module._onKeydown(this.keyDownEvent); + + this.clock.tick(100); + + assert.notCalled(this.Event); + }); + + it('should do nothing if key is pressed which has the comma key-code but is not a comma', function () { + this.keyDownEvent.key = 'ת'; + this.keyDownEvent.which = 188; + + this.module._onKeydown(this.keyDownEvent); + + this.clock.tick(100); + + assert.notCalled(this.Event); + }); + }); +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/autocomplete.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/autocomplete.spec.min.js new file mode 100644 index 00000000..8c989d5b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/autocomplete.spec.min.js @@ -0,0 +1,2 @@ +describe('ckan.modules.AutocompleteModule()',function(){var Autocomplete=ckan.module.registry['autocomplete'];beforeEach(function(){if(jQuery.fn.select2){this.select2=sinon.stub(jQuery.fn,'select2');}else{this.select2=jQuery.fn.select2=sinon.stub().returns({data:sinon.stub().returns({on:sinon.stub()})});} +this.el=document.createElement('input');this.sandbox=ckan.sandbox();this.sandbox.body=this.fixture;this.module=new Autocomplete(this.el,{},this.sandbox);});afterEach(function(){this.module.teardown();if(this.select2.restore){this.select2.restore();}else{delete jQuery.fn.select2;}});describe('.initialize()',function(){it('should bind callback methods to the module',function(){var target=sinon.stub(jQuery,'proxyAll');this.module.initialize();assert.called(target);assert.calledWith(target,this.module,/_on/,/format/);target.restore();});it('should setup the autocomplete plugin',function(){var target=sinon.stub(this.module,'setupAutoComplete');this.module.initialize();assert.called(target);});});describe('.setupAutoComplete()',function(){it('should initialize the autocomplete plugin',function(){this.module.setupAutoComplete();assert.called(this.select2);assert.calledWith(this.select2,{width:'resolve',query:this.module._onQuery,dropdownCssClass:'',containerCssClass:'',formatResult:this.module.formatResult,formatNoMatches:this.module.formatNoMatches,formatInputTooShort:this.module.formatInputTooShort,createSearchChoice:this.module.formatTerm,initSelection:this.module.formatInitialValue});});it('should initialize the autocomplete plugin with a tags callback if options.tags is true',function(){this.module.options.tags=true;this.module.setupAutoComplete();assert.called(this.select2);assert.calledWith(this.select2,{width:'resolve',tags:this.module._onQuery,dropdownCssClass:'',containerCssClass:'',formatResult:this.module.formatResult,formatNoMatches:this.module.formatNoMatches,formatInputTooShort:this.module.formatInputTooShort,initSelection:this.module.formatInitialValue});it('should watch the keydown event on the select2 input');it('should allow a custom css class to be added to the dropdown',function(){this.module.options.dropdownClass='tags';this.module.setupAutoComplete();assert.called(this.select2);assert.calledWith(this.select2,{width:'resolve',tags:this.module._onQuery,dropdownCssClass:'tags',containerCssClass:'',formatResult:this.module.formatResult,formatNoMatches:this.module.formatNoMatches,formatInputTooShort:this.module.formatInputTooShort,initSelection:this.module.formatInitialValue});});it('should allow a custom css class to be added to the container',function(){this.module.options.containerClass='tags';this.module.setupAutoComplete();assert.called(this.select2);assert.calledWith(this.select2,{width:'resolve',tags:this.module._onQuery,dropdownCssClass:'',containerCssClass:'tags',formatResult:this.module.formatResult,formatNoMatches:this.module.formatNoMatches,formatInputTooShort:this.module.formatInputTooShort,initSelection:this.module.formatInitialValue});});});});describe('.getCompletions(term, fn)',function(){beforeEach(function(){this.term='term';this.module.options.source='http://example.com?term=?';this.target=sinon.stub(this.sandbox.client,'getCompletions');});it('should get the completions from the client',function(){this.module.getCompletions(this.term);assert.called(this.target);});it('should replace the last ? in the source url with the term',function(){this.module.getCompletions(this.term);assert.calledWith(this.target,'http://example.com?term=term');});it('should escape special characters in the term',function(){this.module.getCompletions('term with spaces');assert.calledWith(this.target,'http://example.com?term=term%20with%20spaces');});});describe('.lookup(term, fn)',function(){beforeEach(function(){sinon.stub(this.module,'getCompletions');this.target=sinon.spy();this.module.setupAutoComplete();});it('should set the _lastTerm property',function(){this.module.lookup('term',this.target);assert.equal(this.module._lastTerm,'term');});it('should call the fn immediately if there is no term',function(){this.module.lookup('',this.target);assert.called(this.target);assert.calledWith(this.target,{results:[]});});it('should debounce the request if there is a term');it('should cancel the last request');});describe('.formatResult(state)',function(){beforeEach(function(){this.module._lastTerm='term';});it('should return the string with the last term wrapped in bold tags',function(){var target=this.module.formatResult({id:'we have termites',text:'we have termites'});assert.equal(target,'we have termites');});it('should return the string with each instance of the term wrapped in bold tags',function(){var target=this.module.formatResult({id:'we have a termite terminology',text:'we have a termite terminology'});assert.equal(target,'we have a termite terminology');});it('should return the term if there is no last term saved',function(){delete this.module._lastTerm;var target=this.module.formatResult({id:'we have a termite terminology',text:'we have a termite terminology'});assert.equal(target,'we have a termite terminology');});});describe('.formatNoMatches(term)',function(){it('should return the no matches string if there is a term',function(){var target=this.module.formatNoMatches('term');assert.equal(target,'No matches found');});it('should return the empty string if there is no term',function(){var target=this.module.formatNoMatches('');assert.equal(target,'Start typing…');});});describe('.formatInputTooShort(term, min)',function(){it('should return the plural input too short string',function(){var target=this.module.formatInputTooShort('term',2);assert.equal(target,'Input is too short, must be at least 2 characters');});it('should return the singular input too short string',function(){var target=this.module.formatInputTooShort('term',1);assert.equal(target,'Input is too short, must be at least one character');});});describe('.formatTerm()',function(){it('should return an item object with id and text properties',function(){assert.deepEqual(this.module.formatTerm('test'),{id:'test',text:'test'});});it('should trim whitespace from the value',function(){assert.deepEqual(this.module.formatTerm(' test '),{id:'test',text:'test'});});it('should convert commas in ids into unicode characters',function(){assert.deepEqual(this.module.formatTerm('test, test'),{id:'test\u002C test',text:'test, test'});});});describe('.formatInitialValue(element, callback)',function(){beforeEach(function(){this.callback=sinon.spy();});it('should pass an item object with id and text properties into the callback',function(){var target=jQuery('');this.module.formatInitialValue(target,this.callback);assert.calledWith(this.callback,{id:'test',text:'test'});});it('should pass an array of properties into the callback if options.tags is true',function(){this.module.options.tags=true;var target=jQuery('',{value:"test, test"});this.module.formatInitialValue(target,this.callback);assert.calledWith(this.callback,[{id:'test',text:'test'},{id:'test',text:'test'}]);});it('should return the value if no callback is provided (to support select2 v2.1)',function(){var target=jQuery('');assert.deepEqual(this.module.formatInitialValue(target),{id:'test',text:'test'});});});describe('._onQuery(options)',function(){it('should lookup the current term with the callback',function(){var target=sinon.stub(this.module,'lookup');this.module._onQuery({term:'term',callback:'callback'});assert.called(target);assert.calledWith(target,'term','callback');});it('should do nothing if there is no options object',function(){var target=sinon.stub(this.module,'lookup');this.module._onQuery();assert.notCalled(target);});});describe('._onKeydown(event)',function(){beforeEach(function(){this.keyDownEvent=jQuery.Event("keydown",{key:',',which:188});this.fakeEvent={};this.clock=sinon.useFakeTimers();this.jQuery=sinon.stub(jQuery.fn,'init',jQuery.fn.init);this.Event=sinon.stub(jQuery,'Event').returns(this.fakeEvent);this.trigger=sinon.stub(jQuery.fn,'trigger');});afterEach(function(){this.clock.restore();this.jQuery.restore();this.Event.restore();this.trigger.restore();});it('should trigger fake "return" keypress if a comma is pressed',function(){this.module._onKeydown(this.keyDownEvent);this.clock.tick(100);assert.called(this.jQuery);assert.called(this.Event);assert.called(this.trigger);assert.calledWith(this.trigger,this.fakeEvent);});it('should do nothing if another key is pressed',function(){this.keyDownEvent.key='╚';this.keyDownEvent.which=200;this.module._onKeydown(this.keyDownEvent);this.clock.tick(100);assert.notCalled(this.Event);});it('should do nothing if key is pressed which has the comma key-code but is not a comma',function(){this.keyDownEvent.key='ת';this.keyDownEvent.which=188;this.module._onKeydown(this.keyDownEvent);this.clock.tick(100);assert.notCalled(this.Event);});});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/basic-form.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/basic-form.spec.js new file mode 100644 index 00000000..9d0628fc --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/basic-form.spec.js @@ -0,0 +1,39 @@ +/*globals describe beforeEach afterEach it assert sinon ckan jQuery */ +describe('ckan.module.BasicFormModule()', function () { + var BasicFormModule = ckan.module.registry['basic-form']; + + beforeEach(function () { + sinon.stub(jQuery.fn, 'incompleteFormWarning'); + + this.el = document.createElement('form'); + this.el.innerHTML = '' + this.sandbox = ckan.sandbox(); + this.sandbox.body = this.fixture; + this.sandbox.body.append(this.el) + this.module = new BasicFormModule(this.el, {}, this.sandbox); + }); + + afterEach(function () { + this.module.teardown(); + jQuery.fn.incompleteFormWarning.restore(); + }); + + describe('.initialize()', function () { + it('should attach the jQuery.fn.incompleteFormWarning() to the form', function () { + this.module.initialize(); + assert.called(jQuery.fn.incompleteFormWarning); + }); + + it('should disable the submit button on form submit', function(done) { + this.module.initialize(); + this.module._onSubmit(); + + setTimeout(function() { + var buttonAttrDisabled = this.el.querySelector('button').getAttribute('disabled'); + + assert.ok(buttonAttrDisabled === 'disabled') + done(); + }.bind(this), 0); + }); + }); +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/basic-form.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/basic-form.spec.min.js new file mode 100644 index 00000000..b104d251 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/basic-form.spec.min.js @@ -0,0 +1,4 @@ +describe('ckan.module.BasicFormModule()',function(){var BasicFormModule=ckan.module.registry['basic-form'];beforeEach(function(){sinon.stub(jQuery.fn,'incompleteFormWarning');this.el=document.createElement('form');this.el.innerHTML='' +this.sandbox=ckan.sandbox();this.sandbox.body=this.fixture;this.sandbox.body.append(this.el) +this.module=new BasicFormModule(this.el,{},this.sandbox);});afterEach(function(){this.module.teardown();jQuery.fn.incompleteFormWarning.restore();});describe('.initialize()',function(){it('should attach the jQuery.fn.incompleteFormWarning() to the form',function(){this.module.initialize();assert.called(jQuery.fn.incompleteFormWarning);});it('should disable the submit button on form submit',function(done){this.module.initialize();this.module._onSubmit();setTimeout(function(){var buttonAttrDisabled=this.el.querySelector('button').getAttribute('disabled');assert.ok(buttonAttrDisabled==='disabled') +done();}.bind(this),0);});});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/confirm-action.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/confirm-action.spec.js new file mode 100644 index 00000000..d90b36d6 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/confirm-action.spec.js @@ -0,0 +1,118 @@ +/*globals describe beforeEach afterEach it assert sinon ckan jQuery */ +describe('ckan.module.ConfirmActionModule()', function () { + var ConfirmActionModule = ckan.module.registry['confirm-action']; + + beforeEach(function () { + jQuery.fn.modal = sinon.spy(); + + this.el = document.createElement('button'); + this.sandbox = ckan.sandbox(); + this.sandbox.body = this.fixture; + this.module = new ConfirmActionModule(this.el, {}, this.sandbox); + }); + + afterEach(function () { + this.module.teardown(); + }); + + describe('.initialize()', function () { + it('should watch for clicks on the module element', function () { + var target = sinon.stub(this.module.el, 'on'); + this.module.initialize(); + assert.called(target); + assert.calledWith(target, 'click', this.module._onClick); + }); + }); + + describe('.confirm()', function () { + it('should append the modal to the document body', function () { + this.module.confirm(); + assert.equal(this.fixture.children().length, 1); + assert.equal(this.fixture.find('.modal').length, 1); + }); + + it('should show the modal dialog', function () { + this.module.confirm(); + assert.called(jQuery.fn.modal); + assert.calledWith(jQuery.fn.modal, 'show'); + }); + }); + + describe('.performAction()', function () { + it('should submit the action'); + }); + + describe('.createModal()', function () { + it('should create the modal element', function () { + var target = this.module.createModal(); + + assert.ok(target.hasClass('modal')); + }); + + it('should set the module.modal property', function () { + var target = this.module.createModal(); + + assert.ok(target === this.module.modal); + }); + + it('should bind the success/cancel listeners', function () { + var target = sinon.stub(jQuery.fn, 'on'); + + this.module.createModal(); + + // Not an ideal check as this implementation could be done in many ways. + assert.calledTwice(target); + assert.calledWith(target, 'click', '.btn-primary', this.module._onConfirmSuccess); + assert.calledWith(target, 'click', '.btn-cancel', this.module._onConfirmCancel); + + target.restore(); + }); + + it('should initialise the modal plugin', function () { + this.module.createModal(); + assert.called(jQuery.fn.modal); + assert.calledWith(jQuery.fn.modal, {show: false}); + }); + + it('should allow to customize the content', function () { + this.module.options.content = 'some custom content'; + var target = this.module.createModal(); + + assert.equal(target.find('.modal-body').text(), 'some custom content'); + }); + }); + + describe('._onClick()', function () { + it('should prevent the default action', function () { + var target = {preventDefault: sinon.spy()}; + this.module._onClick(target); + + assert.called(target.preventDefault); + }); + + it('should display the confirmation dialog', function () { + var target = sinon.stub(this.module, 'confirm'); + this.module._onClick({preventDefault: sinon.spy()}); + assert.called(target); + }); + }); + + describe('._onConfirmSuccess()', function () { + it('should perform the action', function () { + var target = sinon.stub(this.module, 'performAction'); + this.module._onConfirmSuccess(jQuery.Event('click')); + assert.called(target); + }); + }); + + describe('._onConfirmCancel()', function () { + it('should hide the modal', function () { + this.module.modal = jQuery('
'); + this.module._onConfirmCancel(jQuery.Event('click')); + + assert.called(jQuery.fn.modal); + assert.calledWith(jQuery.fn.modal, 'hide'); + }); + }); + +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/confirm-action.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/confirm-action.spec.min.js new file mode 100644 index 00000000..ad5a210f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/confirm-action.spec.min.js @@ -0,0 +1 @@ +describe('ckan.module.ConfirmActionModule()',function(){var ConfirmActionModule=ckan.module.registry['confirm-action'];beforeEach(function(){jQuery.fn.modal=sinon.spy();this.el=document.createElement('button');this.sandbox=ckan.sandbox();this.sandbox.body=this.fixture;this.module=new ConfirmActionModule(this.el,{},this.sandbox);});afterEach(function(){this.module.teardown();});describe('.initialize()',function(){it('should watch for clicks on the module element',function(){var target=sinon.stub(this.module.el,'on');this.module.initialize();assert.called(target);assert.calledWith(target,'click',this.module._onClick);});});describe('.confirm()',function(){it('should append the modal to the document body',function(){this.module.confirm();assert.equal(this.fixture.children().length,1);assert.equal(this.fixture.find('.modal').length,1);});it('should show the modal dialog',function(){this.module.confirm();assert.called(jQuery.fn.modal);assert.calledWith(jQuery.fn.modal,'show');});});describe('.performAction()',function(){it('should submit the action');});describe('.createModal()',function(){it('should create the modal element',function(){var target=this.module.createModal();assert.ok(target.hasClass('modal'));});it('should set the module.modal property',function(){var target=this.module.createModal();assert.ok(target===this.module.modal);});it('should bind the success/cancel listeners',function(){var target=sinon.stub(jQuery.fn,'on');this.module.createModal();assert.calledTwice(target);assert.calledWith(target,'click','.btn-primary',this.module._onConfirmSuccess);assert.calledWith(target,'click','.btn-cancel',this.module._onConfirmCancel);target.restore();});it('should initialise the modal plugin',function(){this.module.createModal();assert.called(jQuery.fn.modal);assert.calledWith(jQuery.fn.modal,{show:false});});it('should allow to customize the content',function(){this.module.options.content='some custom content';var target=this.module.createModal();assert.equal(target.find('.modal-body').text(),'some custom content');});});describe('._onClick()',function(){it('should prevent the default action',function(){var target={preventDefault:sinon.spy()};this.module._onClick(target);assert.called(target.preventDefault);});it('should display the confirmation dialog',function(){var target=sinon.stub(this.module,'confirm');this.module._onClick({preventDefault:sinon.spy()});assert.called(target);});});describe('._onConfirmSuccess()',function(){it('should perform the action',function(){var target=sinon.stub(this.module,'performAction');this.module._onConfirmSuccess(jQuery.Event('click'));assert.called(target);});});describe('._onConfirmCancel()',function(){it('should hide the modal',function(){this.module.modal=jQuery('
');this.module._onConfirmCancel(jQuery.Event('click'));assert.called(jQuery.fn.modal);assert.calledWith(jQuery.fn.modal,'hide');});});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/custom-fields.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/custom-fields.spec.js new file mode 100644 index 00000000..93afad1a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/custom-fields.spec.js @@ -0,0 +1,168 @@ +/*globals describe before beforeEach afterEach it assert sinon ckan jQuery */ +describe('ckan.module.CustomFieldsModule()', function () { + var CustomFieldsModule = ckan.module.registry['custom-fields']; + + before(function (done) { + this.loadFixture('custom_fields.html', function (template) { + this.template = template; + done(); + }); + }); + + beforeEach(function () { + this.fixture.html(this.template); + this.el = this.fixture.find('[data-module]'); + this.sandbox = ckan.sandbox(); + this.sandbox.body = this.fixture; + this.module = new CustomFieldsModule(this.el, {}, this.sandbox); + }); + + afterEach(function () { + this.module.teardown(); + }); + + describe('.initialize()', function () { + it('should bind all functions beginning with _on to the module scope', function () { + var target = sinon.stub(jQuery, 'proxyAll'); + + this.module.initialize(); + + assert.called(target); + assert.calledWith(target, this.module, /_on/); + + target.restore(); + }); + + it('should listen for changes to the last "key" input', function () { + var target = sinon.stub(this.module, '_onChange'); + + this.module.initialize(); + this.module.$('input[name*=key]').change(); + + assert.calledOnce(target); + }); + + it('should listen for changes to all checkboxes', function () { + var target = sinon.stub(this.module, '_onRemove'); + + this.module.initialize(); + this.module.$(':checkbox').trigger('change'); + + assert.calledOnce(target); + }); + }); + + describe('.newField(element)', function () { + it('should append a new field to the element', function () { + var element = document.createElement('div'); + sinon.stub(this.module, 'cloneField').returns(element); + + this.module.newField(); + + assert.ok(jQuery.contains(this.module.el[0], element)); + }); + }); + + describe('.cloneField(element)', function () { + it('should clone the provided field', function () { + var element = document.createElement('div'); + var init = sinon.stub(jQuery.fn, 'init', jQuery.fn.init); + var clone = sinon.stub(jQuery.fn, 'clone', jQuery.fn.clone); + + this.module.cloneField(element); + + assert.called(init); + assert.calledWith(init, element); + assert.called(clone); + + init.restore(); + clone.restore(); + }); + + it('should return the cloned element', function () { + var element = document.createElement('div'); + var cloned = document.createElement('div'); + var init = sinon.stub(jQuery.fn, 'init', jQuery.fn.init); + var clone = sinon.stub(jQuery.fn, 'clone').returns(jQuery(cloned)); + + assert.ok(this.module.cloneField(element)[0] === cloned); + + init.restore(); + clone.restore(); + }); + }); + + describe('.resetField(element)', function () { + beforeEach(function () { + this.field = jQuery('
'); + }); + + it('should empty all input values', function () { + var target = this.module.resetField(this.field); + assert.equal(target.find(':input').val(), ''); + }); + + it('should increment any integers in the input names by one', function () { + var target = this.module.resetField(this.field); + assert.equal(target.find(':input').attr('name'), 'field-2'); + }); + + it('should increment any numbers in the label text by one', function () { + var target = this.module.resetField(this.field); + assert.equal(target.find('label').text(), 'Field 2'); + }); + + it('should increment any numbers in the label for by one', function () { + var target = this.module.resetField(this.field); + assert.equal(target.find('label').attr('for'), 'field-2'); + }); + }); + + describe('.disableField(field, disable)', function () { + beforeEach(function () { + this.target = this.module.$('.control-custom:first'); + }); + + it('should add a .disable class to the element', function () { + this.module.disableField(this.target); + assert.isTrue(this.target.hasClass('disabled')); + }); + + it('should remove a .disable class to the element if disable is false', function () { + this.target.addClass('disable'); + + this.module.disableField(this.target, false); + assert.isFalse(this.target.hasClass('disabled')); + }); + + }); + + describe('._onChange(event)', function () { + it('should call .newField() with the custom control', function () { + var target = sinon.stub(this.module, 'newField'); + var field = this.module.$('[name*=key]:last').val('test'); + + this.module._onChange(jQuery.Event('change', {target: field[0]})); + + assert.called(target); + }); + + it('should not call .newField() if the target field is empty', function () { + var target = sinon.stub(this.module, 'newField'); + var field = this.module.$('[name*=key]:last').val(''); + + this.module._onChange(jQuery.Event('change', {target: field[0]})); + + assert.notCalled(target); + }); + }); + + describe('._onRemove(event)', function () { + it('should call .disableField() with the custom control', function () { + var target = sinon.stub(this.module, 'disableField'); + this.module._onRemove(jQuery.Event('change', {target: this.module.$(':checkbox')[0]})); + + assert.called(target); + }); + }); +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/custom-fields.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/custom-fields.spec.min.js new file mode 100644 index 00000000..7d846d50 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/custom-fields.spec.min.js @@ -0,0 +1 @@ +describe('ckan.module.CustomFieldsModule()',function(){var CustomFieldsModule=ckan.module.registry['custom-fields'];before(function(done){this.loadFixture('custom_fields.html',function(template){this.template=template;done();});});beforeEach(function(){this.fixture.html(this.template);this.el=this.fixture.find('[data-module]');this.sandbox=ckan.sandbox();this.sandbox.body=this.fixture;this.module=new CustomFieldsModule(this.el,{},this.sandbox);});afterEach(function(){this.module.teardown();});describe('.initialize()',function(){it('should bind all functions beginning with _on to the module scope',function(){var target=sinon.stub(jQuery,'proxyAll');this.module.initialize();assert.called(target);assert.calledWith(target,this.module,/_on/);target.restore();});it('should listen for changes to the last "key" input',function(){var target=sinon.stub(this.module,'_onChange');this.module.initialize();this.module.$('input[name*=key]').change();assert.calledOnce(target);});it('should listen for changes to all checkboxes',function(){var target=sinon.stub(this.module,'_onRemove');this.module.initialize();this.module.$(':checkbox').trigger('change');assert.calledOnce(target);});});describe('.newField(element)',function(){it('should append a new field to the element',function(){var element=document.createElement('div');sinon.stub(this.module,'cloneField').returns(element);this.module.newField();assert.ok(jQuery.contains(this.module.el[0],element));});});describe('.cloneField(element)',function(){it('should clone the provided field',function(){var element=document.createElement('div');var init=sinon.stub(jQuery.fn,'init',jQuery.fn.init);var clone=sinon.stub(jQuery.fn,'clone',jQuery.fn.clone);this.module.cloneField(element);assert.called(init);assert.calledWith(init,element);assert.called(clone);init.restore();clone.restore();});it('should return the cloned element',function(){var element=document.createElement('div');var cloned=document.createElement('div');var init=sinon.stub(jQuery.fn,'init',jQuery.fn.init);var clone=sinon.stub(jQuery.fn,'clone').returns(jQuery(cloned));assert.ok(this.module.cloneField(element)[0]===cloned);init.restore();clone.restore();});});describe('.resetField(element)',function(){beforeEach(function(){this.field=jQuery('
');});it('should empty all input values',function(){var target=this.module.resetField(this.field);assert.equal(target.find(':input').val(),'');});it('should increment any integers in the input names by one',function(){var target=this.module.resetField(this.field);assert.equal(target.find(':input').attr('name'),'field-2');});it('should increment any numbers in the label text by one',function(){var target=this.module.resetField(this.field);assert.equal(target.find('label').text(),'Field 2');});it('should increment any numbers in the label for by one',function(){var target=this.module.resetField(this.field);assert.equal(target.find('label').attr('for'),'field-2');});});describe('.disableField(field, disable)',function(){beforeEach(function(){this.target=this.module.$('.control-custom:first');});it('should add a .disable class to the element',function(){this.module.disableField(this.target);assert.isTrue(this.target.hasClass('disabled'));});it('should remove a .disable class to the element if disable is false',function(){this.target.addClass('disable');this.module.disableField(this.target,false);assert.isFalse(this.target.hasClass('disabled'));});});describe('._onChange(event)',function(){it('should call .newField() with the custom control',function(){var target=sinon.stub(this.module,'newField');var field=this.module.$('[name*=key]:last').val('test');this.module._onChange(jQuery.Event('change',{target:field[0]}));assert.called(target);});it('should not call .newField() if the target field is empty',function(){var target=sinon.stub(this.module,'newField');var field=this.module.$('[name*=key]:last').val('');this.module._onChange(jQuery.Event('change',{target:field[0]}));assert.notCalled(target);});});describe('._onRemove(event)',function(){it('should call .disableField() with the custom control',function(){var target=sinon.stub(this.module,'disableField');this.module._onRemove(jQuery.Event('change',{target:this.module.$(':checkbox')[0]}));assert.called(target);});});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/followers-counter.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/followers-counter.spec.js new file mode 100644 index 00000000..d52edf6b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/followers-counter.spec.js @@ -0,0 +1,186 @@ +/*globals describe beforeEach afterEach it assert sinon ckan jQuery */ +describe('ckan.module.FollowersCounterModule()', function() { + var FollowersCounterModule = ckan.module.registry['followers-counter']; + + beforeEach(function() { + this.initialCounter = 10; + this.el = jQuery('
' + this.initialCounter + '
'); + this.sandbox = ckan.sandbox(); + this.module = new FollowersCounterModule(this.el, {}, this.sandbox); + this.module.options.num_followers = this.initialCounter; + }); + + afterEach(function() { + this.module.teardown(); + }); + + describe('.initialize()', function() { + it('should bind callback methods to the module', function() { + var target = sinon.stub(jQuery, 'proxyAll'); + + this.module.initialize(); + + assert.called(target); + assert.calledWith(target, this.module, /_on/); + + target.restore(); + }); + + it('should subscribe to the "follow-follow-some-id" event', function() { + var target = sinon.stub(this.sandbox, 'subscribe'); + + this.module.options = {id: 'some-id'}; + this.module.initialize(); + + assert.called(target); + assert.calledWith(target, 'follow-follow-some-id', this.module._onFollow); + + target.restore(); + }); + + it('should subscribe to the "follow-unfollow-some-id" event', function() { + var target = sinon.stub(this.sandbox, 'subscribe'); + + this.module.options = {id: 'some-id'}; + this.module.initialize(); + + assert.called(target); + assert.calledWith(target, 'follow-unfollow-some-id', this.module._onUnfollow); + + target.restore(); + }); + }); + + describe('.teardown()', function() { + it('should unsubscribe to the "follow-follow-some-id" event', function() { + var target = sinon.stub(this.sandbox, 'unsubscribe'); + + this.module.options = {id: 'some-id'}; + this.module.initialize(); + this.module.teardown(); + + assert.called(target); + assert.calledWith(target, 'follow-follow-some-id', this.module._onFollow); + + target.restore(); + }); + + it('should unsubscribe to the "follow-unfollow-some-id" event', function() { + var target = sinon.stub(this.sandbox, 'unsubscribe'); + + this.module.options = {id: 'some-id'}; + this.module.initialize(); + this.module.teardown(); + + assert.called(target); + assert.calledWith(target, 'follow-unfollow-some-id', this.module._onUnfollow); + + target.restore(); + }); + }); + + describe('._onFollow', function() { + it('should call _onFollow on "follow-follow-some-id" event', function() { + var target = sinon.stub(this.module, '_onFollow'); + + this.module.options = {id: 'some-id'}; + this.module.initialize(); + + this.sandbox.publish('follow-follow-some-id'); + + assert.called(target); + }); + + it('should call _updateCounter when ._onFollow is called', function() { + var target = sinon.stub(this.module, '_updateCounter'); + + this.module.options = {id: 'some-id'}; + this.module.initialize(); + + this.module._onFollow(); + + assert.called(target); + assert.calledWith(target, {action: 'follow'}); + }); + }); + + describe('._onUnfollow', function() { + it('should call _onUnfollow on "follow-unfollow-some-id" event', function() { + var target = sinon.stub(this.module, '_onUnfollow'); + + this.module.options = {id: 'some-id'}; + this.module.initialize(); + + this.sandbox.publish('follow-unfollow-some-id'); + + assert.called(target); + }); + + it('should call _updateCounter when ._onUnfollow is called', function() { + var target = sinon.stub(this.module, '_updateCounter'); + + this.module.options = {id: 'some-id'}; + this.module.initialize(); + + this.module._onUnfollow(); + + assert.called(target); + assert.calledWith(target, {action: 'unfollow'}); + }); + }); + + describe('._updateCounter', function() { + it('should increment this.options.num_followers on calling _onFollow', function() { + this.module.initialize(); + this.module._onFollow(); + + assert.equal(this.module.options.num_followers, ++this.initialCounter); + }); + + it('should increment the counter value in the DOM on calling _onFollow', function() { + var counterVal; + + this.module.initialize(); + this.module._onFollow(); + + counterVal = this.module.counterEl.text(); + counterVal = parseInt(counterVal, 10); + + assert.equal(counterVal, ++this.initialCounter); + }); + + it('should decrement this.options.num_followers on calling _onUnfollow', function() { + this.module.initialize(); + this.module._onUnfollow(); + + assert.equal(this.module.options.num_followers, --this.initialCounter); + }); + + it('should decrement the counter value in the DOM on calling _onUnfollow', function() { + var counterVal; + + this.module.initialize(); + this.module._onUnfollow(); + + counterVal = this.module.counterEl.text(); + counterVal = parseInt(counterVal, 10); + + assert.equal(counterVal, --this.initialCounter); + }); + + it('should not change the counter value in the DOM when the value is greater than 1000', function() { + var beforeCounterVal = 1536; + var afterCounterVal; + + this.module.options = {num_followers: beforeCounterVal}; + this.module.initialize(); + this.module.counterEl.text(this.module.options.num_followers); + this.module._onFollow(); + + afterCounterVal = this.module.counterEl.text(); + afterCounterVal = parseInt(afterCounterVal, 10); + + assert.equal(beforeCounterVal, afterCounterVal); + }); + }); +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/followers-counter.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/followers-counter.spec.min.js new file mode 100644 index 00000000..6745689a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/followers-counter.spec.min.js @@ -0,0 +1 @@ +describe('ckan.module.FollowersCounterModule()',function(){var FollowersCounterModule=ckan.module.registry['followers-counter'];beforeEach(function(){this.initialCounter=10;this.el=jQuery('
'+this.initialCounter+'
');this.sandbox=ckan.sandbox();this.module=new FollowersCounterModule(this.el,{},this.sandbox);this.module.options.num_followers=this.initialCounter;});afterEach(function(){this.module.teardown();});describe('.initialize()',function(){it('should bind callback methods to the module',function(){var target=sinon.stub(jQuery,'proxyAll');this.module.initialize();assert.called(target);assert.calledWith(target,this.module,/_on/);target.restore();});it('should subscribe to the "follow-follow-some-id" event',function(){var target=sinon.stub(this.sandbox,'subscribe');this.module.options={id:'some-id'};this.module.initialize();assert.called(target);assert.calledWith(target,'follow-follow-some-id',this.module._onFollow);target.restore();});it('should subscribe to the "follow-unfollow-some-id" event',function(){var target=sinon.stub(this.sandbox,'subscribe');this.module.options={id:'some-id'};this.module.initialize();assert.called(target);assert.calledWith(target,'follow-unfollow-some-id',this.module._onUnfollow);target.restore();});});describe('.teardown()',function(){it('should unsubscribe to the "follow-follow-some-id" event',function(){var target=sinon.stub(this.sandbox,'unsubscribe');this.module.options={id:'some-id'};this.module.initialize();this.module.teardown();assert.called(target);assert.calledWith(target,'follow-follow-some-id',this.module._onFollow);target.restore();});it('should unsubscribe to the "follow-unfollow-some-id" event',function(){var target=sinon.stub(this.sandbox,'unsubscribe');this.module.options={id:'some-id'};this.module.initialize();this.module.teardown();assert.called(target);assert.calledWith(target,'follow-unfollow-some-id',this.module._onUnfollow);target.restore();});});describe('._onFollow',function(){it('should call _onFollow on "follow-follow-some-id" event',function(){var target=sinon.stub(this.module,'_onFollow');this.module.options={id:'some-id'};this.module.initialize();this.sandbox.publish('follow-follow-some-id');assert.called(target);});it('should call _updateCounter when ._onFollow is called',function(){var target=sinon.stub(this.module,'_updateCounter');this.module.options={id:'some-id'};this.module.initialize();this.module._onFollow();assert.called(target);assert.calledWith(target,{action:'follow'});});});describe('._onUnfollow',function(){it('should call _onUnfollow on "follow-unfollow-some-id" event',function(){var target=sinon.stub(this.module,'_onUnfollow');this.module.options={id:'some-id'};this.module.initialize();this.sandbox.publish('follow-unfollow-some-id');assert.called(target);});it('should call _updateCounter when ._onUnfollow is called',function(){var target=sinon.stub(this.module,'_updateCounter');this.module.options={id:'some-id'};this.module.initialize();this.module._onUnfollow();assert.called(target);assert.calledWith(target,{action:'unfollow'});});});describe('._updateCounter',function(){it('should increment this.options.num_followers on calling _onFollow',function(){this.module.initialize();this.module._onFollow();assert.equal(this.module.options.num_followers,++this.initialCounter);});it('should increment the counter value in the DOM on calling _onFollow',function(){var counterVal;this.module.initialize();this.module._onFollow();counterVal=this.module.counterEl.text();counterVal=parseInt(counterVal,10);assert.equal(counterVal,++this.initialCounter);});it('should decrement this.options.num_followers on calling _onUnfollow',function(){this.module.initialize();this.module._onUnfollow();assert.equal(this.module.options.num_followers,--this.initialCounter);});it('should decrement the counter value in the DOM on calling _onUnfollow',function(){var counterVal;this.module.initialize();this.module._onUnfollow();counterVal=this.module.counterEl.text();counterVal=parseInt(counterVal,10);assert.equal(counterVal,--this.initialCounter);});it('should not change the counter value in the DOM when the value is greater than 1000',function(){var beforeCounterVal=1536;var afterCounterVal;this.module.options={num_followers:beforeCounterVal};this.module.initialize();this.module.counterEl.text(this.module.options.num_followers);this.module._onFollow();afterCounterVal=this.module.counterEl.text();afterCounterVal=parseInt(afterCounterVal,10);assert.equal(beforeCounterVal,afterCounterVal);});});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/image-upload.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/image-upload.spec.js new file mode 100644 index 00000000..909550ff --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/image-upload.spec.js @@ -0,0 +1,65 @@ +/*globals describe beforeEach afterEach it assert sinon ckan jQuery */ +describe('ckan.modules.ImageUploadModule()', function () { + var ImageUploadModule = ckan.module.registry['image-upload']; + + beforeEach(function () { + this.el = document.createElement('div'); + this.sandbox = ckan.sandbox(); + this.module = new ImageUploadModule(this.el, {}, this.sandbox); + this.module.el.html([ + '
', + '', + ]); + this.module.initialize(); + this.module.field_name = jQuery('', {type: 'text'}) + }); + + afterEach(function () { + this.module.teardown(); + }); + + describe('._onFromWeb()', function () { + + it('should change name when url changed', function () { + this.module.field_url_input.val('http://example.com/some_image.png'); + this.module._onFromWebBlur(); + assert.equal(this.module.field_name.val(), 'some_image.png'); + + this.module.field_url_input.val('http://example.com/undefined_file'); + this.module._onFromWebBlur(); + assert.equal(this.module.field_name.val(), 'undefined_file'); + }); + + it('should ignore url changes if name was manualy changed', function () { + this.module.field_url_input.val('http://example.com/some_image.png'); + this.module._onFromWebBlur(); + assert.equal(this.module.field_name.val(), 'some_image.png'); + + this.module._onModifyName(); + + this.module.field_url_input.val('http://example.com/undefined_file'); + this.module._onFromWebBlur(); + assert.equal(this.module.field_name.val(), 'some_image.png'); + }); + + it('should ignore url changes if name was filled before', function () { + this.module._nameIsDirty = true; + this.module.field_name.val('prefilled'); + + this.module.field_url_input.val('http://example.com/some_image.png'); + this.module._onFromWebBlur(); + assert.equal(this.module.field_name.val(), 'prefilled'); + + this.module.field_url_input.val('http://example.com/second_some_image.png'); + this.module._onFromWebBlur(); + assert.equal(this.module.field_name.val(), 'prefilled'); + + this.module._onModifyName() + + this.module.field_url_input.val('http://example.com/undefined_file'); + this.module._onFromWebBlur(); + assert.equal(this.module.field_name.val(), 'prefilled'); + }); + }); + +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/image-upload.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/image-upload.spec.min.js new file mode 100644 index 00000000..f03c83fe --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/image-upload.spec.min.js @@ -0,0 +1,2 @@ +describe('ckan.modules.ImageUploadModule()',function(){var ImageUploadModule=ckan.module.registry['image-upload'];beforeEach(function(){this.el=document.createElement('div');this.sandbox=ckan.sandbox();this.module=new ImageUploadModule(this.el,{},this.sandbox);this.module.el.html(['
','',]);this.module.initialize();this.module.field_name=jQuery('',{type:'text'})});afterEach(function(){this.module.teardown();});describe('._onFromWeb()',function(){it('should change name when url changed',function(){this.module.field_url_input.val('http://example.com/some_image.png');this.module._onFromWebBlur();assert.equal(this.module.field_name.val(),'some_image.png');this.module.field_url_input.val('http://example.com/undefined_file');this.module._onFromWebBlur();assert.equal(this.module.field_name.val(),'undefined_file');});it('should ignore url changes if name was manualy changed',function(){this.module.field_url_input.val('http://example.com/some_image.png');this.module._onFromWebBlur();assert.equal(this.module.field_name.val(),'some_image.png');this.module._onModifyName();this.module.field_url_input.val('http://example.com/undefined_file');this.module._onFromWebBlur();assert.equal(this.module.field_name.val(),'some_image.png');});it('should ignore url changes if name was filled before',function(){this.module._nameIsDirty=true;this.module.field_name.val('prefilled');this.module.field_url_input.val('http://example.com/some_image.png');this.module._onFromWebBlur();assert.equal(this.module.field_name.val(),'prefilled');this.module.field_url_input.val('http://example.com/second_some_image.png');this.module._onFromWebBlur();assert.equal(this.module.field_name.val(),'prefilled');this.module._onModifyName() +this.module.field_url_input.val('http://example.com/undefined_file');this.module._onFromWebBlur();assert.equal(this.module.field_name.val(),'prefilled');});});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-form.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-form.spec.js new file mode 100644 index 00000000..78975e32 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-form.spec.js @@ -0,0 +1,74 @@ +/*globals describe beforeEach afterEach it assert sinon ckan jQuery */ +describe('ckan.modules.ResourceFormModule()', function () { + var ResourceFormModule = ckan.module.registry['resource-form']; + + beforeEach(function () { + this.el = document.createElement('form'); + this.sandbox = ckan.sandbox(); + this.module = new ResourceFormModule(this.el, {}, this.sandbox); + }); + + afterEach(function () { + this.module.teardown(); + }); + + describe('.initialize()', function () { + it('should subscribe to the "resource:uploaded" event', function () { + var target = sinon.stub(this.sandbox, 'subscribe'); + + this.module.initialize(); + + assert.called(target); + assert.calledWith(target, 'resource:uploaded', this.module._onResourceUploaded); + + target.restore(); + }); + }); + + describe('.teardown()', function () { + it('should unsubscribe from the "resource:uploaded" event', function () { + var target = sinon.stub(this.sandbox, 'unsubscribe'); + + this.module.teardown(); + + assert.called(target); + assert.calledWith(target, 'resource:uploaded', this.module._onResourceUploaded); + + target.restore(); + }); + }); + + describe('._onResourceUploaded()', function () { + beforeEach(function () { + this.module.el.html([ + '', + '', + '', + '', + '', + '' + ].join('')); + + this.resource = { + text: 'text', + checkbox: "check", + radio: "radio2", + hidden: "hidden", + select: "option1" + }; + }); + + it('should set the values on appropriate fields', function () { + var res = this.resource; + + this.module._onResourceUploaded(res); + + jQuery.each(this.module.el.serializeArray(), function (idx, field) { + assert.equal(field.value, res[field.name]); + }); + }); + }); +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-form.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-form.spec.min.js new file mode 100644 index 00000000..39c34e32 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-form.spec.min.js @@ -0,0 +1 @@ +describe('ckan.modules.ResourceFormModule()',function(){var ResourceFormModule=ckan.module.registry['resource-form'];beforeEach(function(){this.el=document.createElement('form');this.sandbox=ckan.sandbox();this.module=new ResourceFormModule(this.el,{},this.sandbox);});afterEach(function(){this.module.teardown();});describe('.initialize()',function(){it('should subscribe to the "resource:uploaded" event',function(){var target=sinon.stub(this.sandbox,'subscribe');this.module.initialize();assert.called(target);assert.calledWith(target,'resource:uploaded',this.module._onResourceUploaded);target.restore();});});describe('.teardown()',function(){it('should unsubscribe from the "resource:uploaded" event',function(){var target=sinon.stub(this.sandbox,'unsubscribe');this.module.teardown();assert.called(target);assert.calledWith(target,'resource:uploaded',this.module._onResourceUploaded);target.restore();});});describe('._onResourceUploaded()',function(){beforeEach(function(){this.module.el.html(['','','','','',''].join(''));this.resource={text:'text',checkbox:"check",radio:"radio2",hidden:"hidden",select:"option1"};});it('should set the values on appropriate fields',function(){var res=this.resource;this.module._onResourceUploaded(res);jQuery.each(this.module.el.serializeArray(),function(idx,field){assert.equal(field.value,res[field.name]);});});});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-upload-field.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-upload-field.spec.js new file mode 100644 index 00000000..dcf2ca06 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-upload-field.spec.js @@ -0,0 +1,290 @@ +/*globals describe beforeEach afterEach it assert sinon ckan jQuery */ +describe('ckan.modules.ResourceUploadFieldModule()', function () { + var ResourceFileUploadModule = ckan.module.registry['resource-upload-field']; + + beforeEach(function () { + jQuery.fn.fileupload = sinon.spy(); + + this.el = jQuery('
'); + this.sandbox = ckan.sandbox(); + this.module = new ResourceFileUploadModule(this.el, {}, this.sandbox); + this.module.initialize(); + }); + + afterEach(function () { + this.module.teardown(); + }); + + describe('.initialize()', function () { + beforeEach(function () { + // Create un-initialised module. + this.module.teardown(); + this.module = new ResourceFileUploadModule(this.el, {}, this.sandbox); + }); + + it('should create the #upload field', function () { + this.module.initialize(); + assert.ok(typeof this.module.upload === 'object'); + }); + + it('should append the upload field to the module element', function () { + this.module.initialize(); + + assert.ok(jQuery.contains(this.el[0], this.module.upload[0])); + }); + + it('should call .setupFileUpload()', function () { + var target = sinon.stub(this.module, 'setupFileUpload'); + + this.module.initialize(); + + assert.called(target); + }); + }); + + describe('.setupFileUpload()', function () { + it('should set the label text on the form input', function () { + this.module.initialize(); + this.module.setupFileUpload(); + + assert.equal(this.module.upload.find('label').text(), 'Upload a file'); + }); + + it('should setup the file upload with relevant options', function () { + this.module.initialize(); + this.module.setupFileUpload(); + + assert.called(jQuery.fn.fileupload); + assert.calledWith(jQuery.fn.fileupload, { + type: 'POST', + paramName: 'file', + forceIframeTransport: true, // Required for XDomain request. + replaceFileInput: true, + autoUpload: false, + add: this.module._onUploadAdd, + send: this.module._onUploadSend, + done: this.module._onUploadDone, + fail: this.module._onUploadFail, + always: this.module._onUploadComplete + }); + }); + }); + + describe('.loading(show)', function () { + it('should add a loading class to the upload element', function () { + this.module.loading(); + + assert.ok(this.module.upload.hasClass('loading')); + }); + + it('should remove the loading class if false is passed as an argument', function () { + this.module.upload.addClass('loading'); + this.module.loading(); + + assert.ok(!this.module.upload.hasClass('loading')); + }); + }); + + describe('.authenticate(key, data)', function () { + beforeEach(function () { + this.fakeThen = sinon.spy(); + this.fakeProxy = sinon.stub(jQuery, 'proxy').returns('onsuccess'); + + this.target = sinon.stub(this.sandbox.client, 'getStorageAuth'); + this.target.returns({ + then: this.fakeThen + }); + }); + + afterEach(function () { + jQuery.proxy.restore(); + }); + + it('should request authentication for the upload', function () { + this.module.authenticate('test', {}); + assert.called(this.target); + assert.calledWith(this.target, 'test'); + }); + + it('should register success and error callbacks', function () { + this.module.authenticate('test', {}); + assert.called(this.fakeThen); + assert.calledWith(this.fakeThen, 'onsuccess', this.module._onAuthError); + }); + + it('should save the key on the data object', function () { + var data = {}; + + this.module.authenticate('test', data); + + assert.equal(data.key, 'test'); + }); + }); + + describe('.lookupMetadata(key, data)', function () { + beforeEach(function () { + this.fakeThen = sinon.spy(); + this.fakeProxy = sinon.stub(jQuery, 'proxy').returns('onsuccess'); + + this.target = sinon.stub(this.sandbox.client, 'getStorageMetadata'); + this.target.returns({ + then: this.fakeThen + }); + }); + + afterEach(function () { + jQuery.proxy.restore(); + }); + + it('should request metadata for the upload key', function () { + this.module.lookupMetadata('test', {}); + assert.called(this.target); + assert.calledWith(this.target, 'test'); + }); + + it('should register success and error callbacks', function () { + this.module.lookupMetadata('test', {}); + assert.called(this.fakeThen); + assert.calledWith(this.fakeThen, 'onsuccess', this.module._onMetadataError); + }); + }); + + describe('.notify(message, type)', function () { + it('should call the sandbox.notify() method', function () { + var target = sinon.stub(this.sandbox, 'notify'); + + this.module.notify('this is an example message', 'info'); + + assert.called(target); + assert.calledWith(target, 'An Error Occurred', 'this is an example message', 'info'); + }); + }); + + describe('.generateKey(file)', function () { + it('should generate a unique filename prefixed with a timestamp', function () { + var now = new Date(); + var date = jQuery.date.toISOString(now); + var clock = sinon.useFakeTimers(now.getTime()); + var target = this.module.generateKey('this is my file.png'); + + assert.equal(target, date + '/this-is-my-file.png'); + + clock.restore(); + }); + }); + + describe('._onUploadAdd(event, data)', function () { + beforeEach(function () { + this.target = sinon.stub(this.module, 'authenticate'); + sinon.stub(this.module, 'generateKey').returns('stubbed'); + }); + + it('should authenticate the upload if a file is provided', function () { + var data = {files: [{name: 'my_file.jpg'}]}; + this.module._onUploadAdd({}, data); + + assert.called(this.target); + assert.calledWith(this.target, 'stubbed', data); + }); + + it('should not authenticate the upload if no file is provided', function () { + var data = {files: []}; + this.module._onUploadAdd({}, data); + + assert.notCalled(this.target); + }); + }); + + describe('._onUploadSend()', function () { + it('should display the loading spinner', function () { + var target = sinon.stub(this.module, 'loading'); + this.module._onUploadSend({}, {}); + + assert.called(target); + }); + }); + + describe('._onUploadDone()', function () { + it('should request the metadata for the file', function () { + var target = sinon.stub(this.module, 'lookupMetadata'); + this.module._onUploadDone({}, {result: {}}); + + assert.called(target); + }); + + it('should call the fail handler if the "result" key in the data is undefined', function () { + var target = sinon.stub(this.module, '_onUploadFail'); + this.module._onUploadDone({}, {result: undefined}); + + assert.called(target); + }); + + it('should call the fail handler if the "result" object has an "error" key', function () { + var target = sinon.stub(this.module, '_onUploadFail'); + this.module._onUploadDone({}, {result: {error: 'failed'}}); + + assert.called(target); + }); + }); + + describe('._onUploadComplete()', function () { + it('should hide the loading spinner', function () { + var target = sinon.stub(this.module, 'loading'); + this.module._onUploadComplete({}, {}); + + assert.called(target); + assert.calledWith(target, false); + }); + }); + + describe('._onAuthSuccess()', function () { + beforeEach(function () { + this.target = { + submit: sinon.spy() + }; + + this.response = { + action: 'action', + fields: [{name: 'name', value: 'value'}] + }; + }); + + it('should set the data url', function () { + this.module._onAuthSuccess(this.target, this.response); + + assert.equal(this.target.url, this.response.action); + }); + + it('should set the additional form data', function () { + this.module._onAuthSuccess(this.target, this.response); + + assert.deepEqual(this.target.formData, this.response.fields); + }); + + it('should merge the form data with the options', function () { + this.module.options.form.params = [{name: 'option', value: 'option'}]; + this.module._onAuthSuccess(this.target, this.response); + + assert.deepEqual(this.target.formData, [{name: 'option', value: 'option'}, {name: 'name', value: 'value'}]); + }); + + it('should call data.submit()', function () { + this.module._onAuthSuccess(this.target, this.response); + assert.called(this.target.submit); + }); + }); + + describe('._onMetadataSuccess()', function () { + it('should publish the "resource:uploaded" event', function () { + var resource = {url: 'http://', name: 'My File'}; + var target = sinon.stub(this.sandbox, 'publish'); + + sinon.stub(this.sandbox.client, 'convertStorageMetadataToResource').returns(resource); + + this.module._onMetadataSuccess(); + + assert.called(target); + assert.calledWith(target, "resource:uploaded", resource); + }); + }); +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-upload-field.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-upload-field.spec.min.js new file mode 100644 index 00000000..42a450c9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/modules/resource-upload-field.spec.min.js @@ -0,0 +1 @@ +describe('ckan.modules.ResourceUploadFieldModule()',function(){var ResourceFileUploadModule=ckan.module.registry['resource-upload-field'];beforeEach(function(){jQuery.fn.fileupload=sinon.spy();this.el=jQuery('');this.sandbox=ckan.sandbox();this.module=new ResourceFileUploadModule(this.el,{},this.sandbox);this.module.initialize();});afterEach(function(){this.module.teardown();});describe('.initialize()',function(){beforeEach(function(){this.module.teardown();this.module=new ResourceFileUploadModule(this.el,{},this.sandbox);});it('should create the #upload field',function(){this.module.initialize();assert.ok(typeof this.module.upload==='object');});it('should append the upload field to the module element',function(){this.module.initialize();assert.ok(jQuery.contains(this.el[0],this.module.upload[0]));});it('should call .setupFileUpload()',function(){var target=sinon.stub(this.module,'setupFileUpload');this.module.initialize();assert.called(target);});});describe('.setupFileUpload()',function(){it('should set the label text on the form input',function(){this.module.initialize();this.module.setupFileUpload();assert.equal(this.module.upload.find('label').text(),'Upload a file');});it('should setup the file upload with relevant options',function(){this.module.initialize();this.module.setupFileUpload();assert.called(jQuery.fn.fileupload);assert.calledWith(jQuery.fn.fileupload,{type:'POST',paramName:'file',forceIframeTransport:true,replaceFileInput:true,autoUpload:false,add:this.module._onUploadAdd,send:this.module._onUploadSend,done:this.module._onUploadDone,fail:this.module._onUploadFail,always:this.module._onUploadComplete});});});describe('.loading(show)',function(){it('should add a loading class to the upload element',function(){this.module.loading();assert.ok(this.module.upload.hasClass('loading'));});it('should remove the loading class if false is passed as an argument',function(){this.module.upload.addClass('loading');this.module.loading();assert.ok(!this.module.upload.hasClass('loading'));});});describe('.authenticate(key, data)',function(){beforeEach(function(){this.fakeThen=sinon.spy();this.fakeProxy=sinon.stub(jQuery,'proxy').returns('onsuccess');this.target=sinon.stub(this.sandbox.client,'getStorageAuth');this.target.returns({then:this.fakeThen});});afterEach(function(){jQuery.proxy.restore();});it('should request authentication for the upload',function(){this.module.authenticate('test',{});assert.called(this.target);assert.calledWith(this.target,'test');});it('should register success and error callbacks',function(){this.module.authenticate('test',{});assert.called(this.fakeThen);assert.calledWith(this.fakeThen,'onsuccess',this.module._onAuthError);});it('should save the key on the data object',function(){var data={};this.module.authenticate('test',data);assert.equal(data.key,'test');});});describe('.lookupMetadata(key, data)',function(){beforeEach(function(){this.fakeThen=sinon.spy();this.fakeProxy=sinon.stub(jQuery,'proxy').returns('onsuccess');this.target=sinon.stub(this.sandbox.client,'getStorageMetadata');this.target.returns({then:this.fakeThen});});afterEach(function(){jQuery.proxy.restore();});it('should request metadata for the upload key',function(){this.module.lookupMetadata('test',{});assert.called(this.target);assert.calledWith(this.target,'test');});it('should register success and error callbacks',function(){this.module.lookupMetadata('test',{});assert.called(this.fakeThen);assert.calledWith(this.fakeThen,'onsuccess',this.module._onMetadataError);});});describe('.notify(message, type)',function(){it('should call the sandbox.notify() method',function(){var target=sinon.stub(this.sandbox,'notify');this.module.notify('this is an example message','info');assert.called(target);assert.calledWith(target,'An Error Occurred','this is an example message','info');});});describe('.generateKey(file)',function(){it('should generate a unique filename prefixed with a timestamp',function(){var now=new Date();var date=jQuery.date.toISOString(now);var clock=sinon.useFakeTimers(now.getTime());var target=this.module.generateKey('this is my file.png');assert.equal(target,date+'/this-is-my-file.png');clock.restore();});});describe('._onUploadAdd(event, data)',function(){beforeEach(function(){this.target=sinon.stub(this.module,'authenticate');sinon.stub(this.module,'generateKey').returns('stubbed');});it('should authenticate the upload if a file is provided',function(){var data={files:[{name:'my_file.jpg'}]};this.module._onUploadAdd({},data);assert.called(this.target);assert.calledWith(this.target,'stubbed',data);});it('should not authenticate the upload if no file is provided',function(){var data={files:[]};this.module._onUploadAdd({},data);assert.notCalled(this.target);});});describe('._onUploadSend()',function(){it('should display the loading spinner',function(){var target=sinon.stub(this.module,'loading');this.module._onUploadSend({},{});assert.called(target);});});describe('._onUploadDone()',function(){it('should request the metadata for the file',function(){var target=sinon.stub(this.module,'lookupMetadata');this.module._onUploadDone({},{result:{}});assert.called(target);});it('should call the fail handler if the "result" key in the data is undefined',function(){var target=sinon.stub(this.module,'_onUploadFail');this.module._onUploadDone({},{result:undefined});assert.called(target);});it('should call the fail handler if the "result" object has an "error" key',function(){var target=sinon.stub(this.module,'_onUploadFail');this.module._onUploadDone({},{result:{error:'failed'}});assert.called(target);});});describe('._onUploadComplete()',function(){it('should hide the loading spinner',function(){var target=sinon.stub(this.module,'loading');this.module._onUploadComplete({},{});assert.called(target);assert.calledWith(target,false);});});describe('._onAuthSuccess()',function(){beforeEach(function(){this.target={submit:sinon.spy()};this.response={action:'action',fields:[{name:'name',value:'value'}]};});it('should set the data url',function(){this.module._onAuthSuccess(this.target,this.response);assert.equal(this.target.url,this.response.action);});it('should set the additional form data',function(){this.module._onAuthSuccess(this.target,this.response);assert.deepEqual(this.target.formData,this.response.fields);});it('should merge the form data with the options',function(){this.module.options.form.params=[{name:'option',value:'option'}];this.module._onAuthSuccess(this.target,this.response);assert.deepEqual(this.target.formData,[{name:'option',value:'option'},{name:'name',value:'value'}]);});it('should call data.submit()',function(){this.module._onAuthSuccess(this.target,this.response);assert.called(this.target.submit);});});describe('._onMetadataSuccess()',function(){it('should publish the "resource:uploaded" event',function(){var resource={url:'http://',name:'My File'};var target=sinon.stub(this.sandbox,'publish');sinon.stub(this.sandbox.client,'convertStorageMetadataToResource').returns(resource);this.module._onMetadataSuccess();assert.called(target);assert.calledWith(target,"resource:uploaded",resource);});});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.date-helpers.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.date-helpers.spec.js new file mode 100644 index 00000000..6e3a81c0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.date-helpers.spec.js @@ -0,0 +1,37 @@ +/*globals describe beforeEach afterEach it assert sinon ckan jQuery */ +describe('jQuery.date', function () { + beforeEach(function () { + this.now = new Date(); + this.now.setTime(0); + + this.clock = sinon.useFakeTimers(this.now.getTime()); + }); + + afterEach(function () { + this.clock.restore(); + }); + + describe('jQuery.date.format()', function () { + it('should format the date based on the string provided', function () { + var target = jQuery.date.format('yyyy-MM-dd', this.now); + assert.equal(target, '1970-01-01'); + }); + + it('should use the current time if none provided', function () { + var target = jQuery.date.format('yyyy/MM/dd'); + assert.equal(target, '1970/01/01'); + }); + }); + + describe('jQuery.date.toISOString()', function () { + it('should output an ISO8601 compatible string', function () { + var target = jQuery.date.toISOString(this.now); + assert.equal(target, '1970-01-01T00:00:00.000Z'); + }); + + it('should use the current time if none provided', function () { + var target = jQuery.date.toISOString(); + assert.equal(target, '1970-01-01T00:00:00.000Z'); + }); + }); +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.date-helpers.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.date-helpers.spec.min.js new file mode 100644 index 00000000..39e200ba --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.date-helpers.spec.min.js @@ -0,0 +1 @@ +describe('jQuery.date',function(){beforeEach(function(){this.now=new Date();this.now.setTime(0);this.clock=sinon.useFakeTimers(this.now.getTime());});afterEach(function(){this.clock.restore();});describe('jQuery.date.format()',function(){it('should format the date based on the string provided',function(){var target=jQuery.date.format('yyyy-MM-dd',this.now);assert.equal(target,'1970-01-01');});it('should use the current time if none provided',function(){var target=jQuery.date.format('yyyy/MM/dd');assert.equal(target,'1970/01/01');});});describe('jQuery.date.toISOString()',function(){it('should output an ISO8601 compatible string',function(){var target=jQuery.date.toISOString(this.now);assert.equal(target,'1970-01-01T00:00:00.000Z');});it('should use the current time if none provided',function(){var target=jQuery.date.toISOString();assert.equal(target,'1970-01-01T00:00:00.000Z');});});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.form-warning.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.form-warning.spec.js new file mode 100644 index 00000000..406ec36b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.form-warning.spec.js @@ -0,0 +1,42 @@ +describe('jQuery.incompleteFormWarning()', function () { + beforeEach(function () { + this.el = jQuery('').appendTo(this.fixture); + this.el.on('submit', false); + + this.input1 = jQuery('').appendTo(this.el); + this.input2 = jQuery('').appendTo(this.el); + + this.el.incompleteFormWarning('my message'); + + this.on = sinon.stub(jQuery.fn, 'on'); + this.off = sinon.stub(jQuery.fn, 'off'); + }); + + afterEach(function () { + this.on.restore(); + this.off.restore(); + }); + + it('should bind a beforeunload event when the form changes', function () { + this.input1.val('c'); + this.el.change(); + + assert.called(this.on); + }); + + it('should unbind a beforeunload event when a form returns to the original state', function () { + this.input1.val('c'); + this.el.change(); + + this.input1.val('a'); + this.el.change(); + + assert.called(this.off); + }); + + it('should unbind the beforeunload event when the form is submitted', function () { + this.el.submit(); + + assert.called(this.off); + }); +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.form-warning.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.form-warning.spec.min.js new file mode 100644 index 00000000..5e18e1cb --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.form-warning.spec.min.js @@ -0,0 +1 @@ +describe('jQuery.incompleteFormWarning()',function(){beforeEach(function(){this.el=jQuery('').appendTo(this.fixture);this.el.on('submit',false);this.input1=jQuery('').appendTo(this.el);this.input2=jQuery('').appendTo(this.el);this.el.incompleteFormWarning('my message');this.on=sinon.stub(jQuery.fn,'on');this.off=sinon.stub(jQuery.fn,'off');});afterEach(function(){this.on.restore();this.off.restore();});it('should bind a beforeunload event when the form changes',function(){this.input1.val('c');this.el.change();assert.called(this.on);});it('should unbind a beforeunload event when a form returns to the original state',function(){this.input1.val('c');this.el.change();this.input1.val('a');this.el.change();assert.called(this.off);});it('should unbind the beforeunload event when the form is submitted',function(){this.el.submit();assert.called(this.off);});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.inherit.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.inherit.spec.js new file mode 100644 index 00000000..d84bcba9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.inherit.spec.js @@ -0,0 +1,46 @@ +/*globals describe beforeEach afterEach it assert sinon ckan jQuery */ +describe('jQuery.inherit()', function () { + beforeEach(function () { + this.MyClass = function MyClass() {}; + this.MyClass.static = function () {}; + this.MyClass.prototype.method = function () {}; + }); + + it('should create a subclass of the constructor provided', function () { + var target = new (jQuery.inherit(this.MyClass))(); + assert.isTrue(target instanceof this.MyClass); + }); + + it('should set the childs prototype object', function () { + var target = new (jQuery.inherit(this.MyClass))(); + assert.isFunction(target.method); + }); + + it('should copy over the childs static properties', function () { + var Target = jQuery.inherit(this.MyClass); + assert.isFunction(Target.static); + }); + + it('should allow instance properties to be overridden', function () { + function method() {} + + var target = new (jQuery.inherit(this.MyClass, {method: method}))(); + assert.equal(target.method, method); + }); + + it('should allow static properties to be overridden', function () { + function staticmethod() {} + + var Target = jQuery.inherit(this.MyClass, {}, {static: staticmethod}); + assert.equal(Target.static, staticmethod); + }); + + it('should allow a custom constructor to be provided', function () { + var MyConstructor = sinon.spy(); + var Target = jQuery.inherit(this.MyClass, {constructor: MyConstructor}); + + new Target(); + + sinon.assert.called(MyConstructor); + }); +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.inherit.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.inherit.spec.min.js new file mode 100644 index 00000000..6482dfe0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.inherit.spec.min.js @@ -0,0 +1,3 @@ +describe('jQuery.inherit()',function(){beforeEach(function(){this.MyClass=function MyClass(){};this.MyClass.static=function(){};this.MyClass.prototype.method=function(){};});it('should create a subclass of the constructor provided',function(){var target=new(jQuery.inherit(this.MyClass))();assert.isTrue(target instanceof this.MyClass);});it('should set the childs prototype object',function(){var target=new(jQuery.inherit(this.MyClass))();assert.isFunction(target.method);});it('should copy over the childs static properties',function(){var Target=jQuery.inherit(this.MyClass);assert.isFunction(Target.static);});it('should allow instance properties to be overridden',function(){function method(){} +var target=new(jQuery.inherit(this.MyClass,{method:method}))();assert.equal(target.method,method);});it('should allow static properties to be overridden',function(){function staticmethod(){} +var Target=jQuery.inherit(this.MyClass,{},{static:staticmethod});assert.equal(Target.static,staticmethod);});it('should allow a custom constructor to be provided',function(){var MyConstructor=sinon.spy();var Target=jQuery.inherit(this.MyClass,{constructor:MyConstructor});new Target();sinon.assert.called(MyConstructor);});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.proxy-all.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.proxy-all.spec.js new file mode 100644 index 00000000..56dd373c --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.proxy-all.spec.js @@ -0,0 +1,60 @@ +/*globals describe it beforeEach afterEach jQuery sinon assert */ +describe('jQuery.proxyAll(obj, args...)', function () { + beforeEach(function () { + this.proxy = sinon.stub(jQuery, 'proxy').returns(function proxied() {}); + this.target = { + prop1: '', + method1: function method1() {}, + method2: function method2() {}, + method3: function method3() {} + }; + + this.cloned = jQuery.extend({}, this.target); + }); + + afterEach(function () { + this.proxy.restore(); + }); + + it('should bind the methods provided to the object', function () { + jQuery.proxyAll(this.target, 'method1', 'method2'); + + assert.called(this.proxy); + + assert.calledWith(this.proxy, this.cloned.method1, this.target); + assert.calledWith(this.proxy, this.cloned.method2, this.target); + assert.neverCalledWith(this.proxy, this.cloned.method3, this.target); + }); + + it('should allow regular expressions to be provided', function () { + jQuery.proxyAll(this.target, /method[1-2]/); + + assert.called(this.proxy); + + assert.calledWith(this.proxy, this.cloned.method1, this.target); + assert.calledWith(this.proxy, this.cloned.method2, this.target); + assert.neverCalledWith(this.proxy, this.cloned.method3, this.target); + }); + + it('should skip properties that are not functions', function () { + jQuery.proxyAll(this.target, 'prop1'); + assert.notCalled(this.proxy); + }); + + it('should not bind function more than once', function () { + jQuery.proxyAll(this.target, 'method1'); + jQuery.proxyAll(this.target, 'method1'); + + assert.calledOnce(this.proxy); + }); + + it('should not bind function more than once if the method name is passed twice', function () { + jQuery.proxyAll(this.target, 'method1', 'method1'); + + assert.calledOnce(this.proxy); + }); + + it('should return the first argument', function () { + assert.equal(jQuery.proxyAll(this.target), this.target); + }); +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.proxy-all.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.proxy-all.spec.min.js new file mode 100644 index 00000000..7f25f50f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.proxy-all.spec.min.js @@ -0,0 +1 @@ +describe('jQuery.proxyAll(obj, args...)',function(){beforeEach(function(){this.proxy=sinon.stub(jQuery,'proxy').returns(function proxied(){});this.target={prop1:'',method1:function method1(){},method2:function method2(){},method3:function method3(){}};this.cloned=jQuery.extend({},this.target);});afterEach(function(){this.proxy.restore();});it('should bind the methods provided to the object',function(){jQuery.proxyAll(this.target,'method1','method2');assert.called(this.proxy);assert.calledWith(this.proxy,this.cloned.method1,this.target);assert.calledWith(this.proxy,this.cloned.method2,this.target);assert.neverCalledWith(this.proxy,this.cloned.method3,this.target);});it('should allow regular expressions to be provided',function(){jQuery.proxyAll(this.target,/method[1-2]/);assert.called(this.proxy);assert.calledWith(this.proxy,this.cloned.method1,this.target);assert.calledWith(this.proxy,this.cloned.method2,this.target);assert.neverCalledWith(this.proxy,this.cloned.method3,this.target);});it('should skip properties that are not functions',function(){jQuery.proxyAll(this.target,'prop1');assert.notCalled(this.proxy);});it('should not bind function more than once',function(){jQuery.proxyAll(this.target,'method1');jQuery.proxyAll(this.target,'method1');assert.calledOnce(this.proxy);});it('should not bind function more than once if the method name is passed twice',function(){jQuery.proxyAll(this.target,'method1','method1');assert.calledOnce(this.proxy);});it('should return the first argument',function(){assert.equal(jQuery.proxyAll(this.target),this.target);});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug-preview.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug-preview.spec.js new file mode 100644 index 00000000..2ac9ae88 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug-preview.spec.js @@ -0,0 +1,65 @@ +/*globals describe it assert jQuery*/ +describe('jQuery.fn.slugPreview()', function () { + beforeEach(function () { + this.element = jQuery('
'); + }); + + it('should return the preview element', function () { + var target = this.element.slugPreview(); + assert.ok(target.hasClass('slug-preview')); + }); + + it('should restore the stack when .end() is called', function () { + var target = this.element.slugPreview(); + assert.ok(target.end() === this.element); + }); + + it('should allow a prefix to be provided', function () { + var target = this.element.slugPreview({prefix: 'prefix'}); + assert.equal(target.find('.slug-preview-prefix').text(), 'prefix'); + }); + + it('should allow a placeholder to be provided', function () { + var target = this.element.slugPreview({placeholder: 'placeholder'}); + assert.equal(target.find('.slug-preview-value').text(), 'placeholder'); + }); + + it('should allow translations for strings to be provided', function () { + var target = this.element.slugPreview({ + i18n: {'Edit': 'translated'} + }); + assert.equal(target.find('button').text(), 'translated'); + }); + + it('should set preview value to the initial value of the input', function () { + var input = this.element.find('input').val('initial'); + var target = this.element.slugPreview(); + + assert.equal(target.find('.slug-preview-value').text(), 'initial'); + }); + + it('should update the preview value when the target input changes', function () { + var target = this.element.slugPreview(); + var input = this.element.find('input').val('initial'); + + input.val('updated').change(); + assert.equal(target.find('.slug-preview-value').text(), 'updated'); + }); + + it('should hide the original element', function () { + var target = this.element.slugPreview(); + assert.ok(this.element.css('display') === 'none'); + }); + + it('should show the original element when Edit is clicked', function () { + var target = this.element.slugPreview(); + target.find('button').click(); + assert.ok(this.element.css('display') === ''); + }); + + it('should hide the preview element when Edit is clicked', function () { + var target = this.element.slugPreview(); + target.find('button').click(); + assert.ok(target.css('display') === 'none'); + }); +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug-preview.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug-preview.spec.min.js new file mode 100644 index 00000000..1452d511 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug-preview.spec.min.js @@ -0,0 +1 @@ +describe('jQuery.fn.slugPreview()',function(){beforeEach(function(){this.element=jQuery('
');});it('should return the preview element',function(){var target=this.element.slugPreview();assert.ok(target.hasClass('slug-preview'));});it('should restore the stack when .end() is called',function(){var target=this.element.slugPreview();assert.ok(target.end()===this.element);});it('should allow a prefix to be provided',function(){var target=this.element.slugPreview({prefix:'prefix'});assert.equal(target.find('.slug-preview-prefix').text(),'prefix');});it('should allow a placeholder to be provided',function(){var target=this.element.slugPreview({placeholder:'placeholder'});assert.equal(target.find('.slug-preview-value').text(),'placeholder');});it('should allow translations for strings to be provided',function(){var target=this.element.slugPreview({i18n:{'Edit':'translated'}});assert.equal(target.find('button').text(),'translated');});it('should set preview value to the initial value of the input',function(){var input=this.element.find('input').val('initial');var target=this.element.slugPreview();assert.equal(target.find('.slug-preview-value').text(),'initial');});it('should update the preview value when the target input changes',function(){var target=this.element.slugPreview();var input=this.element.find('input').val('initial');input.val('updated').change();assert.equal(target.find('.slug-preview-value').text(),'updated');});it('should hide the original element',function(){var target=this.element.slugPreview();assert.ok(this.element.css('display')==='none');});it('should show the original element when Edit is clicked',function(){var target=this.element.slugPreview();target.find('button').click();assert.ok(this.element.css('display')==='');});it('should hide the preview element when Edit is clicked',function(){var target=this.element.slugPreview();target.find('button').click();assert.ok(target.css('display')==='none');});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug.spec.js new file mode 100644 index 00000000..cc2555f8 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug.spec.js @@ -0,0 +1,34 @@ +/*globals beforeEach describe it assert jQuery*/ +describe('jQuery.fn.slug()', function () { + beforeEach(function () { + this.input = jQuery('').slug(); + this.fixture.append(this.input); + }); + + it('should slugify and append the pressed key', function () { + var e = jQuery.Event('keypress', {charCode: 97 /* a */}); + this.input.trigger(e); + + assert.equal(this.input.val(), 'a', 'append an "a"'); + + e = jQuery.Event('keypress', {charCode: 38 /* & */}); + this.input.trigger(e); + + assert.equal(this.input.val(), 'a-', 'append an "-"'); + }); + + it('should do nothing if a non character key is pressed', function () { + var e = jQuery.Event('keypress', {charCode: 0}); + this.input.val('some other string').trigger(e); + + assert.equal(this.input.val(), 'some other string'); + }); + + it('should slugify the input contents on "blur" and "change" events', function () { + this.input.val('apples & pears').trigger(jQuery.Event('blur')); + assert.equal(this.input.val(), 'apples-pears', 'on blur'); + + this.input.val('apples & pears').trigger(jQuery.Event('change')); + assert.equal(this.input.val(), 'apples-pears', 'on change'); + }); +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug.spec.min.js new file mode 100644 index 00000000..c0825479 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.slug.spec.min.js @@ -0,0 +1 @@ +describe('jQuery.fn.slug()',function(){beforeEach(function(){this.input=jQuery('').slug();this.fixture.append(this.input);});it('should slugify and append the pressed key',function(){var e=jQuery.Event('keypress',{charCode:97});this.input.trigger(e);assert.equal(this.input.val(),'a','append an "a"');e=jQuery.Event('keypress',{charCode:38});this.input.trigger(e);assert.equal(this.input.val(),'a-','append an "-"');});it('should do nothing if a non character key is pressed',function(){var e=jQuery.Event('keypress',{charCode:0});this.input.val('some other string').trigger(e);assert.equal(this.input.val(),'some other string');});it('should slugify the input contents on "blur" and "change" events',function(){this.input.val('apples & pears').trigger(jQuery.Event('blur'));assert.equal(this.input.val(),'apples-pears','on blur');this.input.val('apples & pears').trigger(jQuery.Event('change'));assert.equal(this.input.val(),'apples-pears','on change');});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.url-helpers.spec.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.url-helpers.spec.js new file mode 100644 index 00000000..7737cac2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.url-helpers.spec.js @@ -0,0 +1,52 @@ +/*globals describe it assert jQuery*/ +describe('jQuery.url', function () { + describe('.escape()', function () { + it('should escape special characters', function () { + var target = jQuery.url.escape('&<>=?#/'); + assert.equal(target, '%26%3C%3E%3D%3F%23%2F'); + }); + + it('should convert spaces to + rather than %20', function () { + var target = jQuery.url.escape(' '); + assert.equal(target, '+'); + }); + }); + + describe('.slugify()', function () { + it('should replace spaces with hyphens', function () { + var target = jQuery.url.slugify('apples and pears'); + assert.equal(target, 'apples-and-pears'); + }); + + it('should lowecase all characters', function () { + var target = jQuery.url.slugify('APPLES AND PEARS'); + assert.equal(target, 'apples-and-pears'); + }); + + it('should convert unknown characters to hyphens', function () { + var target = jQuery.url.slugify('apples & pears'); + assert.equal(target, 'apples-pears'); + }); + + it('should nomalise hyphens', function () { + var target = jQuery.url.slugify('apples---pears'); + assert.equal(target, 'apples-pears', 'remove duplicate hyphens'); + + target = jQuery.url.slugify('--apples-pears'); + assert.equal(target, 'apples-pears', 'strip preceding hyphens'); + + target = jQuery.url.slugify('apples-pears--'); + assert.equal(target, 'apples-pears', 'strip trailing hyphens'); + }); + + it('should try and asciify unicode characters', function () { + var target = jQuery.url.slugify('éåøç'); + assert.equal(target, 'eaoc'); + }); + + it('should allow underscore characters', function() { + var target = jQuery.url.slugify('apples_pears'); + assert.equal(target, 'apples_pears'); + }); + }); +}); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.url-helpers.spec.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.url-helpers.spec.min.js new file mode 100644 index 00000000..a800dfe3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/test/spec/plugins/jquery.url-helpers.spec.min.js @@ -0,0 +1 @@ +describe('jQuery.url',function(){describe('.escape()',function(){it('should escape special characters',function(){var target=jQuery.url.escape('&<>=?#/');assert.equal(target,'%26%3C%3E%3D%3F%23%2F');});it('should convert spaces to + rather than %20',function(){var target=jQuery.url.escape(' ');assert.equal(target,'+');});});describe('.slugify()',function(){it('should replace spaces with hyphens',function(){var target=jQuery.url.slugify('apples and pears');assert.equal(target,'apples-and-pears');});it('should lowecase all characters',function(){var target=jQuery.url.slugify('APPLES AND PEARS');assert.equal(target,'apples-and-pears');});it('should convert unknown characters to hyphens',function(){var target=jQuery.url.slugify('apples & pears');assert.equal(target,'apples-pears');});it('should nomalise hyphens',function(){var target=jQuery.url.slugify('apples---pears');assert.equal(target,'apples-pears','remove duplicate hyphens');target=jQuery.url.slugify('--apples-pears');assert.equal(target,'apples-pears','strip preceding hyphens');target=jQuery.url.slugify('apples-pears--');assert.equal(target,'apples-pears','strip trailing hyphens');});it('should try and asciify unicode characters',function(){var target=jQuery.url.slugify('éåøç');assert.equal(target,'eaoc');});it('should allow underscore characters',function(){var target=jQuery.url.slugify('apples_pears');assert.equal(target,'apples_pears');});});}); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..b93a4953fff68df523aa7656497ee339d6026d64 GIT binary patch literal 20127 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1413fc609ab6f21774de0cb7e01360095584f65b GIT binary patch literal 45404 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 G`u_lkUM?>H literal 0 HcmV?d00001 diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..64539b54c3751a6d9adb44c8e3a45ba5a73b77f0 GIT binary patch literal 18028 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/js/bootstrap.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/js/bootstrap.js new file mode 100644 index 00000000..8a2e99a5 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/js/bootstrap.js @@ -0,0 +1,2377 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ + +if (typeof jQuery === 'undefined') { + throw new Error('Bootstrap\'s JavaScript requires jQuery') +} + ++function ($) { + 'use strict'; + var version = $.fn.jquery.split(' ')[0].split('.') + if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) { + throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4') + } +}(jQuery); + +/* ======================================================================== + * Bootstrap: transition.js v3.3.7 + * http://getbootstrap.com/javascript/#transitions + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) + // ============================================================ + + function transitionEnd() { + var el = document.createElement('bootstrap') + + var transEndEventNames = { + WebkitTransition : 'webkitTransitionEnd', + MozTransition : 'transitionend', + OTransition : 'oTransitionEnd otransitionend', + transition : 'transitionend' + } + + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } + } + + return false // explicit for ie8 ( ._.) + } + + // http://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false + var $el = this + $(this).one('bsTransitionEnd', function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + + if (!$.support.transition) return + + $.event.special.bsTransitionEnd = { + bindType: $.support.transition.end, + delegateType: $.support.transition.end, + handle: function (e) { + if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) + } + } + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.3.7 + * http://getbootstrap.com/javascript/#alerts + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.VERSION = '3.3.7' + + Alert.TRANSITION_DURATION = 150 + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = $(selector === '#' ? [] : selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.closest('.alert') + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + // detach from parent, fire event then clean up data + $parent.detach().trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one('bsTransitionEnd', removeElement) + .emulateTransitionEnd(Alert.TRANSITION_DURATION) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.alert + + $.fn.alert = Plugin + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.3.7 + * http://getbootstrap.com/javascript/#buttons + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + this.isLoading = false + } + + Button.VERSION = '3.3.7' + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state += 'Text' + + if (data.resetText == null) $el.data('resetText', $el[val]()) + + // push to event loop to allow forms to submit + setTimeout($.proxy(function () { + $el[val](data[state] == null ? this.options[state] : data[state]) + + if (state == 'loadingText') { + this.isLoading = true + $el.addClass(d).attr(d, d).prop(d, true) + } else if (this.isLoading) { + this.isLoading = false + $el.removeClass(d).removeAttr(d).prop(d, false) + } + }, this), 0) + } + + Button.prototype.toggle = function () { + var changed = true + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + if ($input.prop('type') == 'radio') { + if ($input.prop('checked')) changed = false + $parent.find('.active').removeClass('active') + this.$element.addClass('active') + } else if ($input.prop('type') == 'checkbox') { + if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false + this.$element.toggleClass('active') + } + $input.prop('checked', this.$element.hasClass('active')) + if (changed) $input.trigger('change') + } else { + this.$element.attr('aria-pressed', !this.$element.hasClass('active')) + this.$element.toggleClass('active') + } + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + var old = $.fn.button + + $.fn.button = Plugin + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document) + .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { + var $btn = $(e.target).closest('.btn') + Plugin.call($btn, 'toggle') + if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) { + // Prevent double click on radios, and the double selections (so cancellation) on checkboxes + e.preventDefault() + // The target component still receive the focus + if ($btn.is('input,button')) $btn.trigger('focus') + else $btn.find('input:visible,button:visible').first().trigger('focus') + } + }) + .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { + $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.3.7 + * http://getbootstrap.com/javascript/#carousel + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = null + this.sliding = null + this.interval = null + this.$active = null + this.$items = null + + this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) + + this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element + .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) + .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) + } + + Carousel.VERSION = '3.3.7' + + Carousel.TRANSITION_DURATION = 600 + + Carousel.DEFAULTS = { + interval: 5000, + pause: 'hover', + wrap: true, + keyboard: true + } + + Carousel.prototype.keydown = function (e) { + if (/input|textarea/i.test(e.target.tagName)) return + switch (e.which) { + case 37: this.prev(); break + case 39: this.next(); break + default: return + } + + e.preventDefault() + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getItemIndex = function (item) { + this.$items = item.parent().children('.item') + return this.$items.index(item || this.$active) + } + + Carousel.prototype.getItemForDirection = function (direction, active) { + var activeIndex = this.getItemIndex(active) + var willWrap = (direction == 'prev' && activeIndex === 0) + || (direction == 'next' && activeIndex == (this.$items.length - 1)) + if (willWrap && !this.options.wrap) return active + var delta = direction == 'prev' ? -1 : 1 + var itemIndex = (activeIndex + delta) % this.$items.length + return this.$items.eq(itemIndex) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || this.getItemForDirection(type, $active) + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var that = this + + if ($next.hasClass('active')) return (this.sliding = false) + + var relatedTarget = $next[0] + var slideEvent = $.Event('slide.bs.carousel', { + relatedTarget: relatedTarget, + direction: direction + }) + this.$element.trigger(slideEvent) + if (slideEvent.isDefaultPrevented()) return + + this.sliding = true + + isCycling && this.pause() + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) + $nextIndicator && $nextIndicator.addClass('active') + } + + var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" + if ($.support.transition && this.$element.hasClass('slide')) { + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one('bsTransitionEnd', function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { + that.$element.trigger(slidEvent) + }, 0) + }) + .emulateTransitionEnd(Carousel.TRANSITION_DURATION) + } else { + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger(slidEvent) + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + var old = $.fn.carousel + + $.fn.carousel = Plugin + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + var clickHandler = function (e) { + var href + var $this = $(this) + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 + if (!$target.hasClass('carousel')) return + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + Plugin.call($target, options) + + if (slideIndex) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + } + + $(document) + .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) + .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + Plugin.call($carousel, $carousel.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.3.7 + * http://getbootstrap.com/javascript/#collapse + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + +/* jshint latedef: false */ + ++function ($) { + 'use strict'; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + + '[data-toggle="collapse"][data-target="#' + element.id + '"]') + this.transitioning = null + + if (this.options.parent) { + this.$parent = this.getParent() + } else { + this.addAriaAndCollapsedClass(this.$element, this.$trigger) + } + + if (this.options.toggle) this.toggle() + } + + Collapse.VERSION = '3.3.7' + + Collapse.TRANSITION_DURATION = 350 + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var activesData + var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') + + if (actives && actives.length) { + activesData = actives.data('bs.collapse') + if (activesData && activesData.transitioning) return + } + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + if (actives && actives.length) { + Plugin.call(actives, 'hide') + activesData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing')[dimension](0) + .attr('aria-expanded', true) + + this.$trigger + .removeClass('collapsed') + .attr('aria-expanded', true) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('collapse in')[dimension]('') + this.transitioning = 0 + this.$element + .trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element[dimension](this.$element[dimension]())[0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse in') + .attr('aria-expanded', false) + + this.$trigger + .addClass('collapsed') + .attr('aria-expanded', false) + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .removeClass('collapsing') + .addClass('collapse') + .trigger('hidden.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + Collapse.prototype.getParent = function () { + return $(this.options.parent) + .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') + .each($.proxy(function (i, element) { + var $element = $(element) + this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) + }, this)) + .end() + } + + Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { + var isOpen = $element.hasClass('in') + + $element.attr('aria-expanded', isOpen) + $trigger + .toggleClass('collapsed', !isOpen) + .attr('aria-expanded', isOpen) + } + + function getTargetFromTrigger($trigger) { + var href + var target = $trigger.attr('data-target') + || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 + + return $(target) + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.collapse + + $.fn.collapse = Plugin + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { + var $this = $(this) + + if (!$this.attr('data-target')) e.preventDefault() + + var $target = getTargetFromTrigger($this) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + + Plugin.call($target, option) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.3.7 + * http://getbootstrap.com/javascript/#dropdowns + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle="dropdown"]' + var Dropdown = function (element) { + $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.VERSION = '3.3.7' + + function getParent($this) { + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = selector && $(selector) + + return $parent && $parent.length ? $parent : $this.parent() + } + + function clearMenus(e) { + if (e && e.which === 3) return + $(backdrop).remove() + $(toggle).each(function () { + var $this = $(this) + var $parent = getParent($this) + var relatedTarget = { relatedTarget: this } + + if (!$parent.hasClass('open')) return + + if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return + + $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this.attr('aria-expanded', 'false') + $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) + }) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we use a backdrop because click events don't delegate + $(document.createElement('div')) + .addClass('dropdown-backdrop') + .insertAfter($(this)) + .on('click', clearMenus) + } + + var relatedTarget = { relatedTarget: this } + $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this + .trigger('focus') + .attr('aria-expanded', 'true') + + $parent + .toggleClass('open') + .trigger($.Event('shown.bs.dropdown', relatedTarget)) + } + + return false + } + + Dropdown.prototype.keydown = function (e) { + if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return + + var $this = $(this) + + e.preventDefault() + e.stopPropagation() + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + if (!isActive && e.which != 27 || isActive && e.which == 27) { + if (e.which == 27) $parent.find(toggle).trigger('focus') + return $this.trigger('click') + } + + var desc = ' li:not(.disabled):visible a' + var $items = $parent.find('.dropdown-menu' + desc) + + if (!$items.length) return + + var index = $items.index(e.target) + + if (e.which == 38 && index > 0) index-- // up + if (e.which == 40 && index < $items.length - 1) index++ // down + if (!~index) index = 0 + + $items.eq(index).trigger('focus') + } + + + // DROPDOWN PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.dropdown') + + if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.dropdown + + $.fn.dropdown = Plugin + $.fn.dropdown.Constructor = Dropdown + + + // DROPDOWN NO CONFLICT + // ==================== + + $.fn.dropdown.noConflict = function () { + $.fn.dropdown = old + return this + } + + + // APPLY TO STANDARD DROPDOWN ELEMENTS + // =================================== + + $(document) + .on('click.bs.dropdown.data-api', clearMenus) + .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) + .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) + .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) + .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: modal.js v3.3.7 + * http://getbootstrap.com/javascript/#modals + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // MODAL CLASS DEFINITION + // ====================== + + var Modal = function (element, options) { + this.options = options + this.$body = $(document.body) + this.$element = $(element) + this.$dialog = this.$element.find('.modal-dialog') + this.$backdrop = null + this.isShown = null + this.originalBodyPad = null + this.scrollbarWidth = 0 + this.ignoreBackdropClick = false + + if (this.options.remote) { + this.$element + .find('.modal-content') + .load(this.options.remote, $.proxy(function () { + this.$element.trigger('loaded.bs.modal') + }, this)) + } + } + + Modal.VERSION = '3.3.7' + + Modal.TRANSITION_DURATION = 300 + Modal.BACKDROP_TRANSITION_DURATION = 150 + + Modal.DEFAULTS = { + backdrop: true, + keyboard: true, + show: true + } + + Modal.prototype.toggle = function (_relatedTarget) { + return this.isShown ? this.hide() : this.show(_relatedTarget) + } + + Modal.prototype.show = function (_relatedTarget) { + var that = this + var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + this.isShown = true + + this.checkScrollbar() + this.setScrollbar() + this.$body.addClass('modal-open') + + this.escape() + this.resize() + + this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + + this.$dialog.on('mousedown.dismiss.bs.modal', function () { + that.$element.one('mouseup.dismiss.bs.modal', function (e) { + if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true + }) + }) + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(that.$body) // don't move modals dom position + } + + that.$element + .show() + .scrollTop(0) + + that.adjustDialog() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + that.enforceFocus() + + var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + + transition ? + that.$dialog // wait for modal to slide in + .one('bsTransitionEnd', function () { + that.$element.trigger('focus').trigger(e) + }) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + that.$element.trigger('focus').trigger(e) + }) + } + + Modal.prototype.hide = function (e) { + if (e) e.preventDefault() + + e = $.Event('hide.bs.modal') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + this.escape() + this.resize() + + $(document).off('focusin.bs.modal') + + this.$element + .removeClass('in') + .off('click.dismiss.bs.modal') + .off('mouseup.dismiss.bs.modal') + + this.$dialog.off('mousedown.dismiss.bs.modal') + + $.support.transition && this.$element.hasClass('fade') ? + this.$element + .one('bsTransitionEnd', $.proxy(this.hideModal, this)) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + this.hideModal() + } + + Modal.prototype.enforceFocus = function () { + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function (e) { + if (document !== e.target && + this.$element[0] !== e.target && + !this.$element.has(e.target).length) { + this.$element.trigger('focus') + } + }, this)) + } + + Modal.prototype.escape = function () { + if (this.isShown && this.options.keyboard) { + this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { + e.which == 27 && this.hide() + }, this)) + } else if (!this.isShown) { + this.$element.off('keydown.dismiss.bs.modal') + } + } + + Modal.prototype.resize = function () { + if (this.isShown) { + $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) + } else { + $(window).off('resize.bs.modal') + } + } + + Modal.prototype.hideModal = function () { + var that = this + this.$element.hide() + this.backdrop(function () { + that.$body.removeClass('modal-open') + that.resetAdjustments() + that.resetScrollbar() + that.$element.trigger('hidden.bs.modal') + }) + } + + Modal.prototype.removeBackdrop = function () { + this.$backdrop && this.$backdrop.remove() + this.$backdrop = null + } + + Modal.prototype.backdrop = function (callback) { + var that = this + var animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $(document.createElement('div')) + .addClass('modal-backdrop ' + animate) + .appendTo(this.$body) + + this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { + if (this.ignoreBackdropClick) { + this.ignoreBackdropClick = false + return + } + if (e.target !== e.currentTarget) return + this.options.backdrop == 'static' + ? this.$element[0].focus() + : this.hide() + }, this)) + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + if (!callback) return + + doAnimate ? + this.$backdrop + .one('bsTransitionEnd', callback) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + var callbackRemove = function () { + that.removeBackdrop() + callback && callback() + } + $.support.transition && this.$element.hasClass('fade') ? + this.$backdrop + .one('bsTransitionEnd', callbackRemove) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callbackRemove() + + } else if (callback) { + callback() + } + } + + // these following methods are used to handle overflowing modals + + Modal.prototype.handleUpdate = function () { + this.adjustDialog() + } + + Modal.prototype.adjustDialog = function () { + var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight + + this.$element.css({ + paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', + paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' + }) + } + + Modal.prototype.resetAdjustments = function () { + this.$element.css({ + paddingLeft: '', + paddingRight: '' + }) + } + + Modal.prototype.checkScrollbar = function () { + var fullWindowWidth = window.innerWidth + if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 + var documentElementRect = document.documentElement.getBoundingClientRect() + fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) + } + this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth + this.scrollbarWidth = this.measureScrollbar() + } + + Modal.prototype.setScrollbar = function () { + var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) + this.originalBodyPad = document.body.style.paddingRight || '' + if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) + } + + Modal.prototype.resetScrollbar = function () { + this.$body.css('padding-right', this.originalBodyPad) + } + + Modal.prototype.measureScrollbar = function () { // thx walsh + var scrollDiv = document.createElement('div') + scrollDiv.className = 'modal-scrollbar-measure' + this.$body.append(scrollDiv) + var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + this.$body[0].removeChild(scrollDiv) + return scrollbarWidth + } + + + // MODAL PLUGIN DEFINITION + // ======================= + + function Plugin(option, _relatedTarget) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.modal') + var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option](_relatedTarget) + else if (options.show) data.show(_relatedTarget) + }) + } + + var old = $.fn.modal + + $.fn.modal = Plugin + $.fn.modal.Constructor = Modal + + + // MODAL NO CONFLICT + // ================= + + $.fn.modal.noConflict = function () { + $.fn.modal = old + return this + } + + + // MODAL DATA-API + // ============== + + $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { + var $this = $(this) + var href = $this.attr('href') + var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 + var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + + if ($this.is('a')) e.preventDefault() + + $target.one('show.bs.modal', function (showEvent) { + if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown + $target.one('hidden.bs.modal', function () { + $this.is(':visible') && $this.trigger('focus') + }) + }) + Plugin.call($target, option, this) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tooltip.js v3.3.7 + * http://getbootstrap.com/javascript/#tooltip + * Inspired by the original jQuery.tipsy by Jason Frame + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TOOLTIP PUBLIC CLASS DEFINITION + // =============================== + + var Tooltip = function (element, options) { + this.type = null + this.options = null + this.enabled = null + this.timeout = null + this.hoverState = null + this.$element = null + this.inState = null + + this.init('tooltip', element, options) + } + + Tooltip.VERSION = '3.3.7' + + Tooltip.TRANSITION_DURATION = 150 + + Tooltip.DEFAULTS = { + animation: true, + placement: 'top', + selector: false, + template: '', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + container: false, + viewport: { + selector: 'body', + padding: 0 + } + } + + Tooltip.prototype.init = function (type, element, options) { + this.enabled = true + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) + this.inState = { click: false, hover: false, focus: false } + + if (this.$element[0] instanceof document.constructor && !this.options.selector) { + throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') + } + + var triggers = this.options.trigger.split(' ') + + for (var i = triggers.length; i--;) { + var trigger = triggers[i] + + if (trigger == 'click') { + this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) + } else if (trigger != 'manual') { + var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' + var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' + + this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) + this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) + } + } + + this.options.selector ? + (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : + this.fixTitle() + } + + Tooltip.prototype.getDefaults = function () { + return Tooltip.DEFAULTS + } + + Tooltip.prototype.getOptions = function (options) { + options = $.extend({}, this.getDefaults(), this.$element.data(), options) + + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay, + hide: options.delay + } + } + + return options + } + + Tooltip.prototype.getDelegateOptions = function () { + var options = {} + var defaults = this.getDefaults() + + this._options && $.each(this._options, function (key, value) { + if (defaults[key] != value) options[key] = value + }) + + return options + } + + Tooltip.prototype.enter = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true + } + + if (self.tip().hasClass('in') || self.hoverState == 'in') { + self.hoverState = 'in' + return + } + + clearTimeout(self.timeout) + + self.hoverState = 'in' + + if (!self.options.delay || !self.options.delay.show) return self.show() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) + } + + Tooltip.prototype.isInStateTrue = function () { + for (var key in this.inState) { + if (this.inState[key]) return true + } + + return false + } + + Tooltip.prototype.leave = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false + } + + if (self.isInStateTrue()) return + + clearTimeout(self.timeout) + + self.hoverState = 'out' + + if (!self.options.delay || !self.options.delay.hide) return self.hide() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + + Tooltip.prototype.show = function () { + var e = $.Event('show.bs.' + this.type) + + if (this.hasContent() && this.enabled) { + this.$element.trigger(e) + + var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) + if (e.isDefaultPrevented() || !inDom) return + var that = this + + var $tip = this.tip() + + var tipId = this.getUID(this.type) + + this.setContent() + $tip.attr('id', tipId) + this.$element.attr('aria-describedby', tipId) + + if (this.options.animation) $tip.addClass('fade') + + var placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + + var autoToken = /\s?auto?\s?/i + var autoPlace = autoToken.test(placement) + if (autoPlace) placement = placement.replace(autoToken, '') || 'top' + + $tip + .detach() + .css({ top: 0, left: 0, display: 'block' }) + .addClass(placement) + .data('bs.' + this.type, this) + + this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) + this.$element.trigger('inserted.bs.' + this.type) + + var pos = this.getPosition() + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (autoPlace) { + var orgPlacement = placement + var viewportDim = this.getPosition(this.$viewport) + + placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : + placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : + placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : + placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : + placement + + $tip + .removeClass(orgPlacement) + .addClass(placement) + } + + var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) + + this.applyPlacement(calculatedOffset, placement) + + var complete = function () { + var prevHoverState = that.hoverState + that.$element.trigger('shown.bs.' + that.type) + that.hoverState = null + + if (prevHoverState == 'out') that.leave(that) + } + + $.support.transition && this.$tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + } + } + + Tooltip.prototype.applyPlacement = function (offset, placement) { + var $tip = this.tip() + var width = $tip[0].offsetWidth + var height = $tip[0].offsetHeight + + // manually read margins because getBoundingClientRect includes difference + var marginTop = parseInt($tip.css('margin-top'), 10) + var marginLeft = parseInt($tip.css('margin-left'), 10) + + // we must check for NaN for ie 8/9 + if (isNaN(marginTop)) marginTop = 0 + if (isNaN(marginLeft)) marginLeft = 0 + + offset.top += marginTop + offset.left += marginLeft + + // $.fn.offset doesn't round pixel values + // so we use setOffset directly with our own function B-0 + $.offset.setOffset($tip[0], $.extend({ + using: function (props) { + $tip.css({ + top: Math.round(props.top), + left: Math.round(props.left) + }) + } + }, offset), 0) + + $tip.addClass('in') + + // check to see if placing tip in new offset caused the tip to resize itself + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (placement == 'top' && actualHeight != height) { + offset.top = offset.top + height - actualHeight + } + + var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) + + if (delta.left) offset.left += delta.left + else offset.top += delta.top + + var isVertical = /top|bottom/.test(placement) + var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight + var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' + + $tip.offset(offset) + this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) + } + + Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { + this.arrow() + .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') + .css(isVertical ? 'top' : 'left', '') + } + + Tooltip.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + + $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) + $tip.removeClass('fade in top bottom left right') + } + + Tooltip.prototype.hide = function (callback) { + var that = this + var $tip = $(this.$tip) + var e = $.Event('hide.bs.' + this.type) + + function complete() { + if (that.hoverState != 'in') $tip.detach() + if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary. + that.$element + .removeAttr('aria-describedby') + .trigger('hidden.bs.' + that.type) + } + callback && callback() + } + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + $tip.removeClass('in') + + $.support.transition && $tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + + this.hoverState = null + + return this + } + + Tooltip.prototype.fixTitle = function () { + var $e = this.$element + if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { + $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') + } + } + + Tooltip.prototype.hasContent = function () { + return this.getTitle() + } + + Tooltip.prototype.getPosition = function ($element) { + $element = $element || this.$element + + var el = $element[0] + var isBody = el.tagName == 'BODY' + + var elRect = el.getBoundingClientRect() + if (elRect.width == null) { + // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 + elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) + } + var isSvg = window.SVGElement && el instanceof window.SVGElement + // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3. + // See https://github.com/twbs/bootstrap/issues/20280 + var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset()) + var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } + var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null + + return $.extend({}, elRect, scroll, outerDims, elOffset) + } + + Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { + return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : + /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } + + } + + Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { + var delta = { top: 0, left: 0 } + if (!this.$viewport) return delta + + var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 + var viewportDimensions = this.getPosition(this.$viewport) + + if (/right|left/.test(placement)) { + var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll + var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight + if (topEdgeOffset < viewportDimensions.top) { // top overflow + delta.top = viewportDimensions.top - topEdgeOffset + } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow + delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset + } + } else { + var leftEdgeOffset = pos.left - viewportPadding + var rightEdgeOffset = pos.left + viewportPadding + actualWidth + if (leftEdgeOffset < viewportDimensions.left) { // left overflow + delta.left = viewportDimensions.left - leftEdgeOffset + } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow + delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset + } + } + + return delta + } + + Tooltip.prototype.getTitle = function () { + var title + var $e = this.$element + var o = this.options + + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) + + return title + } + + Tooltip.prototype.getUID = function (prefix) { + do prefix += ~~(Math.random() * 1000000) + while (document.getElementById(prefix)) + return prefix + } + + Tooltip.prototype.tip = function () { + if (!this.$tip) { + this.$tip = $(this.options.template) + if (this.$tip.length != 1) { + throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') + } + } + return this.$tip + } + + Tooltip.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) + } + + Tooltip.prototype.enable = function () { + this.enabled = true + } + + Tooltip.prototype.disable = function () { + this.enabled = false + } + + Tooltip.prototype.toggleEnabled = function () { + this.enabled = !this.enabled + } + + Tooltip.prototype.toggle = function (e) { + var self = this + if (e) { + self = $(e.currentTarget).data('bs.' + this.type) + if (!self) { + self = new this.constructor(e.currentTarget, this.getDelegateOptions()) + $(e.currentTarget).data('bs.' + this.type, self) + } + } + + if (e) { + self.inState.click = !self.inState.click + if (self.isInStateTrue()) self.enter(self) + else self.leave(self) + } else { + self.tip().hasClass('in') ? self.leave(self) : self.enter(self) + } + } + + Tooltip.prototype.destroy = function () { + var that = this + clearTimeout(this.timeout) + this.hide(function () { + that.$element.off('.' + that.type).removeData('bs.' + that.type) + if (that.$tip) { + that.$tip.detach() + } + that.$tip = null + that.$arrow = null + that.$viewport = null + that.$element = null + }) + } + + + // TOOLTIP PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tooltip') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tooltip + + $.fn.tooltip = Plugin + $.fn.tooltip.Constructor = Tooltip + + + // TOOLTIP NO CONFLICT + // =================== + + $.fn.tooltip.noConflict = function () { + $.fn.tooltip = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: popover.js v3.3.7 + * http://getbootstrap.com/javascript/#popovers + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // POPOVER PUBLIC CLASS DEFINITION + // =============================== + + var Popover = function (element, options) { + this.init('popover', element, options) + } + + if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') + + Popover.VERSION = '3.3.7' + + Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { + placement: 'right', + trigger: 'click', + content: '', + template: '' + }) + + + // NOTE: POPOVER EXTENDS tooltip.js + // ================================ + + Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) + + Popover.prototype.constructor = Popover + + Popover.prototype.getDefaults = function () { + return Popover.DEFAULTS + } + + Popover.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + var content = this.getContent() + + $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) + $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events + this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' + ](content) + + $tip.removeClass('fade top bottom left right in') + + // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do + // this manually by checking the contents. + if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() + } + + Popover.prototype.hasContent = function () { + return this.getTitle() || this.getContent() + } + + Popover.prototype.getContent = function () { + var $e = this.$element + var o = this.options + + return $e.attr('data-content') + || (typeof o.content == 'function' ? + o.content.call($e[0]) : + o.content) + } + + Popover.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.arrow')) + } + + + // POPOVER PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.popover') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.popover', (data = new Popover(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.popover + + $.fn.popover = Plugin + $.fn.popover.Constructor = Popover + + + // POPOVER NO CONFLICT + // =================== + + $.fn.popover.noConflict = function () { + $.fn.popover = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: scrollspy.js v3.3.7 + * http://getbootstrap.com/javascript/#scrollspy + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // SCROLLSPY CLASS DEFINITION + // ========================== + + function ScrollSpy(element, options) { + this.$body = $(document.body) + this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) + this.options = $.extend({}, ScrollSpy.DEFAULTS, options) + this.selector = (this.options.target || '') + ' .nav li > a' + this.offsets = [] + this.targets = [] + this.activeTarget = null + this.scrollHeight = 0 + + this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) + this.refresh() + this.process() + } + + ScrollSpy.VERSION = '3.3.7' + + ScrollSpy.DEFAULTS = { + offset: 10 + } + + ScrollSpy.prototype.getScrollHeight = function () { + return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) + } + + ScrollSpy.prototype.refresh = function () { + var that = this + var offsetMethod = 'offset' + var offsetBase = 0 + + this.offsets = [] + this.targets = [] + this.scrollHeight = this.getScrollHeight() + + if (!$.isWindow(this.$scrollElement[0])) { + offsetMethod = 'position' + offsetBase = this.$scrollElement.scrollTop() + } + + this.$body + .find(this.selector) + .map(function () { + var $el = $(this) + var href = $el.data('target') || $el.attr('href') + var $href = /^#./.test(href) && $(href) + + return ($href + && $href.length + && $href.is(':visible') + && [[$href[offsetMethod]().top + offsetBase, href]]) || null + }) + .sort(function (a, b) { return a[0] - b[0] }) + .each(function () { + that.offsets.push(this[0]) + that.targets.push(this[1]) + }) + } + + ScrollSpy.prototype.process = function () { + var scrollTop = this.$scrollElement.scrollTop() + this.options.offset + var scrollHeight = this.getScrollHeight() + var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() + var offsets = this.offsets + var targets = this.targets + var activeTarget = this.activeTarget + var i + + if (this.scrollHeight != scrollHeight) { + this.refresh() + } + + if (scrollTop >= maxScroll) { + return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) + } + + if (activeTarget && scrollTop < offsets[0]) { + this.activeTarget = null + return this.clear() + } + + for (i = offsets.length; i--;) { + activeTarget != targets[i] + && scrollTop >= offsets[i] + && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) + && this.activate(targets[i]) + } + } + + ScrollSpy.prototype.activate = function (target) { + this.activeTarget = target + + this.clear() + + var selector = this.selector + + '[data-target="' + target + '"],' + + this.selector + '[href="' + target + '"]' + + var active = $(selector) + .parents('li') + .addClass('active') + + if (active.parent('.dropdown-menu').length) { + active = active + .closest('li.dropdown') + .addClass('active') + } + + active.trigger('activate.bs.scrollspy') + } + + ScrollSpy.prototype.clear = function () { + $(this.selector) + .parentsUntil(this.options.target, '.active') + .removeClass('active') + } + + + // SCROLLSPY PLUGIN DEFINITION + // =========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.scrollspy') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.scrollspy + + $.fn.scrollspy = Plugin + $.fn.scrollspy.Constructor = ScrollSpy + + + // SCROLLSPY NO CONFLICT + // ===================== + + $.fn.scrollspy.noConflict = function () { + $.fn.scrollspy = old + return this + } + + + // SCROLLSPY DATA-API + // ================== + + $(window).on('load.bs.scrollspy.data-api', function () { + $('[data-spy="scroll"]').each(function () { + var $spy = $(this) + Plugin.call($spy, $spy.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tab.js v3.3.7 + * http://getbootstrap.com/javascript/#tabs + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TAB CLASS DEFINITION + // ==================== + + var Tab = function (element) { + // jscs:disable requireDollarBeforejQueryAssignment + this.element = $(element) + // jscs:enable requireDollarBeforejQueryAssignment + } + + Tab.VERSION = '3.3.7' + + Tab.TRANSITION_DURATION = 150 + + Tab.prototype.show = function () { + var $this = this.element + var $ul = $this.closest('ul:not(.dropdown-menu)') + var selector = $this.data('target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + if ($this.parent('li').hasClass('active')) return + + var $previous = $ul.find('.active:last a') + var hideEvent = $.Event('hide.bs.tab', { + relatedTarget: $this[0] + }) + var showEvent = $.Event('show.bs.tab', { + relatedTarget: $previous[0] + }) + + $previous.trigger(hideEvent) + $this.trigger(showEvent) + + if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return + + var $target = $(selector) + + this.activate($this.closest('li'), $ul) + this.activate($target, $target.parent(), function () { + $previous.trigger({ + type: 'hidden.bs.tab', + relatedTarget: $this[0] + }) + $this.trigger({ + type: 'shown.bs.tab', + relatedTarget: $previous[0] + }) + }) + } + + Tab.prototype.activate = function (element, container, callback) { + var $active = container.find('> .active') + var transition = callback + && $.support.transition + && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) + + function next() { + $active + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', false) + + element + .addClass('active') + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + + if (transition) { + element[0].offsetWidth // reflow for transition + element.addClass('in') + } else { + element.removeClass('fade') + } + + if (element.parent('.dropdown-menu').length) { + element + .closest('li.dropdown') + .addClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + } + + callback && callback() + } + + $active.length && transition ? + $active + .one('bsTransitionEnd', next) + .emulateTransitionEnd(Tab.TRANSITION_DURATION) : + next() + + $active.removeClass('in') + } + + + // TAB PLUGIN DEFINITION + // ===================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tab') + + if (!data) $this.data('bs.tab', (data = new Tab(this))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tab + + $.fn.tab = Plugin + $.fn.tab.Constructor = Tab + + + // TAB NO CONFLICT + // =============== + + $.fn.tab.noConflict = function () { + $.fn.tab = old + return this + } + + + // TAB DATA-API + // ============ + + var clickHandler = function (e) { + e.preventDefault() + Plugin.call($(this), 'show') + } + + $(document) + .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) + .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: affix.js v3.3.7 + * http://getbootstrap.com/javascript/#affix + * ======================================================================== + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // AFFIX CLASS DEFINITION + // ====================== + + var Affix = function (element, options) { + this.options = $.extend({}, Affix.DEFAULTS, options) + + this.$target = $(this.options.target) + .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) + .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) + + this.$element = $(element) + this.affixed = null + this.unpin = null + this.pinnedOffset = null + + this.checkPosition() + } + + Affix.VERSION = '3.3.7' + + Affix.RESET = 'affix affix-top affix-bottom' + + Affix.DEFAULTS = { + offset: 0, + target: window + } + + Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + var targetHeight = this.$target.height() + + if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false + + if (this.affixed == 'bottom') { + if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' + return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' + } + + var initializing = this.affixed == null + var colliderTop = initializing ? scrollTop : position.top + var colliderHeight = initializing ? targetHeight : height + + if (offsetTop != null && scrollTop <= offsetTop) return 'top' + if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' + + return false + } + + Affix.prototype.getPinnedOffset = function () { + if (this.pinnedOffset) return this.pinnedOffset + this.$element.removeClass(Affix.RESET).addClass('affix') + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + return (this.pinnedOffset = position.top - scrollTop) + } + + Affix.prototype.checkPositionWithEventLoop = function () { + setTimeout($.proxy(this.checkPosition, this), 1) + } + + Affix.prototype.checkPosition = function () { + if (!this.$element.is(':visible')) return + + var height = this.$element.height() + var offset = this.options.offset + var offsetTop = offset.top + var offsetBottom = offset.bottom + var scrollHeight = Math.max($(document).height(), $(document.body).height()) + + if (typeof offset != 'object') offsetBottom = offsetTop = offset + if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) + if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) + + var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) + + if (this.affixed != affix) { + if (this.unpin != null) this.$element.css('top', '') + + var affixType = 'affix' + (affix ? '-' + affix : '') + var e = $.Event(affixType + '.bs.affix') + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + this.affixed = affix + this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null + + this.$element + .removeClass(Affix.RESET) + .addClass(affixType) + .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') + } + + if (affix == 'bottom') { + this.$element.offset({ + top: scrollHeight - height - offsetBottom + }) + } + } + + + // AFFIX PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.affix') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.affix', (data = new Affix(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.affix + + $.fn.affix = Plugin + $.fn.affix.Constructor = Affix + + + // AFFIX NO CONFLICT + // ================= + + $.fn.affix.noConflict = function () { + $.fn.affix = old + return this + } + + + // AFFIX DATA-API + // ============== + + $(window).on('load', function () { + $('[data-spy="affix"]').each(function () { + var $spy = $(this) + var data = $spy.data() + + data.offset = data.offset || {} + + if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom + if (data.offsetTop != null) data.offset.top = data.offsetTop + + Plugin.call($spy, data) + }) + }) + +}(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/js/bootstrap.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/js/bootstrap.min.js new file mode 100644 index 00000000..9bcd2fcc --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/alerts.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/alerts.less new file mode 100644 index 00000000..c4199db9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/alerts.less @@ -0,0 +1,73 @@ +// +// Alerts +// -------------------------------------------------- + + +// Base styles +// ------------------------- + +.alert { + padding: @alert-padding; + margin-bottom: @line-height-computed; + border: 1px solid transparent; + border-radius: @alert-border-radius; + + // Headings for larger alerts + h4 { + margin-top: 0; + // Specified for the h4 to prevent conflicts of changing @headings-color + color: inherit; + } + + // Provide class for links that match alerts + .alert-link { + font-weight: @alert-link-font-weight; + } + + // Improve alignment and spacing of inner content + > p, + > ul { + margin-bottom: 0; + } + + > p + p { + margin-top: 5px; + } +} + +// Dismissible alerts +// +// Expand the right padding and account for the close button's positioning. + +.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0. +.alert-dismissible { + padding-right: (@alert-padding + 20); + + // Adjust close link position + .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; + } +} + +// Alternate styles +// +// Generate contextual modifier classes for colorizing the alert. + +.alert-success { + .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text); +} + +.alert-info { + .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text); +} + +.alert-warning { + .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text); +} + +.alert-danger { + .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text); +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/badges.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/badges.less new file mode 100644 index 00000000..6ee16dca --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/badges.less @@ -0,0 +1,66 @@ +// +// Badges +// -------------------------------------------------- + + +// Base class +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: @font-size-small; + font-weight: @badge-font-weight; + color: @badge-color; + line-height: @badge-line-height; + vertical-align: middle; + white-space: nowrap; + text-align: center; + background-color: @badge-bg; + border-radius: @badge-border-radius; + + // Empty badges collapse automatically (not available in IE8) + &:empty { + display: none; + } + + // Quick fix for badges in buttons + .btn & { + position: relative; + top: -1px; + } + + .btn-xs &, + .btn-group-xs > .btn & { + top: 0; + padding: 1px 5px; + } + + // Hover state, but only for links + a& { + &:hover, + &:focus { + color: @badge-link-hover-color; + text-decoration: none; + cursor: pointer; + } + } + + // Account for badges in navs + .list-group-item.active > &, + .nav-pills > .active > a > & { + color: @badge-active-color; + background-color: @badge-active-bg; + } + + .list-group-item > & { + float: right; + } + + .list-group-item > & + & { + margin-right: 5px; + } + + .nav-pills > li > a > & { + margin-left: 3px; + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/bootstrap.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/bootstrap.less new file mode 100644 index 00000000..f0aa08f3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/bootstrap.less @@ -0,0 +1,56 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +// Core variables and mixins +@import "variables.less"; +@import "mixins.less"; + +// Reset and dependencies +@import "normalize.less"; +@import "print.less"; +@import "glyphicons.less"; + +// Core CSS +@import "scaffolding.less"; +@import "type.less"; +@import "code.less"; +@import "grid.less"; +@import "tables.less"; +@import "forms.less"; +@import "buttons.less"; + +// Components +@import "component-animations.less"; +@import "dropdowns.less"; +@import "button-groups.less"; +@import "input-groups.less"; +@import "navs.less"; +@import "navbar.less"; +@import "breadcrumbs.less"; +@import "pagination.less"; +@import "pager.less"; +@import "labels.less"; +@import "badges.less"; +@import "jumbotron.less"; +@import "thumbnails.less"; +@import "alerts.less"; +@import "progress-bars.less"; +@import "media.less"; +@import "list-group.less"; +@import "panels.less"; +@import "responsive-embed.less"; +@import "wells.less"; +@import "close.less"; + +// Components w/ JavaScript +@import "modals.less"; +@import "tooltip.less"; +@import "popovers.less"; +@import "carousel.less"; + +// Utility classes +@import "utilities.less"; +@import "responsive-utilities.less"; diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/breadcrumbs.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/breadcrumbs.less new file mode 100644 index 00000000..cb01d503 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/breadcrumbs.less @@ -0,0 +1,26 @@ +// +// Breadcrumbs +// -------------------------------------------------- + + +.breadcrumb { + padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal; + margin-bottom: @line-height-computed; + list-style: none; + background-color: @breadcrumb-bg; + border-radius: @border-radius-base; + + > li { + display: inline-block; + + + li:before { + content: "@{breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space + padding: 0 5px; + color: @breadcrumb-color; + } + } + + > .active { + color: @breadcrumb-active-color; + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/button-groups.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/button-groups.less new file mode 100644 index 00000000..16db0c61 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/button-groups.less @@ -0,0 +1,244 @@ +// +// Button groups +// -------------------------------------------------- + +// Make the div behave like a button +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; // match .btn alignment given font-size hack above + > .btn { + position: relative; + float: left; + // Bring the "active" button to the front + &:hover, + &:focus, + &:active, + &.active { + z-index: 2; + } + } +} + +// Prevent double borders when buttons are next to each other +.btn-group { + .btn + .btn, + .btn + .btn-group, + .btn-group + .btn, + .btn-group + .btn-group { + margin-left: -1px; + } +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + margin-left: -5px; // Offset the first child's margin + &:extend(.clearfix all); + + .btn, + .btn-group, + .input-group { + float: left; + } + > .btn, + > .btn-group, + > .input-group { + margin-left: 5px; + } +} + +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match +.btn-group > .btn:first-child { + margin-left: 0; + &:not(:last-child):not(.dropdown-toggle) { + .border-right-radius(0); + } +} +// Need .dropdown-toggle since :last-child doesn't apply, given that a .dropdown-menu is used immediately after it +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + .border-left-radius(0); +} + +// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group) +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) { + > .btn:last-child, + > .dropdown-toggle { + .border-right-radius(0); + } +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + .border-left-radius(0); +} + +// On active and open, don't show outline +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + + +// Sizing +// +// Remix the default button sizing classes into new ones for easier manipulation. + +.btn-group-xs > .btn { &:extend(.btn-xs); } +.btn-group-sm > .btn { &:extend(.btn-sm); } +.btn-group-lg > .btn { &:extend(.btn-lg); } + + +// Split button dropdowns +// ---------------------- + +// Give the line between buttons some depth +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; +} + +// The clickable button for toggling the menu +// Remove the gradient and set the same inset shadow as the :active state +.btn-group.open .dropdown-toggle { + .box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + + // Show no shadow for `.btn-link` since it has no other button styles. + &.btn-link { + .box-shadow(none); + } +} + + +// Reposition the caret +.btn .caret { + margin-left: 0; +} +// Carets in other button sizes +.btn-lg .caret { + border-width: @caret-width-large @caret-width-large 0; + border-bottom-width: 0; +} +// Upside down carets for .dropup +.dropup .btn-lg .caret { + border-width: 0 @caret-width-large @caret-width-large; +} + + +// Vertical button groups +// ---------------------- + +.btn-group-vertical { + > .btn, + > .btn-group, + > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; + } + + // Clear floats so dropdown menus can be properly placed + > .btn-group { + &:extend(.clearfix all); + > .btn { + float: none; + } + } + + > .btn + .btn, + > .btn + .btn-group, + > .btn-group + .btn, + > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; + } +} + +.btn-group-vertical > .btn { + &:not(:first-child):not(:last-child) { + border-radius: 0; + } + &:first-child:not(:last-child) { + .border-top-radius(@btn-border-radius-base); + .border-bottom-radius(0); + } + &:last-child:not(:first-child) { + .border-top-radius(0); + .border-bottom-radius(@btn-border-radius-base); + } +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) { + > .btn:last-child, + > .dropdown-toggle { + .border-bottom-radius(0); + } +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + .border-top-radius(0); +} + + +// Justified button groups +// ---------------------- + +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; + > .btn, + > .btn-group { + float: none; + display: table-cell; + width: 1%; + } + > .btn-group .btn { + width: 100%; + } + + > .btn-group .dropdown-menu { + left: auto; + } +} + + +// Checkbox and radio options +// +// In order to support the browser's form validation feedback, powered by the +// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use +// `display: none;` or `visibility: hidden;` as that also hides the popover. +// Simply visually hiding the inputs via `opacity` would leave them clickable in +// certain cases which is prevented by using `clip` and `pointer-events`. +// This way, we ensure a DOM element is visible to position the popover from. +// +// See https://github.com/twbs/bootstrap/pull/12794 and +// https://github.com/twbs/bootstrap/pull/14559 for more information. + +[data-toggle="buttons"] { + > .btn, + > .btn-group > .btn { + input[type="radio"], + input[type="checkbox"] { + position: absolute; + clip: rect(0,0,0,0); + pointer-events: none; + } + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/buttons.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/buttons.less new file mode 100644 index 00000000..9cbb8f41 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/buttons.less @@ -0,0 +1,166 @@ +// +// Buttons +// -------------------------------------------------- + + +// Base styles +// -------------------------------------------------- + +.btn { + display: inline-block; + margin-bottom: 0; // For input.btn + font-weight: @btn-font-weight; + text-align: center; + vertical-align: middle; + touch-action: manipulation; + cursor: pointer; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid transparent; + white-space: nowrap; + .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @btn-border-radius-base); + .user-select(none); + + &, + &:active, + &.active { + &:focus, + &.focus { + .tab-focus(); + } + } + + &:hover, + &:focus, + &.focus { + color: @btn-default-color; + text-decoration: none; + } + + &:active, + &.active { + outline: 0; + background-image: none; + .box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + cursor: @cursor-disabled; + .opacity(.65); + .box-shadow(none); + } + + a& { + &.disabled, + fieldset[disabled] & { + pointer-events: none; // Future-proof disabling of clicks on `` elements + } + } +} + + +// Alternate buttons +// -------------------------------------------------- + +.btn-default { + .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border); +} +.btn-primary { + .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border); +} +// Success appears as green +.btn-success { + .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border); +} +// Info appears as blue-green +.btn-info { + .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border); +} +// Warning appears as orange +.btn-warning { + .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border); +} +// Danger and error appear as red +.btn-danger { + .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border); +} + + +// Link buttons +// ------------------------- + +// Make a button look and behave like a link +.btn-link { + color: @link-color; + font-weight: normal; + border-radius: 0; + + &, + &:active, + &.active, + &[disabled], + fieldset[disabled] & { + background-color: transparent; + .box-shadow(none); + } + &, + &:hover, + &:focus, + &:active { + border-color: transparent; + } + &:hover, + &:focus { + color: @link-hover-color; + text-decoration: @link-hover-decoration; + background-color: transparent; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: @btn-link-disabled-color; + text-decoration: none; + } + } +} + + +// Button Sizes +// -------------------------------------------------- + +.btn-lg { + // line-height: ensure even-numbered height of button next to large input + .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @btn-border-radius-large); +} +.btn-sm { + // line-height: ensure proper height of button next to small input + .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small); +} +.btn-xs { + .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small); +} + + +// Block button +// -------------------------------------------------- + +.btn-block { + display: block; + width: 100%; +} + +// Vertically space out multiple block buttons +.btn-block + .btn-block { + margin-top: 5px; +} + +// Specificity overrides +input[type="submit"], +input[type="reset"], +input[type="button"] { + &.btn-block { + width: 100%; + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/carousel.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/carousel.less new file mode 100644 index 00000000..252011e9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/carousel.less @@ -0,0 +1,270 @@ +// +// Carousel +// -------------------------------------------------- + + +// Wrapper for the slide container and indicators +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + overflow: hidden; + width: 100%; + + > .item { + display: none; + position: relative; + .transition(.6s ease-in-out left); + + // Account for jankitude on images + > img, + > a > img { + &:extend(.img-responsive); + line-height: 1; + } + + // WebKit CSS3 transforms for supported devices + @media all and (transform-3d), (-webkit-transform-3d) { + .transition-transform(~'0.6s ease-in-out'); + .backface-visibility(~'hidden'); + .perspective(1000px); + + &.next, + &.active.right { + .translate3d(100%, 0, 0); + left: 0; + } + &.prev, + &.active.left { + .translate3d(-100%, 0, 0); + left: 0; + } + &.next.left, + &.prev.right, + &.active { + .translate3d(0, 0, 0); + left: 0; + } + } + } + + > .active, + > .next, + > .prev { + display: block; + } + + > .active { + left: 0; + } + + > .next, + > .prev { + position: absolute; + top: 0; + width: 100%; + } + + > .next { + left: 100%; + } + > .prev { + left: -100%; + } + > .next.left, + > .prev.right { + left: 0; + } + + > .active.left { + left: -100%; + } + > .active.right { + left: 100%; + } + +} + +// Left/right controls for nav +// --------------------------- + +.carousel-control { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: @carousel-control-width; + .opacity(@carousel-control-opacity); + font-size: @carousel-control-font-size; + color: @carousel-control-color; + text-align: center; + text-shadow: @carousel-text-shadow; + background-color: rgba(0, 0, 0, 0); // Fix IE9 click-thru bug + // We can't have this transition here because WebKit cancels the carousel + // animation if you trip this while in the middle of another animation. + + // Set gradients for backgrounds + &.left { + #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001)); + } + &.right { + left: auto; + right: 0; + #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5)); + } + + // Hover/focus state + &:hover, + &:focus { + outline: 0; + color: @carousel-control-color; + text-decoration: none; + .opacity(.9); + } + + // Toggles + .icon-prev, + .icon-next, + .glyphicon-chevron-left, + .glyphicon-chevron-right { + position: absolute; + top: 50%; + margin-top: -10px; + z-index: 5; + display: inline-block; + } + .icon-prev, + .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; + } + .icon-next, + .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; + } + .icon-prev, + .icon-next { + width: 20px; + height: 20px; + line-height: 1; + font-family: serif; + } + + + .icon-prev { + &:before { + content: '\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039) + } + } + .icon-next { + &:before { + content: '\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A) + } + } +} + +// Optional indicator pips +// +// Add an unordered list with the following class and add a list item for each +// slide your carousel holds. + +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + margin-left: -30%; + padding-left: 0; + list-style: none; + text-align: center; + + li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + border: 1px solid @carousel-indicator-border-color; + border-radius: 10px; + cursor: pointer; + + // IE8-9 hack for event handling + // + // Internet Explorer 8-9 does not support clicks on elements without a set + // `background-color`. We cannot use `filter` since that's not viewed as a + // background color by the browser. Thus, a hack is needed. + // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer + // + // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we + // set alpha transparency for the best results possible. + background-color: #000 \9; // IE8 + background-color: rgba(0,0,0,0); // IE9 + } + .active { + margin: 0; + width: 12px; + height: 12px; + background-color: @carousel-indicator-active-bg; + } +} + +// Optional captions +// ----------------------------- +// Hidden by default for smaller viewports +.carousel-caption { + position: absolute; + left: 15%; + right: 15%; + bottom: 20px; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: @carousel-caption-color; + text-align: center; + text-shadow: @carousel-text-shadow; + & .btn { + text-shadow: none; // No shadow for button elements in carousel-caption + } +} + + +// Scale up controls for tablets and up +@media screen and (min-width: @screen-sm-min) { + + // Scale up the controls a smidge + .carousel-control { + .glyphicon-chevron-left, + .glyphicon-chevron-right, + .icon-prev, + .icon-next { + width: (@carousel-control-font-size * 1.5); + height: (@carousel-control-font-size * 1.5); + margin-top: (@carousel-control-font-size / -2); + font-size: (@carousel-control-font-size * 1.5); + } + .glyphicon-chevron-left, + .icon-prev { + margin-left: (@carousel-control-font-size / -2); + } + .glyphicon-chevron-right, + .icon-next { + margin-right: (@carousel-control-font-size / -2); + } + } + + // Show and left align the captions + .carousel-caption { + left: 20%; + right: 20%; + padding-bottom: 30px; + } + + // Move up the indicators + .carousel-indicators { + bottom: 20px; + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/close.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/close.less new file mode 100644 index 00000000..6d5bfe08 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/close.less @@ -0,0 +1,34 @@ +// +// Close icons +// -------------------------------------------------- + + +.close { + float: right; + font-size: (@font-size-base * 1.5); + font-weight: @close-font-weight; + line-height: 1; + color: @close-color; + text-shadow: @close-text-shadow; + .opacity(.2); + + &:hover, + &:focus { + color: @close-color; + text-decoration: none; + cursor: pointer; + .opacity(.5); + } + + // Additional properties for button version + // iOS requires the button element instead of an anchor tag. + // If you want the anchor version, it requires `href="#"`. + // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile + button& { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/code.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/code.less new file mode 100644 index 00000000..a08b4d48 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/code.less @@ -0,0 +1,69 @@ +// +// Code (inline and block) +// -------------------------------------------------- + + +// Inline and block code styles +code, +kbd, +pre, +samp { + font-family: @font-family-monospace; +} + +// Inline code +code { + padding: 2px 4px; + font-size: 90%; + color: @code-color; + background-color: @code-bg; + border-radius: @border-radius-base; +} + +// User input typically entered via keyboard +kbd { + padding: 2px 4px; + font-size: 90%; + color: @kbd-color; + background-color: @kbd-bg; + border-radius: @border-radius-small; + box-shadow: inset 0 -1px 0 rgba(0,0,0,.25); + + kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + box-shadow: none; + } +} + +// Blocks of code +pre { + display: block; + padding: ((@line-height-computed - 1) / 2); + margin: 0 0 (@line-height-computed / 2); + font-size: (@font-size-base - 1); // 14px to 13px + line-height: @line-height-base; + word-break: break-all; + word-wrap: break-word; + color: @pre-color; + background-color: @pre-bg; + border: 1px solid @pre-border-color; + border-radius: @border-radius-base; + + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; + } +} + +// Enable scrollable blocks of code +.pre-scrollable { + max-height: @pre-scrollable-max-height; + overflow-y: scroll; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/component-animations.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/component-animations.less new file mode 100644 index 00000000..0bcee910 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/component-animations.less @@ -0,0 +1,33 @@ +// +// Component animations +// -------------------------------------------------- + +// Heads up! +// +// We don't use the `.opacity()` mixin here since it causes a bug with text +// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552. + +.fade { + opacity: 0; + .transition(opacity .15s linear); + &.in { + opacity: 1; + } +} + +.collapse { + display: none; + + &.in { display: block; } + tr&.in { display: table-row; } + tbody&.in { display: table-row-group; } +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + .transition-property(~"height, visibility"); + .transition-duration(.35s); + .transition-timing-function(ease); +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/dropdowns.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/dropdowns.less new file mode 100644 index 00000000..f6876c1a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/dropdowns.less @@ -0,0 +1,216 @@ +// +// Dropdown menus +// -------------------------------------------------- + + +// Dropdown arrow/caret +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: @caret-width-base dashed; + border-top: @caret-width-base solid ~"\9"; // IE8 + border-right: @caret-width-base solid transparent; + border-left: @caret-width-base solid transparent; +} + +// The dropdown wrapper (div) +.dropup, +.dropdown { + position: relative; +} + +// Prevent the focus on the dropdown toggle when closing dropdowns +.dropdown-toggle:focus { + outline: 0; +} + +// The dropdown menu (ul) +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: @zindex-dropdown; + display: none; // none by default, but block on "open" of the menu + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; // override default ul + list-style: none; + font-size: @font-size-base; + text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer) + background-color: @dropdown-bg; + border: 1px solid @dropdown-fallback-border; // IE8 fallback + border: 1px solid @dropdown-border; + border-radius: @border-radius-base; + .box-shadow(0 6px 12px rgba(0,0,0,.175)); + background-clip: padding-box; + + // Aligns the dropdown menu to right + // + // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]` + &.pull-right { + right: 0; + left: auto; + } + + // Dividers (basically an hr) within the dropdown + .divider { + .nav-divider(@dropdown-divider-bg); + } + + // Links within the dropdown menu + > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: @line-height-base; + color: @dropdown-link-color; + white-space: nowrap; // prevent links from randomly breaking onto new lines + } +} + +// Hover/Focus state +.dropdown-menu > li > a { + &:hover, + &:focus { + text-decoration: none; + color: @dropdown-link-hover-color; + background-color: @dropdown-link-hover-bg; + } +} + +// Active state +.dropdown-menu > .active > a { + &, + &:hover, + &:focus { + color: @dropdown-link-active-color; + text-decoration: none; + outline: 0; + background-color: @dropdown-link-active-bg; + } +} + +// Disabled state +// +// Gray out text and ensure the hover/focus state remains gray + +.dropdown-menu > .disabled > a { + &, + &:hover, + &:focus { + color: @dropdown-link-disabled-color; + } + + // Nuke hover/focus effects + &:hover, + &:focus { + text-decoration: none; + background-color: transparent; + background-image: none; // Remove CSS gradient + .reset-filter(); + cursor: @cursor-disabled; + } +} + +// Open state for the dropdown +.open { + // Show the menu + > .dropdown-menu { + display: block; + } + + // Remove the outline when :focus is triggered + > a { + outline: 0; + } +} + +// Menu positioning +// +// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown +// menu with the parent. +.dropdown-menu-right { + left: auto; // Reset the default from `.dropdown-menu` + right: 0; +} +// With v3, we enabled auto-flipping if you have a dropdown within a right +// aligned nav component. To enable the undoing of that, we provide an override +// to restore the default dropdown menu alignment. +// +// This is only for left-aligning a dropdown menu within a `.navbar-right` or +// `.pull-right` nav component. +.dropdown-menu-left { + left: 0; + right: auto; +} + +// Dropdown section headers +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: @font-size-small; + line-height: @line-height-base; + color: @dropdown-header-color; + white-space: nowrap; // as with > li > a +} + +// Backdrop to catch body clicks on mobile, etc. +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: (@zindex-dropdown - 10); +} + +// Right aligned dropdowns +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// +// Just add .dropup after the standard .dropdown class and you're set, bro. +// TODO: abstract this so that the navbar fixed styles are not placed here? + +.dropup, +.navbar-fixed-bottom .dropdown { + // Reverse the caret + .caret { + border-top: 0; + border-bottom: @caret-width-base dashed; + border-bottom: @caret-width-base solid ~"\9"; // IE8 + content: ""; + } + // Different positioning for bottom up menu + .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; + } +} + + +// Component alignment +// +// Reiterate per navbar.less and the modified component alignment there. + +@media (min-width: @grid-float-breakpoint) { + .navbar-right { + .dropdown-menu { + .dropdown-menu-right(); + } + // Necessary for overrides of the default right aligned menu. + // Will remove come v4 in all likelihood. + .dropdown-menu-left { + .dropdown-menu-left(); + } + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/forms.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/forms.less new file mode 100644 index 00000000..9377d384 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/forms.less @@ -0,0 +1,613 @@ +// +// Forms +// -------------------------------------------------- + + +// Normalize non-controls +// +// Restyle and baseline non-control form elements. + +fieldset { + padding: 0; + margin: 0; + border: 0; + // Chrome and Firefox set a `min-width: min-content;` on fieldsets, + // so we reset that to ensure it behaves more like a standard block element. + // See https://github.com/twbs/bootstrap/issues/12359. + min-width: 0; +} + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: @line-height-computed; + font-size: (@font-size-base * 1.5); + line-height: inherit; + color: @legend-color; + border: 0; + border-bottom: 1px solid @legend-border-color; +} + +label { + display: inline-block; + max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141) + margin-bottom: 5px; + font-weight: bold; +} + + +// Normalize form controls +// +// While most of our form styles require extra classes, some basic normalization +// is required to ensure optimum display with or without those classes to better +// address browser inconsistencies. + +// Override content-box in Normalize (* isn't specific enough) +input[type="search"] { + .box-sizing(border-box); +} + +// Position radios and checkboxes better +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; // IE8-9 + line-height: normal; +} + +input[type="file"] { + display: block; +} + +// Make range inputs behave like textual form controls +input[type="range"] { + display: block; + width: 100%; +} + +// Make multiple select elements height not fixed +select[multiple], +select[size] { + height: auto; +} + +// Focus for file, radio, and checkbox +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + .tab-focus(); +} + +// Adjust output element +output { + display: block; + padding-top: (@padding-base-vertical + 1); + font-size: @font-size-base; + line-height: @line-height-base; + color: @input-color; +} + + +// Common form controls +// +// Shared size and type resets for form controls. Apply `.form-control` to any +// of the following form controls: +// +// select +// textarea +// input[type="text"] +// input[type="password"] +// input[type="datetime"] +// input[type="datetime-local"] +// input[type="date"] +// input[type="month"] +// input[type="time"] +// input[type="week"] +// input[type="number"] +// input[type="email"] +// input[type="url"] +// input[type="search"] +// input[type="tel"] +// input[type="color"] + +.form-control { + display: block; + width: 100%; + height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border) + padding: @padding-base-vertical @padding-base-horizontal; + font-size: @font-size-base; + line-height: @line-height-base; + color: @input-color; + background-color: @input-bg; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid @input-border; + border-radius: @input-border-radius; // Note: This has no effect on s in CSS. + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); + .transition(~"border-color ease-in-out .15s, box-shadow ease-in-out .15s"); + + // Customize the `:focus` state to imitate native WebKit styles. + .form-control-focus(); + + // Placeholder + .placeholder(); + + // Unstyle the caret on `` +// element gets special love because it's special, and that's a fact! +.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) { + height: @input-height; + padding: @padding-vertical @padding-horizontal; + font-size: @font-size; + line-height: @line-height; + border-radius: @border-radius; + + select& { + height: @input-height; + line-height: @input-height; + } + + textarea&, + select[multiple]& { + height: auto; + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/gradients.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/gradients.less new file mode 100644 index 00000000..0b88a89c --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/gradients.less @@ -0,0 +1,59 @@ +// Gradients + +#gradient { + + // Horizontal gradient, from left to right + // + // Creates two color stops, start and end, by specifying a color and position for each color stop. + // Color stops are not available in IE9 and below. + .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) { + background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+ + background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12 + background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down + } + + // Vertical gradient, from top to bottom + // + // Creates two color stops, start and end, by specifying a color and position for each color stop. + // Color stops are not available in IE9 and below. + .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) { + background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+ + background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12 + background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down + } + + .directional(@start-color: #555; @end-color: #333; @deg: 45deg) { + background-repeat: repeat-x; + background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+ + background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12 + background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + } + .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) { + background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color); + background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color); + background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback + } + .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) { + background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color); + background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color); + background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback + } + .radial(@inner-color: #555; @outer-color: #333) { + background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color); + background-image: radial-gradient(circle, @inner-color, @outer-color); + background-repeat: no-repeat; + } + .striped(@color: rgba(255,255,255,.15); @angle: 45deg) { + background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/grid-framework.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/grid-framework.less new file mode 100644 index 00000000..8c23eed2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/grid-framework.less @@ -0,0 +1,91 @@ +// Framework grid generation +// +// Used only by Bootstrap to generate the correct number of grid classes given +// any value of `@grid-columns`. + +.make-grid-columns() { + // Common styles for all sizes of grid columns, widths 1-12 + .col(@index) { // initial + @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}"; + .col((@index + 1), @item); + } + .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo + @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}"; + .col((@index + 1), ~"@{list}, @{item}"); + } + .col(@index, @list) when (@index > @grid-columns) { // terminal + @{list} { + position: relative; + // Prevent columns from collapsing when empty + min-height: 1px; + // Inner gutter via padding + padding-left: ceil((@grid-gutter-width / 2)); + padding-right: floor((@grid-gutter-width / 2)); + } + } + .col(1); // kickstart it +} + +.float-grid-columns(@class) { + .col(@index) { // initial + @item: ~".col-@{class}-@{index}"; + .col((@index + 1), @item); + } + .col(@index, @list) when (@index =< @grid-columns) { // general + @item: ~".col-@{class}-@{index}"; + .col((@index + 1), ~"@{list}, @{item}"); + } + .col(@index, @list) when (@index > @grid-columns) { // terminal + @{list} { + float: left; + } + } + .col(1); // kickstart it +} + +.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) { + .col-@{class}-@{index} { + width: percentage((@index / @grid-columns)); + } +} +.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) { + .col-@{class}-push-@{index} { + left: percentage((@index / @grid-columns)); + } +} +.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) { + .col-@{class}-push-0 { + left: auto; + } +} +.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) { + .col-@{class}-pull-@{index} { + right: percentage((@index / @grid-columns)); + } +} +.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) { + .col-@{class}-pull-0 { + right: auto; + } +} +.calc-grid-column(@index, @class, @type) when (@type = offset) { + .col-@{class}-offset-@{index} { + margin-left: percentage((@index / @grid-columns)); + } +} + +// Basic looping in LESS +.loop-grid-columns(@index, @class, @type) when (@index >= 0) { + .calc-grid-column(@index, @class, @type); + // next iteration + .loop-grid-columns((@index - 1), @class, @type); +} + +// Create grid for specific class +.make-grid(@class) { + .float-grid-columns(@class); + .loop-grid-columns(@grid-columns, @class, width); + .loop-grid-columns(@grid-columns, @class, pull); + .loop-grid-columns(@grid-columns, @class, push); + .loop-grid-columns(@grid-columns, @class, offset); +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/grid.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/grid.less new file mode 100644 index 00000000..df496d0b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/grid.less @@ -0,0 +1,122 @@ +// Grid system +// +// Generate semantic grid columns with these mixins. + +// Centered container element +.container-fixed(@gutter: @grid-gutter-width) { + margin-right: auto; + margin-left: auto; + padding-left: floor((@gutter / 2)); + padding-right: ceil((@gutter / 2)); + &:extend(.clearfix all); +} + +// Creates a wrapper for a series of columns +.make-row(@gutter: @grid-gutter-width) { + margin-left: ceil((@gutter / -2)); + margin-right: floor((@gutter / -2)); + &:extend(.clearfix all); +} + +// Generate the extra small columns +.make-xs-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + float: left; + width: percentage((@columns / @grid-columns)); + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); +} +.make-xs-column-offset(@columns) { + margin-left: percentage((@columns / @grid-columns)); +} +.make-xs-column-push(@columns) { + left: percentage((@columns / @grid-columns)); +} +.make-xs-column-pull(@columns) { + right: percentage((@columns / @grid-columns)); +} + +// Generate the small columns +.make-sm-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + @media (min-width: @screen-sm-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-offset(@columns) { + @media (min-width: @screen-sm-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-push(@columns) { + @media (min-width: @screen-sm-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-pull(@columns) { + @media (min-width: @screen-sm-min) { + right: percentage((@columns / @grid-columns)); + } +} + +// Generate the medium columns +.make-md-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + @media (min-width: @screen-md-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} +.make-md-column-offset(@columns) { + @media (min-width: @screen-md-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-md-column-push(@columns) { + @media (min-width: @screen-md-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-md-column-pull(@columns) { + @media (min-width: @screen-md-min) { + right: percentage((@columns / @grid-columns)); + } +} + +// Generate the large columns +.make-lg-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + @media (min-width: @screen-lg-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-offset(@columns) { + @media (min-width: @screen-lg-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-push(@columns) { + @media (min-width: @screen-lg-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-pull(@columns) { + @media (min-width: @screen-lg-min) { + right: percentage((@columns / @grid-columns)); + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/hide-text.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/hide-text.less new file mode 100644 index 00000000..2bb84a3b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/hide-text.less @@ -0,0 +1,21 @@ +// CSS image replacement +// +// Heads up! v3 launched with only `.hide-text()`, but per our pattern for +// mixins being reused as classes with the same name, this doesn't hold up. As +// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`. +// +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 + +// Deprecated as of v3.0.1 (has been removed in v4) +.hide-text() { + font: ~"0/0" a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +// New mixin to use as of v3.0.1 +.text-hide() { + .hide-text(); +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/image.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/image.less new file mode 100644 index 00000000..f233cb3e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/image.less @@ -0,0 +1,33 @@ +// Image Mixins +// - Responsive image +// - Retina image + + +// Responsive image +// +// Keep images from scaling beyond the width of their parents. +.img-responsive(@display: block) { + display: @display; + max-width: 100%; // Part 1: Set a maximum relative to the parent + height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching +} + + +// Retina image +// +// Short retina mixin for setting background-image and -size. Note that the +// spelling of `min--moz-device-pixel-ratio` is intentional. +.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) { + background-image: url("@{file-1x}"); + + @media + only screen and (-webkit-min-device-pixel-ratio: 2), + only screen and ( min--moz-device-pixel-ratio: 2), + only screen and ( -o-min-device-pixel-ratio: 2/1), + only screen and ( min-device-pixel-ratio: 2), + only screen and ( min-resolution: 192dpi), + only screen and ( min-resolution: 2dppx) { + background-image: url("@{file-2x}"); + background-size: @width-1x @height-1x; + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/labels.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/labels.less new file mode 100644 index 00000000..9f7a67ee --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/labels.less @@ -0,0 +1,12 @@ +// Labels + +.label-variant(@color) { + background-color: @color; + + &[href] { + &:hover, + &:focus { + background-color: darken(@color, 10%); + } + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/list-group.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/list-group.less new file mode 100644 index 00000000..03aa1906 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/list-group.less @@ -0,0 +1,30 @@ +// List Groups + +.list-group-item-variant(@state; @background; @color) { + .list-group-item-@{state} { + color: @color; + background-color: @background; + + a&, + button& { + color: @color; + + .list-group-item-heading { + color: inherit; + } + + &:hover, + &:focus { + color: @color; + background-color: darken(@background, 5%); + } + &.active, + &.active:hover, + &.active:focus { + color: #fff; + background-color: @color; + border-color: @color; + } + } + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/nav-divider.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/nav-divider.less new file mode 100644 index 00000000..feb1e9ed --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/nav-divider.less @@ -0,0 +1,10 @@ +// Horizontal dividers +// +// Dividers (basically an hr) within dropdowns and nav lists + +.nav-divider(@color: #e5e5e5) { + height: 1px; + margin: ((@line-height-computed / 2) - 1) 0; + overflow: hidden; + background-color: @color; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/nav-vertical-align.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/nav-vertical-align.less new file mode 100644 index 00000000..d458c786 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/nav-vertical-align.less @@ -0,0 +1,9 @@ +// Navbar vertical align +// +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin. + +.navbar-vertical-align(@element-height) { + margin-top: ((@navbar-height - @element-height) / 2); + margin-bottom: ((@navbar-height - @element-height) / 2); +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/opacity.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/opacity.less new file mode 100644 index 00000000..33ed25ce --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/opacity.less @@ -0,0 +1,8 @@ +// Opacity + +.opacity(@opacity) { + opacity: @opacity; + // IE8 filter + @opacity-ie: (@opacity * 100); + filter: ~"alpha(opacity=@{opacity-ie})"; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/pagination.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/pagination.less new file mode 100644 index 00000000..618804f2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/pagination.less @@ -0,0 +1,24 @@ +// Pagination + +.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) { + > li { + > a, + > span { + padding: @padding-vertical @padding-horizontal; + font-size: @font-size; + line-height: @line-height; + } + &:first-child { + > a, + > span { + .border-left-radius(@border-radius); + } + } + &:last-child { + > a, + > span { + .border-right-radius(@border-radius); + } + } + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/panels.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/panels.less new file mode 100644 index 00000000..49ee10d4 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/panels.less @@ -0,0 +1,24 @@ +// Panels + +.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) { + border-color: @border; + + & > .panel-heading { + color: @heading-text-color; + background-color: @heading-bg-color; + border-color: @heading-border; + + + .panel-collapse > .panel-body { + border-top-color: @border; + } + .badge { + color: @heading-bg-color; + background-color: @heading-text-color; + } + } + & > .panel-footer { + + .panel-collapse > .panel-body { + border-bottom-color: @border; + } + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/progress-bar.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/progress-bar.less new file mode 100644 index 00000000..f07996a3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/progress-bar.less @@ -0,0 +1,10 @@ +// Progress bars + +.progress-bar-variant(@color) { + background-color: @color; + + // Deprecated parent class requirement as of v3.2.0 + .progress-striped & { + #gradient > .striped(); + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/reset-filter.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/reset-filter.less new file mode 100644 index 00000000..68cdb5e1 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/reset-filter.less @@ -0,0 +1,8 @@ +// Reset filters for IE +// +// When you need to remove a gradient background, do not forget to use this to reset +// the IE filter for IE9 and below. + +.reset-filter() { + filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/reset-text.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/reset-text.less new file mode 100644 index 00000000..58dd4d19 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/reset-text.less @@ -0,0 +1,18 @@ +.reset-text() { + font-family: @font-family-base; + // We deliberately do NOT reset font-size. + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-break: auto; + line-height: @line-height-base; + text-align: left; // Fallback for where `start` is not supported + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/resize.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/resize.less new file mode 100644 index 00000000..3acd3afd --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/resize.less @@ -0,0 +1,6 @@ +// Resize anything + +.resizable(@direction) { + resize: @direction; // Options: horizontal, vertical, both + overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible` +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/responsive-visibility.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/responsive-visibility.less new file mode 100644 index 00000000..ecf1e979 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/responsive-visibility.less @@ -0,0 +1,15 @@ +// Responsive utilities + +// +// More easily include all the states for responsive-utilities.less. +.responsive-visibility() { + display: block !important; + table& { display: table !important; } + tr& { display: table-row !important; } + th&, + td& { display: table-cell !important; } +} + +.responsive-invisibility() { + display: none !important; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/size.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/size.less new file mode 100644 index 00000000..a8be6508 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/size.less @@ -0,0 +1,10 @@ +// Sizing shortcuts + +.size(@width; @height) { + width: @width; + height: @height; +} + +.square(@size) { + .size(@size; @size); +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/tab-focus.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/tab-focus.less new file mode 100644 index 00000000..d12d2362 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/tab-focus.less @@ -0,0 +1,9 @@ +// WebKit-style focus + +.tab-focus() { + // WebKit-specific. Other browsers will keep their default outline style. + // (Initially tried to also force default via `outline: initial`, + // but that seems to erroneously remove the outline in Firefox altogether.) + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/table-row.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/table-row.less new file mode 100644 index 00000000..0f287f1a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/table-row.less @@ -0,0 +1,28 @@ +// Tables + +.table-row-variant(@state; @background) { + // Exact selectors below required to override `.table-striped` and prevent + // inheritance to nested tables. + .table > thead > tr, + .table > tbody > tr, + .table > tfoot > tr { + > td.@{state}, + > th.@{state}, + &.@{state} > td, + &.@{state} > th { + background-color: @background; + } + } + + // Hover states for `.table-hover` + // Note: this is not available for cells or rows within `thead` or `tfoot`. + .table-hover > tbody > tr { + > td.@{state}:hover, + > th.@{state}:hover, + &.@{state}:hover > td, + &:hover > .@{state}, + &.@{state}:hover > th { + background-color: darken(@background, 5%); + } + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/text-emphasis.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/text-emphasis.less new file mode 100644 index 00000000..9e8a77a6 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/text-emphasis.less @@ -0,0 +1,9 @@ +// Typography + +.text-emphasis-variant(@color) { + color: @color; + a&:hover, + a&:focus { + color: darken(@color, 10%); + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/text-overflow.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/text-overflow.less new file mode 100644 index 00000000..c11ad2fb --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/text-overflow.less @@ -0,0 +1,8 @@ +// Text overflow +// Requires inline-block or block for proper styling + +.text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/vendor-prefixes.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/vendor-prefixes.less new file mode 100644 index 00000000..2b5e74b9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/mixins/vendor-prefixes.less @@ -0,0 +1,227 @@ +// Vendor Prefixes +// +// All vendor mixins are deprecated as of v3.2.0 due to the introduction of +// Autoprefixer in our Gruntfile. They have been removed in v4. + +// - Animations +// - Backface visibility +// - Box shadow +// - Box sizing +// - Content columns +// - Hyphens +// - Placeholder text +// - Transformations +// - Transitions +// - User Select + + +// Animations +.animation(@animation) { + -webkit-animation: @animation; + -o-animation: @animation; + animation: @animation; +} +.animation-name(@name) { + -webkit-animation-name: @name; + animation-name: @name; +} +.animation-duration(@duration) { + -webkit-animation-duration: @duration; + animation-duration: @duration; +} +.animation-timing-function(@timing-function) { + -webkit-animation-timing-function: @timing-function; + animation-timing-function: @timing-function; +} +.animation-delay(@delay) { + -webkit-animation-delay: @delay; + animation-delay: @delay; +} +.animation-iteration-count(@iteration-count) { + -webkit-animation-iteration-count: @iteration-count; + animation-iteration-count: @iteration-count; +} +.animation-direction(@direction) { + -webkit-animation-direction: @direction; + animation-direction: @direction; +} +.animation-fill-mode(@fill-mode) { + -webkit-animation-fill-mode: @fill-mode; + animation-fill-mode: @fill-mode; +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden` + +.backface-visibility(@visibility) { + -webkit-backface-visibility: @visibility; + -moz-backface-visibility: @visibility; + backface-visibility: @visibility; +} + +// Drop shadows +// +// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's +// supported browsers that have box shadow capabilities now support it. + +.box-shadow(@shadow) { + -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1 + box-shadow: @shadow; +} + +// Box sizing +.box-sizing(@boxmodel) { + -webkit-box-sizing: @boxmodel; + -moz-box-sizing: @boxmodel; + box-sizing: @boxmodel; +} + +// CSS3 Content Columns +.content-columns(@column-count; @column-gap: @grid-gutter-width) { + -webkit-column-count: @column-count; + -moz-column-count: @column-count; + column-count: @column-count; + -webkit-column-gap: @column-gap; + -moz-column-gap: @column-gap; + column-gap: @column-gap; +} + +// Optional hyphenation +.hyphens(@mode: auto) { + word-wrap: break-word; + -webkit-hyphens: @mode; + -moz-hyphens: @mode; + -ms-hyphens: @mode; // IE10+ + -o-hyphens: @mode; + hyphens: @mode; +} + +// Placeholder text +.placeholder(@color: @input-color-placeholder) { + // Firefox + &::-moz-placeholder { + color: @color; + opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526 + } + &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+ + &::-webkit-input-placeholder { color: @color; } // Safari and Chrome +} + +// Transformations +.scale(@ratio) { + -webkit-transform: scale(@ratio); + -ms-transform: scale(@ratio); // IE9 only + -o-transform: scale(@ratio); + transform: scale(@ratio); +} +.scale(@ratioX; @ratioY) { + -webkit-transform: scale(@ratioX, @ratioY); + -ms-transform: scale(@ratioX, @ratioY); // IE9 only + -o-transform: scale(@ratioX, @ratioY); + transform: scale(@ratioX, @ratioY); +} +.scaleX(@ratio) { + -webkit-transform: scaleX(@ratio); + -ms-transform: scaleX(@ratio); // IE9 only + -o-transform: scaleX(@ratio); + transform: scaleX(@ratio); +} +.scaleY(@ratio) { + -webkit-transform: scaleY(@ratio); + -ms-transform: scaleY(@ratio); // IE9 only + -o-transform: scaleY(@ratio); + transform: scaleY(@ratio); +} +.skew(@x; @y) { + -webkit-transform: skewX(@x) skewY(@y); + -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+ + -o-transform: skewX(@x) skewY(@y); + transform: skewX(@x) skewY(@y); +} +.translate(@x; @y) { + -webkit-transform: translate(@x, @y); + -ms-transform: translate(@x, @y); // IE9 only + -o-transform: translate(@x, @y); + transform: translate(@x, @y); +} +.translate3d(@x; @y; @z) { + -webkit-transform: translate3d(@x, @y, @z); + transform: translate3d(@x, @y, @z); +} +.rotate(@degrees) { + -webkit-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); // IE9 only + -o-transform: rotate(@degrees); + transform: rotate(@degrees); +} +.rotateX(@degrees) { + -webkit-transform: rotateX(@degrees); + -ms-transform: rotateX(@degrees); // IE9 only + -o-transform: rotateX(@degrees); + transform: rotateX(@degrees); +} +.rotateY(@degrees) { + -webkit-transform: rotateY(@degrees); + -ms-transform: rotateY(@degrees); // IE9 only + -o-transform: rotateY(@degrees); + transform: rotateY(@degrees); +} +.perspective(@perspective) { + -webkit-perspective: @perspective; + -moz-perspective: @perspective; + perspective: @perspective; +} +.perspective-origin(@perspective) { + -webkit-perspective-origin: @perspective; + -moz-perspective-origin: @perspective; + perspective-origin: @perspective; +} +.transform-origin(@origin) { + -webkit-transform-origin: @origin; + -moz-transform-origin: @origin; + -ms-transform-origin: @origin; // IE9 only + transform-origin: @origin; +} + + +// Transitions + +.transition(@transition) { + -webkit-transition: @transition; + -o-transition: @transition; + transition: @transition; +} +.transition-property(@transition-property) { + -webkit-transition-property: @transition-property; + transition-property: @transition-property; +} +.transition-delay(@transition-delay) { + -webkit-transition-delay: @transition-delay; + transition-delay: @transition-delay; +} +.transition-duration(@transition-duration) { + -webkit-transition-duration: @transition-duration; + transition-duration: @transition-duration; +} +.transition-timing-function(@timing-function) { + -webkit-transition-timing-function: @timing-function; + transition-timing-function: @timing-function; +} +.transition-transform(@transition) { + -webkit-transition: -webkit-transform @transition; + -moz-transition: -moz-transform @transition; + -o-transition: -o-transform @transition; + transition: transform @transition; +} + + +// User select +// For selecting text on the page + +.user-select(@select) { + -webkit-user-select: @select; + -moz-user-select: @select; + -ms-user-select: @select; // IE10+ + user-select: @select; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/modals.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/modals.less new file mode 100644 index 00000000..767ce36b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/modals.less @@ -0,0 +1,150 @@ +// +// Modals +// -------------------------------------------------- + +// .modal-open - body class for killing the scroll +// .modal - container to scroll within +// .modal-dialog - positioning shell for the actual modal +// .modal-content - actual modal w/ bg and corners and shit + +// Kill the scroll on the body +.modal-open { + overflow: hidden; +} + +// Container that the modal scrolls within +.modal { + display: none; + overflow: hidden; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: @zindex-modal; + -webkit-overflow-scrolling: touch; + + // Prevent Chrome on Windows from adding a focus outline. For details, see + // https://github.com/twbs/bootstrap/pull/10951. + outline: 0; + + // When fading in the modal, animate it to slide down + &.fade .modal-dialog { + .translate(0, -25%); + .transition-transform(~"0.3s ease-out"); + } + &.in .modal-dialog { .translate(0, 0) } +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} + +// Shell div to position the modal with bottom padding +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} + +// Actual modal +.modal-content { + position: relative; + background-color: @modal-content-bg; + border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc) + border: 1px solid @modal-content-border-color; + border-radius: @border-radius-large; + .box-shadow(0 3px 9px rgba(0,0,0,.5)); + background-clip: padding-box; + // Remove focus outline from opened modal + outline: 0; +} + +// Modal background +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: @zindex-modal-background; + background-color: @modal-backdrop-bg; + // Fade for backdrop + &.fade { .opacity(0); } + &.in { .opacity(@modal-backdrop-opacity); } +} + +// Modal header +// Top section of the modal w/ title and dismiss +.modal-header { + padding: @modal-title-padding; + border-bottom: 1px solid @modal-header-border-color; + &:extend(.clearfix all); +} +// Close icon +.modal-header .close { + margin-top: -2px; +} + +// Title text within header +.modal-title { + margin: 0; + line-height: @modal-title-line-height; +} + +// Modal body +// Where all modal content resides (sibling of .modal-header and .modal-footer) +.modal-body { + position: relative; + padding: @modal-inner-padding; +} + +// Footer (for actions) +.modal-footer { + padding: @modal-inner-padding; + text-align: right; // right align buttons + border-top: 1px solid @modal-footer-border-color; + &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons + + // Properly space out buttons + .btn + .btn { + margin-left: 5px; + margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs + } + // but override that for button groups + .btn-group .btn + .btn { + margin-left: -1px; + } + // and override it for block buttons as well + .btn-block + .btn-block { + margin-left: 0; + } +} + +// Measure scrollbar width for padding body during modal show/hide +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} + +// Scale up the modal +@media (min-width: @screen-sm-min) { + // Automatically set modal's width for larger viewports + .modal-dialog { + width: @modal-md; + margin: 30px auto; + } + .modal-content { + .box-shadow(0 5px 15px rgba(0,0,0,.5)); + } + + // Modal sizes + .modal-sm { width: @modal-sm; } +} + +@media (min-width: @screen-md-min) { + .modal-lg { width: @modal-lg; } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/navbar.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/navbar.less new file mode 100644 index 00000000..6d751bb9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/navbar.less @@ -0,0 +1,660 @@ +// +// Navbars +// -------------------------------------------------- + + +// Wrapper and base class +// +// Provide a static navbar from which we expand to create full-width, fixed, and +// other navbar variations. + +.navbar { + position: relative; + min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode) + margin-bottom: @navbar-margin-bottom; + border: 1px solid transparent; + + // Prevent floats from breaking the navbar + &:extend(.clearfix all); + + @media (min-width: @grid-float-breakpoint) { + border-radius: @navbar-border-radius; + } +} + + +// Navbar heading +// +// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy +// styling of responsive aspects. + +.navbar-header { + &:extend(.clearfix all); + + @media (min-width: @grid-float-breakpoint) { + float: left; + } +} + + +// Navbar collapse (body) +// +// Group your navbar content into this for easy collapsing and expanding across +// various device sizes. By default, this content is collapsed when <768px, but +// will expand past that for a horizontal display. +// +// To start (on mobile devices) the navbar links, forms, and buttons are stacked +// vertically and include a `max-height` to overflow in case you have too much +// content for the user's viewport. + +.navbar-collapse { + overflow-x: visible; + padding-right: @navbar-padding-horizontal; + padding-left: @navbar-padding-horizontal; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255,255,255,.1); + &:extend(.clearfix all); + -webkit-overflow-scrolling: touch; + + &.in { + overflow-y: auto; + } + + @media (min-width: @grid-float-breakpoint) { + width: auto; + border-top: 0; + box-shadow: none; + + &.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; // Override default setting + overflow: visible !important; + } + + &.in { + overflow-y: visible; + } + + // Undo the collapse side padding for navbars with containers to ensure + // alignment of right-aligned contents. + .navbar-fixed-top &, + .navbar-static-top &, + .navbar-fixed-bottom & { + padding-left: 0; + padding-right: 0; + } + } +} + +.navbar-fixed-top, +.navbar-fixed-bottom { + .navbar-collapse { + max-height: @navbar-collapse-max-height; + + @media (max-device-width: @screen-xs-min) and (orientation: landscape) { + max-height: 200px; + } + } +} + + +// Both navbar header and collapse +// +// When a container is present, change the behavior of the header and collapse. + +.container, +.container-fluid { + > .navbar-header, + > .navbar-collapse { + margin-right: -@navbar-padding-horizontal; + margin-left: -@navbar-padding-horizontal; + + @media (min-width: @grid-float-breakpoint) { + margin-right: 0; + margin-left: 0; + } + } +} + + +// +// Navbar alignment options +// +// Display the navbar across the entirety of the page or fixed it to the top or +// bottom of the page. + +// Static top (unfixed, but 100% wide) navbar +.navbar-static-top { + z-index: @zindex-navbar; + border-width: 0 0 1px; + + @media (min-width: @grid-float-breakpoint) { + border-radius: 0; + } +} + +// Fix the top/bottom navbars when screen real estate supports it +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: @zindex-navbar-fixed; + + // Undo the rounded corners + @media (min-width: @grid-float-breakpoint) { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; // override .navbar defaults + border-width: 1px 0 0; +} + + +// Brand/project name + +.navbar-brand { + float: left; + padding: @navbar-padding-vertical @navbar-padding-horizontal; + font-size: @font-size-large; + line-height: @line-height-computed; + height: @navbar-height; + + &:hover, + &:focus { + text-decoration: none; + } + + > img { + display: block; + } + + @media (min-width: @grid-float-breakpoint) { + .navbar > .container &, + .navbar > .container-fluid & { + margin-left: -@navbar-padding-horizontal; + } + } +} + + +// Navbar toggle +// +// Custom button for toggling the `.navbar-collapse`, powered by the collapse +// JavaScript plugin. + +.navbar-toggle { + position: relative; + float: right; + margin-right: @navbar-padding-horizontal; + padding: 9px 10px; + .navbar-vertical-align(34px); + background-color: transparent; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid transparent; + border-radius: @border-radius-base; + + // We remove the `outline` here, but later compensate by attaching `:hover` + // styles to `:focus`. + &:focus { + outline: 0; + } + + // Bars + .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; + } + .icon-bar + .icon-bar { + margin-top: 4px; + } + + @media (min-width: @grid-float-breakpoint) { + display: none; + } +} + + +// Navbar nav links +// +// Builds on top of the `.nav` components with its own modifier class to make +// the nav the full height of the horizontal nav (above 768px). + +.navbar-nav { + margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal; + + > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: @line-height-computed; + } + + @media (max-width: @grid-float-breakpoint-max) { + // Dropdowns get custom display when collapsed + .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + > li > a, + .dropdown-header { + padding: 5px 15px 5px 25px; + } + > li > a { + line-height: @line-height-computed; + &:hover, + &:focus { + background-image: none; + } + } + } + } + + // Uncollapse the nav + @media (min-width: @grid-float-breakpoint) { + float: left; + margin: 0; + + > li { + float: left; + > a { + padding-top: @navbar-padding-vertical; + padding-bottom: @navbar-padding-vertical; + } + } + } +} + + +// Navbar form +// +// Extension of the `.form-inline` with some extra flavor for optimum display in +// our navbars. + +.navbar-form { + margin-left: -@navbar-padding-horizontal; + margin-right: -@navbar-padding-horizontal; + padding: 10px @navbar-padding-horizontal; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1); + .box-shadow(@shadow); + + // Mixin behavior for optimum display + .form-inline(); + + .form-group { + @media (max-width: @grid-float-breakpoint-max) { + margin-bottom: 5px; + + &:last-child { + margin-bottom: 0; + } + } + } + + // Vertically center in expanded, horizontal navbar + .navbar-vertical-align(@input-height-base); + + // Undo 100% width for pull classes + @media (min-width: @grid-float-breakpoint) { + width: auto; + border: 0; + margin-left: 0; + margin-right: 0; + padding-top: 0; + padding-bottom: 0; + .box-shadow(none); + } +} + + +// Dropdown menus + +// Menu position and menu carets +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + .border-top-radius(0); +} +// Menu position and menu caret support for dropups via extra dropup class +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + .border-top-radius(@navbar-border-radius); + .border-bottom-radius(0); +} + + +// Buttons in navbars +// +// Vertically center a button within a navbar (when *not* in a form). + +.navbar-btn { + .navbar-vertical-align(@input-height-base); + + &.btn-sm { + .navbar-vertical-align(@input-height-small); + } + &.btn-xs { + .navbar-vertical-align(22); + } +} + + +// Text in navbars +// +// Add a class to make any element properly align itself vertically within the navbars. + +.navbar-text { + .navbar-vertical-align(@line-height-computed); + + @media (min-width: @grid-float-breakpoint) { + float: left; + margin-left: @navbar-padding-horizontal; + margin-right: @navbar-padding-horizontal; + } +} + + +// Component alignment +// +// Repurpose the pull utilities as their own navbar utilities to avoid specificity +// issues with parents and chaining. Only do this when the navbar is uncollapsed +// though so that navbar contents properly stack and align in mobile. +// +// Declared after the navbar components to ensure more specificity on the margins. + +@media (min-width: @grid-float-breakpoint) { + .navbar-left { .pull-left(); } + .navbar-right { + .pull-right(); + margin-right: -@navbar-padding-horizontal; + + ~ .navbar-right { + margin-right: 0; + } + } +} + + +// Alternate navbars +// -------------------------------------------------- + +// Default navbar +.navbar-default { + background-color: @navbar-default-bg; + border-color: @navbar-default-border; + + .navbar-brand { + color: @navbar-default-brand-color; + &:hover, + &:focus { + color: @navbar-default-brand-hover-color; + background-color: @navbar-default-brand-hover-bg; + } + } + + .navbar-text { + color: @navbar-default-color; + } + + .navbar-nav { + > li > a { + color: @navbar-default-link-color; + + &:hover, + &:focus { + color: @navbar-default-link-hover-color; + background-color: @navbar-default-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: @navbar-default-link-active-color; + background-color: @navbar-default-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: @navbar-default-link-disabled-color; + background-color: @navbar-default-link-disabled-bg; + } + } + } + + .navbar-toggle { + border-color: @navbar-default-toggle-border-color; + &:hover, + &:focus { + background-color: @navbar-default-toggle-hover-bg; + } + .icon-bar { + background-color: @navbar-default-toggle-icon-bar-bg; + } + } + + .navbar-collapse, + .navbar-form { + border-color: @navbar-default-border; + } + + // Dropdown menu items + .navbar-nav { + // Remove background color from open dropdown + > .open > a { + &, + &:hover, + &:focus { + background-color: @navbar-default-link-active-bg; + color: @navbar-default-link-active-color; + } + } + + @media (max-width: @grid-float-breakpoint-max) { + // Dropdowns get custom display when collapsed + .open .dropdown-menu { + > li > a { + color: @navbar-default-link-color; + &:hover, + &:focus { + color: @navbar-default-link-hover-color; + background-color: @navbar-default-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: @navbar-default-link-active-color; + background-color: @navbar-default-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: @navbar-default-link-disabled-color; + background-color: @navbar-default-link-disabled-bg; + } + } + } + } + } + + + // Links in navbars + // + // Add a class to ensure links outside the navbar nav are colored correctly. + + .navbar-link { + color: @navbar-default-link-color; + &:hover { + color: @navbar-default-link-hover-color; + } + } + + .btn-link { + color: @navbar-default-link-color; + &:hover, + &:focus { + color: @navbar-default-link-hover-color; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: @navbar-default-link-disabled-color; + } + } + } +} + +// Inverse navbar + +.navbar-inverse { + background-color: @navbar-inverse-bg; + border-color: @navbar-inverse-border; + + .navbar-brand { + color: @navbar-inverse-brand-color; + &:hover, + &:focus { + color: @navbar-inverse-brand-hover-color; + background-color: @navbar-inverse-brand-hover-bg; + } + } + + .navbar-text { + color: @navbar-inverse-color; + } + + .navbar-nav { + > li > a { + color: @navbar-inverse-link-color; + + &:hover, + &:focus { + color: @navbar-inverse-link-hover-color; + background-color: @navbar-inverse-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: @navbar-inverse-link-active-color; + background-color: @navbar-inverse-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: @navbar-inverse-link-disabled-color; + background-color: @navbar-inverse-link-disabled-bg; + } + } + } + + // Darken the responsive nav toggle + .navbar-toggle { + border-color: @navbar-inverse-toggle-border-color; + &:hover, + &:focus { + background-color: @navbar-inverse-toggle-hover-bg; + } + .icon-bar { + background-color: @navbar-inverse-toggle-icon-bar-bg; + } + } + + .navbar-collapse, + .navbar-form { + border-color: darken(@navbar-inverse-bg, 7%); + } + + // Dropdowns + .navbar-nav { + > .open > a { + &, + &:hover, + &:focus { + background-color: @navbar-inverse-link-active-bg; + color: @navbar-inverse-link-active-color; + } + } + + @media (max-width: @grid-float-breakpoint-max) { + // Dropdowns get custom display + .open .dropdown-menu { + > .dropdown-header { + border-color: @navbar-inverse-border; + } + .divider { + background-color: @navbar-inverse-border; + } + > li > a { + color: @navbar-inverse-link-color; + &:hover, + &:focus { + color: @navbar-inverse-link-hover-color; + background-color: @navbar-inverse-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: @navbar-inverse-link-active-color; + background-color: @navbar-inverse-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: @navbar-inverse-link-disabled-color; + background-color: @navbar-inverse-link-disabled-bg; + } + } + } + } + } + + .navbar-link { + color: @navbar-inverse-link-color; + &:hover { + color: @navbar-inverse-link-hover-color; + } + } + + .btn-link { + color: @navbar-inverse-link-color; + &:hover, + &:focus { + color: @navbar-inverse-link-hover-color; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: @navbar-inverse-link-disabled-color; + } + } + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/navs.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/navs.less new file mode 100644 index 00000000..a3d11b13 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/navs.less @@ -0,0 +1,242 @@ +// +// Navs +// -------------------------------------------------- + + +// Base class +// -------------------------------------------------- + +.nav { + margin-bottom: 0; + padding-left: 0; // Override default ul/ol + list-style: none; + &:extend(.clearfix all); + + > li { + position: relative; + display: block; + + > a { + position: relative; + display: block; + padding: @nav-link-padding; + &:hover, + &:focus { + text-decoration: none; + background-color: @nav-link-hover-bg; + } + } + + // Disabled state sets text to gray and nukes hover/tab effects + &.disabled > a { + color: @nav-disabled-link-color; + + &:hover, + &:focus { + color: @nav-disabled-link-hover-color; + text-decoration: none; + background-color: transparent; + cursor: @cursor-disabled; + } + } + } + + // Open dropdowns + .open > a { + &, + &:hover, + &:focus { + background-color: @nav-link-hover-bg; + border-color: @link-color; + } + } + + // Nav dividers (deprecated with v3.0.1) + // + // This should have been removed in v3 with the dropping of `.nav-list`, but + // we missed it. We don't currently support this anywhere, but in the interest + // of maintaining backward compatibility in case you use it, it's deprecated. + .nav-divider { + .nav-divider(); + } + + // Prevent IE8 from misplacing imgs + // + // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989 + > li > a > img { + max-width: none; + } +} + + +// Tabs +// ------------------------- + +// Give the tabs something to sit on +.nav-tabs { + border-bottom: 1px solid @nav-tabs-border-color; + > li { + float: left; + // Make the list-items overlay the bottom border + margin-bottom: -1px; + + // Actual tabs (as links) + > a { + margin-right: 2px; + line-height: @line-height-base; + border: 1px solid transparent; + border-radius: @border-radius-base @border-radius-base 0 0; + &:hover { + border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color; + } + } + + // Active state, and its :hover to override normal :hover + &.active > a { + &, + &:hover, + &:focus { + color: @nav-tabs-active-link-hover-color; + background-color: @nav-tabs-active-link-hover-bg; + border: 1px solid @nav-tabs-active-link-hover-border-color; + border-bottom-color: transparent; + cursor: default; + } + } + } + // pulling this in mainly for less shorthand + &.nav-justified { + .nav-justified(); + .nav-tabs-justified(); + } +} + + +// Pills +// ------------------------- +.nav-pills { + > li { + float: left; + + // Links rendered as pills + > a { + border-radius: @nav-pills-border-radius; + } + + li { + margin-left: 2px; + } + + // Active state + &.active > a { + &, + &:hover, + &:focus { + color: @nav-pills-active-link-hover-color; + background-color: @nav-pills-active-link-hover-bg; + } + } + } +} + + +// Stacked pills +.nav-stacked { + > li { + float: none; + + li { + margin-top: 2px; + margin-left: 0; // no need for this gap between nav items + } + } +} + + +// Nav variations +// -------------------------------------------------- + +// Justified nav links +// ------------------------- + +.nav-justified { + width: 100%; + + > li { + float: none; + > a { + text-align: center; + margin-bottom: 5px; + } + } + + > .dropdown .dropdown-menu { + top: auto; + left: auto; + } + + @media (min-width: @screen-sm-min) { + > li { + display: table-cell; + width: 1%; + > a { + margin-bottom: 0; + } + } + } +} + +// Move borders to anchors instead of bottom of list +// +// Mixin for adding on top the shared `.nav-justified` styles for our tabs +.nav-tabs-justified { + border-bottom: 0; + + > li > a { + // Override margin from .nav-tabs + margin-right: 0; + border-radius: @border-radius-base; + } + + > .active > a, + > .active > a:hover, + > .active > a:focus { + border: 1px solid @nav-tabs-justified-link-border-color; + } + + @media (min-width: @screen-sm-min) { + > li > a { + border-bottom: 1px solid @nav-tabs-justified-link-border-color; + border-radius: @border-radius-base @border-radius-base 0 0; + } + > .active > a, + > .active > a:hover, + > .active > a:focus { + border-bottom-color: @nav-tabs-justified-active-link-border-color; + } + } +} + + +// Tabbable tabs +// ------------------------- + +// Hide tabbable panes to start, show them when `.active` +.tab-content { + > .tab-pane { + display: none; + } + > .active { + display: block; + } +} + + +// Dropdowns +// ------------------------- + +// Specific dropdowns +.nav-tabs .dropdown-menu { + // make dropdown border overlap tab border + margin-top: -1px; + // Remove the top rounded corners here since there is a hard edge above the menu + .border-top-radius(0); +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/normalize.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/normalize.less new file mode 100644 index 00000000..9dddf73a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/normalize.less @@ -0,0 +1,424 @@ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ + +// +// 1. Set default font family to sans-serif. +// 2. Prevent iOS and IE text size adjust after device orientation change, +// without disabling user zoom. +// + +html { + font-family: sans-serif; // 1 + -ms-text-size-adjust: 100%; // 2 + -webkit-text-size-adjust: 100%; // 2 +} + +// +// Remove default margin. +// + +body { + margin: 0; +} + +// HTML5 display definitions +// ========================================================================== + +// +// Correct `block` display not defined for any HTML5 element in IE 8/9. +// Correct `block` display not defined for `details` or `summary` in IE 10/11 +// and Firefox. +// Correct `block` display not defined for `main` in IE 11. +// + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +// +// 1. Correct `inline-block` display not defined in IE 8/9. +// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. +// + +audio, +canvas, +progress, +video { + display: inline-block; // 1 + vertical-align: baseline; // 2 +} + +// +// Prevent modern browsers from displaying `audio` without controls. +// Remove excess height in iOS 5 devices. +// + +audio:not([controls]) { + display: none; + height: 0; +} + +// +// Address `[hidden]` styling not present in IE 8/9/10. +// Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. +// + +[hidden], +template { + display: none; +} + +// Links +// ========================================================================== + +// +// Remove the gray background color from active links in IE 10. +// + +a { + background-color: transparent; +} + +// +// Improve readability of focused elements when they are also in an +// active/hover state. +// + +a:active, +a:hover { + outline: 0; +} + +// Text-level semantics +// ========================================================================== + +// +// Address styling not present in IE 8/9/10/11, Safari, and Chrome. +// + +abbr[title] { + border-bottom: 1px dotted; +} + +// +// Address style set to `bolder` in Firefox 4+, Safari, and Chrome. +// + +b, +strong { + font-weight: bold; +} + +// +// Address styling not present in Safari and Chrome. +// + +dfn { + font-style: italic; +} + +// +// Address variable `h1` font-size and margin within `section` and `article` +// contexts in Firefox 4+, Safari, and Chrome. +// + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +// +// Address styling not present in IE 8/9. +// + +mark { + background: #ff0; + color: #000; +} + +// +// Address inconsistent and variable font size in all browsers. +// + +small { + font-size: 80%; +} + +// +// Prevent `sub` and `sup` affecting `line-height` in all browsers. +// + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +// Embedded content +// ========================================================================== + +// +// Remove border when inside `a` element in IE 8/9/10. +// + +img { + border: 0; +} + +// +// Correct overflow not hidden in IE 9/10/11. +// + +svg:not(:root) { + overflow: hidden; +} + +// Grouping content +// ========================================================================== + +// +// Address margin not present in IE 8/9 and Safari. +// + +figure { + margin: 1em 40px; +} + +// +// Address differences between Firefox and other browsers. +// + +hr { + box-sizing: content-box; + height: 0; +} + +// +// Contain overflow in all browsers. +// + +pre { + overflow: auto; +} + +// +// Address odd `em`-unit font size rendering in all browsers. +// + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +// Forms +// ========================================================================== + +// +// Known limitation: by default, Chrome and Safari on OS X allow very limited +// styling of `select`, unless a `border` property is set. +// + +// +// 1. Correct color not being inherited. +// Known issue: affects color of disabled elements. +// 2. Correct font properties not being inherited. +// 3. Address margins set differently in Firefox 4+, Safari, and Chrome. +// + +button, +input, +optgroup, +select, +textarea { + color: inherit; // 1 + font: inherit; // 2 + margin: 0; // 3 +} + +// +// Address `overflow` set to `hidden` in IE 8/9/10/11. +// + +button { + overflow: visible; +} + +// +// Address inconsistent `text-transform` inheritance for `button` and `select`. +// All other form control elements do not inherit `text-transform` values. +// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. +// Correct `select` style inheritance in Firefox. +// + +button, +select { + text-transform: none; +} + +// +// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` +// and `video` controls. +// 2. Correct inability to style clickable `input` types in iOS. +// 3. Improve usability and consistency of cursor style between image-type +// `input` and others. +// + +button, +html input[type="button"], // 1 +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; // 2 + cursor: pointer; // 3 +} + +// +// Re-set default cursor for disabled elements. +// + +button[disabled], +html input[disabled] { + cursor: default; +} + +// +// Remove inner padding and border in Firefox 4+. +// + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +// +// Address Firefox 4+ setting `line-height` on `input` using `!important` in +// the UA stylesheet. +// + +input { + line-height: normal; +} + +// +// It's recommended that you don't attempt to style these elements. +// Firefox's implementation doesn't respect box-sizing, padding, or width. +// +// 1. Address box sizing set to `content-box` in IE 8/9/10. +// 2. Remove excess padding in IE 8/9/10. +// + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; // 1 + padding: 0; // 2 +} + +// +// Fix the cursor style for Chrome's increment/decrement buttons. For certain +// `font-size` values of the `input`, it causes the cursor style of the +// decrement button to change from `default` to `text`. +// + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +// +// 1. Address `appearance` set to `searchfield` in Safari and Chrome. +// 2. Address `box-sizing` set to `border-box` in Safari and Chrome. +// + +input[type="search"] { + -webkit-appearance: textfield; // 1 + box-sizing: content-box; //2 +} + +// +// Remove inner padding and search cancel button in Safari and Chrome on OS X. +// Safari (but not Chrome) clips the cancel button when the search input has +// padding (and `textfield` appearance). +// + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +// +// Define consistent border, margin, and padding. +// + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +// +// 1. Correct `color` not being inherited in IE 8/9/10/11. +// 2. Remove padding so people aren't caught out if they zero out fieldsets. +// + +legend { + border: 0; // 1 + padding: 0; // 2 +} + +// +// Remove default vertical scrollbar in IE 8/9/10/11. +// + +textarea { + overflow: auto; +} + +// +// Don't inherit the `font-weight` (applied by a rule above). +// NOTE: the default cannot safely be changed in Chrome and Safari on OS X. +// + +optgroup { + font-weight: bold; +} + +// Tables +// ========================================================================== + +// +// Remove most spacing between table cells. +// + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/pager.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/pager.less new file mode 100644 index 00000000..41abaaad --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/pager.less @@ -0,0 +1,54 @@ +// +// Pager pagination +// -------------------------------------------------- + + +.pager { + padding-left: 0; + margin: @line-height-computed 0; + list-style: none; + text-align: center; + &:extend(.clearfix all); + li { + display: inline; + > a, + > span { + display: inline-block; + padding: 5px 14px; + background-color: @pager-bg; + border: 1px solid @pager-border; + border-radius: @pager-border-radius; + } + + > a:hover, + > a:focus { + text-decoration: none; + background-color: @pager-hover-bg; + } + } + + .next { + > a, + > span { + float: right; + } + } + + .previous { + > a, + > span { + float: left; + } + } + + .disabled { + > a, + > a:hover, + > a:focus, + > span { + color: @pager-disabled-color; + background-color: @pager-bg; + cursor: @cursor-disabled; + } + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/pagination.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/pagination.less new file mode 100644 index 00000000..31f77aae --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/pagination.less @@ -0,0 +1,89 @@ +// +// Pagination (multiple pages) +// -------------------------------------------------- +.pagination { + display: inline-block; + padding-left: 0; + margin: @line-height-computed 0; + border-radius: @border-radius-base; + + > li { + display: inline; // Remove list-style and block-level defaults + > a, + > span { + position: relative; + float: left; // Collapse white-space + padding: @padding-base-vertical @padding-base-horizontal; + line-height: @line-height-base; + text-decoration: none; + color: @pagination-color; + background-color: @pagination-bg; + border: 1px solid @pagination-border; + margin-left: -1px; + } + &:first-child { + > a, + > span { + margin-left: 0; + .border-left-radius(@border-radius-base); + } + } + &:last-child { + > a, + > span { + .border-right-radius(@border-radius-base); + } + } + } + + > li > a, + > li > span { + &:hover, + &:focus { + z-index: 2; + color: @pagination-hover-color; + background-color: @pagination-hover-bg; + border-color: @pagination-hover-border; + } + } + + > .active > a, + > .active > span { + &, + &:hover, + &:focus { + z-index: 3; + color: @pagination-active-color; + background-color: @pagination-active-bg; + border-color: @pagination-active-border; + cursor: default; + } + } + + > .disabled { + > span, + > span:hover, + > span:focus, + > a, + > a:hover, + > a:focus { + color: @pagination-disabled-color; + background-color: @pagination-disabled-bg; + border-color: @pagination-disabled-border; + cursor: @cursor-disabled; + } + } +} + +// Sizing +// -------------------------------------------------- + +// Large +.pagination-lg { + .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large); +} + +// Small +.pagination-sm { + .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small); +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/panels.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/panels.less new file mode 100644 index 00000000..65aa3a83 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/panels.less @@ -0,0 +1,271 @@ +// +// Panels +// -------------------------------------------------- + + +// Base class +.panel { + margin-bottom: @line-height-computed; + background-color: @panel-bg; + border: 1px solid transparent; + border-radius: @panel-border-radius; + .box-shadow(0 1px 1px rgba(0,0,0,.05)); +} + +// Panel contents +.panel-body { + padding: @panel-body-padding; + &:extend(.clearfix all); +} + +// Optional heading +.panel-heading { + padding: @panel-heading-padding; + border-bottom: 1px solid transparent; + .border-top-radius((@panel-border-radius - 1)); + + > .dropdown .dropdown-toggle { + color: inherit; + } +} + +// Within heading, strip any `h*` tag of its default margins for spacing. +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: ceil((@font-size-base * 1.125)); + color: inherit; + + > a, + > small, + > .small, + > small > a, + > .small > a { + color: inherit; + } +} + +// Optional footer (stays gray in every modifier class) +.panel-footer { + padding: @panel-footer-padding; + background-color: @panel-footer-bg; + border-top: 1px solid @panel-inner-border; + .border-bottom-radius((@panel-border-radius - 1)); +} + + +// List groups in panels +// +// By default, space out list group content from panel headings to account for +// any kind of custom content between the two. + +.panel { + > .list-group, + > .panel-collapse > .list-group { + margin-bottom: 0; + + .list-group-item { + border-width: 1px 0; + border-radius: 0; + } + + // Add border top radius for first one + &:first-child { + .list-group-item:first-child { + border-top: 0; + .border-top-radius((@panel-border-radius - 1)); + } + } + + // Add border bottom radius for last one + &:last-child { + .list-group-item:last-child { + border-bottom: 0; + .border-bottom-radius((@panel-border-radius - 1)); + } + } + } + > .panel-heading + .panel-collapse > .list-group { + .list-group-item:first-child { + .border-top-radius(0); + } + } +} +// Collapse space between when there's no additional content. +.panel-heading + .list-group { + .list-group-item:first-child { + border-top-width: 0; + } +} +.list-group + .panel-footer { + border-top-width: 0; +} + +// Tables in panels +// +// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and +// watch it go full width. + +.panel { + > .table, + > .table-responsive > .table, + > .panel-collapse > .table { + margin-bottom: 0; + + caption { + padding-left: @panel-body-padding; + padding-right: @panel-body-padding; + } + } + // Add border top radius for first one + > .table:first-child, + > .table-responsive:first-child > .table:first-child { + .border-top-radius((@panel-border-radius - 1)); + + > thead:first-child, + > tbody:first-child { + > tr:first-child { + border-top-left-radius: (@panel-border-radius - 1); + border-top-right-radius: (@panel-border-radius - 1); + + td:first-child, + th:first-child { + border-top-left-radius: (@panel-border-radius - 1); + } + td:last-child, + th:last-child { + border-top-right-radius: (@panel-border-radius - 1); + } + } + } + } + // Add border bottom radius for last one + > .table:last-child, + > .table-responsive:last-child > .table:last-child { + .border-bottom-radius((@panel-border-radius - 1)); + + > tbody:last-child, + > tfoot:last-child { + > tr:last-child { + border-bottom-left-radius: (@panel-border-radius - 1); + border-bottom-right-radius: (@panel-border-radius - 1); + + td:first-child, + th:first-child { + border-bottom-left-radius: (@panel-border-radius - 1); + } + td:last-child, + th:last-child { + border-bottom-right-radius: (@panel-border-radius - 1); + } + } + } + } + > .panel-body + .table, + > .panel-body + .table-responsive, + > .table + .panel-body, + > .table-responsive + .panel-body { + border-top: 1px solid @table-border-color; + } + > .table > tbody:first-child > tr:first-child th, + > .table > tbody:first-child > tr:first-child td { + border-top: 0; + } + > .table-bordered, + > .table-responsive > .table-bordered { + border: 0; + > thead, + > tbody, + > tfoot { + > tr { + > th:first-child, + > td:first-child { + border-left: 0; + } + > th:last-child, + > td:last-child { + border-right: 0; + } + } + } + > thead, + > tbody { + > tr:first-child { + > td, + > th { + border-bottom: 0; + } + } + } + > tbody, + > tfoot { + > tr:last-child { + > td, + > th { + border-bottom: 0; + } + } + } + } + > .table-responsive { + border: 0; + margin-bottom: 0; + } +} + + +// Collapsible panels (aka, accordion) +// +// Wrap a series of panels in `.panel-group` to turn them into an accordion with +// the help of our collapse JavaScript plugin. + +.panel-group { + margin-bottom: @line-height-computed; + + // Tighten up margin so it's only between panels + .panel { + margin-bottom: 0; + border-radius: @panel-border-radius; + + + .panel { + margin-top: 5px; + } + } + + .panel-heading { + border-bottom: 0; + + + .panel-collapse > .panel-body, + + .panel-collapse > .list-group { + border-top: 1px solid @panel-inner-border; + } + } + + .panel-footer { + border-top: 0; + + .panel-collapse .panel-body { + border-bottom: 1px solid @panel-inner-border; + } + } +} + + +// Contextual variations +.panel-default { + .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border); +} +.panel-primary { + .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border); +} +.panel-success { + .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border); +} +.panel-info { + .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border); +} +.panel-warning { + .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border); +} +.panel-danger { + .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border); +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/popovers.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/popovers.less new file mode 100644 index 00000000..3a62a645 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/popovers.less @@ -0,0 +1,131 @@ +// +// Popovers +// -------------------------------------------------- + + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: @zindex-popover; + display: none; + max-width: @popover-max-width; + padding: 1px; + // Our parent element can be arbitrary since popovers are by default inserted as a sibling of their target element. + // So reset our font and text properties to avoid inheriting weird values. + .reset-text(); + font-size: @font-size-base; + + background-color: @popover-bg; + background-clip: padding-box; + border: 1px solid @popover-fallback-border-color; + border: 1px solid @popover-border-color; + border-radius: @border-radius-large; + .box-shadow(0 5px 10px rgba(0,0,0,.2)); + + // Offset the popover to account for the popover arrow + &.top { margin-top: -@popover-arrow-width; } + &.right { margin-left: @popover-arrow-width; } + &.bottom { margin-top: @popover-arrow-width; } + &.left { margin-left: -@popover-arrow-width; } +} + +.popover-title { + margin: 0; // reset heading margin + padding: 8px 14px; + font-size: @font-size-base; + background-color: @popover-title-bg; + border-bottom: 1px solid darken(@popover-title-bg, 5%); + border-radius: (@border-radius-large - 1) (@border-radius-large - 1) 0 0; +} + +.popover-content { + padding: 9px 14px; +} + +// Arrows +// +// .arrow is outer, .arrow:after is inner + +.popover > .arrow { + &, + &:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + } +} +.popover > .arrow { + border-width: @popover-arrow-outer-width; +} +.popover > .arrow:after { + border-width: @popover-arrow-width; + content: ""; +} + +.popover { + &.top > .arrow { + left: 50%; + margin-left: -@popover-arrow-outer-width; + border-bottom-width: 0; + border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback + border-top-color: @popover-arrow-outer-color; + bottom: -@popover-arrow-outer-width; + &:after { + content: " "; + bottom: 1px; + margin-left: -@popover-arrow-width; + border-bottom-width: 0; + border-top-color: @popover-arrow-color; + } + } + &.right > .arrow { + top: 50%; + left: -@popover-arrow-outer-width; + margin-top: -@popover-arrow-outer-width; + border-left-width: 0; + border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback + border-right-color: @popover-arrow-outer-color; + &:after { + content: " "; + left: 1px; + bottom: -@popover-arrow-width; + border-left-width: 0; + border-right-color: @popover-arrow-color; + } + } + &.bottom > .arrow { + left: 50%; + margin-left: -@popover-arrow-outer-width; + border-top-width: 0; + border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback + border-bottom-color: @popover-arrow-outer-color; + top: -@popover-arrow-outer-width; + &:after { + content: " "; + top: 1px; + margin-left: -@popover-arrow-width; + border-top-width: 0; + border-bottom-color: @popover-arrow-color; + } + } + + &.left > .arrow { + top: 50%; + right: -@popover-arrow-outer-width; + margin-top: -@popover-arrow-outer-width; + border-right-width: 0; + border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback + border-left-color: @popover-arrow-outer-color; + &:after { + content: " "; + right: 1px; + border-right-width: 0; + border-left-color: @popover-arrow-color; + bottom: -@popover-arrow-width; + } + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/print.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/print.less new file mode 100644 index 00000000..66e54ab4 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/print.less @@ -0,0 +1,101 @@ +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ + +// ========================================================================== +// Print styles. +// Inlined to avoid the additional HTTP request: h5bp.com/r +// ========================================================================== + +@media print { + *, + *:before, + *:after { + background: transparent !important; + color: #000 !important; // Black prints faster: h5bp.com/s + box-shadow: none !important; + text-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " (" attr(href) ")"; + } + + abbr[title]:after { + content: " (" attr(title) ")"; + } + + // Don't show links that are fragment identifiers, + // or use the `javascript:` pseudo protocol + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + thead { + display: table-header-group; // h5bp.com/t + } + + tr, + img { + page-break-inside: avoid; + } + + img { + max-width: 100% !important; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } + + // Bootstrap specific changes start + + // Bootstrap components + .navbar { + display: none; + } + .btn, + .dropup > .btn { + > .caret { + border-top-color: #000 !important; + } + } + .label { + border: 1px solid #000; + } + + .table { + border-collapse: collapse !important; + + td, + th { + background-color: #fff !important; + } + } + .table-bordered { + th, + td { + border: 1px solid #ddd !important; + } + } + + // Bootstrap specific changes end +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/progress-bars.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/progress-bars.less new file mode 100644 index 00000000..8868a1fe --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/progress-bars.less @@ -0,0 +1,87 @@ +// +// Progress bars +// -------------------------------------------------- + + +// Bar animations +// ------------------------- + +// WebKit +@-webkit-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// Spec and IE10+ +@keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + + +// Bar itself +// ------------------------- + +// Outer container +.progress { + overflow: hidden; + height: @line-height-computed; + margin-bottom: @line-height-computed; + background-color: @progress-bg; + border-radius: @progress-border-radius; + .box-shadow(inset 0 1px 2px rgba(0,0,0,.1)); +} + +// Bar of progress +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: @font-size-small; + line-height: @line-height-computed; + color: @progress-bar-color; + text-align: center; + background-color: @progress-bar-bg; + .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15)); + .transition(width .6s ease); +} + +// Striped bars +// +// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the +// `.progress-bar-striped` class, which you just add to an existing +// `.progress-bar`. +.progress-striped .progress-bar, +.progress-bar-striped { + #gradient > .striped(); + background-size: 40px 40px; +} + +// Call animation for the active one +// +// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the +// `.progress-bar.active` approach. +.progress.active .progress-bar, +.progress-bar.active { + .animation(progress-bar-stripes 2s linear infinite); +} + + +// Variations +// ------------------------- + +.progress-bar-success { + .progress-bar-variant(@progress-bar-success-bg); +} + +.progress-bar-info { + .progress-bar-variant(@progress-bar-info-bg); +} + +.progress-bar-warning { + .progress-bar-variant(@progress-bar-warning-bg); +} + +.progress-bar-danger { + .progress-bar-variant(@progress-bar-danger-bg); +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/responsive-embed.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/responsive-embed.less new file mode 100644 index 00000000..080a5118 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/responsive-embed.less @@ -0,0 +1,35 @@ +// Embeds responsive +// +// Credit: Nicolas Gallagher and SUIT CSS. + +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; + + .embed-responsive-item, + iframe, + embed, + object, + video { + position: absolute; + top: 0; + left: 0; + bottom: 0; + height: 100%; + width: 100%; + border: 0; + } +} + +// Modifier class for 16:9 aspect ratio +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} + +// Modifier class for 4:3 aspect ratio +.embed-responsive-4by3 { + padding-bottom: 75%; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/responsive-utilities.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/responsive-utilities.less new file mode 100644 index 00000000..b1db31d7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/responsive-utilities.less @@ -0,0 +1,194 @@ +// +// Responsive: Utility classes +// -------------------------------------------------- + + +// IE10 in Windows (Phone) 8 +// +// Support for responsive views via media queries is kind of borked in IE10, for +// Surface/desktop in split view and for Windows Phone 8. This particular fix +// must be accompanied by a snippet of JavaScript to sniff the user agent and +// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at +// our Getting Started page for more information on this bug. +// +// For more information, see the following: +// +// Issue: https://github.com/twbs/bootstrap/issues/10497 +// Docs: http://getbootstrap.com/getting-started/#support-ie10-width +// Source: http://timkadlec.com/2013/01/windows-phone-8-and-device-width/ +// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/ + +@-ms-viewport { + width: device-width; +} + + +// Visibility utilities +// Note: Deprecated .visible-xs, .visible-sm, .visible-md, and .visible-lg as of v3.2.0 +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + .responsive-invisibility(); +} + +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} + +.visible-xs { + @media (max-width: @screen-xs-max) { + .responsive-visibility(); + } +} +.visible-xs-block { + @media (max-width: @screen-xs-max) { + display: block !important; + } +} +.visible-xs-inline { + @media (max-width: @screen-xs-max) { + display: inline !important; + } +} +.visible-xs-inline-block { + @media (max-width: @screen-xs-max) { + display: inline-block !important; + } +} + +.visible-sm { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + .responsive-visibility(); + } +} +.visible-sm-block { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + display: block !important; + } +} +.visible-sm-inline { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + display: inline !important; + } +} +.visible-sm-inline-block { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + display: inline-block !important; + } +} + +.visible-md { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + .responsive-visibility(); + } +} +.visible-md-block { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + display: block !important; + } +} +.visible-md-inline { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + display: inline !important; + } +} +.visible-md-inline-block { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + display: inline-block !important; + } +} + +.visible-lg { + @media (min-width: @screen-lg-min) { + .responsive-visibility(); + } +} +.visible-lg-block { + @media (min-width: @screen-lg-min) { + display: block !important; + } +} +.visible-lg-inline { + @media (min-width: @screen-lg-min) { + display: inline !important; + } +} +.visible-lg-inline-block { + @media (min-width: @screen-lg-min) { + display: inline-block !important; + } +} + +.hidden-xs { + @media (max-width: @screen-xs-max) { + .responsive-invisibility(); + } +} +.hidden-sm { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + .responsive-invisibility(); + } +} +.hidden-md { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + .responsive-invisibility(); + } +} +.hidden-lg { + @media (min-width: @screen-lg-min) { + .responsive-invisibility(); + } +} + + +// Print utilities +// +// Media queries are placed on the inside to be mixin-friendly. + +// Note: Deprecated .visible-print as of v3.2.0 +.visible-print { + .responsive-invisibility(); + + @media print { + .responsive-visibility(); + } +} +.visible-print-block { + display: none !important; + + @media print { + display: block !important; + } +} +.visible-print-inline { + display: none !important; + + @media print { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; + + @media print { + display: inline-block !important; + } +} + +.hidden-print { + @media print { + .responsive-invisibility(); + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/scaffolding.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/scaffolding.less new file mode 100644 index 00000000..64a29c6a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/scaffolding.less @@ -0,0 +1,161 @@ +// +// Scaffolding +// -------------------------------------------------- + + +// Reset the box-sizing +// +// Heads up! This reset may cause conflicts with some third-party widgets. +// For recommendations on resolving such conflicts, see +// http://getbootstrap.com/getting-started/#third-box-sizing +* { + .box-sizing(border-box); +} +*:before, +*:after { + .box-sizing(border-box); +} + + +// Body reset + +html { + font-size: 10px; + -webkit-tap-highlight-color: rgba(0,0,0,0); +} + +body { + font-family: @font-family-base; + font-size: @font-size-base; + line-height: @line-height-base; + color: @text-color; + background-color: @body-bg; +} + +// Reset fonts for relevant elements +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + + +// Links + +a { + color: @link-color; + text-decoration: none; + + &:hover, + &:focus { + color: @link-hover-color; + text-decoration: @link-hover-decoration; + } + + &:focus { + .tab-focus(); + } +} + + +// Figures +// +// We reset this here because previously Normalize had no `figure` margins. This +// ensures we don't break anyone's use of the element. + +figure { + margin: 0; +} + + +// Images + +img { + vertical-align: middle; +} + +// Responsive images (ensure images don't scale beyond their parents) +.img-responsive { + .img-responsive(); +} + +// Rounded corners +.img-rounded { + border-radius: @border-radius-large; +} + +// Image thumbnails +// +// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`. +.img-thumbnail { + padding: @thumbnail-padding; + line-height: @line-height-base; + background-color: @thumbnail-bg; + border: 1px solid @thumbnail-border; + border-radius: @thumbnail-border-radius; + .transition(all .2s ease-in-out); + + // Keep them at most 100% wide + .img-responsive(inline-block); +} + +// Perfect circle +.img-circle { + border-radius: 50%; // set radius in percents +} + + +// Horizontal rules + +hr { + margin-top: @line-height-computed; + margin-bottom: @line-height-computed; + border: 0; + border-top: 1px solid @hr-border; +} + + +// Only display content to screen readers +// +// See: http://a11yproject.com/posts/how-to-hide-content + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// Credit: HTML5 Boilerplate + +.sr-only-focusable { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } +} + + +// iOS "clickable elements" fix for role="button" +// +// Fixes "clickability" issue (and more generally, the firing of events such as focus as well) +// for traditionally non-focusable elements with role="button" +// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile + +[role="button"] { + cursor: pointer; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/tables.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/tables.less new file mode 100644 index 00000000..2242c036 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/tables.less @@ -0,0 +1,234 @@ +// +// Tables +// -------------------------------------------------- + + +table { + background-color: @table-bg; +} +caption { + padding-top: @table-cell-padding; + padding-bottom: @table-cell-padding; + color: @text-muted; + text-align: left; +} +th { + text-align: left; +} + + +// Baseline styles + +.table { + width: 100%; + max-width: 100%; + margin-bottom: @line-height-computed; + // Cells + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + padding: @table-cell-padding; + line-height: @line-height-base; + vertical-align: top; + border-top: 1px solid @table-border-color; + } + } + } + // Bottom align for column headings + > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid @table-border-color; + } + // Remove top border from thead by default + > caption + thead, + > colgroup + thead, + > thead:first-child { + > tr:first-child { + > th, + > td { + border-top: 0; + } + } + } + // Account for multiple tbody instances + > tbody + tbody { + border-top: 2px solid @table-border-color; + } + + // Nesting + .table { + background-color: @body-bg; + } +} + + +// Condensed table w/ half padding + +.table-condensed { + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + padding: @table-condensed-cell-padding; + } + } + } +} + + +// Bordered version +// +// Add borders all around the table and between all the columns. + +.table-bordered { + border: 1px solid @table-border-color; + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + border: 1px solid @table-border-color; + } + } + } + > thead > tr { + > th, + > td { + border-bottom-width: 2px; + } + } +} + + +// Zebra-striping +// +// Default zebra-stripe styles (alternating gray and transparent backgrounds) + +.table-striped { + > tbody > tr:nth-of-type(odd) { + background-color: @table-bg-accent; + } +} + + +// Hover effect +// +// Placed here since it has to come after the potential zebra striping + +.table-hover { + > tbody > tr:hover { + background-color: @table-bg-hover; + } +} + + +// Table cell sizing +// +// Reset default table behavior + +table col[class*="col-"] { + position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623) + float: none; + display: table-column; +} +table { + td, + th { + &[class*="col-"] { + position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623) + float: none; + display: table-cell; + } + } +} + + +// Table backgrounds +// +// Exact selectors below required to override `.table-striped` and prevent +// inheritance to nested tables. + +// Generate the contextual variants +.table-row-variant(active; @table-bg-active); +.table-row-variant(success; @state-success-bg); +.table-row-variant(info; @state-info-bg); +.table-row-variant(warning; @state-warning-bg); +.table-row-variant(danger; @state-danger-bg); + + +// Responsive tables +// +// Wrap your tables in `.table-responsive` and we'll make them mobile friendly +// by enabling horizontal scrolling. Only applies <768px. Everything above that +// will display normally. + +.table-responsive { + overflow-x: auto; + min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837) + + @media screen and (max-width: @screen-xs-max) { + width: 100%; + margin-bottom: (@line-height-computed * 0.75); + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid @table-border-color; + + // Tighten up spacing + > .table { + margin-bottom: 0; + + // Ensure the content doesn't wrap + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + white-space: nowrap; + } + } + } + } + + // Special overrides for the bordered tables + > .table-bordered { + border: 0; + + // Nuke the appropriate borders so that the parent can handle them + > thead, + > tbody, + > tfoot { + > tr { + > th:first-child, + > td:first-child { + border-left: 0; + } + > th:last-child, + > td:last-child { + border-right: 0; + } + } + } + + // Only nuke the last row's bottom-border in `tbody` and `tfoot` since + // chances are there will be only one `tr` in a `thead` and that would + // remove the border altogether. + > tbody, + > tfoot { + > tr:last-child { + > th, + > td { + border-bottom: 0; + } + } + } + + } + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/theme.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/theme.less new file mode 100644 index 00000000..fb617442 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/theme.less @@ -0,0 +1,291 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +// +// Load core variables and mixins +// -------------------------------------------------- + +@import "variables.less"; +@import "mixins.less"; + + +// +// Buttons +// -------------------------------------------------- + +// Common styles +.btn-default, +.btn-primary, +.btn-success, +.btn-info, +.btn-warning, +.btn-danger { + text-shadow: 0 -1px 0 rgba(0,0,0,.2); + @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075); + .box-shadow(@shadow); + + // Reset the shadow + &:active, + &.active { + .box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + .box-shadow(none); + } + + .badge { + text-shadow: none; + } +} + +// Mixin for generating new styles +.btn-styles(@btn-color: #555) { + #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%)); + .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620 + background-repeat: repeat-x; + border-color: darken(@btn-color, 14%); + + &:hover, + &:focus { + background-color: darken(@btn-color, 12%); + background-position: 0 -15px; + } + + &:active, + &.active { + background-color: darken(@btn-color, 12%); + border-color: darken(@btn-color, 14%); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + &, + &:hover, + &:focus, + &.focus, + &:active, + &.active { + background-color: darken(@btn-color, 12%); + background-image: none; + } + } +} + +// Common styles +.btn { + // Remove the gradient for the pressed/active state + &:active, + &.active { + background-image: none; + } +} + +// Apply the mixin to the buttons +.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; } +.btn-primary { .btn-styles(@btn-primary-bg); } +.btn-success { .btn-styles(@btn-success-bg); } +.btn-info { .btn-styles(@btn-info-bg); } +.btn-warning { .btn-styles(@btn-warning-bg); } +.btn-danger { .btn-styles(@btn-danger-bg); } + + +// +// Images +// -------------------------------------------------- + +.thumbnail, +.img-thumbnail { + .box-shadow(0 1px 2px rgba(0,0,0,.075)); +} + + +// +// Dropdowns +// -------------------------------------------------- + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%)); + background-color: darken(@dropdown-link-hover-bg, 5%); +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%)); + background-color: darken(@dropdown-link-active-bg, 5%); +} + + +// +// Navbar +// -------------------------------------------------- + +// Default navbar +.navbar-default { + #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg); + .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered + border-radius: @navbar-border-radius; + @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075); + .box-shadow(@shadow); + + .navbar-nav > .open > a, + .navbar-nav > .active > a { + #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%)); + .box-shadow(inset 0 3px 9px rgba(0,0,0,.075)); + } +} +.navbar-brand, +.navbar-nav > li > a { + text-shadow: 0 1px 0 rgba(255,255,255,.25); +} + +// Inverted navbar +.navbar-inverse { + #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg); + .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257 + border-radius: @navbar-border-radius; + .navbar-nav > .open > a, + .navbar-nav > .active > a { + #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%)); + .box-shadow(inset 0 3px 9px rgba(0,0,0,.25)); + } + + .navbar-brand, + .navbar-nav > li > a { + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + } +} + +// Undo rounded corners in static and fixed navbars +.navbar-static-top, +.navbar-fixed-top, +.navbar-fixed-bottom { + border-radius: 0; +} + +// Fix active state of dropdown items in collapsed mode +@media (max-width: @grid-float-breakpoint-max) { + .navbar .navbar-nav .open .dropdown-menu > .active > a { + &, + &:hover, + &:focus { + color: #fff; + #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%)); + } + } +} + + +// +// Alerts +// -------------------------------------------------- + +// Common styles +.alert { + text-shadow: 0 1px 0 rgba(255,255,255,.2); + @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05); + .box-shadow(@shadow); +} + +// Mixin for generating new styles +.alert-styles(@color) { + #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%)); + border-color: darken(@color, 15%); +} + +// Apply the mixin to the alerts +.alert-success { .alert-styles(@alert-success-bg); } +.alert-info { .alert-styles(@alert-info-bg); } +.alert-warning { .alert-styles(@alert-warning-bg); } +.alert-danger { .alert-styles(@alert-danger-bg); } + + +// +// Progress bars +// -------------------------------------------------- + +// Give the progress background some depth +.progress { + #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg) +} + +// Mixin for generating new styles +.progress-bar-styles(@color) { + #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%)); +} + +// Apply the mixin to the progress bars +.progress-bar { .progress-bar-styles(@progress-bar-bg); } +.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); } +.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); } +.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); } +.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); } + +// Reset the striped class because our mixins don't do multiple gradients and +// the above custom styles override the new `.progress-bar-striped` in v3.2.0. +.progress-bar-striped { + #gradient > .striped(); +} + + +// +// List groups +// -------------------------------------------------- + +.list-group { + border-radius: @border-radius-base; + .box-shadow(0 1px 2px rgba(0,0,0,.075)); +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%); + #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%)); + border-color: darken(@list-group-active-border, 7.5%); + + .badge { + text-shadow: none; + } +} + + +// +// Panels +// -------------------------------------------------- + +// Common styles +.panel { + .box-shadow(0 1px 2px rgba(0,0,0,.05)); +} + +// Mixin for generating new styles +.panel-heading-styles(@color) { + #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%)); +} + +// Apply the mixin to the panel headings only +.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); } +.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); } +.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); } +.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); } +.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); } +.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); } + + +// +// Wells +// -------------------------------------------------- + +.well { + #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg); + border-color: darken(@well-bg, 10%); + @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1); + .box-shadow(@shadow); +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/thumbnails.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/thumbnails.less new file mode 100644 index 00000000..0713e67d --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/thumbnails.less @@ -0,0 +1,36 @@ +// +// Thumbnails +// -------------------------------------------------- + + +// Mixin and adjust the regular image class +.thumbnail { + display: block; + padding: @thumbnail-padding; + margin-bottom: @line-height-computed; + line-height: @line-height-base; + background-color: @thumbnail-bg; + border: 1px solid @thumbnail-border; + border-radius: @thumbnail-border-radius; + .transition(border .2s ease-in-out); + + > img, + a > img { + &:extend(.img-responsive); + margin-left: auto; + margin-right: auto; + } + + // Add a hover state for linked versions only + a&:hover, + a&:focus, + a&.active { + border-color: @link-color; + } + + // Image captions + .caption { + padding: @thumbnail-caption-padding; + color: @thumbnail-caption-color; + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/tooltip.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/tooltip.less new file mode 100644 index 00000000..b48d63e0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/tooltip.less @@ -0,0 +1,101 @@ +// +// Tooltips +// -------------------------------------------------- + + +// Base class +.tooltip { + position: absolute; + z-index: @zindex-tooltip; + display: block; + // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element. + // So reset our font and text properties to avoid inheriting weird values. + .reset-text(); + font-size: @font-size-small; + + .opacity(0); + + &.in { .opacity(@tooltip-opacity); } + &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; } + &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; } + &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; } + &.left { margin-left: -3px; padding: 0 @tooltip-arrow-width; } +} + +// Wrapper for the tooltip content +.tooltip-inner { + max-width: @tooltip-max-width; + padding: 3px 8px; + color: @tooltip-color; + text-align: center; + background-color: @tooltip-bg; + border-radius: @border-radius-base; +} + +// Arrows +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +// Note: Deprecated .top-left, .top-right, .bottom-left, and .bottom-right as of v3.3.1 +.tooltip { + &.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -@tooltip-arrow-width; + border-width: @tooltip-arrow-width @tooltip-arrow-width 0; + border-top-color: @tooltip-arrow-color; + } + &.top-left .tooltip-arrow { + bottom: 0; + right: @tooltip-arrow-width; + margin-bottom: -@tooltip-arrow-width; + border-width: @tooltip-arrow-width @tooltip-arrow-width 0; + border-top-color: @tooltip-arrow-color; + } + &.top-right .tooltip-arrow { + bottom: 0; + left: @tooltip-arrow-width; + margin-bottom: -@tooltip-arrow-width; + border-width: @tooltip-arrow-width @tooltip-arrow-width 0; + border-top-color: @tooltip-arrow-color; + } + &.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -@tooltip-arrow-width; + border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0; + border-right-color: @tooltip-arrow-color; + } + &.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -@tooltip-arrow-width; + border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width; + border-left-color: @tooltip-arrow-color; + } + &.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -@tooltip-arrow-width; + border-width: 0 @tooltip-arrow-width @tooltip-arrow-width; + border-bottom-color: @tooltip-arrow-color; + } + &.bottom-left .tooltip-arrow { + top: 0; + right: @tooltip-arrow-width; + margin-top: -@tooltip-arrow-width; + border-width: 0 @tooltip-arrow-width @tooltip-arrow-width; + border-bottom-color: @tooltip-arrow-color; + } + &.bottom-right .tooltip-arrow { + top: 0; + left: @tooltip-arrow-width; + margin-top: -@tooltip-arrow-width; + border-width: 0 @tooltip-arrow-width @tooltip-arrow-width; + border-bottom-color: @tooltip-arrow-color; + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/type.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/type.less new file mode 100644 index 00000000..0d4fee48 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/type.less @@ -0,0 +1,302 @@ +// +// Typography +// -------------------------------------------------- + + +// Headings +// ------------------------- + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + font-family: @headings-font-family; + font-weight: @headings-font-weight; + line-height: @headings-line-height; + color: @headings-color; + + small, + .small { + font-weight: normal; + line-height: 1; + color: @headings-small-color; + } +} + +h1, .h1, +h2, .h2, +h3, .h3 { + margin-top: @line-height-computed; + margin-bottom: (@line-height-computed / 2); + + small, + .small { + font-size: 65%; + } +} +h4, .h4, +h5, .h5, +h6, .h6 { + margin-top: (@line-height-computed / 2); + margin-bottom: (@line-height-computed / 2); + + small, + .small { + font-size: 75%; + } +} + +h1, .h1 { font-size: @font-size-h1; } +h2, .h2 { font-size: @font-size-h2; } +h3, .h3 { font-size: @font-size-h3; } +h4, .h4 { font-size: @font-size-h4; } +h5, .h5 { font-size: @font-size-h5; } +h6, .h6 { font-size: @font-size-h6; } + + +// Body text +// ------------------------- + +p { + margin: 0 0 (@line-height-computed / 2); +} + +.lead { + margin-bottom: @line-height-computed; + font-size: floor((@font-size-base * 1.15)); + font-weight: 300; + line-height: 1.4; + + @media (min-width: @screen-sm-min) { + font-size: (@font-size-base * 1.5); + } +} + + +// Emphasis & misc +// ------------------------- + +// Ex: (12px small font / 14px base font) * 100% = about 85% +small, +.small { + font-size: floor((100% * @font-size-small / @font-size-base)); +} + +mark, +.mark { + background-color: @state-warning-bg; + padding: .2em; +} + +// Alignment +.text-left { text-align: left; } +.text-right { text-align: right; } +.text-center { text-align: center; } +.text-justify { text-align: justify; } +.text-nowrap { white-space: nowrap; } + +// Transformation +.text-lowercase { text-transform: lowercase; } +.text-uppercase { text-transform: uppercase; } +.text-capitalize { text-transform: capitalize; } + +// Contextual colors +.text-muted { + color: @text-muted; +} +.text-primary { + .text-emphasis-variant(@brand-primary); +} +.text-success { + .text-emphasis-variant(@state-success-text); +} +.text-info { + .text-emphasis-variant(@state-info-text); +} +.text-warning { + .text-emphasis-variant(@state-warning-text); +} +.text-danger { + .text-emphasis-variant(@state-danger-text); +} + +// Contextual backgrounds +// For now we'll leave these alongside the text classes until v4 when we can +// safely shift things around (per SemVer rules). +.bg-primary { + // Given the contrast here, this is the only class to have its color inverted + // automatically. + color: #fff; + .bg-variant(@brand-primary); +} +.bg-success { + .bg-variant(@state-success-bg); +} +.bg-info { + .bg-variant(@state-info-bg); +} +.bg-warning { + .bg-variant(@state-warning-bg); +} +.bg-danger { + .bg-variant(@state-danger-bg); +} + + +// Page header +// ------------------------- + +.page-header { + padding-bottom: ((@line-height-computed / 2) - 1); + margin: (@line-height-computed * 2) 0 @line-height-computed; + border-bottom: 1px solid @page-header-border-color; +} + + +// Lists +// ------------------------- + +// Unordered and Ordered lists +ul, +ol { + margin-top: 0; + margin-bottom: (@line-height-computed / 2); + ul, + ol { + margin-bottom: 0; + } +} + +// List options + +// Unstyled keeps list items block level, just removes default browser padding and list-style +.list-unstyled { + padding-left: 0; + list-style: none; +} + +// Inline turns list items into inline-block +.list-inline { + .list-unstyled(); + margin-left: -5px; + + > li { + display: inline-block; + padding-left: 5px; + padding-right: 5px; + } +} + +// Description Lists +dl { + margin-top: 0; // Remove browser default + margin-bottom: @line-height-computed; +} +dt, +dd { + line-height: @line-height-base; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; // Undo browser default +} + +// Horizontal description lists +// +// Defaults to being stacked without any of the below styles applied, until the +// grid breakpoint is reached (default of ~768px). + +.dl-horizontal { + dd { + &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present + } + + @media (min-width: @dl-horizontal-breakpoint) { + dt { + float: left; + width: (@dl-horizontal-offset - 20); + clear: left; + text-align: right; + .text-overflow(); + } + dd { + margin-left: @dl-horizontal-offset; + } + } +} + + +// Misc +// ------------------------- + +// Abbreviations and acronyms +abbr[title], +// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257 +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted @abbr-border-color; +} +.initialism { + font-size: 90%; + .text-uppercase(); +} + +// Blockquotes +blockquote { + padding: (@line-height-computed / 2) @line-height-computed; + margin: 0 0 @line-height-computed; + font-size: @blockquote-font-size; + border-left: 5px solid @blockquote-border-color; + + p, + ul, + ol { + &:last-child { + margin-bottom: 0; + } + } + + // Note: Deprecated small and .small as of v3.1.0 + // Context: https://github.com/twbs/bootstrap/issues/11660 + footer, + small, + .small { + display: block; + font-size: 80%; // back to default font-size + line-height: @line-height-base; + color: @blockquote-small-color; + + &:before { + content: '\2014 \00A0'; // em dash, nbsp + } + } +} + +// Opposite alignment of blockquote +// +// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0. +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid @blockquote-border-color; + border-left: 0; + text-align: right; + + // Account for citation + footer, + small, + .small { + &:before { content: ''; } + &:after { + content: '\00A0 \2014'; // nbsp, em dash + } + } +} + +// Addresses +address { + margin-bottom: @line-height-computed; + font-style: normal; + line-height: @line-height-base; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/utilities.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/utilities.less new file mode 100644 index 00000000..7a8ca27a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/utilities.less @@ -0,0 +1,55 @@ +// +// Utility classes +// -------------------------------------------------- + + +// Floats +// ------------------------- + +.clearfix { + .clearfix(); +} +.center-block { + .center-block(); +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} + + +// Toggling content +// ------------------------- + +// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1 +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + .text-hide(); +} + + +// Hide from screenreaders and browsers +// +// Credit: HTML5 Boilerplate + +.hidden { + display: none !important; +} + + +// For Affix plugin +// ------------------------- + +.affix { + position: fixed; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/variables.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/variables.less new file mode 100644 index 00000000..03b54980 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/variables.less @@ -0,0 +1,869 @@ +// +// Variables +// -------------------------------------------------- + + +//== Colors +// +//## Gray and brand colors for use across Bootstrap. + +@gray-base: #000; +@gray-darker: lighten(@gray-base, 13.5%); // #222 +@gray-dark: lighten(@gray-base, 20%); // #333 +@gray: lighten(@gray-base, 33.5%); // #555 +@gray-light: lighten(@gray-base, 46.7%); // #777 +@gray-lighter: lighten(@gray-base, 93.5%); // #eee + +@brand-primary: darken(#428bca, 6.5%); // #337ab7 +@brand-success: #5cb85c; +@brand-info: #5bc0de; +@brand-warning: #f0ad4e; +@brand-danger: #d9534f; + + +//== Scaffolding +// +//## Settings for some of the most global styles. + +//** Background color for ``. +@body-bg: #fff; +//** Global text color on ``. +@text-color: @gray-dark; + +//** Global textual link color. +@link-color: @brand-primary; +//** Link hover color set via `darken()` function. +@link-hover-color: darken(@link-color, 15%); +//** Link hover decoration. +@link-hover-decoration: underline; + + +//== Typography +// +//## Font, line-height, and color for body text, headings, and more. + +@font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif; +@font-family-serif: Georgia, "Times New Roman", Times, serif; +//** Default monospace fonts for ``, ``, and `
`.
+@font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
+@font-family-base:        @font-family-sans-serif;
+
+@font-size-base:          14px;
+@font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
+@font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
+
+@font-size-h1:            floor((@font-size-base * 2.6)); // ~36px
+@font-size-h2:            floor((@font-size-base * 2.15)); // ~30px
+@font-size-h3:            ceil((@font-size-base * 1.7)); // ~24px
+@font-size-h4:            ceil((@font-size-base * 1.25)); // ~18px
+@font-size-h5:            @font-size-base;
+@font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px
+
+//** Unit-less `line-height` for use in components like buttons.
+@line-height-base:        1.428571429; // 20/14
+//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
+@line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
+
+//** By default, this inherits from the ``.
+@headings-font-family:    inherit;
+@headings-font-weight:    500;
+@headings-line-height:    1.1;
+@headings-color:          inherit;
+
+
+//== Iconography
+//
+//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
+
+//** Load fonts from this directory.
+@icon-font-path:          "../fonts/";
+//** File name for all font files.
+@icon-font-name:          "glyphicons-halflings-regular";
+//** Element ID within SVG icon file.
+@icon-font-svg-id:        "glyphicons_halflingsregular";
+
+
+//== Components
+//
+//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
+
+@padding-base-vertical:     6px;
+@padding-base-horizontal:   12px;
+
+@padding-large-vertical:    10px;
+@padding-large-horizontal:  16px;
+
+@padding-small-vertical:    5px;
+@padding-small-horizontal:  10px;
+
+@padding-xs-vertical:       1px;
+@padding-xs-horizontal:     5px;
+
+@line-height-large:         1.3333333; // extra decimals for Win 8.1 Chrome
+@line-height-small:         1.5;
+
+@border-radius-base:        4px;
+@border-radius-large:       6px;
+@border-radius-small:       3px;
+
+//** Global color for active items (e.g., navs or dropdowns).
+@component-active-color:    #fff;
+//** Global background color for active items (e.g., navs or dropdowns).
+@component-active-bg:       @brand-primary;
+
+//** Width of the `border` for generating carets that indicate dropdowns.
+@caret-width-base:          4px;
+//** Carets increase slightly in size for larger components.
+@caret-width-large:         5px;
+
+
+//== Tables
+//
+//## Customizes the `.table` component with basic values, each used across all table variations.
+
+//** Padding for ``s and ``s.
+@table-cell-padding:            8px;
+//** Padding for cells in `.table-condensed`.
+@table-condensed-cell-padding:  5px;
+
+//** Default background color used for all tables.
+@table-bg:                      transparent;
+//** Background color used for `.table-striped`.
+@table-bg-accent:               #f9f9f9;
+//** Background color used for `.table-hover`.
+@table-bg-hover:                #f5f5f5;
+@table-bg-active:               @table-bg-hover;
+
+//** Border color for table and cell borders.
+@table-border-color:            #ddd;
+
+
+//== Buttons
+//
+//## For each of Bootstrap's buttons, define text, background and border color.
+
+@btn-font-weight:                normal;
+
+@btn-default-color:              #333;
+@btn-default-bg:                 #fff;
+@btn-default-border:             #ccc;
+
+@btn-primary-color:              #fff;
+@btn-primary-bg:                 @brand-primary;
+@btn-primary-border:             darken(@btn-primary-bg, 5%);
+
+@btn-success-color:              #fff;
+@btn-success-bg:                 @brand-success;
+@btn-success-border:             darken(@btn-success-bg, 5%);
+
+@btn-info-color:                 #fff;
+@btn-info-bg:                    @brand-info;
+@btn-info-border:                darken(@btn-info-bg, 5%);
+
+@btn-warning-color:              #fff;
+@btn-warning-bg:                 @brand-warning;
+@btn-warning-border:             darken(@btn-warning-bg, 5%);
+
+@btn-danger-color:               #fff;
+@btn-danger-bg:                  @brand-danger;
+@btn-danger-border:              darken(@btn-danger-bg, 5%);
+
+@btn-link-disabled-color:        @gray-light;
+
+// Allows for customizing button radius independently from global border radius
+@btn-border-radius-base:         @border-radius-base;
+@btn-border-radius-large:        @border-radius-large;
+@btn-border-radius-small:        @border-radius-small;
+
+
+//== Forms
+//
+//##
+
+//** `` background color
+@input-bg:                       #fff;
+//** `` background color
+@input-bg-disabled:              @gray-lighter;
+
+//** Text color for ``s
+@input-color:                    @gray;
+//** `` border color
+@input-border:                   #ccc;
+
+// TODO: Rename `@input-border-radius` to `@input-border-radius-base` in v4
+//** Default `.form-control` border radius
+// This has no effect on ``s in CSS.
+@input-border-radius:            @border-radius-base;
+//** Large `.form-control` border radius
+@input-border-radius-large:      @border-radius-large;
+//** Small `.form-control` border radius
+@input-border-radius-small:      @border-radius-small;
+
+//** Border color for inputs on focus
+@input-border-focus:             #66afe9;
+
+//** Placeholder text color
+@input-color-placeholder:        #999;
+
+//** Default `.form-control` height
+@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
+//** Large `.form-control` height
+@input-height-large:             (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
+//** Small `.form-control` height
+@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
+
+//** `.form-group` margin
+@form-group-margin-bottom:       15px;
+
+@legend-color:                   @gray-dark;
+@legend-border-color:            #e5e5e5;
+
+//** Background color for textual input addons
+@input-group-addon-bg:           @gray-lighter;
+//** Border color for textual input addons
+@input-group-addon-border-color: @input-border;
+
+//** Disabled cursor for form controls and buttons.
+@cursor-disabled:                not-allowed;
+
+
+//== Dropdowns
+//
+//## Dropdown menu container and contents.
+
+//** Background for the dropdown menu.
+@dropdown-bg:                    #fff;
+//** Dropdown menu `border-color`.
+@dropdown-border:                rgba(0,0,0,.15);
+//** Dropdown menu `border-color` **for IE8**.
+@dropdown-fallback-border:       #ccc;
+//** Divider color for between dropdown items.
+@dropdown-divider-bg:            #e5e5e5;
+
+//** Dropdown link text color.
+@dropdown-link-color:            @gray-dark;
+//** Hover color for dropdown links.
+@dropdown-link-hover-color:      darken(@gray-dark, 5%);
+//** Hover background for dropdown links.
+@dropdown-link-hover-bg:         #f5f5f5;
+
+//** Active dropdown menu item text color.
+@dropdown-link-active-color:     @component-active-color;
+//** Active dropdown menu item background color.
+@dropdown-link-active-bg:        @component-active-bg;
+
+//** Disabled dropdown menu item background color.
+@dropdown-link-disabled-color:   @gray-light;
+
+//** Text color for headers within dropdown menus.
+@dropdown-header-color:          @gray-light;
+
+//** Deprecated `@dropdown-caret-color` as of v3.1.0
+@dropdown-caret-color:           #000;
+
+
+//-- Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+//
+// Note: These variables are not generated into the Customizer.
+
+@zindex-navbar:            1000;
+@zindex-dropdown:          1000;
+@zindex-popover:           1060;
+@zindex-tooltip:           1070;
+@zindex-navbar-fixed:      1030;
+@zindex-modal-background:  1040;
+@zindex-modal:             1050;
+
+
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
+
+// Extra small screen / phone
+//** Deprecated `@screen-xs` as of v3.0.1
+@screen-xs:                  480px;
+//** Deprecated `@screen-xs-min` as of v3.2.0
+@screen-xs-min:              @screen-xs;
+//** Deprecated `@screen-phone` as of v3.0.1
+@screen-phone:               @screen-xs-min;
+
+// Small screen / tablet
+//** Deprecated `@screen-sm` as of v3.0.1
+@screen-sm:                  768px;
+@screen-sm-min:              @screen-sm;
+//** Deprecated `@screen-tablet` as of v3.0.1
+@screen-tablet:              @screen-sm-min;
+
+// Medium screen / desktop
+//** Deprecated `@screen-md` as of v3.0.1
+@screen-md:                  992px;
+@screen-md-min:              @screen-md;
+//** Deprecated `@screen-desktop` as of v3.0.1
+@screen-desktop:             @screen-md-min;
+
+// Large screen / wide desktop
+//** Deprecated `@screen-lg` as of v3.0.1
+@screen-lg:                  1200px;
+@screen-lg-min:              @screen-lg;
+//** Deprecated `@screen-lg-desktop` as of v3.0.1
+@screen-lg-desktop:          @screen-lg-min;
+
+// So media queries don't overlap when required, provide a maximum
+@screen-xs-max:              (@screen-sm-min - 1);
+@screen-sm-max:              (@screen-md-min - 1);
+@screen-md-max:              (@screen-lg-min - 1);
+
+
+//== Grid system
+//
+//## Define your custom responsive grid.
+
+//** Number of columns in the grid.
+@grid-columns:              12;
+//** Padding between columns. Gets divided in half for the left and right.
+@grid-gutter-width:         30px;
+// Navbar collapse
+//** Point at which the navbar becomes uncollapsed.
+@grid-float-breakpoint:     @screen-sm-min;
+//** Point at which the navbar begins collapsing.
+@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
+
+
+//== Container sizes
+//
+//## Define the maximum width of `.container` for different screen sizes.
+
+// Small screen / tablet
+@container-tablet:             (720px + @grid-gutter-width);
+//** For `@screen-sm-min` and up.
+@container-sm:                 @container-tablet;
+
+// Medium screen / desktop
+@container-desktop:            (940px + @grid-gutter-width);
+//** For `@screen-md-min` and up.
+@container-md:                 @container-desktop;
+
+// Large screen / wide desktop
+@container-large-desktop:      (1140px + @grid-gutter-width);
+//** For `@screen-lg-min` and up.
+@container-lg:                 @container-large-desktop;
+
+
+//== Navbar
+//
+//##
+
+// Basics of a navbar
+@navbar-height:                    50px;
+@navbar-margin-bottom:             @line-height-computed;
+@navbar-border-radius:             @border-radius-base;
+@navbar-padding-horizontal:        floor((@grid-gutter-width / 2));
+@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);
+@navbar-collapse-max-height:       340px;
+
+@navbar-default-color:             #777;
+@navbar-default-bg:                #f8f8f8;
+@navbar-default-border:            darken(@navbar-default-bg, 6.5%);
+
+// Navbar links
+@navbar-default-link-color:                #777;
+@navbar-default-link-hover-color:          #333;
+@navbar-default-link-hover-bg:             transparent;
+@navbar-default-link-active-color:         #555;
+@navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);
+@navbar-default-link-disabled-color:       #ccc;
+@navbar-default-link-disabled-bg:          transparent;
+
+// Navbar brand label
+@navbar-default-brand-color:               @navbar-default-link-color;
+@navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);
+@navbar-default-brand-hover-bg:            transparent;
+
+// Navbar toggle
+@navbar-default-toggle-hover-bg:           #ddd;
+@navbar-default-toggle-icon-bar-bg:        #888;
+@navbar-default-toggle-border-color:       #ddd;
+
+
+//=== Inverted navbar
+// Reset inverted navbar basics
+@navbar-inverse-color:                      lighten(@gray-light, 15%);
+@navbar-inverse-bg:                         #222;
+@navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);
+
+// Inverted navbar links
+@navbar-inverse-link-color:                 lighten(@gray-light, 15%);
+@navbar-inverse-link-hover-color:           #fff;
+@navbar-inverse-link-hover-bg:              transparent;
+@navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;
+@navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 10%);
+@navbar-inverse-link-disabled-color:        #444;
+@navbar-inverse-link-disabled-bg:           transparent;
+
+// Inverted navbar brand label
+@navbar-inverse-brand-color:                @navbar-inverse-link-color;
+@navbar-inverse-brand-hover-color:          #fff;
+@navbar-inverse-brand-hover-bg:             transparent;
+
+// Inverted navbar toggle
+@navbar-inverse-toggle-hover-bg:            #333;
+@navbar-inverse-toggle-icon-bar-bg:         #fff;
+@navbar-inverse-toggle-border-color:        #333;
+
+
+//== Navs
+//
+//##
+
+//=== Shared nav styles
+@nav-link-padding:                          10px 15px;
+@nav-link-hover-bg:                         @gray-lighter;
+
+@nav-disabled-link-color:                   @gray-light;
+@nav-disabled-link-hover-color:             @gray-light;
+
+//== Tabs
+@nav-tabs-border-color:                     #ddd;
+
+@nav-tabs-link-hover-border-color:          @gray-lighter;
+
+@nav-tabs-active-link-hover-bg:             @body-bg;
+@nav-tabs-active-link-hover-color:          @gray;
+@nav-tabs-active-link-hover-border-color:   #ddd;
+
+@nav-tabs-justified-link-border-color:            #ddd;
+@nav-tabs-justified-active-link-border-color:     @body-bg;
+
+//== Pills
+@nav-pills-border-radius:                   @border-radius-base;
+@nav-pills-active-link-hover-bg:            @component-active-bg;
+@nav-pills-active-link-hover-color:         @component-active-color;
+
+
+//== Pagination
+//
+//##
+
+@pagination-color:                     @link-color;
+@pagination-bg:                        #fff;
+@pagination-border:                    #ddd;
+
+@pagination-hover-color:               @link-hover-color;
+@pagination-hover-bg:                  @gray-lighter;
+@pagination-hover-border:              #ddd;
+
+@pagination-active-color:              #fff;
+@pagination-active-bg:                 @brand-primary;
+@pagination-active-border:             @brand-primary;
+
+@pagination-disabled-color:            @gray-light;
+@pagination-disabled-bg:               #fff;
+@pagination-disabled-border:           #ddd;
+
+
+//== Pager
+//
+//##
+
+@pager-bg:                             @pagination-bg;
+@pager-border:                         @pagination-border;
+@pager-border-radius:                  15px;
+
+@pager-hover-bg:                       @pagination-hover-bg;
+
+@pager-active-bg:                      @pagination-active-bg;
+@pager-active-color:                   @pagination-active-color;
+
+@pager-disabled-color:                 @pagination-disabled-color;
+
+
+//== Jumbotron
+//
+//##
+
+@jumbotron-padding:              30px;
+@jumbotron-color:                inherit;
+@jumbotron-bg:                   @gray-lighter;
+@jumbotron-heading-color:        inherit;
+@jumbotron-font-size:            ceil((@font-size-base * 1.5));
+@jumbotron-heading-font-size:    ceil((@font-size-base * 4.5));
+
+
+//== Form states and alerts
+//
+//## Define colors for form feedback states and, by default, alerts.
+
+@state-success-text:             #3c763d;
+@state-success-bg:               #dff0d8;
+@state-success-border:           darken(spin(@state-success-bg, -10), 5%);
+
+@state-info-text:                #31708f;
+@state-info-bg:                  #d9edf7;
+@state-info-border:              darken(spin(@state-info-bg, -10), 7%);
+
+@state-warning-text:             #8a6d3b;
+@state-warning-bg:               #fcf8e3;
+@state-warning-border:           darken(spin(@state-warning-bg, -10), 5%);
+
+@state-danger-text:              #a94442;
+@state-danger-bg:                #f2dede;
+@state-danger-border:            darken(spin(@state-danger-bg, -10), 5%);
+
+
+//== Tooltips
+//
+//##
+
+//** Tooltip max width
+@tooltip-max-width:           200px;
+//** Tooltip text color
+@tooltip-color:               #fff;
+//** Tooltip background color
+@tooltip-bg:                  #000;
+@tooltip-opacity:             .9;
+
+//** Tooltip arrow width
+@tooltip-arrow-width:         5px;
+//** Tooltip arrow color
+@tooltip-arrow-color:         @tooltip-bg;
+
+
+//== Popovers
+//
+//##
+
+//** Popover body background color
+@popover-bg:                          #fff;
+//** Popover maximum width
+@popover-max-width:                   276px;
+//** Popover border color
+@popover-border-color:                rgba(0,0,0,.2);
+//** Popover fallback border color
+@popover-fallback-border-color:       #ccc;
+
+//** Popover title background color
+@popover-title-bg:                    darken(@popover-bg, 3%);
+
+//** Popover arrow width
+@popover-arrow-width:                 10px;
+//** Popover arrow color
+@popover-arrow-color:                 @popover-bg;
+
+//** Popover outer arrow width
+@popover-arrow-outer-width:           (@popover-arrow-width + 1);
+//** Popover outer arrow color
+@popover-arrow-outer-color:           fadein(@popover-border-color, 5%);
+//** Popover outer arrow fallback color
+@popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);
+
+
+//== Labels
+//
+//##
+
+//** Default label background color
+@label-default-bg:            @gray-light;
+//** Primary label background color
+@label-primary-bg:            @brand-primary;
+//** Success label background color
+@label-success-bg:            @brand-success;
+//** Info label background color
+@label-info-bg:               @brand-info;
+//** Warning label background color
+@label-warning-bg:            @brand-warning;
+//** Danger label background color
+@label-danger-bg:             @brand-danger;
+
+//** Default label text color
+@label-color:                 #fff;
+//** Default text color of a linked label
+@label-link-hover-color:      #fff;
+
+
+//== Modals
+//
+//##
+
+//** Padding applied to the modal body
+@modal-inner-padding:         15px;
+
+//** Padding applied to the modal title
+@modal-title-padding:         15px;
+//** Modal title line-height
+@modal-title-line-height:     @line-height-base;
+
+//** Background color of modal content area
+@modal-content-bg:                             #fff;
+//** Modal content border color
+@modal-content-border-color:                   rgba(0,0,0,.2);
+//** Modal content border color **for IE8**
+@modal-content-fallback-border-color:          #999;
+
+//** Modal backdrop background color
+@modal-backdrop-bg:           #000;
+//** Modal backdrop opacity
+@modal-backdrop-opacity:      .5;
+//** Modal header border color
+@modal-header-border-color:   #e5e5e5;
+//** Modal footer border color
+@modal-footer-border-color:   @modal-header-border-color;
+
+@modal-lg:                    900px;
+@modal-md:                    600px;
+@modal-sm:                    300px;
+
+
+//== Alerts
+//
+//## Define alert colors, border radius, and padding.
+
+@alert-padding:               15px;
+@alert-border-radius:         @border-radius-base;
+@alert-link-font-weight:      bold;
+
+@alert-success-bg:            @state-success-bg;
+@alert-success-text:          @state-success-text;
+@alert-success-border:        @state-success-border;
+
+@alert-info-bg:               @state-info-bg;
+@alert-info-text:             @state-info-text;
+@alert-info-border:           @state-info-border;
+
+@alert-warning-bg:            @state-warning-bg;
+@alert-warning-text:          @state-warning-text;
+@alert-warning-border:        @state-warning-border;
+
+@alert-danger-bg:             @state-danger-bg;
+@alert-danger-text:           @state-danger-text;
+@alert-danger-border:         @state-danger-border;
+
+
+//== Progress bars
+//
+//##
+
+//** Background color of the whole progress component
+@progress-bg:                 #f5f5f5;
+//** Progress bar text color
+@progress-bar-color:          #fff;
+//** Variable for setting rounded corners on progress bar.
+@progress-border-radius:      @border-radius-base;
+
+//** Default progress bar color
+@progress-bar-bg:             @brand-primary;
+//** Success progress bar color
+@progress-bar-success-bg:     @brand-success;
+//** Warning progress bar color
+@progress-bar-warning-bg:     @brand-warning;
+//** Danger progress bar color
+@progress-bar-danger-bg:      @brand-danger;
+//** Info progress bar color
+@progress-bar-info-bg:        @brand-info;
+
+
+//== List group
+//
+//##
+
+//** Background color on `.list-group-item`
+@list-group-bg:                 #fff;
+//** `.list-group-item` border color
+@list-group-border:             #ddd;
+//** List group border radius
+@list-group-border-radius:      @border-radius-base;
+
+//** Background color of single list items on hover
+@list-group-hover-bg:           #f5f5f5;
+//** Text color of active list items
+@list-group-active-color:       @component-active-color;
+//** Background color of active list items
+@list-group-active-bg:          @component-active-bg;
+//** Border color of active list elements
+@list-group-active-border:      @list-group-active-bg;
+//** Text color for content within active list items
+@list-group-active-text-color:  lighten(@list-group-active-bg, 40%);
+
+//** Text color of disabled list items
+@list-group-disabled-color:      @gray-light;
+//** Background color of disabled list items
+@list-group-disabled-bg:         @gray-lighter;
+//** Text color for content within disabled list items
+@list-group-disabled-text-color: @list-group-disabled-color;
+
+@list-group-link-color:         #555;
+@list-group-link-hover-color:   @list-group-link-color;
+@list-group-link-heading-color: #333;
+
+
+//== Panels
+//
+//##
+
+@panel-bg:                    #fff;
+@panel-body-padding:          15px;
+@panel-heading-padding:       10px 15px;
+@panel-footer-padding:        @panel-heading-padding;
+@panel-border-radius:         @border-radius-base;
+
+//** Border color for elements within panels
+@panel-inner-border:          #ddd;
+@panel-footer-bg:             #f5f5f5;
+
+@panel-default-text:          @gray-dark;
+@panel-default-border:        #ddd;
+@panel-default-heading-bg:    #f5f5f5;
+
+@panel-primary-text:          #fff;
+@panel-primary-border:        @brand-primary;
+@panel-primary-heading-bg:    @brand-primary;
+
+@panel-success-text:          @state-success-text;
+@panel-success-border:        @state-success-border;
+@panel-success-heading-bg:    @state-success-bg;
+
+@panel-info-text:             @state-info-text;
+@panel-info-border:           @state-info-border;
+@panel-info-heading-bg:       @state-info-bg;
+
+@panel-warning-text:          @state-warning-text;
+@panel-warning-border:        @state-warning-border;
+@panel-warning-heading-bg:    @state-warning-bg;
+
+@panel-danger-text:           @state-danger-text;
+@panel-danger-border:         @state-danger-border;
+@panel-danger-heading-bg:     @state-danger-bg;
+
+
+//== Thumbnails
+//
+//##
+
+//** Padding around the thumbnail image
+@thumbnail-padding:           4px;
+//** Thumbnail background color
+@thumbnail-bg:                @body-bg;
+//** Thumbnail border color
+@thumbnail-border:            #ddd;
+//** Thumbnail border radius
+@thumbnail-border-radius:     @border-radius-base;
+
+//** Custom text color for thumbnail captions
+@thumbnail-caption-color:     @text-color;
+//** Padding around the thumbnail caption
+@thumbnail-caption-padding:   9px;
+
+
+//== Wells
+//
+//##
+
+@well-bg:                     #f5f5f5;
+@well-border:                 darken(@well-bg, 7%);
+
+
+//== Badges
+//
+//##
+
+@badge-color:                 #fff;
+//** Linked badge text color on hover
+@badge-link-hover-color:      #fff;
+@badge-bg:                    @gray-light;
+
+//** Badge text color in active nav link
+@badge-active-color:          @link-color;
+//** Badge background color in active nav link
+@badge-active-bg:             #fff;
+
+@badge-font-weight:           bold;
+@badge-line-height:           1;
+@badge-border-radius:         10px;
+
+
+//== Breadcrumbs
+//
+//##
+
+@breadcrumb-padding-vertical:   8px;
+@breadcrumb-padding-horizontal: 15px;
+//** Breadcrumb background color
+@breadcrumb-bg:                 #f5f5f5;
+//** Breadcrumb text color
+@breadcrumb-color:              #ccc;
+//** Text color of current page in the breadcrumb
+@breadcrumb-active-color:       @gray-light;
+//** Textual separator for between breadcrumb elements
+@breadcrumb-separator:          "/";
+
+
+//== Carousel
+//
+//##
+
+@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);
+
+@carousel-control-color:                      #fff;
+@carousel-control-width:                      15%;
+@carousel-control-opacity:                    .5;
+@carousel-control-font-size:                  20px;
+
+@carousel-indicator-active-bg:                #fff;
+@carousel-indicator-border-color:             #fff;
+
+@carousel-caption-color:                      #fff;
+
+
+//== Close
+//
+//##
+
+@close-font-weight:           bold;
+@close-color:                 #000;
+@close-text-shadow:           0 1px 0 #fff;
+
+
+//== Code
+//
+//##
+
+@code-color:                  #c7254e;
+@code-bg:                     #f9f2f4;
+
+@kbd-color:                   #fff;
+@kbd-bg:                      #333;
+
+@pre-bg:                      #f5f5f5;
+@pre-color:                   @gray-dark;
+@pre-border-color:            #ccc;
+@pre-scrollable-max-height:   340px;
+
+
+//== Type
+//
+//##
+
+//** Horizontal offset for forms and lists.
+@component-offset-horizontal: 180px;
+//** Text muted color
+@text-muted:                  @gray-light;
+//** Abbreviations and acronyms border color
+@abbr-border-color:           @gray-light;
+//** Headings small color
+@headings-small-color:        @gray-light;
+//** Blockquote small color
+@blockquote-small-color:      @gray-light;
+//** Blockquote font size
+@blockquote-font-size:        (@font-size-base * 1.25);
+//** Blockquote border color
+@blockquote-border-color:     @gray-lighter;
+//** Page header border color
+@page-header-border-color:    @gray-lighter;
+//** Width of horizontal description list titles
+@dl-horizontal-offset:        @component-offset-horizontal;
+//** Point at which .dl-horizontal becomes horizontal
+@dl-horizontal-breakpoint:    @grid-float-breakpoint;
+//** Horizontal line color.
+@hr-border:                   @gray-lighter;
diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/wells.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/wells.less
new file mode 100644
index 00000000..15d072b0
--- /dev/null
+++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/bootstrap/less/wells.less
@@ -0,0 +1,29 @@
+//
+// Wells
+// --------------------------------------------------
+
+
+// Base class
+.well {
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: @well-bg;
+  border: 1px solid @well-border;
+  border-radius: @border-radius-base;
+  .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
+  blockquote {
+    border-color: #ddd;
+    border-color: rgba(0,0,0,.15);
+  }
+}
+
+// Sizes
+.well-lg {
+  padding: 24px;
+  border-radius: @border-radius-large;
+}
+.well-sm {
+  padding: 9px;
+  border-radius: @border-radius-small;
+}
diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/css/font-awesome.css b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/css/font-awesome.css
new file mode 100644
index 00000000..ee906a81
--- /dev/null
+++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/css/font-awesome.css
@@ -0,0 +1,2337 @@
+/*!
+ *  Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
+ *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */
+/* FONT PATH
+ * -------------------------- */
+@font-face {
+  font-family: 'FontAwesome';
+  src: url('../fonts/fontawesome-webfont.eot?v=4.7.0');
+  src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
+  font-weight: normal;
+  font-style: normal;
+}
+.fa {
+  display: inline-block;
+  font: normal normal normal 14px/1 FontAwesome;
+  font-size: inherit;
+  text-rendering: auto;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+/* makes the font 33% larger relative to the icon container */
+.fa-lg {
+  font-size: 1.33333333em;
+  line-height: 0.75em;
+  vertical-align: -15%;
+}
+.fa-2x {
+  font-size: 2em;
+}
+.fa-3x {
+  font-size: 3em;
+}
+.fa-4x {
+  font-size: 4em;
+}
+.fa-5x {
+  font-size: 5em;
+}
+.fa-fw {
+  width: 1.28571429em;
+  text-align: center;
+}
+.fa-ul {
+  padding-left: 0;
+  margin-left: 2.14285714em;
+  list-style-type: none;
+}
+.fa-ul > li {
+  position: relative;
+}
+.fa-li {
+  position: absolute;
+  left: -2.14285714em;
+  width: 2.14285714em;
+  top: 0.14285714em;
+  text-align: center;
+}
+.fa-li.fa-lg {
+  left: -1.85714286em;
+}
+.fa-border {
+  padding: .2em .25em .15em;
+  border: solid 0.08em #eeeeee;
+  border-radius: .1em;
+}
+.fa-pull-left {
+  float: left;
+}
+.fa-pull-right {
+  float: right;
+}
+.fa.fa-pull-left {
+  margin-right: .3em;
+}
+.fa.fa-pull-right {
+  margin-left: .3em;
+}
+/* Deprecated as of 4.4.0 */
+.pull-right {
+  float: right;
+}
+.pull-left {
+  float: left;
+}
+.fa.pull-left {
+  margin-right: .3em;
+}
+.fa.pull-right {
+  margin-left: .3em;
+}
+.fa-spin {
+  -webkit-animation: fa-spin 2s infinite linear;
+  animation: fa-spin 2s infinite linear;
+}
+.fa-pulse {
+  -webkit-animation: fa-spin 1s infinite steps(8);
+  animation: fa-spin 1s infinite steps(8);
+}
+@-webkit-keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(359deg);
+    transform: rotate(359deg);
+  }
+}
+@keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(359deg);
+    transform: rotate(359deg);
+  }
+}
+.fa-rotate-90 {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
+  -webkit-transform: rotate(90deg);
+  -ms-transform: rotate(90deg);
+  transform: rotate(90deg);
+}
+.fa-rotate-180 {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
+  -webkit-transform: rotate(180deg);
+  -ms-transform: rotate(180deg);
+  transform: rotate(180deg);
+}
+.fa-rotate-270 {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
+  -webkit-transform: rotate(270deg);
+  -ms-transform: rotate(270deg);
+  transform: rotate(270deg);
+}
+.fa-flip-horizontal {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
+  -webkit-transform: scale(-1, 1);
+  -ms-transform: scale(-1, 1);
+  transform: scale(-1, 1);
+}
+.fa-flip-vertical {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
+  -webkit-transform: scale(1, -1);
+  -ms-transform: scale(1, -1);
+  transform: scale(1, -1);
+}
+:root .fa-rotate-90,
+:root .fa-rotate-180,
+:root .fa-rotate-270,
+:root .fa-flip-horizontal,
+:root .fa-flip-vertical {
+  filter: none;
+}
+.fa-stack {
+  position: relative;
+  display: inline-block;
+  width: 2em;
+  height: 2em;
+  line-height: 2em;
+  vertical-align: middle;
+}
+.fa-stack-1x,
+.fa-stack-2x {
+  position: absolute;
+  left: 0;
+  width: 100%;
+  text-align: center;
+}
+.fa-stack-1x {
+  line-height: inherit;
+}
+.fa-stack-2x {
+  font-size: 2em;
+}
+.fa-inverse {
+  color: #ffffff;
+}
+/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
+   readers do not read off random characters that represent icons */
+.fa-glass:before {
+  content: "\f000";
+}
+.fa-music:before {
+  content: "\f001";
+}
+.fa-search:before {
+  content: "\f002";
+}
+.fa-envelope-o:before {
+  content: "\f003";
+}
+.fa-heart:before {
+  content: "\f004";
+}
+.fa-star:before {
+  content: "\f005";
+}
+.fa-star-o:before {
+  content: "\f006";
+}
+.fa-user:before {
+  content: "\f007";
+}
+.fa-film:before {
+  content: "\f008";
+}
+.fa-th-large:before {
+  content: "\f009";
+}
+.fa-th:before {
+  content: "\f00a";
+}
+.fa-th-list:before {
+  content: "\f00b";
+}
+.fa-check:before {
+  content: "\f00c";
+}
+.fa-remove:before,
+.fa-close:before,
+.fa-times:before {
+  content: "\f00d";
+}
+.fa-search-plus:before {
+  content: "\f00e";
+}
+.fa-search-minus:before {
+  content: "\f010";
+}
+.fa-power-off:before {
+  content: "\f011";
+}
+.fa-signal:before {
+  content: "\f012";
+}
+.fa-gear:before,
+.fa-cog:before {
+  content: "\f013";
+}
+.fa-trash-o:before {
+  content: "\f014";
+}
+.fa-home:before {
+  content: "\f015";
+}
+.fa-file-o:before {
+  content: "\f016";
+}
+.fa-clock-o:before {
+  content: "\f017";
+}
+.fa-road:before {
+  content: "\f018";
+}
+.fa-download:before {
+  content: "\f019";
+}
+.fa-arrow-circle-o-down:before {
+  content: "\f01a";
+}
+.fa-arrow-circle-o-up:before {
+  content: "\f01b";
+}
+.fa-inbox:before {
+  content: "\f01c";
+}
+.fa-play-circle-o:before {
+  content: "\f01d";
+}
+.fa-rotate-right:before,
+.fa-repeat:before {
+  content: "\f01e";
+}
+.fa-refresh:before {
+  content: "\f021";
+}
+.fa-list-alt:before {
+  content: "\f022";
+}
+.fa-lock:before {
+  content: "\f023";
+}
+.fa-flag:before {
+  content: "\f024";
+}
+.fa-headphones:before {
+  content: "\f025";
+}
+.fa-volume-off:before {
+  content: "\f026";
+}
+.fa-volume-down:before {
+  content: "\f027";
+}
+.fa-volume-up:before {
+  content: "\f028";
+}
+.fa-qrcode:before {
+  content: "\f029";
+}
+.fa-barcode:before {
+  content: "\f02a";
+}
+.fa-tag:before {
+  content: "\f02b";
+}
+.fa-tags:before {
+  content: "\f02c";
+}
+.fa-book:before {
+  content: "\f02d";
+}
+.fa-bookmark:before {
+  content: "\f02e";
+}
+.fa-print:before {
+  content: "\f02f";
+}
+.fa-camera:before {
+  content: "\f030";
+}
+.fa-font:before {
+  content: "\f031";
+}
+.fa-bold:before {
+  content: "\f032";
+}
+.fa-italic:before {
+  content: "\f033";
+}
+.fa-text-height:before {
+  content: "\f034";
+}
+.fa-text-width:before {
+  content: "\f035";
+}
+.fa-align-left:before {
+  content: "\f036";
+}
+.fa-align-center:before {
+  content: "\f037";
+}
+.fa-align-right:before {
+  content: "\f038";
+}
+.fa-align-justify:before {
+  content: "\f039";
+}
+.fa-list:before {
+  content: "\f03a";
+}
+.fa-dedent:before,
+.fa-outdent:before {
+  content: "\f03b";
+}
+.fa-indent:before {
+  content: "\f03c";
+}
+.fa-video-camera:before {
+  content: "\f03d";
+}
+.fa-photo:before,
+.fa-image:before,
+.fa-picture-o:before {
+  content: "\f03e";
+}
+.fa-pencil:before {
+  content: "\f040";
+}
+.fa-map-marker:before {
+  content: "\f041";
+}
+.fa-adjust:before {
+  content: "\f042";
+}
+.fa-tint:before {
+  content: "\f043";
+}
+.fa-edit:before,
+.fa-pencil-square-o:before {
+  content: "\f044";
+}
+.fa-share-square-o:before {
+  content: "\f045";
+}
+.fa-check-square-o:before {
+  content: "\f046";
+}
+.fa-arrows:before {
+  content: "\f047";
+}
+.fa-step-backward:before {
+  content: "\f048";
+}
+.fa-fast-backward:before {
+  content: "\f049";
+}
+.fa-backward:before {
+  content: "\f04a";
+}
+.fa-play:before {
+  content: "\f04b";
+}
+.fa-pause:before {
+  content: "\f04c";
+}
+.fa-stop:before {
+  content: "\f04d";
+}
+.fa-forward:before {
+  content: "\f04e";
+}
+.fa-fast-forward:before {
+  content: "\f050";
+}
+.fa-step-forward:before {
+  content: "\f051";
+}
+.fa-eject:before {
+  content: "\f052";
+}
+.fa-chevron-left:before {
+  content: "\f053";
+}
+.fa-chevron-right:before {
+  content: "\f054";
+}
+.fa-plus-circle:before {
+  content: "\f055";
+}
+.fa-minus-circle:before {
+  content: "\f056";
+}
+.fa-times-circle:before {
+  content: "\f057";
+}
+.fa-check-circle:before {
+  content: "\f058";
+}
+.fa-question-circle:before {
+  content: "\f059";
+}
+.fa-info-circle:before {
+  content: "\f05a";
+}
+.fa-crosshairs:before {
+  content: "\f05b";
+}
+.fa-times-circle-o:before {
+  content: "\f05c";
+}
+.fa-check-circle-o:before {
+  content: "\f05d";
+}
+.fa-ban:before {
+  content: "\f05e";
+}
+.fa-arrow-left:before {
+  content: "\f060";
+}
+.fa-arrow-right:before {
+  content: "\f061";
+}
+.fa-arrow-up:before {
+  content: "\f062";
+}
+.fa-arrow-down:before {
+  content: "\f063";
+}
+.fa-mail-forward:before,
+.fa-share:before {
+  content: "\f064";
+}
+.fa-expand:before {
+  content: "\f065";
+}
+.fa-compress:before {
+  content: "\f066";
+}
+.fa-plus:before {
+  content: "\f067";
+}
+.fa-minus:before {
+  content: "\f068";
+}
+.fa-asterisk:before {
+  content: "\f069";
+}
+.fa-exclamation-circle:before {
+  content: "\f06a";
+}
+.fa-gift:before {
+  content: "\f06b";
+}
+.fa-leaf:before {
+  content: "\f06c";
+}
+.fa-fire:before {
+  content: "\f06d";
+}
+.fa-eye:before {
+  content: "\f06e";
+}
+.fa-eye-slash:before {
+  content: "\f070";
+}
+.fa-warning:before,
+.fa-exclamation-triangle:before {
+  content: "\f071";
+}
+.fa-plane:before {
+  content: "\f072";
+}
+.fa-calendar:before {
+  content: "\f073";
+}
+.fa-random:before {
+  content: "\f074";
+}
+.fa-comment:before {
+  content: "\f075";
+}
+.fa-magnet:before {
+  content: "\f076";
+}
+.fa-chevron-up:before {
+  content: "\f077";
+}
+.fa-chevron-down:before {
+  content: "\f078";
+}
+.fa-retweet:before {
+  content: "\f079";
+}
+.fa-shopping-cart:before {
+  content: "\f07a";
+}
+.fa-folder:before {
+  content: "\f07b";
+}
+.fa-folder-open:before {
+  content: "\f07c";
+}
+.fa-arrows-v:before {
+  content: "\f07d";
+}
+.fa-arrows-h:before {
+  content: "\f07e";
+}
+.fa-bar-chart-o:before,
+.fa-bar-chart:before {
+  content: "\f080";
+}
+.fa-twitter-square:before {
+  content: "\f081";
+}
+.fa-facebook-square:before {
+  content: "\f082";
+}
+.fa-camera-retro:before {
+  content: "\f083";
+}
+.fa-key:before {
+  content: "\f084";
+}
+.fa-gears:before,
+.fa-cogs:before {
+  content: "\f085";
+}
+.fa-comments:before {
+  content: "\f086";
+}
+.fa-thumbs-o-up:before {
+  content: "\f087";
+}
+.fa-thumbs-o-down:before {
+  content: "\f088";
+}
+.fa-star-half:before {
+  content: "\f089";
+}
+.fa-heart-o:before {
+  content: "\f08a";
+}
+.fa-sign-out:before {
+  content: "\f08b";
+}
+.fa-linkedin-square:before {
+  content: "\f08c";
+}
+.fa-thumb-tack:before {
+  content: "\f08d";
+}
+.fa-external-link:before {
+  content: "\f08e";
+}
+.fa-sign-in:before {
+  content: "\f090";
+}
+.fa-trophy:before {
+  content: "\f091";
+}
+.fa-github-square:before {
+  content: "\f092";
+}
+.fa-upload:before {
+  content: "\f093";
+}
+.fa-lemon-o:before {
+  content: "\f094";
+}
+.fa-phone:before {
+  content: "\f095";
+}
+.fa-square-o:before {
+  content: "\f096";
+}
+.fa-bookmark-o:before {
+  content: "\f097";
+}
+.fa-phone-square:before {
+  content: "\f098";
+}
+.fa-twitter:before {
+  content: "\f099";
+}
+.fa-facebook-f:before,
+.fa-facebook:before {
+  content: "\f09a";
+}
+.fa-github:before {
+  content: "\f09b";
+}
+.fa-unlock:before {
+  content: "\f09c";
+}
+.fa-credit-card:before {
+  content: "\f09d";
+}
+.fa-feed:before,
+.fa-rss:before {
+  content: "\f09e";
+}
+.fa-hdd-o:before {
+  content: "\f0a0";
+}
+.fa-bullhorn:before {
+  content: "\f0a1";
+}
+.fa-bell:before {
+  content: "\f0f3";
+}
+.fa-certificate:before {
+  content: "\f0a3";
+}
+.fa-hand-o-right:before {
+  content: "\f0a4";
+}
+.fa-hand-o-left:before {
+  content: "\f0a5";
+}
+.fa-hand-o-up:before {
+  content: "\f0a6";
+}
+.fa-hand-o-down:before {
+  content: "\f0a7";
+}
+.fa-arrow-circle-left:before {
+  content: "\f0a8";
+}
+.fa-arrow-circle-right:before {
+  content: "\f0a9";
+}
+.fa-arrow-circle-up:before {
+  content: "\f0aa";
+}
+.fa-arrow-circle-down:before {
+  content: "\f0ab";
+}
+.fa-globe:before {
+  content: "\f0ac";
+}
+.fa-wrench:before {
+  content: "\f0ad";
+}
+.fa-tasks:before {
+  content: "\f0ae";
+}
+.fa-filter:before {
+  content: "\f0b0";
+}
+.fa-briefcase:before {
+  content: "\f0b1";
+}
+.fa-arrows-alt:before {
+  content: "\f0b2";
+}
+.fa-group:before,
+.fa-users:before {
+  content: "\f0c0";
+}
+.fa-chain:before,
+.fa-link:before {
+  content: "\f0c1";
+}
+.fa-cloud:before {
+  content: "\f0c2";
+}
+.fa-flask:before {
+  content: "\f0c3";
+}
+.fa-cut:before,
+.fa-scissors:before {
+  content: "\f0c4";
+}
+.fa-copy:before,
+.fa-files-o:before {
+  content: "\f0c5";
+}
+.fa-paperclip:before {
+  content: "\f0c6";
+}
+.fa-save:before,
+.fa-floppy-o:before {
+  content: "\f0c7";
+}
+.fa-square:before {
+  content: "\f0c8";
+}
+.fa-navicon:before,
+.fa-reorder:before,
+.fa-bars:before {
+  content: "\f0c9";
+}
+.fa-list-ul:before {
+  content: "\f0ca";
+}
+.fa-list-ol:before {
+  content: "\f0cb";
+}
+.fa-strikethrough:before {
+  content: "\f0cc";
+}
+.fa-underline:before {
+  content: "\f0cd";
+}
+.fa-table:before {
+  content: "\f0ce";
+}
+.fa-magic:before {
+  content: "\f0d0";
+}
+.fa-truck:before {
+  content: "\f0d1";
+}
+.fa-pinterest:before {
+  content: "\f0d2";
+}
+.fa-pinterest-square:before {
+  content: "\f0d3";
+}
+.fa-google-plus-square:before {
+  content: "\f0d4";
+}
+.fa-google-plus:before {
+  content: "\f0d5";
+}
+.fa-money:before {
+  content: "\f0d6";
+}
+.fa-caret-down:before {
+  content: "\f0d7";
+}
+.fa-caret-up:before {
+  content: "\f0d8";
+}
+.fa-caret-left:before {
+  content: "\f0d9";
+}
+.fa-caret-right:before {
+  content: "\f0da";
+}
+.fa-columns:before {
+  content: "\f0db";
+}
+.fa-unsorted:before,
+.fa-sort:before {
+  content: "\f0dc";
+}
+.fa-sort-down:before,
+.fa-sort-desc:before {
+  content: "\f0dd";
+}
+.fa-sort-up:before,
+.fa-sort-asc:before {
+  content: "\f0de";
+}
+.fa-envelope:before {
+  content: "\f0e0";
+}
+.fa-linkedin:before {
+  content: "\f0e1";
+}
+.fa-rotate-left:before,
+.fa-undo:before {
+  content: "\f0e2";
+}
+.fa-legal:before,
+.fa-gavel:before {
+  content: "\f0e3";
+}
+.fa-dashboard:before,
+.fa-tachometer:before {
+  content: "\f0e4";
+}
+.fa-comment-o:before {
+  content: "\f0e5";
+}
+.fa-comments-o:before {
+  content: "\f0e6";
+}
+.fa-flash:before,
+.fa-bolt:before {
+  content: "\f0e7";
+}
+.fa-sitemap:before {
+  content: "\f0e8";
+}
+.fa-umbrella:before {
+  content: "\f0e9";
+}
+.fa-paste:before,
+.fa-clipboard:before {
+  content: "\f0ea";
+}
+.fa-lightbulb-o:before {
+  content: "\f0eb";
+}
+.fa-exchange:before {
+  content: "\f0ec";
+}
+.fa-cloud-download:before {
+  content: "\f0ed";
+}
+.fa-cloud-upload:before {
+  content: "\f0ee";
+}
+.fa-user-md:before {
+  content: "\f0f0";
+}
+.fa-stethoscope:before {
+  content: "\f0f1";
+}
+.fa-suitcase:before {
+  content: "\f0f2";
+}
+.fa-bell-o:before {
+  content: "\f0a2";
+}
+.fa-coffee:before {
+  content: "\f0f4";
+}
+.fa-cutlery:before {
+  content: "\f0f5";
+}
+.fa-file-text-o:before {
+  content: "\f0f6";
+}
+.fa-building-o:before {
+  content: "\f0f7";
+}
+.fa-hospital-o:before {
+  content: "\f0f8";
+}
+.fa-ambulance:before {
+  content: "\f0f9";
+}
+.fa-medkit:before {
+  content: "\f0fa";
+}
+.fa-fighter-jet:before {
+  content: "\f0fb";
+}
+.fa-beer:before {
+  content: "\f0fc";
+}
+.fa-h-square:before {
+  content: "\f0fd";
+}
+.fa-plus-square:before {
+  content: "\f0fe";
+}
+.fa-angle-double-left:before {
+  content: "\f100";
+}
+.fa-angle-double-right:before {
+  content: "\f101";
+}
+.fa-angle-double-up:before {
+  content: "\f102";
+}
+.fa-angle-double-down:before {
+  content: "\f103";
+}
+.fa-angle-left:before {
+  content: "\f104";
+}
+.fa-angle-right:before {
+  content: "\f105";
+}
+.fa-angle-up:before {
+  content: "\f106";
+}
+.fa-angle-down:before {
+  content: "\f107";
+}
+.fa-desktop:before {
+  content: "\f108";
+}
+.fa-laptop:before {
+  content: "\f109";
+}
+.fa-tablet:before {
+  content: "\f10a";
+}
+.fa-mobile-phone:before,
+.fa-mobile:before {
+  content: "\f10b";
+}
+.fa-circle-o:before {
+  content: "\f10c";
+}
+.fa-quote-left:before {
+  content: "\f10d";
+}
+.fa-quote-right:before {
+  content: "\f10e";
+}
+.fa-spinner:before {
+  content: "\f110";
+}
+.fa-circle:before {
+  content: "\f111";
+}
+.fa-mail-reply:before,
+.fa-reply:before {
+  content: "\f112";
+}
+.fa-github-alt:before {
+  content: "\f113";
+}
+.fa-folder-o:before {
+  content: "\f114";
+}
+.fa-folder-open-o:before {
+  content: "\f115";
+}
+.fa-smile-o:before {
+  content: "\f118";
+}
+.fa-frown-o:before {
+  content: "\f119";
+}
+.fa-meh-o:before {
+  content: "\f11a";
+}
+.fa-gamepad:before {
+  content: "\f11b";
+}
+.fa-keyboard-o:before {
+  content: "\f11c";
+}
+.fa-flag-o:before {
+  content: "\f11d";
+}
+.fa-flag-checkered:before {
+  content: "\f11e";
+}
+.fa-terminal:before {
+  content: "\f120";
+}
+.fa-code:before {
+  content: "\f121";
+}
+.fa-mail-reply-all:before,
+.fa-reply-all:before {
+  content: "\f122";
+}
+.fa-star-half-empty:before,
+.fa-star-half-full:before,
+.fa-star-half-o:before {
+  content: "\f123";
+}
+.fa-location-arrow:before {
+  content: "\f124";
+}
+.fa-crop:before {
+  content: "\f125";
+}
+.fa-code-fork:before {
+  content: "\f126";
+}
+.fa-unlink:before,
+.fa-chain-broken:before {
+  content: "\f127";
+}
+.fa-question:before {
+  content: "\f128";
+}
+.fa-info:before {
+  content: "\f129";
+}
+.fa-exclamation:before {
+  content: "\f12a";
+}
+.fa-superscript:before {
+  content: "\f12b";
+}
+.fa-subscript:before {
+  content: "\f12c";
+}
+.fa-eraser:before {
+  content: "\f12d";
+}
+.fa-puzzle-piece:before {
+  content: "\f12e";
+}
+.fa-microphone:before {
+  content: "\f130";
+}
+.fa-microphone-slash:before {
+  content: "\f131";
+}
+.fa-shield:before {
+  content: "\f132";
+}
+.fa-calendar-o:before {
+  content: "\f133";
+}
+.fa-fire-extinguisher:before {
+  content: "\f134";
+}
+.fa-rocket:before {
+  content: "\f135";
+}
+.fa-maxcdn:before {
+  content: "\f136";
+}
+.fa-chevron-circle-left:before {
+  content: "\f137";
+}
+.fa-chevron-circle-right:before {
+  content: "\f138";
+}
+.fa-chevron-circle-up:before {
+  content: "\f139";
+}
+.fa-chevron-circle-down:before {
+  content: "\f13a";
+}
+.fa-html5:before {
+  content: "\f13b";
+}
+.fa-css3:before {
+  content: "\f13c";
+}
+.fa-anchor:before {
+  content: "\f13d";
+}
+.fa-unlock-alt:before {
+  content: "\f13e";
+}
+.fa-bullseye:before {
+  content: "\f140";
+}
+.fa-ellipsis-h:before {
+  content: "\f141";
+}
+.fa-ellipsis-v:before {
+  content: "\f142";
+}
+.fa-rss-square:before {
+  content: "\f143";
+}
+.fa-play-circle:before {
+  content: "\f144";
+}
+.fa-ticket:before {
+  content: "\f145";
+}
+.fa-minus-square:before {
+  content: "\f146";
+}
+.fa-minus-square-o:before {
+  content: "\f147";
+}
+.fa-level-up:before {
+  content: "\f148";
+}
+.fa-level-down:before {
+  content: "\f149";
+}
+.fa-check-square:before {
+  content: "\f14a";
+}
+.fa-pencil-square:before {
+  content: "\f14b";
+}
+.fa-external-link-square:before {
+  content: "\f14c";
+}
+.fa-share-square:before {
+  content: "\f14d";
+}
+.fa-compass:before {
+  content: "\f14e";
+}
+.fa-toggle-down:before,
+.fa-caret-square-o-down:before {
+  content: "\f150";
+}
+.fa-toggle-up:before,
+.fa-caret-square-o-up:before {
+  content: "\f151";
+}
+.fa-toggle-right:before,
+.fa-caret-square-o-right:before {
+  content: "\f152";
+}
+.fa-euro:before,
+.fa-eur:before {
+  content: "\f153";
+}
+.fa-gbp:before {
+  content: "\f154";
+}
+.fa-dollar:before,
+.fa-usd:before {
+  content: "\f155";
+}
+.fa-rupee:before,
+.fa-inr:before {
+  content: "\f156";
+}
+.fa-cny:before,
+.fa-rmb:before,
+.fa-yen:before,
+.fa-jpy:before {
+  content: "\f157";
+}
+.fa-ruble:before,
+.fa-rouble:before,
+.fa-rub:before {
+  content: "\f158";
+}
+.fa-won:before,
+.fa-krw:before {
+  content: "\f159";
+}
+.fa-bitcoin:before,
+.fa-btc:before {
+  content: "\f15a";
+}
+.fa-file:before {
+  content: "\f15b";
+}
+.fa-file-text:before {
+  content: "\f15c";
+}
+.fa-sort-alpha-asc:before {
+  content: "\f15d";
+}
+.fa-sort-alpha-desc:before {
+  content: "\f15e";
+}
+.fa-sort-amount-asc:before {
+  content: "\f160";
+}
+.fa-sort-amount-desc:before {
+  content: "\f161";
+}
+.fa-sort-numeric-asc:before {
+  content: "\f162";
+}
+.fa-sort-numeric-desc:before {
+  content: "\f163";
+}
+.fa-thumbs-up:before {
+  content: "\f164";
+}
+.fa-thumbs-down:before {
+  content: "\f165";
+}
+.fa-youtube-square:before {
+  content: "\f166";
+}
+.fa-youtube:before {
+  content: "\f167";
+}
+.fa-xing:before {
+  content: "\f168";
+}
+.fa-xing-square:before {
+  content: "\f169";
+}
+.fa-youtube-play:before {
+  content: "\f16a";
+}
+.fa-dropbox:before {
+  content: "\f16b";
+}
+.fa-stack-overflow:before {
+  content: "\f16c";
+}
+.fa-instagram:before {
+  content: "\f16d";
+}
+.fa-flickr:before {
+  content: "\f16e";
+}
+.fa-adn:before {
+  content: "\f170";
+}
+.fa-bitbucket:before {
+  content: "\f171";
+}
+.fa-bitbucket-square:before {
+  content: "\f172";
+}
+.fa-tumblr:before {
+  content: "\f173";
+}
+.fa-tumblr-square:before {
+  content: "\f174";
+}
+.fa-long-arrow-down:before {
+  content: "\f175";
+}
+.fa-long-arrow-up:before {
+  content: "\f176";
+}
+.fa-long-arrow-left:before {
+  content: "\f177";
+}
+.fa-long-arrow-right:before {
+  content: "\f178";
+}
+.fa-apple:before {
+  content: "\f179";
+}
+.fa-windows:before {
+  content: "\f17a";
+}
+.fa-android:before {
+  content: "\f17b";
+}
+.fa-linux:before {
+  content: "\f17c";
+}
+.fa-dribbble:before {
+  content: "\f17d";
+}
+.fa-skype:before {
+  content: "\f17e";
+}
+.fa-foursquare:before {
+  content: "\f180";
+}
+.fa-trello:before {
+  content: "\f181";
+}
+.fa-female:before {
+  content: "\f182";
+}
+.fa-male:before {
+  content: "\f183";
+}
+.fa-gittip:before,
+.fa-gratipay:before {
+  content: "\f184";
+}
+.fa-sun-o:before {
+  content: "\f185";
+}
+.fa-moon-o:before {
+  content: "\f186";
+}
+.fa-archive:before {
+  content: "\f187";
+}
+.fa-bug:before {
+  content: "\f188";
+}
+.fa-vk:before {
+  content: "\f189";
+}
+.fa-weibo:before {
+  content: "\f18a";
+}
+.fa-renren:before {
+  content: "\f18b";
+}
+.fa-pagelines:before {
+  content: "\f18c";
+}
+.fa-stack-exchange:before {
+  content: "\f18d";
+}
+.fa-arrow-circle-o-right:before {
+  content: "\f18e";
+}
+.fa-arrow-circle-o-left:before {
+  content: "\f190";
+}
+.fa-toggle-left:before,
+.fa-caret-square-o-left:before {
+  content: "\f191";
+}
+.fa-dot-circle-o:before {
+  content: "\f192";
+}
+.fa-wheelchair:before {
+  content: "\f193";
+}
+.fa-vimeo-square:before {
+  content: "\f194";
+}
+.fa-turkish-lira:before,
+.fa-try:before {
+  content: "\f195";
+}
+.fa-plus-square-o:before {
+  content: "\f196";
+}
+.fa-space-shuttle:before {
+  content: "\f197";
+}
+.fa-slack:before {
+  content: "\f198";
+}
+.fa-envelope-square:before {
+  content: "\f199";
+}
+.fa-wordpress:before {
+  content: "\f19a";
+}
+.fa-openid:before {
+  content: "\f19b";
+}
+.fa-institution:before,
+.fa-bank:before,
+.fa-university:before {
+  content: "\f19c";
+}
+.fa-mortar-board:before,
+.fa-graduation-cap:before {
+  content: "\f19d";
+}
+.fa-yahoo:before {
+  content: "\f19e";
+}
+.fa-google:before {
+  content: "\f1a0";
+}
+.fa-reddit:before {
+  content: "\f1a1";
+}
+.fa-reddit-square:before {
+  content: "\f1a2";
+}
+.fa-stumbleupon-circle:before {
+  content: "\f1a3";
+}
+.fa-stumbleupon:before {
+  content: "\f1a4";
+}
+.fa-delicious:before {
+  content: "\f1a5";
+}
+.fa-digg:before {
+  content: "\f1a6";
+}
+.fa-pied-piper-pp:before {
+  content: "\f1a7";
+}
+.fa-pied-piper-alt:before {
+  content: "\f1a8";
+}
+.fa-drupal:before {
+  content: "\f1a9";
+}
+.fa-joomla:before {
+  content: "\f1aa";
+}
+.fa-language:before {
+  content: "\f1ab";
+}
+.fa-fax:before {
+  content: "\f1ac";
+}
+.fa-building:before {
+  content: "\f1ad";
+}
+.fa-child:before {
+  content: "\f1ae";
+}
+.fa-paw:before {
+  content: "\f1b0";
+}
+.fa-spoon:before {
+  content: "\f1b1";
+}
+.fa-cube:before {
+  content: "\f1b2";
+}
+.fa-cubes:before {
+  content: "\f1b3";
+}
+.fa-behance:before {
+  content: "\f1b4";
+}
+.fa-behance-square:before {
+  content: "\f1b5";
+}
+.fa-steam:before {
+  content: "\f1b6";
+}
+.fa-steam-square:before {
+  content: "\f1b7";
+}
+.fa-recycle:before {
+  content: "\f1b8";
+}
+.fa-automobile:before,
+.fa-car:before {
+  content: "\f1b9";
+}
+.fa-cab:before,
+.fa-taxi:before {
+  content: "\f1ba";
+}
+.fa-tree:before {
+  content: "\f1bb";
+}
+.fa-spotify:before {
+  content: "\f1bc";
+}
+.fa-deviantart:before {
+  content: "\f1bd";
+}
+.fa-soundcloud:before {
+  content: "\f1be";
+}
+.fa-database:before {
+  content: "\f1c0";
+}
+.fa-file-pdf-o:before {
+  content: "\f1c1";
+}
+.fa-file-word-o:before {
+  content: "\f1c2";
+}
+.fa-file-excel-o:before {
+  content: "\f1c3";
+}
+.fa-file-powerpoint-o:before {
+  content: "\f1c4";
+}
+.fa-file-photo-o:before,
+.fa-file-picture-o:before,
+.fa-file-image-o:before {
+  content: "\f1c5";
+}
+.fa-file-zip-o:before,
+.fa-file-archive-o:before {
+  content: "\f1c6";
+}
+.fa-file-sound-o:before,
+.fa-file-audio-o:before {
+  content: "\f1c7";
+}
+.fa-file-movie-o:before,
+.fa-file-video-o:before {
+  content: "\f1c8";
+}
+.fa-file-code-o:before {
+  content: "\f1c9";
+}
+.fa-vine:before {
+  content: "\f1ca";
+}
+.fa-codepen:before {
+  content: "\f1cb";
+}
+.fa-jsfiddle:before {
+  content: "\f1cc";
+}
+.fa-life-bouy:before,
+.fa-life-buoy:before,
+.fa-life-saver:before,
+.fa-support:before,
+.fa-life-ring:before {
+  content: "\f1cd";
+}
+.fa-circle-o-notch:before {
+  content: "\f1ce";
+}
+.fa-ra:before,
+.fa-resistance:before,
+.fa-rebel:before {
+  content: "\f1d0";
+}
+.fa-ge:before,
+.fa-empire:before {
+  content: "\f1d1";
+}
+.fa-git-square:before {
+  content: "\f1d2";
+}
+.fa-git:before {
+  content: "\f1d3";
+}
+.fa-y-combinator-square:before,
+.fa-yc-square:before,
+.fa-hacker-news:before {
+  content: "\f1d4";
+}
+.fa-tencent-weibo:before {
+  content: "\f1d5";
+}
+.fa-qq:before {
+  content: "\f1d6";
+}
+.fa-wechat:before,
+.fa-weixin:before {
+  content: "\f1d7";
+}
+.fa-send:before,
+.fa-paper-plane:before {
+  content: "\f1d8";
+}
+.fa-send-o:before,
+.fa-paper-plane-o:before {
+  content: "\f1d9";
+}
+.fa-history:before {
+  content: "\f1da";
+}
+.fa-circle-thin:before {
+  content: "\f1db";
+}
+.fa-header:before {
+  content: "\f1dc";
+}
+.fa-paragraph:before {
+  content: "\f1dd";
+}
+.fa-sliders:before {
+  content: "\f1de";
+}
+.fa-share-alt:before {
+  content: "\f1e0";
+}
+.fa-share-alt-square:before {
+  content: "\f1e1";
+}
+.fa-bomb:before {
+  content: "\f1e2";
+}
+.fa-soccer-ball-o:before,
+.fa-futbol-o:before {
+  content: "\f1e3";
+}
+.fa-tty:before {
+  content: "\f1e4";
+}
+.fa-binoculars:before {
+  content: "\f1e5";
+}
+.fa-plug:before {
+  content: "\f1e6";
+}
+.fa-slideshare:before {
+  content: "\f1e7";
+}
+.fa-twitch:before {
+  content: "\f1e8";
+}
+.fa-yelp:before {
+  content: "\f1e9";
+}
+.fa-newspaper-o:before {
+  content: "\f1ea";
+}
+.fa-wifi:before {
+  content: "\f1eb";
+}
+.fa-calculator:before {
+  content: "\f1ec";
+}
+.fa-paypal:before {
+  content: "\f1ed";
+}
+.fa-google-wallet:before {
+  content: "\f1ee";
+}
+.fa-cc-visa:before {
+  content: "\f1f0";
+}
+.fa-cc-mastercard:before {
+  content: "\f1f1";
+}
+.fa-cc-discover:before {
+  content: "\f1f2";
+}
+.fa-cc-amex:before {
+  content: "\f1f3";
+}
+.fa-cc-paypal:before {
+  content: "\f1f4";
+}
+.fa-cc-stripe:before {
+  content: "\f1f5";
+}
+.fa-bell-slash:before {
+  content: "\f1f6";
+}
+.fa-bell-slash-o:before {
+  content: "\f1f7";
+}
+.fa-trash:before {
+  content: "\f1f8";
+}
+.fa-copyright:before {
+  content: "\f1f9";
+}
+.fa-at:before {
+  content: "\f1fa";
+}
+.fa-eyedropper:before {
+  content: "\f1fb";
+}
+.fa-paint-brush:before {
+  content: "\f1fc";
+}
+.fa-birthday-cake:before {
+  content: "\f1fd";
+}
+.fa-area-chart:before {
+  content: "\f1fe";
+}
+.fa-pie-chart:before {
+  content: "\f200";
+}
+.fa-line-chart:before {
+  content: "\f201";
+}
+.fa-lastfm:before {
+  content: "\f202";
+}
+.fa-lastfm-square:before {
+  content: "\f203";
+}
+.fa-toggle-off:before {
+  content: "\f204";
+}
+.fa-toggle-on:before {
+  content: "\f205";
+}
+.fa-bicycle:before {
+  content: "\f206";
+}
+.fa-bus:before {
+  content: "\f207";
+}
+.fa-ioxhost:before {
+  content: "\f208";
+}
+.fa-angellist:before {
+  content: "\f209";
+}
+.fa-cc:before {
+  content: "\f20a";
+}
+.fa-shekel:before,
+.fa-sheqel:before,
+.fa-ils:before {
+  content: "\f20b";
+}
+.fa-meanpath:before {
+  content: "\f20c";
+}
+.fa-buysellads:before {
+  content: "\f20d";
+}
+.fa-connectdevelop:before {
+  content: "\f20e";
+}
+.fa-dashcube:before {
+  content: "\f210";
+}
+.fa-forumbee:before {
+  content: "\f211";
+}
+.fa-leanpub:before {
+  content: "\f212";
+}
+.fa-sellsy:before {
+  content: "\f213";
+}
+.fa-shirtsinbulk:before {
+  content: "\f214";
+}
+.fa-simplybuilt:before {
+  content: "\f215";
+}
+.fa-skyatlas:before {
+  content: "\f216";
+}
+.fa-cart-plus:before {
+  content: "\f217";
+}
+.fa-cart-arrow-down:before {
+  content: "\f218";
+}
+.fa-diamond:before {
+  content: "\f219";
+}
+.fa-ship:before {
+  content: "\f21a";
+}
+.fa-user-secret:before {
+  content: "\f21b";
+}
+.fa-motorcycle:before {
+  content: "\f21c";
+}
+.fa-street-view:before {
+  content: "\f21d";
+}
+.fa-heartbeat:before {
+  content: "\f21e";
+}
+.fa-venus:before {
+  content: "\f221";
+}
+.fa-mars:before {
+  content: "\f222";
+}
+.fa-mercury:before {
+  content: "\f223";
+}
+.fa-intersex:before,
+.fa-transgender:before {
+  content: "\f224";
+}
+.fa-transgender-alt:before {
+  content: "\f225";
+}
+.fa-venus-double:before {
+  content: "\f226";
+}
+.fa-mars-double:before {
+  content: "\f227";
+}
+.fa-venus-mars:before {
+  content: "\f228";
+}
+.fa-mars-stroke:before {
+  content: "\f229";
+}
+.fa-mars-stroke-v:before {
+  content: "\f22a";
+}
+.fa-mars-stroke-h:before {
+  content: "\f22b";
+}
+.fa-neuter:before {
+  content: "\f22c";
+}
+.fa-genderless:before {
+  content: "\f22d";
+}
+.fa-facebook-official:before {
+  content: "\f230";
+}
+.fa-pinterest-p:before {
+  content: "\f231";
+}
+.fa-whatsapp:before {
+  content: "\f232";
+}
+.fa-server:before {
+  content: "\f233";
+}
+.fa-user-plus:before {
+  content: "\f234";
+}
+.fa-user-times:before {
+  content: "\f235";
+}
+.fa-hotel:before,
+.fa-bed:before {
+  content: "\f236";
+}
+.fa-viacoin:before {
+  content: "\f237";
+}
+.fa-train:before {
+  content: "\f238";
+}
+.fa-subway:before {
+  content: "\f239";
+}
+.fa-medium:before {
+  content: "\f23a";
+}
+.fa-yc:before,
+.fa-y-combinator:before {
+  content: "\f23b";
+}
+.fa-optin-monster:before {
+  content: "\f23c";
+}
+.fa-opencart:before {
+  content: "\f23d";
+}
+.fa-expeditedssl:before {
+  content: "\f23e";
+}
+.fa-battery-4:before,
+.fa-battery:before,
+.fa-battery-full:before {
+  content: "\f240";
+}
+.fa-battery-3:before,
+.fa-battery-three-quarters:before {
+  content: "\f241";
+}
+.fa-battery-2:before,
+.fa-battery-half:before {
+  content: "\f242";
+}
+.fa-battery-1:before,
+.fa-battery-quarter:before {
+  content: "\f243";
+}
+.fa-battery-0:before,
+.fa-battery-empty:before {
+  content: "\f244";
+}
+.fa-mouse-pointer:before {
+  content: "\f245";
+}
+.fa-i-cursor:before {
+  content: "\f246";
+}
+.fa-object-group:before {
+  content: "\f247";
+}
+.fa-object-ungroup:before {
+  content: "\f248";
+}
+.fa-sticky-note:before {
+  content: "\f249";
+}
+.fa-sticky-note-o:before {
+  content: "\f24a";
+}
+.fa-cc-jcb:before {
+  content: "\f24b";
+}
+.fa-cc-diners-club:before {
+  content: "\f24c";
+}
+.fa-clone:before {
+  content: "\f24d";
+}
+.fa-balance-scale:before {
+  content: "\f24e";
+}
+.fa-hourglass-o:before {
+  content: "\f250";
+}
+.fa-hourglass-1:before,
+.fa-hourglass-start:before {
+  content: "\f251";
+}
+.fa-hourglass-2:before,
+.fa-hourglass-half:before {
+  content: "\f252";
+}
+.fa-hourglass-3:before,
+.fa-hourglass-end:before {
+  content: "\f253";
+}
+.fa-hourglass:before {
+  content: "\f254";
+}
+.fa-hand-grab-o:before,
+.fa-hand-rock-o:before {
+  content: "\f255";
+}
+.fa-hand-stop-o:before,
+.fa-hand-paper-o:before {
+  content: "\f256";
+}
+.fa-hand-scissors-o:before {
+  content: "\f257";
+}
+.fa-hand-lizard-o:before {
+  content: "\f258";
+}
+.fa-hand-spock-o:before {
+  content: "\f259";
+}
+.fa-hand-pointer-o:before {
+  content: "\f25a";
+}
+.fa-hand-peace-o:before {
+  content: "\f25b";
+}
+.fa-trademark:before {
+  content: "\f25c";
+}
+.fa-registered:before {
+  content: "\f25d";
+}
+.fa-creative-commons:before {
+  content: "\f25e";
+}
+.fa-gg:before {
+  content: "\f260";
+}
+.fa-gg-circle:before {
+  content: "\f261";
+}
+.fa-tripadvisor:before {
+  content: "\f262";
+}
+.fa-odnoklassniki:before {
+  content: "\f263";
+}
+.fa-odnoklassniki-square:before {
+  content: "\f264";
+}
+.fa-get-pocket:before {
+  content: "\f265";
+}
+.fa-wikipedia-w:before {
+  content: "\f266";
+}
+.fa-safari:before {
+  content: "\f267";
+}
+.fa-chrome:before {
+  content: "\f268";
+}
+.fa-firefox:before {
+  content: "\f269";
+}
+.fa-opera:before {
+  content: "\f26a";
+}
+.fa-internet-explorer:before {
+  content: "\f26b";
+}
+.fa-tv:before,
+.fa-television:before {
+  content: "\f26c";
+}
+.fa-contao:before {
+  content: "\f26d";
+}
+.fa-500px:before {
+  content: "\f26e";
+}
+.fa-amazon:before {
+  content: "\f270";
+}
+.fa-calendar-plus-o:before {
+  content: "\f271";
+}
+.fa-calendar-minus-o:before {
+  content: "\f272";
+}
+.fa-calendar-times-o:before {
+  content: "\f273";
+}
+.fa-calendar-check-o:before {
+  content: "\f274";
+}
+.fa-industry:before {
+  content: "\f275";
+}
+.fa-map-pin:before {
+  content: "\f276";
+}
+.fa-map-signs:before {
+  content: "\f277";
+}
+.fa-map-o:before {
+  content: "\f278";
+}
+.fa-map:before {
+  content: "\f279";
+}
+.fa-commenting:before {
+  content: "\f27a";
+}
+.fa-commenting-o:before {
+  content: "\f27b";
+}
+.fa-houzz:before {
+  content: "\f27c";
+}
+.fa-vimeo:before {
+  content: "\f27d";
+}
+.fa-black-tie:before {
+  content: "\f27e";
+}
+.fa-fonticons:before {
+  content: "\f280";
+}
+.fa-reddit-alien:before {
+  content: "\f281";
+}
+.fa-edge:before {
+  content: "\f282";
+}
+.fa-credit-card-alt:before {
+  content: "\f283";
+}
+.fa-codiepie:before {
+  content: "\f284";
+}
+.fa-modx:before {
+  content: "\f285";
+}
+.fa-fort-awesome:before {
+  content: "\f286";
+}
+.fa-usb:before {
+  content: "\f287";
+}
+.fa-product-hunt:before {
+  content: "\f288";
+}
+.fa-mixcloud:before {
+  content: "\f289";
+}
+.fa-scribd:before {
+  content: "\f28a";
+}
+.fa-pause-circle:before {
+  content: "\f28b";
+}
+.fa-pause-circle-o:before {
+  content: "\f28c";
+}
+.fa-stop-circle:before {
+  content: "\f28d";
+}
+.fa-stop-circle-o:before {
+  content: "\f28e";
+}
+.fa-shopping-bag:before {
+  content: "\f290";
+}
+.fa-shopping-basket:before {
+  content: "\f291";
+}
+.fa-hashtag:before {
+  content: "\f292";
+}
+.fa-bluetooth:before {
+  content: "\f293";
+}
+.fa-bluetooth-b:before {
+  content: "\f294";
+}
+.fa-percent:before {
+  content: "\f295";
+}
+.fa-gitlab:before {
+  content: "\f296";
+}
+.fa-wpbeginner:before {
+  content: "\f297";
+}
+.fa-wpforms:before {
+  content: "\f298";
+}
+.fa-envira:before {
+  content: "\f299";
+}
+.fa-universal-access:before {
+  content: "\f29a";
+}
+.fa-wheelchair-alt:before {
+  content: "\f29b";
+}
+.fa-question-circle-o:before {
+  content: "\f29c";
+}
+.fa-blind:before {
+  content: "\f29d";
+}
+.fa-audio-description:before {
+  content: "\f29e";
+}
+.fa-volume-control-phone:before {
+  content: "\f2a0";
+}
+.fa-braille:before {
+  content: "\f2a1";
+}
+.fa-assistive-listening-systems:before {
+  content: "\f2a2";
+}
+.fa-asl-interpreting:before,
+.fa-american-sign-language-interpreting:before {
+  content: "\f2a3";
+}
+.fa-deafness:before,
+.fa-hard-of-hearing:before,
+.fa-deaf:before {
+  content: "\f2a4";
+}
+.fa-glide:before {
+  content: "\f2a5";
+}
+.fa-glide-g:before {
+  content: "\f2a6";
+}
+.fa-signing:before,
+.fa-sign-language:before {
+  content: "\f2a7";
+}
+.fa-low-vision:before {
+  content: "\f2a8";
+}
+.fa-viadeo:before {
+  content: "\f2a9";
+}
+.fa-viadeo-square:before {
+  content: "\f2aa";
+}
+.fa-snapchat:before {
+  content: "\f2ab";
+}
+.fa-snapchat-ghost:before {
+  content: "\f2ac";
+}
+.fa-snapchat-square:before {
+  content: "\f2ad";
+}
+.fa-pied-piper:before {
+  content: "\f2ae";
+}
+.fa-first-order:before {
+  content: "\f2b0";
+}
+.fa-yoast:before {
+  content: "\f2b1";
+}
+.fa-themeisle:before {
+  content: "\f2b2";
+}
+.fa-google-plus-circle:before,
+.fa-google-plus-official:before {
+  content: "\f2b3";
+}
+.fa-fa:before,
+.fa-font-awesome:before {
+  content: "\f2b4";
+}
+.fa-handshake-o:before {
+  content: "\f2b5";
+}
+.fa-envelope-open:before {
+  content: "\f2b6";
+}
+.fa-envelope-open-o:before {
+  content: "\f2b7";
+}
+.fa-linode:before {
+  content: "\f2b8";
+}
+.fa-address-book:before {
+  content: "\f2b9";
+}
+.fa-address-book-o:before {
+  content: "\f2ba";
+}
+.fa-vcard:before,
+.fa-address-card:before {
+  content: "\f2bb";
+}
+.fa-vcard-o:before,
+.fa-address-card-o:before {
+  content: "\f2bc";
+}
+.fa-user-circle:before {
+  content: "\f2bd";
+}
+.fa-user-circle-o:before {
+  content: "\f2be";
+}
+.fa-user-o:before {
+  content: "\f2c0";
+}
+.fa-id-badge:before {
+  content: "\f2c1";
+}
+.fa-drivers-license:before,
+.fa-id-card:before {
+  content: "\f2c2";
+}
+.fa-drivers-license-o:before,
+.fa-id-card-o:before {
+  content: "\f2c3";
+}
+.fa-quora:before {
+  content: "\f2c4";
+}
+.fa-free-code-camp:before {
+  content: "\f2c5";
+}
+.fa-telegram:before {
+  content: "\f2c6";
+}
+.fa-thermometer-4:before,
+.fa-thermometer:before,
+.fa-thermometer-full:before {
+  content: "\f2c7";
+}
+.fa-thermometer-3:before,
+.fa-thermometer-three-quarters:before {
+  content: "\f2c8";
+}
+.fa-thermometer-2:before,
+.fa-thermometer-half:before {
+  content: "\f2c9";
+}
+.fa-thermometer-1:before,
+.fa-thermometer-quarter:before {
+  content: "\f2ca";
+}
+.fa-thermometer-0:before,
+.fa-thermometer-empty:before {
+  content: "\f2cb";
+}
+.fa-shower:before {
+  content: "\f2cc";
+}
+.fa-bathtub:before,
+.fa-s15:before,
+.fa-bath:before {
+  content: "\f2cd";
+}
+.fa-podcast:before {
+  content: "\f2ce";
+}
+.fa-window-maximize:before {
+  content: "\f2d0";
+}
+.fa-window-minimize:before {
+  content: "\f2d1";
+}
+.fa-window-restore:before {
+  content: "\f2d2";
+}
+.fa-times-rectangle:before,
+.fa-window-close:before {
+  content: "\f2d3";
+}
+.fa-times-rectangle-o:before,
+.fa-window-close-o:before {
+  content: "\f2d4";
+}
+.fa-bandcamp:before {
+  content: "\f2d5";
+}
+.fa-grav:before {
+  content: "\f2d6";
+}
+.fa-etsy:before {
+  content: "\f2d7";
+}
+.fa-imdb:before {
+  content: "\f2d8";
+}
+.fa-ravelry:before {
+  content: "\f2d9";
+}
+.fa-eercast:before {
+  content: "\f2da";
+}
+.fa-microchip:before {
+  content: "\f2db";
+}
+.fa-snowflake-o:before {
+  content: "\f2dc";
+}
+.fa-superpowers:before {
+  content: "\f2dd";
+}
+.fa-wpexplorer:before {
+  content: "\f2de";
+}
+.fa-meetup:before {
+  content: "\f2e0";
+}
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  border: 0;
+}
+.sr-only-focusable:active,
+.sr-only-focusable:focus {
+  position: static;
+  width: auto;
+  height: auto;
+  margin: 0;
+  overflow: visible;
+  clip: auto;
+}
diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/css/font-awesome.css.map b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/css/font-awesome.css.map
new file mode 100644
index 00000000..60763a86
--- /dev/null
+++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/css/font-awesome.css.map
@@ -0,0 +1,7 @@
+{
+"version": 3,
+"mappings": "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",
+"sources": ["../scss/_path.scss","../scss/_core.scss","../scss/_larger.scss","../scss/_fixed-width.scss","../scss/_list.scss","../scss/_variables.scss","../scss/_bordered-pulled.scss","../scss/_animated.scss","../scss/_rotated-flipped.scss","../scss/_mixins.scss","../scss/_stacked.scss","../scss/_icons.scss"],
+"names": [],
+"file": "font-awesome.css"
+}
diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/css/font-awesome.min.css b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/css/font-awesome.min.css
new file mode 100644
index 00000000..540440ce
--- /dev/null
+++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/css/font-awesome.min.css
@@ -0,0 +1,4 @@
+/*!
+ *  Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
+ *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}
diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/FontAwesome.otf b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/FontAwesome.otf
new file mode 100644
index 0000000000000000000000000000000000000000..401ec0f36e4f73b8efa40bd6f604fe80d286db70
GIT binary patch
literal 134808
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 literal 0 HcmV?d00001 diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.eot b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..e9f60ca953f93e35eab4108bd414bc02ddcf3928 GIT binary patch literal 165742 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 G*yFsbY~cLNXLP73aA74Mq6M9f^&YV`isWW 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

5q!RY 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 + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.ttf b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..35acda2fa1196aad98c2adf4378a7611dd713aa3 GIT binary patch literal 165548 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 z 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

'); + // javascript:false as initial iframe src + // prevents warning popups on HTTPS in IE6. + // IE versions below IE8 cannot set the name property of + // elements that have already been added to the DOM, + // so we set the name along with the iframe HTML markup: + iframe = $( + '' + ).bind('load', function () { + var fileInputClones, + paramNames = $.isArray(options.paramName) ? + options.paramName : [options.paramName]; + iframe + .unbind('load') + .bind('load', function () { + var response; + // Wrap in a try/catch block to catch exceptions thrown + // when trying to access cross-domain iframe contents: + try { + response = iframe.contents(); + // Google Chrome and Firefox do not throw an + // exception when calling iframe.contents() on + // cross-domain requests, so we unify the response: + if (!response.length || !response[0].firstChild) { + throw new Error(); + } + } catch (e) { + response = undefined; + } + // The complete callback returns the + // iframe content document as response object: + completeCallback( + 200, + 'success', + {'iframe': response} + ); + // Fix for IE endless progress bar activity bug + // (happens on form submits to iframe targets): + $('') + .appendTo(form); + form.remove(); + }); + form + .prop('target', iframe.prop('name')) + .prop('action', options.url) + .prop('method', options.type); + if (options.formData) { + $.each(options.formData, function (index, field) { + $('') + .prop('name', field.name) + .val(field.value) + .appendTo(form); + }); + } + if (options.fileInput && options.fileInput.length && + options.type === 'POST') { + fileInputClones = options.fileInput.clone(); + // Insert a clone for each file input field: + options.fileInput.after(function (index) { + return fileInputClones[index]; + }); + if (options.paramName) { + options.fileInput.each(function (index) { + $(this).prop( + 'name', + paramNames[index] || options.paramName + ); + }); + } + // Appending the file input fields to the hidden form + // removes them from their original location: + form + .append(options.fileInput) + .prop('enctype', 'multipart/form-data') + // enctype must be set as encoding for IE: + .prop('encoding', 'multipart/form-data'); + } + form.submit(); + // Insert the file input fields at their original location + // by replacing the clones with the originals: + if (fileInputClones && fileInputClones.length) { + options.fileInput.each(function (index, input) { + var clone = $(fileInputClones[index]); + $(input).prop('name', clone.prop('name')); + clone.replaceWith(input); + }); + } + }); + form.append(iframe).appendTo(document.body); + }, + abort: function () { + if (iframe) { + // javascript:false as iframe src aborts the request + // and prevents warning popups on HTTPS in IE6. + // concat is used to avoid the "Script URL" JSLint error: + iframe + .unbind('load') + .prop('src', 'javascript'.concat(':false;')); + } + if (form) { + form.remove(); + } + } + }; + } + }); + + // The iframe transport returns the iframe content document as response. + // The following adds converters from iframe to text, json, html, and script: + $.ajaxSetup({ + converters: { + 'iframe text': function (iframe) { + return $(iframe[0].body).text(); + }, + 'iframe json': function (iframe) { + return $.parseJSON($(iframe[0].body).text()); + }, + 'iframe html': function (iframe) { + return $(iframe[0].body).html(); + }, + 'iframe script': function (iframe) { + return $.globalEval($(iframe[0].body).text()); + } + } + }); + +})); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.iframe-transport.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.iframe-transport.min.js new file mode 100644 index 00000000..a6a9c6cd --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.iframe-transport.min.js @@ -0,0 +1,7 @@ +(function(factory){'use strict';if(typeof define==='function'&&define.amd){define(['jquery'],factory);}else{factory(window.jQuery);}}(function($){'use strict';var counter=0;$.ajaxTransport('iframe',function(options){if(options.async&&(options.type==='POST'||options.type==='GET')){var form,iframe;return{send:function(_,completeCallback){form=$('
');iframe=$('').bind('load',function(){var fileInputClones,paramNames=$.isArray(options.paramName)?options.paramName:[options.paramName];iframe.unbind('load').bind('load',function(){var response;try{response=iframe.contents();if(!response.length||!response[0].firstChild){throw new Error();}}catch(e){response=undefined;} +completeCallback(200,'success',{'iframe':response});$('').appendTo(form);form.remove();});form.prop('target',iframe.prop('name')).prop('action',options.url).prop('method',options.type);if(options.formData){$.each(options.formData,function(index,field){$('').prop('name',field.name).val(field.value).appendTo(form);});} +if(options.fileInput&&options.fileInput.length&&options.type==='POST'){fileInputClones=options.fileInput.clone();options.fileInput.after(function(index){return fileInputClones[index];});if(options.paramName){options.fileInput.each(function(index){$(this).prop('name',paramNames[index]||options.paramName);});} +form.append(options.fileInput).prop('enctype','multipart/form-data').prop('encoding','multipart/form-data');} +form.submit();if(fileInputClones&&fileInputClones.length){options.fileInput.each(function(index,input){var clone=$(fileInputClones[index]);$(input).prop('name',clone.prop('name'));clone.replaceWith(input);});}});form.append(iframe).appendTo(document.body);},abort:function(){if(iframe){iframe.unbind('load').prop('src','javascript'.concat(':false;'));} +if(form){form.remove();}}};}});$.ajaxSetup({converters:{'iframe text':function(iframe){return $(iframe[0].body).text();},'iframe json':function(iframe){return $.parseJSON($(iframe[0].body).text());},'iframe html':function(iframe){return $(iframe[0].body).html();},'iframe script':function(iframe){return $.globalEval($(iframe[0].body).text());}}});})); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/.gitignore b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/.gitignore new file mode 100644 index 00000000..c6ef2182 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/.gitignore @@ -0,0 +1,2 @@ +.idea + diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/CONTRIBUTING.md b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/CONTRIBUTING.md new file mode 100644 index 00000000..bca4baf6 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/CONTRIBUTING.md @@ -0,0 +1,107 @@ +Contributing to Select2 +======================= +Looking to contribute something to Select2? **Here's how you can help.** + +Please take a moment to review this document in order to make the contribution +process easy and effective for everyone involved. + +Following these guidelines helps to communicate that you respect the time of +the developers managing and developing this open source project. In return, +they should reciprocate that respect in addressing your issue or assessing +patches and features. + +Using the issue tracker +----------------------- +When [reporting bugs][reporting-bugs] or +[requesting features][requesting-features], the +[issue tracker on GitHub][issue-tracker] is the recommended channel to use. + +The issue tracker **is not** a place for support requests. The +[mailing list][mailing-list] or [IRC channel][irc-channel] are better places to +get help. + +Reporting bugs with Select2 +--------------------------- +We really appreciate clear bug reports that _consistently_ show an issue +_within Select2_. + +The ideal bug report follows these guidelines: + +1. **Use the [GitHub issue search][issue-search]** — Check if the issue + has already been reported. +2. **Check if the issue has been fixed** — Try to reproduce the problem + using the code in the `master` branch. +3. **Isolate the problem** — Try to create an + [isolated test case][isolated-case] that consistently reproduces the problem. + +Please try to be as detailed as possible in your bug report, especially if an +isolated test case cannot be made. Some useful questions to include the answer +to are: + +- What steps can be used to reproduce the issue? +- What is the bug and what is the expected outcome? +- What browser(s) and Operating System have you tested with? +- Does the bug happen consistently across all tested browsers? +- What version of jQuery are you using? And what version of Select2? +- Are you using Select2 with other plugins? + +All of these questions will help people fix and identify any potential bugs. + +Requesting features in Select2 +------------------------------ +Select2 is a large library that carries with it a lot of functionality. Because +of this, many feature requests will not be implemented in the core library. + +Before starting work on a major feature for Select2, **contact the +[community][community] first** or you may risk spending a considerable amount of +time on something which the project developers are not interested in bringing +into the project. + +### Select2 4.0 + +Many feature requests will be closed off until 4.0, where Select2 plans to adopt +a more flexible API. If you are interested in helping with the development of +the next major Select2 release, please send a message to the +[mailing list][mailing-list] or [irc channel][irc-channel] for more information. + +Triaging issues and pull requests +--------------------------------- +Anyone can help the project maintainers triage issues and review pull requests. + +### Handling new issues + +Select2 regularly receives new issues which need to be tested and organized. + +When a new issue that comes in that is similar to another existing issue, it +should be checked to make sure it is not a duplicate. Duplicates issues should +be marked by replying to the issue with "Duplicate of #[issue number]" where +`[issue number]` is the url or issue number for the existing issue. This will +allow the project maintainers to quickly close off additional issues and keep +the discussion focused within a single issue. + +If you can test issues that are reported to Select2 that contain test cases and +confirm under what conditions bugs happen, that will allow others to identify +what causes a bug quicker. + +### Reviewing pull requests + +It is very common for pull requests to be opened for issues that contain a clear +solution to the problem. These pull requests should be rigorously reviewed by +the community before being accepted. If you are not sure about a piece of +submitted code, or know of a better way to do something, do not hesitate to make +a comment on the pull request. + +It should also be made clear that **all code contributed to Select** must be +licensable under the [Apache 2 or GPL 2 licenses][licensing]. Code that cannot +be released under either of these licenses **cannot be accepted** into the +project. + +[community]: https://github.com/ivaynberg/select2#community +[reporting-bugs]: #reporting-bugs-with-select2 +[requesting-features]: #requesting-features-in-select2 +[issue-tracker]: https://github.com/ivaynberg/select2/issues +[mailing-list]: https://github.com/ivaynberg/select2#mailing-list +[irc-channel]: https://github.com/ivaynberg/select2#irc-channel +[issue-search]: https://github.com/ivaynberg/select2/search?q=&type=Issues +[isolated-case]: http://css-tricks.com/6263-reduced-test-cases/ +[licensing]: https://github.com/ivaynberg/select2#copyright-and-license diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/README.md b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/README.md new file mode 100644 index 00000000..cb64dee9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/README.md @@ -0,0 +1,115 @@ +Select2 +======= + +Select2 is a jQuery-based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results. + +To get started, checkout examples and documentation at http://select2.github.io/select2/ + +Use cases +--------- + +* Enhancing native selects with search. +* Enhancing native selects with a better multi-select interface. +* Loading data from JavaScript: easily load items via ajax and have them searchable. +* Nesting optgroups: native selects only support one level of nested. Select2 does not have this restriction. +* Tagging: ability to add new items on the fly. +* Working with large, remote datasets: ability to partially load a dataset based on the search term. +* Paging of large datasets: easy support for loading more pages when the results are scrolled to the end. +* Templating: support for custom rendering of results and selections. + +Browser compatibility +--------------------- +* IE 8+ +* Chrome 8+ +* Firefox 10+ +* Safari 3+ +* Opera 10.6+ + +Usage +----- +You can source Select2 directly from a CDN like [jsDelivr](http://www.jsdelivr.com/#!select2) or [CDNJS](http://www.cdnjs.com/libraries/select2), [download it from this GitHub repo](https://github.com/select2/select2/tags), or use one of the integrations below. + +Integrations +------------ + +* [Wicket-Select2](https://github.com/ivaynberg/wicket-select2) (Java / [Apache Wicket](http://wicket.apache.org)) +* [select2-rails](https://github.com/argerim/select2-rails) (Ruby on Rails) +* [AngularUI](http://angular-ui.github.io/#ui-select) ([AngularJS](https://angularjs.org/)) +* [Django](https://github.com/applegrew/django-select2) +* [Symfony](https://github.com/19Gerhard85/sfSelect2WidgetsPlugin) +* [Symfony2](https://github.com/avocode/FormExtensions) +* [Bootstrap 2](https://github.com/t0m/select2-bootstrap-css) and [Bootstrap 3](https://github.com/t0m/select2-bootstrap-css/tree/bootstrap3) (CSS skins) +* [Meteor](https://github.com/nate-strauser/meteor-select2) (modern reactive JavaScript framework; + [Bootstrap 3 skin](https://github.com/esperadomedia/meteor-select2-bootstrap3-css/)) +* [Meteor](https://jquery-select2.meteor.com) +* [Yii 2.x](http://demos.krajee.com/widgets#select2) +* [Yii 1.x](https://github.com/tonybolzan/yii-select2) +* [AtmosphereJS](https://atmospherejs.com/package/jquery-select2) +* [EmberJS](https://github.com/iStefo/ember-select-2) + +### Example Integrations + +* [Knockout.js](https://github.com/ivaynberg/select2/wiki/Knockout.js-Integration) +* [Socket.IO](https://github.com/ivaynberg/select2/wiki/Socket.IO-Integration) +* [PHP](https://github.com/ivaynberg/select2/wiki/PHP-Example) +* [.Net MVC] (https://github.com/ivaynberg/select2/wiki/.Net-MVC-Example) + +Internationalization (i18n) +--------------------------- + +Select2 supports multiple languages by simply including the right language JS +file (`select2_locale_it.js`, `select2_locale_nl.js`, etc.) after `select2.js`. + +Missing a language? Just copy `select2_locale_en.js.template`, translate +it, and make a pull request back to Select2 here on GitHub. + +Documentation +------------- + +The documentation for Select2 is available [through GitHub Pages](http://select2.github.io/select2/) and is located within this repository in the [`gh-pages` branch](https://github.com/ivaynberg/select2/tree/gh-pages). + +Community +--------- + +### Bug tracker + +Have a bug? Please create an issue here on GitHub! + +https://github.com/ivaynberg/select2/issues + +### Mailing list + +Have a question? Ask on our mailing list! + +select2@googlegroups.com + +https://groups.google.com/d/forum/select2 + +### IRC channel + +Need help implementing Select2 in your project? Ask in our IRC channel! + +**Network:** [Freenode](https://freenode.net/) (`chat.freenode.net`) + +**Channel:** `#select2` + +**Web access:** https://webchat.freenode.net/?channels=select2 + +Copyright and license +--------------------- + +Copyright 2015 Igor Vaynberg + +This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU +General Public License version 2 (the "GPL License"). You may choose either license to govern your +use of this software only upon the condition that you accept all of the terms of either the Apache +License or the GPL License. + +You may obtain a copy of the Apache License and the GPL License in the LICENSE file, or at: + +http://www.apache.org/licenses/LICENSE-2.0 +http://www.gnu.org/licenses/gpl-2.0.html + +Unless required by applicable law or agreed to in writing, software distributed under the Apache License +or the GPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +either express or implied. See the Apache License and the GPL License for the specific language governing +permissions and limitations under the Apache License and the GPL License. diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/bower.json b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/bower.json new file mode 100644 index 00000000..c727c1e4 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/bower.json @@ -0,0 +1,8 @@ +{ + "name": "select2", + "version": "3.5.4", + "main": ["select2.js", "select2.css", "select2.png", "select2x2.png", "select2-spinner.gif"], + "dependencies": { + "jquery": ">= 1.7.1" + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/component.json b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/component.json new file mode 100644 index 00000000..1d942e65 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/component.json @@ -0,0 +1,66 @@ +{ + "name": "select2", + "repo": "ivaynberg/select2", + "description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.", + "version": "3.5.4", + "demo": "http://ivaynberg.github.io/select2/", + "keywords": [ + "jquery" + ], + "main": "select2.js", + "styles": [ + "select2.css", + "select2-bootstrap.css" + ], + "scripts": [ + "select2.js", + "select2_locale_ar.js", + "select2_locale_bg.js", + "select2_locale_ca.js", + "select2_locale_cs.js", + "select2_locale_da.js", + "select2_locale_de.js", + "select2_locale_el.js", + "select2_locale_es.js", + "select2_locale_et.js", + "select2_locale_eu.js", + "select2_locale_fa.js", + "select2_locale_fi.js", + "select2_locale_fr.js", + "select2_locale_gl.js", + "select2_locale_he.js", + "select2_locale_hr.js", + "select2_locale_hu.js", + "select2_locale_id.js", + "select2_locale_is.js", + "select2_locale_it.js", + "select2_locale_ja.js", + "select2_locale_ka.js", + "select2_locale_ko.js", + "select2_locale_lt.js", + "select2_locale_lv.js", + "select2_locale_mk.js", + "select2_locale_ms.js", + "select2_locale_nl.js", + "select2_locale_no.js", + "select2_locale_pl.js", + "select2_locale_pt-BR.js", + "select2_locale_pt-PT.js", + "select2_locale_ro.js", + "select2_locale_ru.js", + "select2_locale_sk.js", + "select2_locale_sv.js", + "select2_locale_th.js", + "select2_locale_tr.js", + "select2_locale_uk.js", + "select2_locale_vi.js", + "select2_locale_zh-CN.js", + "select2_locale_zh-TW.js" + ], + "images": [ + "select2-spinner.gif", + "select2.png", + "select2x2.png" + ], + "license": "MIT" +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/composer.json b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/composer.json new file mode 100644 index 00000000..cb8ddb5f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/composer.json @@ -0,0 +1,29 @@ +{ + "name": + "ivaynberg/select2", + "description": "Select2 is a jQuery based replacement for select boxes.", + "version": "3.5.4", + "type": "component", + "homepage": "http://ivaynberg.github.io/select2/", + "license": "Apache-2.0", + "require": { + "robloach/component-installer": "*", + "components/jquery": ">=1.7.1" + }, + "extra": { + "component": { + "scripts": [ + "select2.js" + ], + "files": [ + "select2.js", + "select2_locale_*.js", + "select2.css", + "select2-bootstrap.css", + "select2-spinner.gif", + "select2.png", + "select2x2.png" + ] + } + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/package.json b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/package.json new file mode 100644 index 00000000..d61868bd --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/package.json @@ -0,0 +1,20 @@ +{ + "name" : "Select2", + "description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.", + "homepage": "http://ivaynberg.github.io/select2", + "author": "Igor Vaynberg", + "repository": {"type": "git", "url": "git://github.com/ivaynberg/select2.git"}, + "main": "select2.js", + "version": "3.5.4", + "jspm": { + "main": "select2", + "files": ["select2.js", "select2.png", "select2.css", "select2-spinner.gif"], + "shim": { + "select2": { + "imports": ["jquery", "./select2.css!"], + "exports": "$" + } + }, + "buildConfig": { "uglify": true } + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/release.sh b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/release.sh new file mode 100755 index 00000000..3b63f966 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/release.sh @@ -0,0 +1,79 @@ +#!/bin/bash +set -e + +echo -n "Enter the version for this release: " + +read ver + +if [ ! $ver ]; then + echo "Invalid version." + exit +fi + +name="select2" +js="$name.js" +mini="$name.min.js" +css="$name.css" +release="$name-$ver" +tag="$ver" +branch="build-$ver" +curbranch=`git branch | grep "*" | sed "s/* //"` +timestamp=$(date) +tokens="s/@@ver@@/$ver/g;s/\@@timestamp@@/$timestamp/g" +remote="origin" + +echo "Pulling from origin" + +git pull + +echo "Updating Version Identifiers" + +sed -E -e "s/\"version\": \"([0-9\.]+)\",/\"version\": \"$ver\",/g" -i -- bower.json select2.jquery.json component.json composer.json package.json + +git add bower.json +git add select2.jquery.json +git add component.json +git add composer.json +git add package.json + +git commit -m "modified version identifiers in descriptors for release $ver" +git push + +git branch "$branch" +git checkout "$branch" + +echo "Tokenizing..." + +find . -name "$js" | xargs -I{} sed -e "$tokens" -i -- {} +find . -name "$css" | xargs -I{} sed -e "$tokens" -i -- {} + +sed -e "s/latest/$ver/g" -i -- bower.json + +git add "$js" +git add "$css" + +echo "Minifying..." + +echo "/*" > "$mini" +cat LICENSE | sed "$tokens" >> "$mini" +echo "*/" >> "$mini" + +curl -s \ + --data-urlencode "js_code@$js" \ + http://marijnhaverbeke.nl/uglifyjs \ + >> "$mini" + +git add "$mini" + +git commit -m "release $ver" + +echo "Tagging..." +git tag -a "$tag" -m "tagged version $ver" +git push "$remote" --tags + +echo "Cleaning Up..." + +git checkout "$curbranch" +git branch -D "$branch" + +echo "Done" diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2-bootstrap.css b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2-bootstrap.css new file mode 100644 index 00000000..3b83f0a2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2-bootstrap.css @@ -0,0 +1,87 @@ +.form-control .select2-choice { + border: 0; + border-radius: 2px; +} + +.form-control .select2-choice .select2-arrow { + border-radius: 0 2px 2px 0; +} + +.form-control.select2-container { + height: auto !important; + padding: 0; +} + +.form-control.select2-container.select2-dropdown-open { + border-color: #5897FB; + border-radius: 3px 3px 0 0; +} + +.form-control .select2-container.select2-dropdown-open .select2-choices { + border-radius: 3px 3px 0 0; +} + +.form-control.select2-container .select2-choices { + border: 0 !important; + border-radius: 3px; +} + +.control-group.warning .select2-container .select2-choice, +.control-group.warning .select2-container .select2-choices, +.control-group.warning .select2-container-active .select2-choice, +.control-group.warning .select2-container-active .select2-choices, +.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choice, +.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choices, +.control-group.warning .select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #C09853 !important; +} + +.control-group.warning .select2-container .select2-choice div { + border-left: 1px solid #C09853 !important; + background: #FCF8E3 !important; +} + +.control-group.error .select2-container .select2-choice, +.control-group.error .select2-container .select2-choices, +.control-group.error .select2-container-active .select2-choice, +.control-group.error .select2-container-active .select2-choices, +.control-group.error .select2-dropdown-open.select2-drop-above .select2-choice, +.control-group.error .select2-dropdown-open.select2-drop-above .select2-choices, +.control-group.error .select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #B94A48 !important; +} + +.control-group.error .select2-container .select2-choice div { + border-left: 1px solid #B94A48 !important; + background: #F2DEDE !important; +} + +.control-group.info .select2-container .select2-choice, +.control-group.info .select2-container .select2-choices, +.control-group.info .select2-container-active .select2-choice, +.control-group.info .select2-container-active .select2-choices, +.control-group.info .select2-dropdown-open.select2-drop-above .select2-choice, +.control-group.info .select2-dropdown-open.select2-drop-above .select2-choices, +.control-group.info .select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #3A87AD !important; +} + +.control-group.info .select2-container .select2-choice div { + border-left: 1px solid #3A87AD !important; + background: #D9EDF7 !important; +} + +.control-group.success .select2-container .select2-choice, +.control-group.success .select2-container .select2-choices, +.control-group.success .select2-container-active .select2-choice, +.control-group.success .select2-container-active .select2-choices, +.control-group.success .select2-dropdown-open.select2-drop-above .select2-choice, +.control-group.success .select2-dropdown-open.select2-drop-above .select2-choices, +.control-group.success .select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #468847 !important; +} + +.control-group.success .select2-container .select2-choice div { + border-left: 1px solid #468847 !important; + background: #DFF0D8 !important; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2-spinner.gif b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2-spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..5b33f7e54f4e55b6b8774d86d96895db9af044b4 GIT binary patch literal 1849 zcma*odr(tX9tZI2z31lM+(&YVk%mZ}5P~KlG2s=WSbGzm0!x7^P##Mnh7t-jP!X0Q zk_SQ}Po-L1tlDK;6l?(>v)e5ZBQx4|Y-Q?nr@Px3?9h(3ZWr3^tj=`TP57gKr87N$ zp2wWee1GRRCwo_xahnw)5cxNPJbCg2L6DV|6`#+yw6v6!mDS$f9-JvFD^n;GQ&UrZ zzh5jCkByB101O60U0q#p_1BM>Cv-vP?&s4@g_((4_1L=L$(a91)0=J91Gas#R{McE znYG^9*0A5YZ>#;~+Wkn(W5B0^yELIYLP!K}mB~<)AM@1&nqekynuaEGqPrzoH|KodRXJy)%+w_fu3nE5>@Bd_b zqC$EQ;{c`T&?EsNO|igL9gC7Ygxv?aQUEXMq?~>wg{EyW;VcJ37CUF#HjrT=KQO_* zS>M9yydXk18D(+QDJ1>r);Lav_uYKp$T?4vr{Q$lTo&pKv^?(>L-)G2*lwH!Ah7k? z7oH<8h-(KTKt5V6$8gF)C7Io&P5=SjTh)=zV=E2EUhQZP##L8S{d%UK>>+y82>+FV+#^BzW7u3F)Bb>=lYQ%%j`F>ASe zo*cw@V#u6T`A2He;70mR(V&iV&-7{qP~=SRf&jm9-T{*ZeZ}$rd0#6c&fLG^xJcf5 z+p<`wJYgW+_s*V{uI$nMB;%8`S_3>PfGOj3Rq}@Cx^+j?rk92fANSFDBYnOqQ>Vdj z)(|$AhP4t&Lb=Gvo2#3Gl%9<=Gv`Mz?Po@P4iLF!x}GUWJICDlFk-hS^Whyh7x~VH z@0vD1>HYD4&e+~yzS*-sFR{9`{QEEZO1zg7>R&7cHts-6j!xHVdA8eI+ZlVzd%`es zJT@$#GX(gvCJ1oJN%yLBK}{V=V;seo;!w|Yte!W1%5qLNFWqvZW>h&IiH+oPT=b@E zPhGzv5=(Un*X>v`>%8h_nj^NdYcE6NHS_ifkCV$*D)Tqrbu`s;<=t<4 zAHNqNV?6(g<1PY-w@#I-WYFViz?9TrkMr)u0g`O`u|>T;k|2sV*YF^punvT;$SuTy{j3Gv)yqD!R_CF>yR)MzmmYS5v+~R zXAdD%ng9?df;wd8GxR#%3O+gz};Vo;)sK%Bj-q>Oq%R7JU-KD?vYu>#2UjaDo z&8$>5xW~?KPD_#XFToU1hIb*VOMidUr6iYiO0N|i-7s`T8!cFT`rN!^1Pt78J93i6 z5HI1wIM$94m{3SLDvISDe6$ZG1;eq_D9RTaaC>=cO{@Bs>$IlPCPJJ$h$)-3vzNUQ6OsN#_zWxey!_9%hxwH2_dEJi=yY|1c7nDm2_Lm!Cof8-R_+9UkS zcBE(o47yE)oMR(Q=dp1a2wTX5KvvGyLqlWTa7V&!A*|w|)ax~1_~aJ0=_Lilg*0iQk7#ZD EAHN$8j{pDw literal 0 HcmV?d00001 diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.css b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.css new file mode 100644 index 00000000..91534703 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.css @@ -0,0 +1,692 @@ +/* +Version: 3.5.4 Timestamp: Sun Aug 30 13:30:32 EDT 2015 +*/ +.select2-container { + margin: 0; + position: relative; + display: inline-block; + vertical-align: middle; +} + +.select2-container, +.select2-drop, +.select2-search, +.select2-search input { + /* + Force border-box so that % widths fit the parent + container without overlap because of margin/padding. + More Info : http://www.quirksmode.org/css/box.html + */ + -webkit-box-sizing: border-box; /* webkit */ + -moz-box-sizing: border-box; /* firefox */ + box-sizing: border-box; /* css3 */ +} + +.select2-container .select2-choice { + display: block; + height: 26px; + padding: 0 0 0 8px; + overflow: hidden; + position: relative; + + border: 1px solid #aaa; + white-space: nowrap; + line-height: 26px; + color: #444; + text-decoration: none; + + border-radius: 4px; + + background-clip: padding-box; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + background-color: #fff; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff)); + background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%); + background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0); + background-image: linear-gradient(to top, #eee 0%, #fff 50%); +} + +html[dir="rtl"] .select2-container .select2-choice { + padding: 0 8px 0 0; +} + +.select2-container.select2-drop-above .select2-choice { + border-bottom-color: #aaa; + + border-radius: 0 0 4px 4px; + + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff)); + background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%); + background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0); + background-image: linear-gradient(to bottom, #eee 0%, #fff 90%); +} + +.select2-container.select2-allowclear .select2-choice .select2-chosen { + margin-right: 42px; +} + +.select2-container .select2-choice > .select2-chosen { + margin-right: 26px; + display: block; + overflow: hidden; + + white-space: nowrap; + + text-overflow: ellipsis; + float: none; + width: auto; +} + +html[dir="rtl"] .select2-container .select2-choice > .select2-chosen { + margin-left: 26px; + margin-right: 0; +} + +.select2-container .select2-choice abbr { + display: none; + width: 12px; + height: 12px; + position: absolute; + right: 24px; + top: 8px; + + font-size: 1px; + text-decoration: none; + + border: 0; + background: url('select2.png') right top no-repeat; + cursor: pointer; + outline: 0; +} + +.select2-container.select2-allowclear .select2-choice abbr { + display: inline-block; +} + +.select2-container .select2-choice abbr:hover { + background-position: right -11px; + cursor: pointer; +} + +.select2-drop-mask { + border: 0; + margin: 0; + padding: 0; + position: fixed; + left: 0; + top: 0; + min-height: 100%; + min-width: 100%; + height: auto; + width: auto; + opacity: 0; + z-index: 9998; + /* styles required for IE to work */ + background-color: #fff; + filter: alpha(opacity=0); +} + +.select2-drop { + width: 100%; + margin-top: -1px; + position: absolute; + z-index: 9999; + top: 100%; + + background: #fff; + color: #000; + border: 1px solid #aaa; + border-top: 0; + + border-radius: 0 0 4px 4px; + + -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); + box-shadow: 0 4px 5px rgba(0, 0, 0, .15); +} + +.select2-drop.select2-drop-above { + margin-top: 1px; + border-top: 1px solid #aaa; + border-bottom: 0; + + border-radius: 4px 4px 0 0; + + -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); + box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); +} + +.select2-drop-active { + border: 1px solid #5897fb; + border-top: none; +} + +.select2-drop.select2-drop-above.select2-drop-active { + border-top: 1px solid #5897fb; +} + +.select2-drop-auto-width { + border-top: 1px solid #aaa; + width: auto; +} + +.select2-container .select2-choice .select2-arrow { + display: inline-block; + width: 18px; + height: 100%; + position: absolute; + right: 0; + top: 0; + + border-left: 1px solid #aaa; + border-radius: 0 4px 4px 0; + + background-clip: padding-box; + + background: #ccc; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee)); + background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%); + background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0); + background-image: linear-gradient(to top, #ccc 0%, #eee 60%); +} + +html[dir="rtl"] .select2-container .select2-choice .select2-arrow { + left: 0; + right: auto; + + border-left: none; + border-right: 1px solid #aaa; + border-radius: 4px 0 0 4px; +} + +.select2-container .select2-choice .select2-arrow b { + display: block; + width: 100%; + height: 100%; + background: url('select2.png') no-repeat 0 1px; +} + +html[dir="rtl"] .select2-container .select2-choice .select2-arrow b { + background-position: 2px 1px; +} + +.select2-search { + display: inline-block; + width: 100%; + min-height: 26px; + margin: 0; + padding: 4px 4px 0 4px; + + position: relative; + z-index: 10000; + + white-space: nowrap; +} + +.select2-search input { + width: 100%; + height: auto !important; + min-height: 26px; + padding: 4px 20px 4px 5px; + margin: 0; + + outline: 0; + font-family: sans-serif; + font-size: 1em; + + border: 1px solid #aaa; + border-radius: 0; + + -webkit-box-shadow: none; + box-shadow: none; + + background: #fff url('select2.png') no-repeat 100% -22px; + background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); + background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; +} + +html[dir="rtl"] .select2-search input { + padding: 4px 5px 4px 20px; + + background: #fff url('select2.png') no-repeat -37px -22px; + background: url('select2.png') no-repeat -37px -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); + background: url('select2.png') no-repeat -37px -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2.png') no-repeat -37px -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2.png') no-repeat -37px -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; +} + +.select2-search input.select2-active { + background: #fff url('select2-spinner.gif') no-repeat 100%; + background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); + background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; +} + +.select2-container-active .select2-choice, +.select2-container-active .select2-choices { + border: 1px solid #5897fb; + outline: none; + + -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); + box-shadow: 0 0 5px rgba(0, 0, 0, .3); +} + +.select2-dropdown-open .select2-choice { + border-bottom-color: transparent; + -webkit-box-shadow: 0 1px 0 #fff inset; + box-shadow: 0 1px 0 #fff inset; + + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + + background-color: #eee; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee)); + background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%); + background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0); + background-image: linear-gradient(to top, #fff 0%, #eee 50%); +} + +.select2-dropdown-open.select2-drop-above .select2-choice, +.select2-dropdown-open.select2-drop-above .select2-choices { + border: 1px solid #5897fb; + border-top-color: transparent; + + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee)); + background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%); + background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0); + background-image: linear-gradient(to bottom, #fff 0%, #eee 50%); +} + +.select2-dropdown-open .select2-choice .select2-arrow { + background: transparent; + border-left: none; + filter: none; +} +html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow { + border-right: none; +} + +.select2-dropdown-open .select2-choice .select2-arrow b { + background-position: -18px 1px; +} + +html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow b { + background-position: -16px 1px; +} + +.select2-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +/* results */ +.select2-results { + max-height: 200px; + padding: 0 0 0 4px; + margin: 4px 4px 4px 0; + position: relative; + overflow-x: hidden; + overflow-y: auto; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +html[dir="rtl"] .select2-results { + padding: 0 4px 0 0; + margin: 4px 0 4px 4px; +} + +.select2-results ul.select2-result-sub { + margin: 0; + padding-left: 0; +} + +.select2-results li { + list-style: none; + display: list-item; + background-image: none; +} + +.select2-results li.select2-result-with-children > .select2-result-label { + font-weight: bold; +} + +.select2-results .select2-result-label { + padding: 3px 7px 4px; + margin: 0; + cursor: pointer; + + min-height: 1em; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.select2-results-dept-1 .select2-result-label { padding-left: 20px } +.select2-results-dept-2 .select2-result-label { padding-left: 40px } +.select2-results-dept-3 .select2-result-label { padding-left: 60px } +.select2-results-dept-4 .select2-result-label { padding-left: 80px } +.select2-results-dept-5 .select2-result-label { padding-left: 100px } +.select2-results-dept-6 .select2-result-label { padding-left: 110px } +.select2-results-dept-7 .select2-result-label { padding-left: 120px } + +.select2-results .select2-highlighted { + background: #3875d7; + color: #fff; +} + +.select2-results li em { + background: #feffde; + font-style: normal; +} + +.select2-results .select2-highlighted em { + background: transparent; +} + +.select2-results .select2-highlighted ul { + background: #fff; + color: #000; +} + +.select2-results .select2-no-results, +.select2-results .select2-searching, +.select2-results .select2-ajax-error, +.select2-results .select2-selection-limit { + background: #f4f4f4; + display: list-item; + padding-left: 5px; +} + +/* +disabled look for disabled choices in the results dropdown +*/ +.select2-results .select2-disabled.select2-highlighted { + color: #666; + background: #f4f4f4; + display: list-item; + cursor: default; +} +.select2-results .select2-disabled { + background: #f4f4f4; + display: list-item; + cursor: default; +} + +.select2-results .select2-selected { + display: none; +} + +.select2-more-results.select2-active { + background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%; +} + +.select2-results .select2-ajax-error { + background: rgba(255, 50, 50, .2); +} + +.select2-more-results { + background: #f4f4f4; + display: list-item; +} + +/* disabled styles */ + +.select2-container.select2-container-disabled .select2-choice { + background-color: #f4f4f4; + background-image: none; + border: 1px solid #ddd; + cursor: default; +} + +.select2-container.select2-container-disabled .select2-choice .select2-arrow { + background-color: #f4f4f4; + background-image: none; + border-left: 0; +} + +.select2-container.select2-container-disabled .select2-choice abbr { + display: none; +} + + +/* multiselect */ + +.select2-container-multi .select2-choices { + height: auto !important; + height: 1%; + margin: 0; + padding: 0 5px 0 0; + position: relative; + + border: 1px solid #aaa; + cursor: text; + overflow: hidden; + + background-color: #fff; + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff)); + background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%); + background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%); + background-image: linear-gradient(to bottom, #eee 1%, #fff 15%); +} + +html[dir="rtl"] .select2-container-multi .select2-choices { + padding: 0 0 0 5px; +} + +.select2-locked { + padding: 3px 5px 3px 5px !important; +} + +.select2-container-multi .select2-choices { + min-height: 26px; +} + +.select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #5897fb; + outline: none; + + -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); + box-shadow: 0 0 5px rgba(0, 0, 0, .3); +} +.select2-container-multi .select2-choices li { + float: left; + list-style: none; +} +html[dir="rtl"] .select2-container-multi .select2-choices li +{ + float: right; +} +.select2-container-multi .select2-choices .select2-search-field { + margin: 0; + padding: 0; + white-space: nowrap; +} + +.select2-container-multi .select2-choices .select2-search-field input { + padding: 5px; + margin: 1px 0; + + font-family: sans-serif; + font-size: 100%; + color: #666; + outline: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + background: transparent !important; +} + +.select2-container-multi .select2-choices .select2-search-field input.select2-active { + background: #fff url('select2-spinner.gif') no-repeat 100% !important; +} + +.select2-default { + color: #999 !important; +} + +.select2-container-multi .select2-choices .select2-search-choice { + padding: 3px 5px 3px 18px; + margin: 3px 0 3px 5px; + position: relative; + + line-height: 13px; + color: #333; + cursor: default; + border: 1px solid #aaaaaa; + + border-radius: 3px; + + -webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); + box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); + + background-clip: padding-box; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + background-color: #e4e4e4; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee)); + background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); + background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); + background-image: linear-gradient(to bottom, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); +} +html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice +{ + margin: 3px 5px 3px 0; + padding: 3px 18px 3px 5px; +} +.select2-container-multi .select2-choices .select2-search-choice .select2-chosen { + cursor: default; +} +.select2-container-multi .select2-choices .select2-search-choice-focus { + background: #d4d4d4; +} + +.select2-search-choice-close { + display: block; + width: 12px; + height: 13px; + position: absolute; + right: 3px; + top: 4px; + + font-size: 1px; + outline: none; + background: url('select2.png') right top no-repeat; +} +html[dir="rtl"] .select2-search-choice-close { + right: auto; + left: 3px; +} + +.select2-container-multi .select2-search-choice-close { + left: 3px; +} + +html[dir="rtl"] .select2-container-multi .select2-search-choice-close { + left: auto; + right: 2px; +} + +.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover { + background-position: right -11px; +} +.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close { + background-position: right -11px; +} + +/* disabled styles */ +.select2-container-multi.select2-container-disabled .select2-choices { + background-color: #f4f4f4; + background-image: none; + border: 1px solid #ddd; + cursor: default; +} + +.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice { + padding: 3px 5px 3px 5px; + border: 1px solid #ddd; + background-image: none; + background-color: #f4f4f4; +} + +.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none; + background: none; +} +/* end multiselect */ + + +.select2-result-selectable .select2-match, +.select2-result-unselectable .select2-match { + text-decoration: underline; +} + +.select2-offscreen, .select2-offscreen:focus { + clip: rect(0 0 0 0) !important; + width: 1px !important; + height: 1px !important; + border: 0 !important; + margin: 0 !important; + padding: 0 !important; + overflow: hidden !important; + position: absolute !important; + outline: 0 !important; + left: 0px !important; + top: 0px !important; +} + +.select2-display-none { + display: none; +} + +.select2-measure-scrollbar { + position: absolute; + top: -10000px; + left: -10000px; + width: 100px; + height: 100px; + overflow: scroll; +} + +/* Retina-ize icons */ + +@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 2dppx) { + .select2-search input, + .select2-search-choice-close, + .select2-container .select2-choice abbr, + .select2-container .select2-choice .select2-arrow b { + background-image: url('select2x2.png') !important; + background-repeat: no-repeat !important; + background-size: 60px 40px !important; + } + + .select2-search input { + background-position: 100% -21px !important; + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.jquery.json b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.jquery.json new file mode 100644 index 00000000..ee7443cf --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.jquery.json @@ -0,0 +1,36 @@ +{ + "name": "select2", + "title": "Select2", + "description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.", + "keywords": [ + "select", + "autocomplete", + "typeahead", + "dropdown", + "multiselect", + "tag", + "tagging" + ], + "version": "3.5.4", + "author": { + "name": "Igor Vaynberg", + "url": "https://github.com/ivaynberg" + }, + "licenses": [ + { + "type": "Apache", + "url": "http://www.apache.org/licenses/LICENSE-2.0" + }, + { + "type": "GPL v2", + "url": "http://www.gnu.org/licenses/gpl-2.0.html" + } + ], + "bugs": "https://github.com/ivaynberg/select2/issues", + "homepage": "http://ivaynberg.github.com/select2", + "docs": "http://ivaynberg.github.com/select2/", + "download": "https://github.com/ivaynberg/select2/tags", + "dependencies": { + "jquery": ">=1.7.1" + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.js new file mode 100644 index 00000000..195ccee5 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.js @@ -0,0 +1,3729 @@ +/* +Copyright 2012 Igor Vaynberg + +Version: 3.5.4 Timestamp: Sun Aug 30 13:30:32 EDT 2015 + +This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU +General Public License version 2 (the "GPL License"). You may choose either license to govern your +use of this software only upon the condition that you accept all of the terms of either the Apache +License or the GPL License. + +You may obtain a copy of the Apache License and the GPL License at: + + http://www.apache.org/licenses/LICENSE-2.0 + http://www.gnu.org/licenses/gpl-2.0.html + +Unless required by applicable law or agreed to in writing, software distributed under the +Apache License or the GPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the Apache License and the GPL License for +the specific language governing permissions and limitations under the Apache License and the GPL License. +*/ +(function ($) { + if(typeof $.fn.each2 == "undefined") { + $.extend($.fn, { + /* + * 4-10 times faster .each replacement + * use it carefully, as it overrides jQuery context of element on each iteration + */ + each2 : function (c) { + var j = $([0]), i = -1, l = this.length; + while ( + ++i < l + && (j.context = j[0] = this[i]) + && c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object + ); + return this; + } + }); + } +})(jQuery); + +(function ($, undefined) { + "use strict"; + /*global document, window, jQuery, console */ + + if (window.Select2 !== undefined) { + return; + } + + var AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer, + lastMousePosition={x:0,y:0}, $document, scrollBarDimensions, + + KEY = { + TAB: 9, + ENTER: 13, + ESC: 27, + SPACE: 32, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + SHIFT: 16, + CTRL: 17, + ALT: 18, + PAGE_UP: 33, + PAGE_DOWN: 34, + HOME: 36, + END: 35, + BACKSPACE: 8, + DELETE: 46, + isArrow: function (k) { + k = k.which ? k.which : k; + switch (k) { + case KEY.LEFT: + case KEY.RIGHT: + case KEY.UP: + case KEY.DOWN: + return true; + } + return false; + }, + isControl: function (e) { + var k = e.which; + switch (k) { + case KEY.SHIFT: + case KEY.CTRL: + case KEY.ALT: + return true; + } + + if (e.metaKey) return true; + + return false; + }, + isFunctionKey: function (k) { + k = k.which ? k.which : k; + return k >= 112 && k <= 123; + } + }, + MEASURE_SCROLLBAR_TEMPLATE = "
", + + DIACRITICS = {"\u24B6":"A","\uFF21":"A","\u00C0":"A","\u00C1":"A","\u00C2":"A","\u1EA6":"A","\u1EA4":"A","\u1EAA":"A","\u1EA8":"A","\u00C3":"A","\u0100":"A","\u0102":"A","\u1EB0":"A","\u1EAE":"A","\u1EB4":"A","\u1EB2":"A","\u0226":"A","\u01E0":"A","\u00C4":"A","\u01DE":"A","\u1EA2":"A","\u00C5":"A","\u01FA":"A","\u01CD":"A","\u0200":"A","\u0202":"A","\u1EA0":"A","\u1EAC":"A","\u1EB6":"A","\u1E00":"A","\u0104":"A","\u023A":"A","\u2C6F":"A","\uA732":"AA","\u00C6":"AE","\u01FC":"AE","\u01E2":"AE","\uA734":"AO","\uA736":"AU","\uA738":"AV","\uA73A":"AV","\uA73C":"AY","\u24B7":"B","\uFF22":"B","\u1E02":"B","\u1E04":"B","\u1E06":"B","\u0243":"B","\u0182":"B","\u0181":"B","\u24B8":"C","\uFF23":"C","\u0106":"C","\u0108":"C","\u010A":"C","\u010C":"C","\u00C7":"C","\u1E08":"C","\u0187":"C","\u023B":"C","\uA73E":"C","\u24B9":"D","\uFF24":"D","\u1E0A":"D","\u010E":"D","\u1E0C":"D","\u1E10":"D","\u1E12":"D","\u1E0E":"D","\u0110":"D","\u018B":"D","\u018A":"D","\u0189":"D","\uA779":"D","\u01F1":"DZ","\u01C4":"DZ","\u01F2":"Dz","\u01C5":"Dz","\u24BA":"E","\uFF25":"E","\u00C8":"E","\u00C9":"E","\u00CA":"E","\u1EC0":"E","\u1EBE":"E","\u1EC4":"E","\u1EC2":"E","\u1EBC":"E","\u0112":"E","\u1E14":"E","\u1E16":"E","\u0114":"E","\u0116":"E","\u00CB":"E","\u1EBA":"E","\u011A":"E","\u0204":"E","\u0206":"E","\u1EB8":"E","\u1EC6":"E","\u0228":"E","\u1E1C":"E","\u0118":"E","\u1E18":"E","\u1E1A":"E","\u0190":"E","\u018E":"E","\u24BB":"F","\uFF26":"F","\u1E1E":"F","\u0191":"F","\uA77B":"F","\u24BC":"G","\uFF27":"G","\u01F4":"G","\u011C":"G","\u1E20":"G","\u011E":"G","\u0120":"G","\u01E6":"G","\u0122":"G","\u01E4":"G","\u0193":"G","\uA7A0":"G","\uA77D":"G","\uA77E":"G","\u24BD":"H","\uFF28":"H","\u0124":"H","\u1E22":"H","\u1E26":"H","\u021E":"H","\u1E24":"H","\u1E28":"H","\u1E2A":"H","\u0126":"H","\u2C67":"H","\u2C75":"H","\uA78D":"H","\u24BE":"I","\uFF29":"I","\u00CC":"I","\u00CD":"I","\u00CE":"I","\u0128":"I","\u012A":"I","\u012C":"I","\u0130":"I","\u00CF":"I","\u1E2E":"I","\u1EC8":"I","\u01CF":"I","\u0208":"I","\u020A":"I","\u1ECA":"I","\u012E":"I","\u1E2C":"I","\u0197":"I","\u24BF":"J","\uFF2A":"J","\u0134":"J","\u0248":"J","\u24C0":"K","\uFF2B":"K","\u1E30":"K","\u01E8":"K","\u1E32":"K","\u0136":"K","\u1E34":"K","\u0198":"K","\u2C69":"K","\uA740":"K","\uA742":"K","\uA744":"K","\uA7A2":"K","\u24C1":"L","\uFF2C":"L","\u013F":"L","\u0139":"L","\u013D":"L","\u1E36":"L","\u1E38":"L","\u013B":"L","\u1E3C":"L","\u1E3A":"L","\u0141":"L","\u023D":"L","\u2C62":"L","\u2C60":"L","\uA748":"L","\uA746":"L","\uA780":"L","\u01C7":"LJ","\u01C8":"Lj","\u24C2":"M","\uFF2D":"M","\u1E3E":"M","\u1E40":"M","\u1E42":"M","\u2C6E":"M","\u019C":"M","\u24C3":"N","\uFF2E":"N","\u01F8":"N","\u0143":"N","\u00D1":"N","\u1E44":"N","\u0147":"N","\u1E46":"N","\u0145":"N","\u1E4A":"N","\u1E48":"N","\u0220":"N","\u019D":"N","\uA790":"N","\uA7A4":"N","\u01CA":"NJ","\u01CB":"Nj","\u24C4":"O","\uFF2F":"O","\u00D2":"O","\u00D3":"O","\u00D4":"O","\u1ED2":"O","\u1ED0":"O","\u1ED6":"O","\u1ED4":"O","\u00D5":"O","\u1E4C":"O","\u022C":"O","\u1E4E":"O","\u014C":"O","\u1E50":"O","\u1E52":"O","\u014E":"O","\u022E":"O","\u0230":"O","\u00D6":"O","\u022A":"O","\u1ECE":"O","\u0150":"O","\u01D1":"O","\u020C":"O","\u020E":"O","\u01A0":"O","\u1EDC":"O","\u1EDA":"O","\u1EE0":"O","\u1EDE":"O","\u1EE2":"O","\u1ECC":"O","\u1ED8":"O","\u01EA":"O","\u01EC":"O","\u00D8":"O","\u01FE":"O","\u0186":"O","\u019F":"O","\uA74A":"O","\uA74C":"O","\u01A2":"OI","\uA74E":"OO","\u0222":"OU","\u24C5":"P","\uFF30":"P","\u1E54":"P","\u1E56":"P","\u01A4":"P","\u2C63":"P","\uA750":"P","\uA752":"P","\uA754":"P","\u24C6":"Q","\uFF31":"Q","\uA756":"Q","\uA758":"Q","\u024A":"Q","\u24C7":"R","\uFF32":"R","\u0154":"R","\u1E58":"R","\u0158":"R","\u0210":"R","\u0212":"R","\u1E5A":"R","\u1E5C":"R","\u0156":"R","\u1E5E":"R","\u024C":"R","\u2C64":"R","\uA75A":"R","\uA7A6":"R","\uA782":"R","\u24C8":"S","\uFF33":"S","\u1E9E":"S","\u015A":"S","\u1E64":"S","\u015C":"S","\u1E60":"S","\u0160":"S","\u1E66":"S","\u1E62":"S","\u1E68":"S","\u0218":"S","\u015E":"S","\u2C7E":"S","\uA7A8":"S","\uA784":"S","\u24C9":"T","\uFF34":"T","\u1E6A":"T","\u0164":"T","\u1E6C":"T","\u021A":"T","\u0162":"T","\u1E70":"T","\u1E6E":"T","\u0166":"T","\u01AC":"T","\u01AE":"T","\u023E":"T","\uA786":"T","\uA728":"TZ","\u24CA":"U","\uFF35":"U","\u00D9":"U","\u00DA":"U","\u00DB":"U","\u0168":"U","\u1E78":"U","\u016A":"U","\u1E7A":"U","\u016C":"U","\u00DC":"U","\u01DB":"U","\u01D7":"U","\u01D5":"U","\u01D9":"U","\u1EE6":"U","\u016E":"U","\u0170":"U","\u01D3":"U","\u0214":"U","\u0216":"U","\u01AF":"U","\u1EEA":"U","\u1EE8":"U","\u1EEE":"U","\u1EEC":"U","\u1EF0":"U","\u1EE4":"U","\u1E72":"U","\u0172":"U","\u1E76":"U","\u1E74":"U","\u0244":"U","\u24CB":"V","\uFF36":"V","\u1E7C":"V","\u1E7E":"V","\u01B2":"V","\uA75E":"V","\u0245":"V","\uA760":"VY","\u24CC":"W","\uFF37":"W","\u1E80":"W","\u1E82":"W","\u0174":"W","\u1E86":"W","\u1E84":"W","\u1E88":"W","\u2C72":"W","\u24CD":"X","\uFF38":"X","\u1E8A":"X","\u1E8C":"X","\u24CE":"Y","\uFF39":"Y","\u1EF2":"Y","\u00DD":"Y","\u0176":"Y","\u1EF8":"Y","\u0232":"Y","\u1E8E":"Y","\u0178":"Y","\u1EF6":"Y","\u1EF4":"Y","\u01B3":"Y","\u024E":"Y","\u1EFE":"Y","\u24CF":"Z","\uFF3A":"Z","\u0179":"Z","\u1E90":"Z","\u017B":"Z","\u017D":"Z","\u1E92":"Z","\u1E94":"Z","\u01B5":"Z","\u0224":"Z","\u2C7F":"Z","\u2C6B":"Z","\uA762":"Z","\u24D0":"a","\uFF41":"a","\u1E9A":"a","\u00E0":"a","\u00E1":"a","\u00E2":"a","\u1EA7":"a","\u1EA5":"a","\u1EAB":"a","\u1EA9":"a","\u00E3":"a","\u0101":"a","\u0103":"a","\u1EB1":"a","\u1EAF":"a","\u1EB5":"a","\u1EB3":"a","\u0227":"a","\u01E1":"a","\u00E4":"a","\u01DF":"a","\u1EA3":"a","\u00E5":"a","\u01FB":"a","\u01CE":"a","\u0201":"a","\u0203":"a","\u1EA1":"a","\u1EAD":"a","\u1EB7":"a","\u1E01":"a","\u0105":"a","\u2C65":"a","\u0250":"a","\uA733":"aa","\u00E6":"ae","\u01FD":"ae","\u01E3":"ae","\uA735":"ao","\uA737":"au","\uA739":"av","\uA73B":"av","\uA73D":"ay","\u24D1":"b","\uFF42":"b","\u1E03":"b","\u1E05":"b","\u1E07":"b","\u0180":"b","\u0183":"b","\u0253":"b","\u24D2":"c","\uFF43":"c","\u0107":"c","\u0109":"c","\u010B":"c","\u010D":"c","\u00E7":"c","\u1E09":"c","\u0188":"c","\u023C":"c","\uA73F":"c","\u2184":"c","\u24D3":"d","\uFF44":"d","\u1E0B":"d","\u010F":"d","\u1E0D":"d","\u1E11":"d","\u1E13":"d","\u1E0F":"d","\u0111":"d","\u018C":"d","\u0256":"d","\u0257":"d","\uA77A":"d","\u01F3":"dz","\u01C6":"dz","\u24D4":"e","\uFF45":"e","\u00E8":"e","\u00E9":"e","\u00EA":"e","\u1EC1":"e","\u1EBF":"e","\u1EC5":"e","\u1EC3":"e","\u1EBD":"e","\u0113":"e","\u1E15":"e","\u1E17":"e","\u0115":"e","\u0117":"e","\u00EB":"e","\u1EBB":"e","\u011B":"e","\u0205":"e","\u0207":"e","\u1EB9":"e","\u1EC7":"e","\u0229":"e","\u1E1D":"e","\u0119":"e","\u1E19":"e","\u1E1B":"e","\u0247":"e","\u025B":"e","\u01DD":"e","\u24D5":"f","\uFF46":"f","\u1E1F":"f","\u0192":"f","\uA77C":"f","\u24D6":"g","\uFF47":"g","\u01F5":"g","\u011D":"g","\u1E21":"g","\u011F":"g","\u0121":"g","\u01E7":"g","\u0123":"g","\u01E5":"g","\u0260":"g","\uA7A1":"g","\u1D79":"g","\uA77F":"g","\u24D7":"h","\uFF48":"h","\u0125":"h","\u1E23":"h","\u1E27":"h","\u021F":"h","\u1E25":"h","\u1E29":"h","\u1E2B":"h","\u1E96":"h","\u0127":"h","\u2C68":"h","\u2C76":"h","\u0265":"h","\u0195":"hv","\u24D8":"i","\uFF49":"i","\u00EC":"i","\u00ED":"i","\u00EE":"i","\u0129":"i","\u012B":"i","\u012D":"i","\u00EF":"i","\u1E2F":"i","\u1EC9":"i","\u01D0":"i","\u0209":"i","\u020B":"i","\u1ECB":"i","\u012F":"i","\u1E2D":"i","\u0268":"i","\u0131":"i","\u24D9":"j","\uFF4A":"j","\u0135":"j","\u01F0":"j","\u0249":"j","\u24DA":"k","\uFF4B":"k","\u1E31":"k","\u01E9":"k","\u1E33":"k","\u0137":"k","\u1E35":"k","\u0199":"k","\u2C6A":"k","\uA741":"k","\uA743":"k","\uA745":"k","\uA7A3":"k","\u24DB":"l","\uFF4C":"l","\u0140":"l","\u013A":"l","\u013E":"l","\u1E37":"l","\u1E39":"l","\u013C":"l","\u1E3D":"l","\u1E3B":"l","\u017F":"l","\u0142":"l","\u019A":"l","\u026B":"l","\u2C61":"l","\uA749":"l","\uA781":"l","\uA747":"l","\u01C9":"lj","\u24DC":"m","\uFF4D":"m","\u1E3F":"m","\u1E41":"m","\u1E43":"m","\u0271":"m","\u026F":"m","\u24DD":"n","\uFF4E":"n","\u01F9":"n","\u0144":"n","\u00F1":"n","\u1E45":"n","\u0148":"n","\u1E47":"n","\u0146":"n","\u1E4B":"n","\u1E49":"n","\u019E":"n","\u0272":"n","\u0149":"n","\uA791":"n","\uA7A5":"n","\u01CC":"nj","\u24DE":"o","\uFF4F":"o","\u00F2":"o","\u00F3":"o","\u00F4":"o","\u1ED3":"o","\u1ED1":"o","\u1ED7":"o","\u1ED5":"o","\u00F5":"o","\u1E4D":"o","\u022D":"o","\u1E4F":"o","\u014D":"o","\u1E51":"o","\u1E53":"o","\u014F":"o","\u022F":"o","\u0231":"o","\u00F6":"o","\u022B":"o","\u1ECF":"o","\u0151":"o","\u01D2":"o","\u020D":"o","\u020F":"o","\u01A1":"o","\u1EDD":"o","\u1EDB":"o","\u1EE1":"o","\u1EDF":"o","\u1EE3":"o","\u1ECD":"o","\u1ED9":"o","\u01EB":"o","\u01ED":"o","\u00F8":"o","\u01FF":"o","\u0254":"o","\uA74B":"o","\uA74D":"o","\u0275":"o","\u01A3":"oi","\u0223":"ou","\uA74F":"oo","\u24DF":"p","\uFF50":"p","\u1E55":"p","\u1E57":"p","\u01A5":"p","\u1D7D":"p","\uA751":"p","\uA753":"p","\uA755":"p","\u24E0":"q","\uFF51":"q","\u024B":"q","\uA757":"q","\uA759":"q","\u24E1":"r","\uFF52":"r","\u0155":"r","\u1E59":"r","\u0159":"r","\u0211":"r","\u0213":"r","\u1E5B":"r","\u1E5D":"r","\u0157":"r","\u1E5F":"r","\u024D":"r","\u027D":"r","\uA75B":"r","\uA7A7":"r","\uA783":"r","\u24E2":"s","\uFF53":"s","\u00DF":"s","\u015B":"s","\u1E65":"s","\u015D":"s","\u1E61":"s","\u0161":"s","\u1E67":"s","\u1E63":"s","\u1E69":"s","\u0219":"s","\u015F":"s","\u023F":"s","\uA7A9":"s","\uA785":"s","\u1E9B":"s","\u24E3":"t","\uFF54":"t","\u1E6B":"t","\u1E97":"t","\u0165":"t","\u1E6D":"t","\u021B":"t","\u0163":"t","\u1E71":"t","\u1E6F":"t","\u0167":"t","\u01AD":"t","\u0288":"t","\u2C66":"t","\uA787":"t","\uA729":"tz","\u24E4":"u","\uFF55":"u","\u00F9":"u","\u00FA":"u","\u00FB":"u","\u0169":"u","\u1E79":"u","\u016B":"u","\u1E7B":"u","\u016D":"u","\u00FC":"u","\u01DC":"u","\u01D8":"u","\u01D6":"u","\u01DA":"u","\u1EE7":"u","\u016F":"u","\u0171":"u","\u01D4":"u","\u0215":"u","\u0217":"u","\u01B0":"u","\u1EEB":"u","\u1EE9":"u","\u1EEF":"u","\u1EED":"u","\u1EF1":"u","\u1EE5":"u","\u1E73":"u","\u0173":"u","\u1E77":"u","\u1E75":"u","\u0289":"u","\u24E5":"v","\uFF56":"v","\u1E7D":"v","\u1E7F":"v","\u028B":"v","\uA75F":"v","\u028C":"v","\uA761":"vy","\u24E6":"w","\uFF57":"w","\u1E81":"w","\u1E83":"w","\u0175":"w","\u1E87":"w","\u1E85":"w","\u1E98":"w","\u1E89":"w","\u2C73":"w","\u24E7":"x","\uFF58":"x","\u1E8B":"x","\u1E8D":"x","\u24E8":"y","\uFF59":"y","\u1EF3":"y","\u00FD":"y","\u0177":"y","\u1EF9":"y","\u0233":"y","\u1E8F":"y","\u00FF":"y","\u1EF7":"y","\u1E99":"y","\u1EF5":"y","\u01B4":"y","\u024F":"y","\u1EFF":"y","\u24E9":"z","\uFF5A":"z","\u017A":"z","\u1E91":"z","\u017C":"z","\u017E":"z","\u1E93":"z","\u1E95":"z","\u01B6":"z","\u0225":"z","\u0240":"z","\u2C6C":"z","\uA763":"z","\u0386":"\u0391","\u0388":"\u0395","\u0389":"\u0397","\u038A":"\u0399","\u03AA":"\u0399","\u038C":"\u039F","\u038E":"\u03A5","\u03AB":"\u03A5","\u038F":"\u03A9","\u03AC":"\u03B1","\u03AD":"\u03B5","\u03AE":"\u03B7","\u03AF":"\u03B9","\u03CA":"\u03B9","\u0390":"\u03B9","\u03CC":"\u03BF","\u03CD":"\u03C5","\u03CB":"\u03C5","\u03B0":"\u03C5","\u03C9":"\u03C9","\u03C2":"\u03C3"}; + + $document = $(document); + + nextUid=(function() { var counter=1; return function() { return counter++; }; }()); + + + function reinsertElement(element) { + var placeholder = $(document.createTextNode('')); + + element.before(placeholder); + placeholder.before(element); + placeholder.remove(); + } + + function stripDiacritics(str) { + // Used 'uni range + named function' from http://jsperf.com/diacritics/18 + function match(a) { + return DIACRITICS[a] || a; + } + + return str.replace(/[^\u0000-\u007E]/g, match); + } + + function indexOf(value, array) { + var i = 0, l = array.length; + for (; i < l; i = i + 1) { + if (equal(value, array[i])) return i; + } + return -1; + } + + function measureScrollbar () { + var $template = $( MEASURE_SCROLLBAR_TEMPLATE ); + $template.appendTo(document.body); + + var dim = { + width: $template.width() - $template[0].clientWidth, + height: $template.height() - $template[0].clientHeight + }; + $template.remove(); + + return dim; + } + + /** + * Compares equality of a and b + * @param a + * @param b + */ + function equal(a, b) { + if (a === b) return true; + if (a === undefined || b === undefined) return false; + if (a === null || b === null) return false; + // Check whether 'a' or 'b' is a string (primitive or object). + // The concatenation of an empty string (+'') converts its argument to a string's primitive. + if (a.constructor === String) return a+'' === b+''; // a+'' - in case 'a' is a String object + if (b.constructor === String) return b+'' === a+''; // b+'' - in case 'b' is a String object + return false; + } + + /** + * Splits the string into an array of values, transforming each value. An empty array is returned for nulls or empty + * strings + * @param string + * @param separator + */ + function splitVal(string, separator, transform) { + var val, i, l; + if (string === null || string.length < 1) return []; + val = string.split(separator); + for (i = 0, l = val.length; i < l; i = i + 1) val[i] = transform(val[i]); + return val; + } + + function getSideBorderPadding(element) { + return element.outerWidth(false) - element.width(); + } + + function installKeyUpChangeEvent(element) { + var key="keyup-change-value"; + element.on("keydown", function () { + if ($.data(element, key) === undefined) { + $.data(element, key, element.val()); + } + }); + element.on("keyup", function () { + var val= $.data(element, key); + if (val !== undefined && element.val() !== val) { + $.removeData(element, key); + element.trigger("keyup-change"); + } + }); + } + + + /** + * filters mouse events so an event is fired only if the mouse moved. + * + * filters out mouse events that occur when mouse is stationary but + * the elements under the pointer are scrolled. + */ + function installFilteredMouseMove(element) { + element.on("mousemove", function (e) { + var lastpos = lastMousePosition; + if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) { + $(e.target).trigger("mousemove-filtered", e); + } + }); + } + + /** + * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made + * within the last quietMillis milliseconds. + * + * @param quietMillis number of milliseconds to wait before invoking fn + * @param fn function to be debounced + * @param ctx object to be used as this reference within fn + * @return debounced version of fn + */ + function debounce(quietMillis, fn, ctx) { + ctx = ctx || undefined; + var timeout; + return function () { + var args = arguments; + window.clearTimeout(timeout); + timeout = window.setTimeout(function() { + fn.apply(ctx, args); + }, quietMillis); + }; + } + + function installDebouncedScroll(threshold, element) { + var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);}); + element.on("scroll", function (e) { + if (indexOf(e.target, element.get()) >= 0) notify(e); + }); + } + + function focus($el) { + if ($el[0] === document.activeElement) return; + + /* set the focus in a 0 timeout - that way the focus is set after the processing + of the current event has finished - which seems like the only reliable way + to set focus */ + window.setTimeout(function() { + var el=$el[0], pos=$el.val().length, range; + + $el.focus(); + + /* make sure el received focus so we do not error out when trying to manipulate the caret. + sometimes modals or others listeners may steal it after its set */ + var isVisible = (el.offsetWidth > 0 || el.offsetHeight > 0); + if (isVisible && el === document.activeElement) { + + /* after the focus is set move the caret to the end, necessary when we val() + just before setting focus */ + if(el.setSelectionRange) + { + el.setSelectionRange(pos, pos); + } + else if (el.createTextRange) { + range = el.createTextRange(); + range.collapse(false); + range.select(); + } + } + }, 0); + } + + function getCursorInfo(el) { + el = $(el)[0]; + var offset = 0; + var length = 0; + if ('selectionStart' in el) { + offset = el.selectionStart; + length = el.selectionEnd - offset; + } else if ('selection' in document) { + el.focus(); + var sel = document.selection.createRange(); + length = document.selection.createRange().text.length; + sel.moveStart('character', -el.value.length); + offset = sel.text.length - length; + } + return { offset: offset, length: length }; + } + + function killEvent(event) { + event.preventDefault(); + event.stopPropagation(); + } + function killEventImmediately(event) { + event.preventDefault(); + event.stopImmediatePropagation(); + } + + function measureTextWidth(e) { + if (!sizer){ + var style = e[0].currentStyle || window.getComputedStyle(e[0], null); + sizer = $(document.createElement("div")).css({ + position: "absolute", + left: "-10000px", + top: "-10000px", + display: "none", + fontSize: style.fontSize, + fontFamily: style.fontFamily, + fontStyle: style.fontStyle, + fontWeight: style.fontWeight, + letterSpacing: style.letterSpacing, + textTransform: style.textTransform, + whiteSpace: "nowrap" + }); + sizer.attr("class","select2-sizer"); + $(document.body).append(sizer); + } + sizer.text(e.val()); + return sizer.width(); + } + + function syncCssClasses(dest, src, adapter) { + var classes, replacements = [], adapted; + + classes = $.trim(dest.attr("class")); + + if (classes) { + classes = '' + classes; // for IE which returns object + + $(classes.split(/\s+/)).each2(function() { + if (this.indexOf("select2-") === 0) { + replacements.push(this); + } + }); + } + + classes = $.trim(src.attr("class")); + + if (classes) { + classes = '' + classes; // for IE which returns object + + $(classes.split(/\s+/)).each2(function() { + if (this.indexOf("select2-") !== 0) { + adapted = adapter(this); + + if (adapted) { + replacements.push(adapted); + } + } + }); + } + + dest.attr("class", replacements.join(" ")); + } + + + function markMatch(text, term, markup, escapeMarkup) { + var match=stripDiacritics(text.toUpperCase()).indexOf(stripDiacritics(term.toUpperCase())), + tl=term.length; + + if (match<0) { + markup.push(escapeMarkup(text)); + return; + } + + markup.push(escapeMarkup(text.substring(0, match))); + markup.push(""); + markup.push(escapeMarkup(text.substring(match, match + tl))); + markup.push(""); + markup.push(escapeMarkup(text.substring(match + tl, text.length))); + } + + function defaultEscapeMarkup(markup) { + var replace_map = { + '\\': '\', + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + "/": '/' + }; + + return String(markup).replace(/[&<>"'\/\\]/g, function (match) { + return replace_map[match]; + }); + } + + /** + * Produces an ajax-based query function + * + * @param options object containing configuration parameters + * @param options.params parameter map for the transport ajax call, can contain such options as cache, jsonpCallback, etc. see $.ajax + * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax + * @param options.url url for the data + * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url. + * @param options.dataType request data type: ajax, jsonp, other datatypes supported by jQuery's $.ajax function or the transport function if specified + * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often + * @param options.results a function(remoteData, pageNumber, query) that converts data returned form the remote request to the format expected by Select2. + * The expected format is an object containing the following keys: + * results array of objects that will be used as choices + * more (optional) boolean indicating whether there are more results available + * Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true} + */ + function ajax(options) { + var timeout, // current scheduled but not yet executed request + handler = null, + quietMillis = options.quietMillis || 100, + ajaxUrl = options.url, + self = this; + + return function (query) { + window.clearTimeout(timeout); + timeout = window.setTimeout(function () { + var data = options.data, // ajax data function + url = ajaxUrl, // ajax url string or function + transport = options.transport || $.fn.select2.ajaxDefaults.transport, + // deprecated - to be removed in 4.0 - use params instead + deprecated = { + type: options.type || 'GET', // set type of request (GET or POST) + cache: options.cache || false, + jsonpCallback: options.jsonpCallback||undefined, + dataType: options.dataType||"json" + }, + params = $.extend({}, $.fn.select2.ajaxDefaults.params, deprecated); + + data = data ? data.call(self, query.term, query.page, query.context) : null; + url = (typeof url === 'function') ? url.call(self, query.term, query.page, query.context) : url; + + if (handler && typeof handler.abort === "function") { handler.abort(); } + + if (options.params) { + if ($.isFunction(options.params)) { + $.extend(params, options.params.call(self)); + } else { + $.extend(params, options.params); + } + } + + $.extend(params, { + url: url, + dataType: options.dataType, + data: data, + success: function (data) { + // TODO - replace query.page with query so users have access to term, page, etc. + // added query as third paramter to keep backwards compatibility + var results = options.results(data, query.page, query); + query.callback(results); + }, + error: function(jqXHR, textStatus, errorThrown){ + var results = { + hasError: true, + jqXHR: jqXHR, + textStatus: textStatus, + errorThrown: errorThrown + }; + + query.callback(results); + } + }); + handler = transport.call(self, params); + }, quietMillis); + }; + } + + /** + * Produces a query function that works with a local array + * + * @param options object containing configuration parameters. The options parameter can either be an array or an + * object. + * + * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys. + * + * If the object form is used it is assumed that it contains 'data' and 'text' keys. The 'data' key should contain + * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text' + * key can either be a String in which case it is expected that each element in the 'data' array has a key with the + * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract + * the text. + */ + function local(options) { + var data = options, // data elements + dataText, + tmp, + text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search + + if ($.isArray(data)) { + tmp = data; + data = { results: tmp }; + } + + if ($.isFunction(data) === false) { + tmp = data; + data = function() { return tmp; }; + } + + var dataItem = data(); + if (dataItem.text) { + text = dataItem.text; + // if text is not a function we assume it to be a key name + if (!$.isFunction(text)) { + dataText = dataItem.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available + text = function (item) { return item[dataText]; }; + } + } + + return function (query) { + var t = query.term, filtered = { results: [] }, process; + if (t === "") { + query.callback(data()); + return; + } + + process = function(datum, collection) { + var group, attr; + datum = datum[0]; + if (datum.children) { + group = {}; + for (attr in datum) { + if (datum.hasOwnProperty(attr)) group[attr]=datum[attr]; + } + group.children=[]; + $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); }); + if (group.children.length || query.matcher(t, text(group), datum)) { + collection.push(group); + } + } else { + if (query.matcher(t, text(datum), datum)) { + collection.push(datum); + } + } + }; + + $(data().results).each2(function(i, datum) { process(datum, filtered.results); }); + query.callback(filtered); + }; + } + + // TODO javadoc + function tags(data) { + var isFunc = $.isFunction(data); + return function (query) { + var t = query.term, filtered = {results: []}; + var result = isFunc ? data(query) : data; + if ($.isArray(result)) { + $(result).each(function () { + var isObject = this.text !== undefined, + text = isObject ? this.text : this; + if (t === "" || query.matcher(t, text)) { + filtered.results.push(isObject ? this : {id: this, text: this}); + } + }); + query.callback(filtered); + } + }; + } + + /** + * Checks if the formatter function should be used. + * + * Throws an error if it is not a function. Returns true if it should be used, + * false if no formatting should be performed. + * + * @param formatter + */ + function checkFormatter(formatter, formatterName) { + if ($.isFunction(formatter)) return true; + if (!formatter) return false; + if (typeof(formatter) === 'string') return true; + throw new Error(formatterName +" must be a string, function, or falsy value"); + } + + /** + * Returns a given value + * If given a function, returns its output + * + * @param val string|function + * @param context value of "this" to be passed to function + * @returns {*} + */ + function evaluate(val, context) { + if ($.isFunction(val)) { + var args = Array.prototype.slice.call(arguments, 2); + return val.apply(context, args); + } + return val; + } + + function countResults(results) { + var count = 0; + $.each(results, function(i, item) { + if (item.children) { + count += countResults(item.children); + } else { + count++; + } + }); + return count; + } + + /** + * Default tokenizer. This function uses breaks the input on substring match of any string from the + * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those + * two options have to be defined in order for the tokenizer to work. + * + * @param input text user has typed so far or pasted into the search field + * @param selection currently selected choices + * @param selectCallback function(choice) callback tho add the choice to selection + * @param opts select2's opts + * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value + */ + function defaultTokenizer(input, selection, selectCallback, opts) { + var original = input, // store the original so we can compare and know if we need to tell the search to update its text + dupe = false, // check for whether a token we extracted represents a duplicate selected choice + token, // token + index, // position at which the separator was found + i, l, // looping variables + separator; // the matched separator + + if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined; + + while (true) { + index = -1; + + for (i = 0, l = opts.tokenSeparators.length; i < l; i++) { + separator = opts.tokenSeparators[i]; + index = input.indexOf(separator); + if (index >= 0) break; + } + + if (index < 0) break; // did not find any token separator in the input string, bail + + token = input.substring(0, index); + input = input.substring(index + separator.length); + + if (token.length > 0) { + token = opts.createSearchChoice.call(this, token, selection); + if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) { + dupe = false; + for (i = 0, l = selection.length; i < l; i++) { + if (equal(opts.id(token), opts.id(selection[i]))) { + dupe = true; break; + } + } + + if (!dupe) selectCallback(token); + } + } + } + + if (original!==input) return input; + } + + function cleanupJQueryElements() { + var self = this; + + $.each(arguments, function (i, element) { + self[element].remove(); + self[element] = null; + }); + } + + /** + * Creates a new class + * + * @param superClass + * @param methods + */ + function clazz(SuperClass, methods) { + var constructor = function () {}; + constructor.prototype = new SuperClass; + constructor.prototype.constructor = constructor; + constructor.prototype.parent = SuperClass.prototype; + constructor.prototype = $.extend(constructor.prototype, methods); + return constructor; + } + + AbstractSelect2 = clazz(Object, { + + // abstract + bind: function (func) { + var self = this; + return function () { + func.apply(self, arguments); + }; + }, + + // abstract + init: function (opts) { + var results, search, resultsSelector = ".select2-results"; + + // prepare options + this.opts = opts = this.prepareOpts(opts); + + this.id=opts.id; + + // destroy if called on an existing component + if (opts.element.data("select2") !== undefined && + opts.element.data("select2") !== null) { + opts.element.data("select2").destroy(); + } + + this.container = this.createContainer(); + + this.liveRegion = $('.select2-hidden-accessible'); + if (this.liveRegion.length == 0) { + this.liveRegion = $("", { + role: "status", + "aria-live": "polite" + }) + .addClass("select2-hidden-accessible") + .appendTo(document.body); + } + + this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid()); + this.containerEventName= this.containerId + .replace(/([.])/g, '_') + .replace(/([;&,\-\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1'); + this.container.attr("id", this.containerId); + + this.container.attr("title", opts.element.attr("title")); + + this.body = $(document.body); + + syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass); + + this.container.attr("style", opts.element.attr("style")); + this.container.css(evaluate(opts.containerCss, this.opts.element)); + this.container.addClass(evaluate(opts.containerCssClass, this.opts.element)); + + this.elementTabIndex = this.opts.element.attr("tabindex"); + + // swap container for the element + this.opts.element + .data("select2", this) + .attr("tabindex", "-1") + .before(this.container) + .on("click.select2", killEvent); // do not leak click events + + this.container.data("select2", this); + + this.dropdown = this.container.find(".select2-drop"); + + syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass); + + this.dropdown.addClass(evaluate(opts.dropdownCssClass, this.opts.element)); + this.dropdown.data("select2", this); + this.dropdown.on("click", killEvent); + + this.results = results = this.container.find(resultsSelector); + this.search = search = this.container.find("input.select2-input"); + + this.queryCount = 0; + this.resultsPage = 0; + this.context = null; + + // initialize the container + this.initContainer(); + + this.container.on("click", killEvent); + + installFilteredMouseMove(this.results); + + this.dropdown.on("mousemove-filtered", resultsSelector, this.bind(this.highlightUnderEvent)); + this.dropdown.on("touchstart touchmove touchend", resultsSelector, this.bind(function (event) { + this._touchEvent = true; + this.highlightUnderEvent(event); + })); + this.dropdown.on("touchmove", resultsSelector, this.bind(this.touchMoved)); + this.dropdown.on("touchstart touchend", resultsSelector, this.bind(this.clearTouchMoved)); + + // Waiting for a click event on touch devices to select option and hide dropdown + // otherwise click will be triggered on an underlying element + this.dropdown.on('click', this.bind(function (event) { + if (this._touchEvent) { + this._touchEvent = false; + this.selectHighlighted(); + } + })); + + installDebouncedScroll(80, this.results); + this.dropdown.on("scroll-debounced", resultsSelector, this.bind(this.loadMoreIfNeeded)); + + // do not propagate change event from the search field out of the component + $(this.container).on("change", ".select2-input", function(e) {e.stopPropagation();}); + $(this.dropdown).on("change", ".select2-input", function(e) {e.stopPropagation();}); + + // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel + if ($.fn.mousewheel) { + results.mousewheel(function (e, delta, deltaX, deltaY) { + var top = results.scrollTop(); + if (deltaY > 0 && top - deltaY <= 0) { + results.scrollTop(0); + killEvent(e); + } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) { + results.scrollTop(results.get(0).scrollHeight - results.height()); + killEvent(e); + } + }); + } + + installKeyUpChangeEvent(search); + search.on("keyup-change input paste", this.bind(this.updateResults)); + search.on("focus", function () { search.addClass("select2-focused"); }); + search.on("blur", function () { search.removeClass("select2-focused");}); + + this.dropdown.on("mouseup", resultsSelector, this.bind(function (e) { + if ($(e.target).closest(".select2-result-selectable").length > 0) { + this.highlightUnderEvent(e); + this.selectHighlighted(e); + } + })); + + // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening + // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's + // dom it will trigger the popup close, which is not what we want + // focusin can cause focus wars between modals and select2 since the dropdown is outside the modal. + this.dropdown.on("click mouseup mousedown touchstart touchend focusin", function (e) { e.stopPropagation(); }); + + this.lastSearchTerm = undefined; + + if ($.isFunction(this.opts.initSelection)) { + // initialize selection based on the current value of the source element + this.initSelection(); + + // if the user has provided a function that can set selection based on the value of the source element + // we monitor the change event on the element and trigger it, allowing for two way synchronization + this.monitorSource(); + } + + if (opts.maximumInputLength !== null) { + this.search.attr("maxlength", opts.maximumInputLength); + } + + var disabled = opts.element.prop("disabled"); + if (disabled === undefined) disabled = false; + this.enable(!disabled); + + var readonly = opts.element.prop("readonly"); + if (readonly === undefined) readonly = false; + this.readonly(readonly); + + // Calculate size of scrollbar + scrollBarDimensions = scrollBarDimensions || measureScrollbar(); + + this.autofocus = opts.element.prop("autofocus"); + opts.element.prop("autofocus", false); + if (this.autofocus) this.focus(); + + this.search.attr("placeholder", opts.searchInputPlaceholder); + }, + + // abstract + destroy: function () { + var element=this.opts.element, select2 = element.data("select2"), self = this; + + this.close(); + + if (element.length && element[0].detachEvent && self._sync) { + element.each(function () { + if (self._sync) { + this.detachEvent("onpropertychange", self._sync); + } + }); + } + if (this.propertyObserver) { + this.propertyObserver.disconnect(); + this.propertyObserver = null; + } + this._sync = null; + + if (select2 !== undefined) { + select2.container.remove(); + select2.liveRegion.remove(); + select2.dropdown.remove(); + element.removeData("select2") + .off(".select2"); + if (!element.is("input[type='hidden']")) { + element + .show() + .prop("autofocus", this.autofocus || false); + if (this.elementTabIndex) { + element.attr({tabindex: this.elementTabIndex}); + } else { + element.removeAttr("tabindex"); + } + element.show(); + } else { + element.css("display", ""); + } + } + + cleanupJQueryElements.call(this, + "container", + "liveRegion", + "dropdown", + "results", + "search" + ); + }, + + // abstract + optionToData: function(element) { + if (element.is("option")) { + return { + id:element.prop("value"), + text:element.text(), + element: element.get(), + css: element.attr("class"), + disabled: element.prop("disabled"), + locked: equal(element.attr("locked"), "locked") || equal(element.data("locked"), true) + }; + } else if (element.is("optgroup")) { + return { + text:element.attr("label"), + children:[], + element: element.get(), + css: element.attr("class") + }; + } + }, + + // abstract + prepareOpts: function (opts) { + var element, select, idKey, ajaxUrl, self = this; + + element = opts.element; + + if (element.get(0).tagName.toLowerCase() === "select") { + this.select = select = opts.element; + } + + if (select) { + // these options are not allowed when attached to a select because they are picked up off the element itself + $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () { + if (this in opts) { + throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a ", + "
", + " ", + "
    ", + "
", + "
"].join("")); + return container; + }, + + // single + enableInterface: function() { + if (this.parent.enableInterface.apply(this, arguments)) { + this.focusser.prop("disabled", !this.isInterfaceEnabled()); + } + }, + + // single + opening: function () { + var el, range, len; + + if (this.opts.minimumResultsForSearch >= 0) { + this.showSearch(true); + } + + this.parent.opening.apply(this, arguments); + + if (this.showSearchInput !== false) { + // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range + // all other browsers handle this just fine + + this.search.val(this.focusser.val()); + } + if (this.opts.shouldFocusInput(this)) { + this.search.focus(); + // move the cursor to the end after focussing, otherwise it will be at the beginning and + // new text will appear *before* focusser.val() + el = this.search.get(0); + if (el.createTextRange) { + range = el.createTextRange(); + range.collapse(false); + range.select(); + } else if (el.setSelectionRange) { + len = this.search.val().length; + el.setSelectionRange(len, len); + } + } + + this.prefillNextSearchTerm(); + + this.focusser.prop("disabled", true).val(""); + this.updateResults(true); + this.opts.element.trigger($.Event("select2-open")); + }, + + // single + close: function () { + if (!this.opened()) return; + this.parent.close.apply(this, arguments); + + this.focusser.prop("disabled", false); + + if (this.opts.shouldFocusInput(this)) { + this.focusser.focus(); + } + }, + + // single + focus: function () { + if (this.opened()) { + this.close(); + } else { + this.focusser.prop("disabled", false); + if (this.opts.shouldFocusInput(this)) { + this.focusser.focus(); + } + } + }, + + // single + isFocused: function () { + return this.container.hasClass("select2-container-active"); + }, + + // single + cancel: function () { + this.parent.cancel.apply(this, arguments); + this.focusser.prop("disabled", false); + + if (this.opts.shouldFocusInput(this)) { + this.focusser.focus(); + } + }, + + // single + destroy: function() { + $("label[for='" + this.focusser.attr('id') + "']") + .attr('for', this.opts.element.attr("id")); + this.parent.destroy.apply(this, arguments); + + cleanupJQueryElements.call(this, + "selection", + "focusser" + ); + }, + + // single + initContainer: function () { + + var selection, + container = this.container, + dropdown = this.dropdown, + idSuffix = nextUid(), + elementLabel; + + if (this.opts.minimumResultsForSearch < 0) { + this.showSearch(false); + } else { + this.showSearch(true); + } + + this.selection = selection = container.find(".select2-choice"); + + this.focusser = container.find(".select2-focusser"); + + // add aria associations + selection.find(".select2-chosen").attr("id", "select2-chosen-"+idSuffix); + this.focusser.attr("aria-labelledby", "select2-chosen-"+idSuffix); + this.results.attr("id", "select2-results-"+idSuffix); + this.search.attr("aria-owns", "select2-results-"+idSuffix); + + // rewrite labels from original element to focusser + this.focusser.attr("id", "s2id_autogen"+idSuffix); + + elementLabel = $("label[for='" + this.opts.element.attr("id") + "']"); + this.opts.element.on('focus.select2', this.bind(function () { this.focus(); })); + + this.focusser.prev() + .text(elementLabel.text()) + .attr('for', this.focusser.attr('id')); + + // Ensure the original element retains an accessible name + var originalTitle = this.opts.element.attr("title"); + this.opts.element.attr("title", (originalTitle || elementLabel.text())); + + this.focusser.attr("tabindex", this.elementTabIndex); + + // write label for search field using the label from the focusser element + this.search.attr("id", this.focusser.attr('id') + '_search'); + + this.search.prev() + .text($("label[for='" + this.focusser.attr('id') + "']").text()) + .attr('for', this.search.attr('id')); + + this.search.on("keydown", this.bind(function (e) { + if (!this.isInterfaceEnabled()) return; + + // filter 229 keyCodes (input method editor is processing key input) + if (229 == e.keyCode) return; + + if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { + // prevent the page from scrolling + killEvent(e); + return; + } + + switch (e.which) { + case KEY.UP: + case KEY.DOWN: + this.moveHighlight((e.which === KEY.UP) ? -1 : 1); + killEvent(e); + return; + case KEY.ENTER: + this.selectHighlighted(); + killEvent(e); + return; + case KEY.TAB: + this.selectHighlighted({noFocus: true}); + return; + case KEY.ESC: + this.cancel(e); + killEvent(e); + return; + } + })); + + this.search.on("blur", this.bind(function(e) { + // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown. + // without this the search field loses focus which is annoying + if (document.activeElement === this.body.get(0)) { + window.setTimeout(this.bind(function() { + if (this.opened() && this.results && this.results.length > 1) { + this.search.focus(); + } + }), 0); + } + })); + + this.focusser.on("keydown", this.bind(function (e) { + if (!this.isInterfaceEnabled()) return; + + if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) { + return; + } + + if (this.opts.openOnEnter === false && e.which === KEY.ENTER) { + killEvent(e); + return; + } + + if (e.which == KEY.DOWN || e.which == KEY.UP + || (e.which == KEY.ENTER && this.opts.openOnEnter)) { + + if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return; + + this.open(); + killEvent(e); + return; + } + + if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) { + if (this.opts.allowClear) { + this.clear(); + } + killEvent(e); + return; + } + })); + + + installKeyUpChangeEvent(this.focusser); + this.focusser.on("keyup-change input", this.bind(function(e) { + if (this.opts.minimumResultsForSearch >= 0) { + e.stopPropagation(); + if (this.opened()) return; + this.open(); + } + })); + + selection.on("mousedown touchstart", "abbr", this.bind(function (e) { + if (!this.isInterfaceEnabled()) { + return; + } + + this.clear(); + killEventImmediately(e); + this.close(); + + if (this.selection) { + this.selection.focus(); + } + })); + + selection.on("mousedown touchstart", this.bind(function (e) { + // Prevent IE from generating a click event on the body + reinsertElement(selection); + + if (!this.container.hasClass("select2-container-active")) { + this.opts.element.trigger($.Event("select2-focus")); + } + + if (this.opened()) { + this.close(); + } else if (this.isInterfaceEnabled()) { + this.open(); + } + + killEvent(e); + })); + + dropdown.on("mousedown touchstart", this.bind(function() { + if (this.opts.shouldFocusInput(this)) { + this.search.focus(); + } + })); + + selection.on("focus", this.bind(function(e) { + killEvent(e); + })); + + this.focusser.on("focus", this.bind(function(){ + if (!this.container.hasClass("select2-container-active")) { + this.opts.element.trigger($.Event("select2-focus")); + } + this.container.addClass("select2-container-active"); + })).on("blur", this.bind(function() { + if (!this.opened()) { + this.container.removeClass("select2-container-active"); + this.opts.element.trigger($.Event("select2-blur")); + } + })); + this.search.on("focus", this.bind(function(){ + if (!this.container.hasClass("select2-container-active")) { + this.opts.element.trigger($.Event("select2-focus")); + } + this.container.addClass("select2-container-active"); + })); + + this.initContainerWidth(); + this.opts.element.hide(); + this.setPlaceholder(); + + }, + + // single + clear: function(triggerChange) { + var data=this.selection.data("select2-data"); + if (data) { // guard against queued quick consecutive clicks + var evt = $.Event("select2-clearing"); + this.opts.element.trigger(evt); + if (evt.isDefaultPrevented()) { + return; + } + var placeholderOption = this.getPlaceholderOption(); + this.opts.element.val(placeholderOption ? placeholderOption.val() : ""); + this.selection.find(".select2-chosen").empty(); + this.selection.removeData("select2-data"); + this.setPlaceholder(); + + if (triggerChange !== false){ + this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data }); + this.triggerChange({removed:data}); + } + } + }, + + /** + * Sets selection based on source element's value + */ + // single + initSelection: function () { + var selected; + if (this.isPlaceholderOptionSelected()) { + this.updateSelection(null); + this.close(); + this.setPlaceholder(); + } else { + var self = this; + this.opts.initSelection.call(null, this.opts.element, function(selected){ + if (selected !== undefined && selected !== null) { + self.updateSelection(selected); + self.close(); + self.setPlaceholder(); + self.lastSearchTerm = self.search.val(); + } + }); + } + }, + + isPlaceholderOptionSelected: function() { + var placeholderOption; + if (this.getPlaceholder() === undefined) return false; // no placeholder specified so no option should be considered + return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.prop("selected")) + || (this.opts.element.val() === "") + || (this.opts.element.val() === undefined) + || (this.opts.element.val() === null); + }, + + // single + prepareOpts: function () { + var opts = this.parent.prepareOpts.apply(this, arguments), + self=this; + + if (opts.element.get(0).tagName.toLowerCase() === "select") { + // install the selection initializer + opts.initSelection = function (element, callback) { + var selected = element.find("option").filter(function() { return this.selected && !this.disabled }); + // a single select box always has a value, no need to null check 'selected' + callback(self.optionToData(selected)); + }; + } else if ("data" in opts) { + // install default initSelection when applied to hidden input and data is local + opts.initSelection = opts.initSelection || function (element, callback) { + var id = element.val(); + //search in data by id, storing the actual matching item + var match = null; + opts.query({ + matcher: function(term, text, el){ + var is_match = equal(id, opts.id(el)); + if (is_match) { + match = el; + } + return is_match; + }, + callback: !$.isFunction(callback) ? $.noop : function() { + callback(match); + } + }); + }; + } + + return opts; + }, + + // single + getPlaceholder: function() { + // if a placeholder is specified on a single select without a valid placeholder option ignore it + if (this.select) { + if (this.getPlaceholderOption() === undefined) { + return undefined; + } + } + + return this.parent.getPlaceholder.apply(this, arguments); + }, + + // single + setPlaceholder: function () { + var placeholder = this.getPlaceholder(); + + if (this.isPlaceholderOptionSelected() && placeholder !== undefined) { + + // check for a placeholder option if attached to a select + if (this.select && this.getPlaceholderOption() === undefined) return; + + this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder)); + + this.selection.addClass("select2-default"); + + this.container.removeClass("select2-allowclear"); + } + }, + + // single + postprocessResults: function (data, initial, noHighlightUpdate) { + var selected = 0, self = this, showSearchInput = true; + + // find the selected element in the result list + + this.findHighlightableChoices().each2(function (i, elm) { + if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) { + selected = i; + return false; + } + }); + + // and highlight it + if (noHighlightUpdate !== false) { + if (initial === true && selected >= 0) { + this.highlight(selected); + } else { + this.highlight(0); + } + } + + // hide the search box if this is the first we got the results and there are enough of them for search + + if (initial === true) { + var min = this.opts.minimumResultsForSearch; + if (min >= 0) { + this.showSearch(countResults(data.results) >= min); + } + } + }, + + // single + showSearch: function(showSearchInput) { + if (this.showSearchInput === showSearchInput) return; + + this.showSearchInput = showSearchInput; + + this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput); + this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput); + //add "select2-with-searchbox" to the container if search box is shown + $(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput); + }, + + // single + onSelect: function (data, options) { + + if (!this.triggerSelect(data)) { return; } + + var old = this.opts.element.val(), + oldData = this.data(); + + this.opts.element.val(this.id(data)); + this.updateSelection(data); + + this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data }); + + this.lastSearchTerm = this.search.val(); + this.close(); + + if ((!options || !options.noFocus) && this.opts.shouldFocusInput(this)) { + this.focusser.focus(); + } + + if (!equal(old, this.id(data))) { + this.triggerChange({ added: data, removed: oldData }); + } + }, + + // single + updateSelection: function (data) { + + var container=this.selection.find(".select2-chosen"), formatted, cssClass; + + this.selection.data("select2-data", data); + + container.empty(); + if (data !== null) { + formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup); + } + if (formatted !== undefined) { + container.append(formatted); + } + cssClass=this.opts.formatSelectionCssClass(data, container); + if (cssClass !== undefined) { + container.addClass(cssClass); + } + + this.selection.removeClass("select2-default"); + + if (this.opts.allowClear && this.getPlaceholder() !== undefined) { + this.container.addClass("select2-allowclear"); + } + }, + + // single + val: function () { + var val, + triggerChange = false, + data = null, + self = this, + oldData = this.data(); + + if (arguments.length === 0) { + return this.opts.element.val(); + } + + val = arguments[0]; + + if (arguments.length > 1) { + triggerChange = arguments[1]; + + if (this.opts.debug && console && console.warn) { + console.warn( + 'Select2: The second option to `select2("val")` is not supported in Select2 4.0.0. ' + + 'The `change` event will always be triggered in 4.0.0.' + ); + } + } + + if (this.select) { + if (this.opts.debug && console && console.warn) { + console.warn( + 'Select2: Setting the value on a ", + " ", + "", + "
", + "
    ", + "
", + "
"].join("")); + return container; + }, + + // multi + prepareOpts: function () { + var opts = this.parent.prepareOpts.apply(this, arguments), + self=this; + + // TODO validate placeholder is a string if specified + if (opts.element.get(0).tagName.toLowerCase() === "select") { + // install the selection initializer + opts.initSelection = function (element, callback) { + + var data = []; + + element.find("option").filter(function() { return this.selected && !this.disabled }).each2(function (i, elm) { + data.push(self.optionToData(elm)); + }); + callback(data); + }; + } else if ("data" in opts) { + // install default initSelection when applied to hidden input and data is local + opts.initSelection = opts.initSelection || function (element, callback) { + var ids = splitVal(element.val(), opts.separator, opts.transformVal); + //search in data by array of ids, storing matching items in a list + var matches = []; + opts.query({ + matcher: function(term, text, el){ + var is_match = $.grep(ids, function(id) { + return equal(id, opts.id(el)); + }).length; + if (is_match) { + matches.push(el); + } + return is_match; + }, + callback: !$.isFunction(callback) ? $.noop : function() { + // reorder matches based on the order they appear in the ids array because right now + // they are in the order in which they appear in data array + var ordered = []; + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + for (var j = 0; j < matches.length; j++) { + var match = matches[j]; + if (equal(id, opts.id(match))) { + ordered.push(match); + matches.splice(j, 1); + break; + } + } + } + callback(ordered); + } + }); + }; + } + + return opts; + }, + + // multi + selectChoice: function (choice) { + + var selected = this.container.find(".select2-search-choice-focus"); + if (selected.length && choice && choice[0] == selected[0]) { + + } else { + if (selected.length) { + this.opts.element.trigger("choice-deselected", selected); + } + selected.removeClass("select2-search-choice-focus"); + if (choice && choice.length) { + this.close(); + choice.addClass("select2-search-choice-focus"); + this.opts.element.trigger("choice-selected", choice); + } + } + }, + + // multi + destroy: function() { + $("label[for='" + this.search.attr('id') + "']") + .attr('for', this.opts.element.attr("id")); + this.parent.destroy.apply(this, arguments); + + cleanupJQueryElements.call(this, + "searchContainer", + "selection" + ); + }, + + // multi + initContainer: function () { + + var selector = ".select2-choices", selection; + + this.searchContainer = this.container.find(".select2-search-field"); + this.selection = selection = this.container.find(selector); + + var _this = this; + this.selection.on("click", ".select2-container:not(.select2-container-disabled) .select2-search-choice:not(.select2-locked)", function (e) { + _this.search[0].focus(); + _this.selectChoice($(this)); + }); + + // rewrite labels from original element to focusser + this.search.attr("id", "s2id_autogen"+nextUid()); + + this.search.prev() + .text($("label[for='" + this.opts.element.attr("id") + "']").text()) + .attr('for', this.search.attr('id')); + this.opts.element.on('focus.select2', this.bind(function () { this.focus(); })); + + this.search.on("input paste", this.bind(function() { + if (this.search.attr('placeholder') && this.search.val().length == 0) return; + if (!this.isInterfaceEnabled()) return; + if (!this.opened()) { + this.open(); + } + })); + + this.search.attr("tabindex", this.elementTabIndex); + + this.keydowns = 0; + this.search.on("keydown", this.bind(function (e) { + if (!this.isInterfaceEnabled()) return; + + ++this.keydowns; + var selected = selection.find(".select2-search-choice-focus"); + var prev = selected.prev(".select2-search-choice:not(.select2-locked)"); + var next = selected.next(".select2-search-choice:not(.select2-locked)"); + var pos = getCursorInfo(this.search); + + if (selected.length && + (e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) { + var selectedChoice = selected; + if (e.which == KEY.LEFT && prev.length) { + selectedChoice = prev; + } + else if (e.which == KEY.RIGHT) { + selectedChoice = next.length ? next : null; + } + else if (e.which === KEY.BACKSPACE) { + if (this.unselect(selected.first())) { + this.search.width(10); + selectedChoice = prev.length ? prev : next; + } + } else if (e.which == KEY.DELETE) { + if (this.unselect(selected.first())) { + this.search.width(10); + selectedChoice = next.length ? next : null; + } + } else if (e.which == KEY.ENTER) { + selectedChoice = null; + } + + this.selectChoice(selectedChoice); + killEvent(e); + if (!selectedChoice || !selectedChoice.length) { + this.open(); + } + return; + } else if (((e.which === KEY.BACKSPACE && this.keydowns == 1) + || e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) { + + this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last()); + killEvent(e); + return; + } else { + this.selectChoice(null); + } + + if (this.opened()) { + switch (e.which) { + case KEY.UP: + case KEY.DOWN: + this.moveHighlight((e.which === KEY.UP) ? -1 : 1); + killEvent(e); + return; + case KEY.ENTER: + this.selectHighlighted(); + killEvent(e); + return; + case KEY.TAB: + this.selectHighlighted({noFocus:true}); + this.close(); + return; + case KEY.ESC: + this.cancel(e); + killEvent(e); + return; + } + } + + if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) + || e.which === KEY.BACKSPACE || e.which === KEY.ESC) { + return; + } + + if (e.which === KEY.ENTER) { + if (this.opts.openOnEnter === false) { + return; + } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) { + return; + } + } + + this.open(); + + if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { + // prevent the page from scrolling + killEvent(e); + } + + if (e.which === KEY.ENTER) { + // prevent form from being submitted + killEvent(e); + } + + })); + + this.search.on("keyup", this.bind(function (e) { + this.keydowns = 0; + this.resizeSearch(); + }) + ); + + this.search.on("blur", this.bind(function(e) { + this.container.removeClass("select2-container-active"); + this.search.removeClass("select2-focused"); + this.selectChoice(null); + if (!this.opened()) this.clearSearch(); + e.stopImmediatePropagation(); + this.opts.element.trigger($.Event("select2-blur")); + })); + + this.container.on("click", selector, this.bind(function (e) { + if (!this.isInterfaceEnabled()) return; + if ($(e.target).closest(".select2-search-choice").length > 0) { + // clicked inside a select2 search choice, do not open + return; + } + this.selectChoice(null); + this.clearPlaceholder(); + if (!this.container.hasClass("select2-container-active")) { + this.opts.element.trigger($.Event("select2-focus")); + } + this.open(); + this.focusSearch(); + e.preventDefault(); + })); + + this.container.on("focus", selector, this.bind(function () { + if (!this.isInterfaceEnabled()) return; + if (!this.container.hasClass("select2-container-active")) { + this.opts.element.trigger($.Event("select2-focus")); + } + this.container.addClass("select2-container-active"); + this.dropdown.addClass("select2-drop-active"); + this.clearPlaceholder(); + })); + + this.initContainerWidth(); + this.opts.element.hide(); + + // set the placeholder if necessary + this.clearSearch(); + }, + + // multi + enableInterface: function() { + if (this.parent.enableInterface.apply(this, arguments)) { + this.search.prop("disabled", !this.isInterfaceEnabled()); + } + }, + + // multi + initSelection: function () { + var data; + if (this.opts.element.val() === "" && this.opts.element.text() === "") { + this.updateSelection([]); + this.close(); + // set the placeholder if necessary + this.clearSearch(); + } + if (this.select || this.opts.element.val() !== "") { + var self = this; + this.opts.initSelection.call(null, this.opts.element, function(data){ + if (data !== undefined && data !== null) { + self.updateSelection(data); + self.close(); + // set the placeholder if necessary + self.clearSearch(); + } + }); + } + }, + + // multi + clearSearch: function () { + var placeholder = this.getPlaceholder(), + maxWidth = this.getMaxSearchWidth(); + + if (placeholder !== undefined && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) { + this.search.val(placeholder).addClass("select2-default"); + // stretch the search box to full width of the container so as much of the placeholder is visible as possible + // we could call this.resizeSearch(), but we do not because that requires a sizer and we do not want to create one so early because of a firefox bug, see #944 + this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width")); + } else { + this.search.val("").width(10); + } + }, + + // multi + clearPlaceholder: function () { + if (this.search.hasClass("select2-default")) { + this.search.val("").removeClass("select2-default"); + } + }, + + // multi + opening: function () { + this.clearPlaceholder(); // should be done before super so placeholder is not used to search + this.resizeSearch(); + + this.parent.opening.apply(this, arguments); + + this.focusSearch(); + + this.prefillNextSearchTerm(); + this.updateResults(true); + + if (this.opts.shouldFocusInput(this)) { + this.search.focus(); + } + this.opts.element.trigger($.Event("select2-open")); + }, + + // multi + close: function () { + if (!this.opened()) return; + this.parent.close.apply(this, arguments); + }, + + // multi + focus: function () { + this.close(); + this.search.focus(); + }, + + // multi + isFocused: function () { + return this.search.hasClass("select2-focused"); + }, + + // multi + updateSelection: function (data) { + var ids = {}, filtered = [], self = this; + + // filter out duplicates + $(data).each(function () { + if (!(self.id(this) in ids)) { + ids[self.id(this)] = 0; + filtered.push(this); + } + }); + + this.selection.find(".select2-search-choice").remove(); + this.addSelectedChoice(filtered); + self.postprocessResults(); + }, + + // multi + tokenize: function() { + var input = this.search.val(); + input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts); + if (input != null && input != undefined) { + this.search.val(input); + if (input.length > 0) { + this.open(); + } + } + + }, + + // multi + onSelect: function (data, options) { + + if (!this.triggerSelect(data) || data.text === "") { return; } + + this.addSelectedChoice(data); + + this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data }); + + // keep track of the search's value before it gets cleared + this.lastSearchTerm = this.search.val(); + + this.clearSearch(); + this.updateResults(); + + if (this.select || !this.opts.closeOnSelect) this.postprocessResults(data, false, this.opts.closeOnSelect===true); + + if (this.opts.closeOnSelect) { + this.close(); + this.search.width(10); + } else { + if (this.countSelectableResults()>0) { + this.search.width(10); + this.resizeSearch(); + if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) { + // if we reached max selection size repaint the results so choices + // are replaced with the max selection reached message + this.updateResults(true); + } else { + // initializes search's value with nextSearchTerm and update search result + if (this.prefillNextSearchTerm()) { + this.updateResults(); + } + } + this.positionDropdown(); + } else { + // if nothing left to select close + this.close(); + this.search.width(10); + } + } + + // since its not possible to select an element that has already been + // added we do not need to check if this is a new element before firing change + this.triggerChange({ added: data }); + + if (!options || !options.noFocus) + this.focusSearch(); + }, + + // multi + cancel: function () { + this.close(); + this.focusSearch(); + }, + + addSelectedChoice: function (data) { + var val = this.getVal(), self = this; + $(data).each(function () { + val.push(self.createChoice(this)); + }); + this.setVal(val); + }, + + createChoice: function (data) { + var enableChoice = !data.locked, + enabledItem = $( + "
  • " + + "
    " + + " " + + "
  • "), + disabledItem = $( + "
  • " + + "
    " + + "
  • "); + var choice = enableChoice ? enabledItem : disabledItem, + id = this.id(data), + formatted, + cssClass; + + formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup); + if (formatted != undefined) { + choice.find("div").replaceWith($("
    ").html(formatted)); + } + cssClass=this.opts.formatSelectionCssClass(data, choice.find("div")); + if (cssClass != undefined) { + choice.addClass(cssClass); + } + + if(enableChoice){ + choice.find(".select2-search-choice-close") + .on("mousedown", killEvent) + .on("click dblclick", this.bind(function (e) { + if (!this.isInterfaceEnabled()) return; + + this.unselect($(e.target)); + this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); + killEvent(e); + this.close(); + this.focusSearch(); + })).on("focus", this.bind(function () { + if (!this.isInterfaceEnabled()) return; + this.container.addClass("select2-container-active"); + this.dropdown.addClass("select2-drop-active"); + })); + } + + choice.data("select2-data", data); + choice.insertBefore(this.searchContainer); + + return id; + }, + + // multi + unselect: function (selected) { + var val = this.getVal(), + data, + index; + selected = selected.closest(".select2-search-choice"); + + if (selected.length === 0) { + throw "Invalid argument: " + selected + ". Must be .select2-search-choice"; + } + + data = selected.data("select2-data"); + + if (!data) { + // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued + // and invoked on an element already removed + return; + } + + var evt = $.Event("select2-removing"); + evt.val = this.id(data); + evt.choice = data; + this.opts.element.trigger(evt); + + if (evt.isDefaultPrevented()) { + return false; + } + + while((index = indexOf(this.id(data), val)) >= 0) { + val.splice(index, 1); + this.setVal(val); + if (this.select) this.postprocessResults(); + } + + selected.remove(); + + this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data }); + this.triggerChange({ removed: data }); + + return true; + }, + + // multi + postprocessResults: function (data, initial, noHighlightUpdate) { + var val = this.getVal(), + choices = this.results.find(".select2-result"), + compound = this.results.find(".select2-result-with-children"), + self = this; + + choices.each2(function (i, choice) { + var id = self.id(choice.data("select2-data")); + if (indexOf(id, val) >= 0) { + choice.addClass("select2-selected"); + // mark all children of the selected parent as selected + choice.find(".select2-result-selectable").addClass("select2-selected"); + } + }); + + compound.each2(function(i, choice) { + // hide an optgroup if it doesn't have any selectable children + if (!choice.is('.select2-result-selectable') + && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) { + choice.addClass("select2-selected"); + } + }); + + if (this.highlight() == -1 && noHighlightUpdate !== false && this.opts.closeOnSelect === true){ + self.highlight(0); + } + + //If all results are chosen render formatNoMatches + if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){ + if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) { + if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) { + this.results.append("
  • " + evaluate(self.opts.formatNoMatches, self.opts.element, self.search.val()) + "
  • "); + } + } + } + + }, + + // multi + getMaxSearchWidth: function() { + return this.selection.width() - getSideBorderPadding(this.search); + }, + + // multi + resizeSearch: function () { + var minimumWidth, left, maxWidth, containerLeft, searchWidth, + sideBorderPadding = getSideBorderPadding(this.search); + + minimumWidth = measureTextWidth(this.search) + 10; + + left = this.search.offset().left; + + maxWidth = this.selection.width(); + containerLeft = this.selection.offset().left; + + searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding; + + if (searchWidth < minimumWidth) { + searchWidth = maxWidth - sideBorderPadding; + } + + if (searchWidth < 40) { + searchWidth = maxWidth - sideBorderPadding; + } + + if (searchWidth <= 0) { + searchWidth = minimumWidth; + } + + this.search.width(Math.floor(searchWidth)); + }, + + // multi + getVal: function () { + var val; + if (this.select) { + val = this.select.val(); + return val === null ? [] : val; + } else { + val = this.opts.element.val(); + return splitVal(val, this.opts.separator, this.opts.transformVal); + } + }, + + // multi + setVal: function (val) { + if (this.select) { + this.select.val(val); + } else { + var unique = [], valMap = {}; + // filter out duplicates + $(val).each(function () { + if (!(this in valMap)) { + unique.push(this); + valMap[this] = 0; + } + }); + this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator)); + } + }, + + // multi + buildChangeDetails: function (old, current) { + var current = current.slice(0), + old = old.slice(0); + + // remove intersection from each array + for (var i = 0; i < current.length; i++) { + for (var j = 0; j < old.length; j++) { + if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) { + current.splice(i, 1); + i--; + old.splice(j, 1); + break; + } + } + } + + return {added: current, removed: old}; + }, + + + // multi + val: function (val, triggerChange) { + var oldData, self=this; + + if (arguments.length === 0) { + return this.getVal(); + } + + oldData=this.data(); + if (!oldData.length) oldData=[]; + + // val is an id. !val is true for [undefined,null,'',0] - 0 is legal + if (!val && val !== 0) { + this.opts.element.val(""); + this.updateSelection([]); + this.clearSearch(); + if (triggerChange) { + this.triggerChange({added: this.data(), removed: oldData}); + } + return; + } + + // val is a list of ids + this.setVal(val); + + if (this.select) { + this.opts.initSelection(this.select, this.bind(this.updateSelection)); + if (triggerChange) { + this.triggerChange(this.buildChangeDetails(oldData, this.data())); + } + } else { + if (this.opts.initSelection === undefined) { + throw new Error("val() cannot be called if initSelection() is not defined"); + } + + this.opts.initSelection(this.opts.element, function(data){ + var ids=$.map(data, self.id); + self.setVal(ids); + self.updateSelection(data); + self.clearSearch(); + if (triggerChange) { + self.triggerChange(self.buildChangeDetails(oldData, self.data())); + } + }); + } + this.clearSearch(); + }, + + // multi + onSortStart: function() { + if (this.select) { + throw new Error("Sorting of elements is not supported when attached to instead."); + } + + // collapse search field into 0 width so its container can be collapsed as well + this.search.width(0); + // hide the container + this.searchContainer.hide(); + }, + + // multi + onSortEnd:function() { + + var val=[], self=this; + + // show search and move it to the end of the list + this.searchContainer.show(); + // make sure the search container is the last item in the list + this.searchContainer.appendTo(this.searchContainer.parent()); + // since we collapsed the width in dragStarted, we resize it here + this.resizeSearch(); + + // update selection + this.selection.find(".select2-search-choice").each(function() { + val.push(self.opts.id($(this).data("select2-data"))); + }); + this.setVal(val); + this.triggerChange(); + }, + + // multi + data: function(values, triggerChange) { + var self=this, ids, old; + if (arguments.length === 0) { + return this.selection + .children(".select2-search-choice") + .map(function() { return $(this).data("select2-data"); }) + .get(); + } else { + old = this.data(); + if (!values) { values = []; } + ids = $.map(values, function(e) { return self.opts.id(e); }); + this.setVal(ids); + this.updateSelection(values); + this.clearSearch(); + if (triggerChange) { + this.triggerChange(this.buildChangeDetails(old, this.data())); + } + } + } + }); + + $.fn.select2 = function () { + + var args = Array.prototype.slice.call(arguments, 0), + opts, + select2, + method, value, multiple, + allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "disable", "readonly", "positionDropdown", "data", "search"], + valueMethods = ["opened", "isFocused", "container", "dropdown"], + propertyMethods = ["val", "data"], + methodsMap = { search: "externalSearch" }; + + this.each(function () { + if (args.length === 0 || typeof(args[0]) === "object") { + opts = args.length === 0 ? {} : $.extend({}, args[0]); + opts.element = $(this); + + if (opts.element.get(0).tagName.toLowerCase() === "select") { + multiple = opts.element.prop("multiple"); + } else { + multiple = opts.multiple || false; + if ("tags" in opts) {opts.multiple = multiple = true;} + } + + select2 = multiple ? new window.Select2["class"].multi() : new window.Select2["class"].single(); + select2.init(opts); + } else if (typeof(args[0]) === "string") { + + if (indexOf(args[0], allowedMethods) < 0) { + throw "Unknown method: " + args[0]; + } + + value = undefined; + select2 = $(this).data("select2"); + if (select2 === undefined) return; + + method=args[0]; + + if (method === "container") { + value = select2.container; + } else if (method === "dropdown") { + value = select2.dropdown; + } else { + if (methodsMap[method]) method = methodsMap[method]; + + value = select2[method].apply(select2, args.slice(1)); + } + if (indexOf(args[0], valueMethods) >= 0 + || (indexOf(args[0], propertyMethods) >= 0 && args.length == 1)) { + return false; // abort the iteration, ready to return first matched value + } + } else { + throw "Invalid arguments to select2 plugin: " + args; + } + }); + return (value === undefined) ? this : value; + }; + + // plugin defaults, accessible to users + $.fn.select2.defaults = { + debug: false, + width: "copy", + loadMorePadding: 0, + closeOnSelect: true, + openOnEnter: true, + containerCss: {}, + dropdownCss: {}, + containerCssClass: "", + dropdownCssClass: "", + formatResult: function(result, container, query, escapeMarkup) { + var markup=[]; + markMatch(this.text(result), query.term, markup, escapeMarkup); + return markup.join(""); + }, + transformVal: function(val) { + return $.trim(val); + }, + formatSelection: function (data, container, escapeMarkup) { + return data ? escapeMarkup(this.text(data)) : undefined; + }, + sortResults: function (results, container, query) { + return results; + }, + formatResultCssClass: function(data) {return data.css;}, + formatSelectionCssClass: function(data, container) {return undefined;}, + minimumResultsForSearch: 0, + minimumInputLength: 0, + maximumInputLength: null, + maximumSelectionSize: 0, + id: function (e) { return e == undefined ? null : e.id; }, + text: function (e) { + if (e && this.data && this.data.text) { + if ($.isFunction(this.data.text)) { + return this.data.text(e); + } else { + return e[this.data.text]; + } + } else { + return e.text; + } + }, + matcher: function(term, text) { + return stripDiacritics(''+text).toUpperCase().indexOf(stripDiacritics(''+term).toUpperCase()) >= 0; + }, + separator: ",", + tokenSeparators: [], + tokenizer: defaultTokenizer, + escapeMarkup: defaultEscapeMarkup, + blurOnChange: false, + selectOnBlur: false, + adaptContainerCssClass: function(c) { return c; }, + adaptDropdownCssClass: function(c) { return null; }, + nextSearchTerm: function(selectedObject, currentSearchTerm) { return undefined; }, + searchInputPlaceholder: '', + createSearchChoicePosition: 'top', + shouldFocusInput: function (instance) { + // Attempt to detect touch devices + var supportsTouchEvents = (('ontouchstart' in window) || + (navigator.msMaxTouchPoints > 0)); + + // Only devices which support touch events should be special cased + if (!supportsTouchEvents) { + return true; + } + + // Never focus the input if search is disabled + if (instance.opts.minimumResultsForSearch < 0) { + return false; + } + + return true; + } + }; + + $.fn.select2.locales = []; + + $.fn.select2.locales['en'] = { + formatMatches: function (matches) { if (matches === 1) { return "One result is available, press enter to select it."; } return matches + " results are available, use up and down arrow keys to navigate."; }, + formatNoMatches: function () { return "No matches found"; }, + formatAjaxError: function (jqXHR, textStatus, errorThrown) { return "Loading failed"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " or more character" + (n == 1 ? "" : "s"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1 ? "" : "s"); }, + formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "Loading more results…"; }, + formatSearching: function () { return "Searching…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['en']); + + $.fn.select2.ajaxDefaults = { + transport: $.ajax, + params: { + type: "GET", + cache: false, + dataType: "json" + } + }; + + // exports + window.Select2 = { + query: { + ajax: ajax, + local: local, + tags: tags + }, util: { + debounce: debounce, + markMatch: markMatch, + escapeMarkup: defaultEscapeMarkup, + stripDiacritics: stripDiacritics + }, "class": { + "abstract": AbstractSelect2, + "single": SingleSelect2, + "multi": MultiSelect2 + } + }; + +}(jQuery)); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.png b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2.png new file mode 100644 index 0000000000000000000000000000000000000000..1d804ffb99699b9e030f1010314de0970b5a000d GIT binary patch literal 613 zcmV-r0-F7aP)#WY!I$JQV$)A5aAS1BM||2XVJl=+L1^1S1H% zM-&lx?NZpUrHhn>fk<>POqf2sh40}xxGZfc+t+#Eb(qHy9_3*1(U%t9t)QDnI#YAL(|ACV(>)>6WD-t!8tutHkdb^#3`HzoJG3A2@T`% zA|K@o*b!`R#(7)PWrMFn2))Ca3MR4(zaT`Zr61*kZK5NPnZwQszxh$fyv3?&4c>$q z2m=+yc0dRXRAsPDxF6sD;@rK4JGdR_``1S~o6Xi@2&aR6hcSrEp9HVRzEqVDqBn<1%hR=D4e1f^ra^A|34Cjc=Gny{F(o#MrvPYgZuTJOz(n)-F<| zj()qR;C={)N<0RRvDZ^@6ND+W*}gh-Lip(MDt!(zMSO)!j2j+*hxgzC-e3$@(O2p* zu;+gddm(cZwXTCLx*Ky4THOa*^b^F`woveIeCK^0aR|TJ00000NkvXXu0mjfA#WC6 literal 0 HcmV?d00001 diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ar.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ar.js new file mode 100644 index 00000000..e991b726 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ar.js @@ -0,0 +1,19 @@ +/** + * Select2 Arabic translation. + * + * Author: Adel KEDJOUR + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['ar'] = { + formatNoMatches: function () { return "لم يتم العثور على مطابقات"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; if (n == 1){ return "الرجاء إدخال حرف واحد على الأكثر"; } return n == 2 ? "الرجاء إدخال حرفين على الأكثر" : "الرجاء إدخال " + n + " على الأكثر"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; if (n == 1){ return "الرجاء إدخال حرف واحد على الأقل"; } return n == 2 ? "الرجاء إدخال حرفين على الأقل" : "الرجاء إدخال " + n + " على الأقل "; }, + formatSelectionTooBig: function (limit) { if (limit == 1){ return "يمكنك أن تختار إختيار واحد فقط"; } return limit == 2 ? "يمكنك أن تختار إختيارين فقط" : "يمكنك أن تختار " + limit + " إختيارات فقط"; }, + formatLoadMore: function (pageNumber) { return "تحميل المزيد من النتائج…"; }, + formatSearching: function () { return "البحث…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['ar']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_az.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_az.js new file mode 100644 index 00000000..19fd95b7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_az.js @@ -0,0 +1,20 @@ +/** + * Select2 Azerbaijani translation. + * + * Author: Farhad Safarov + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['az'] = { + formatMatches: function (matches) { return matches + " nəticə mövcuddur, hərəkət etdirmək üçün yuxarı və aşağı düymələrindən istifadə edin."; }, + formatNoMatches: function () { return "Nəticə tapılmadı"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return n + " simvol daxil edin"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return n + " simvol silin"; }, + formatSelectionTooBig: function (limit) { return "Sadəcə " + limit + " element seçə bilərsiniz"; }, + formatLoadMore: function (pageNumber) { return "Daha çox nəticə yüklənir…"; }, + formatSearching: function () { return "Axtarılır…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['az']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_bg.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_bg.js new file mode 100644 index 00000000..3283d0ae --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_bg.js @@ -0,0 +1,20 @@ +/** + * Select2 Bulgarian translation. + * + * @author Lubomir Vikev + * @author Uriy Efremochkin + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['bg'] = { + formatNoMatches: function () { return "Няма намерени съвпадения"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Моля въведете още " + n + " символ" + (n > 1 ? "а" : ""); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Моля въведете с " + n + " по-малко символ" + (n > 1 ? "а" : ""); }, + formatSelectionTooBig: function (limit) { return "Можете да направите до " + limit + (limit > 1 ? " избора" : " избор"); }, + formatLoadMore: function (pageNumber) { return "Зареждат се още…"; }, + formatSearching: function () { return "Търсене…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['bg']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ca.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ca.js new file mode 100644 index 00000000..dbea39e9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ca.js @@ -0,0 +1,19 @@ +/** + * Select2 Catalan translation. + * + * Author: David Planella + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['ca'] = { + formatNoMatches: function () { return "No s'ha trobat cap coincidència"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Introduïu " + n + " caràcter" + (n == 1 ? "" : "s") + " més"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Introduïu " + n + " caràcter" + (n == 1? "" : "s") + "menys"; }, + formatSelectionTooBig: function (limit) { return "Només podeu seleccionar " + limit + " element" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "S'estan carregant més resultats…"; }, + formatSearching: function () { return "S'està cercant…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['ca']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_cs.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_cs.js new file mode 100644 index 00000000..ef121856 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_cs.js @@ -0,0 +1,51 @@ +/** + * Select2 Czech translation. + * + * Author: Michal Marek + * Author - sklonovani: David Vallner + */ +(function ($) { + "use strict"; + // use text for the numbers 2 through 4 + var smallNumbers = { + 2: function(masc) { return (masc ? "dva" : "dvě"); }, + 3: function() { return "tři"; }, + 4: function() { return "čtyři"; } + } + $.fn.select2.locales['cs'] = { + formatNoMatches: function () { return "Nenalezeny žádné položky"; }, + formatInputTooShort: function (input, min) { + var n = min - input.length; + if (n == 1) { + return "Prosím zadejte ještě jeden znak"; + } else if (n <= 4) { + return "Prosím zadejte ještě další "+smallNumbers[n](true)+" znaky"; + } else { + return "Prosím zadejte ještě dalších "+n+" znaků"; + } + }, + formatInputTooLong: function (input, max) { + var n = input.length - max; + if (n == 1) { + return "Prosím zadejte o jeden znak méně"; + } else if (n <= 4) { + return "Prosím zadejte o "+smallNumbers[n](true)+" znaky méně"; + } else { + return "Prosím zadejte o "+n+" znaků méně"; + } + }, + formatSelectionTooBig: function (limit) { + if (limit == 1) { + return "Můžete zvolit jen jednu položku"; + } else if (limit <= 4) { + return "Můžete zvolit maximálně "+smallNumbers[limit](false)+" položky"; + } else { + return "Můžete zvolit maximálně "+limit+" položek"; + } + }, + formatLoadMore: function (pageNumber) { return "Načítají se další výsledky…"; }, + formatSearching: function () { return "Vyhledávání…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['cs']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_da.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_da.js new file mode 100644 index 00000000..702238b9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_da.js @@ -0,0 +1,19 @@ +/** + * Select2 Danish translation. + * + * Author: Anders Jenbo + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['da'] = { + formatNoMatches: function () { return "Ingen resultater fundet"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Angiv venligst " + n + " tegn mere"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Angiv venligst " + n + " tegn mindre"; }, + formatSelectionTooBig: function (limit) { return "Du kan kun vælge " + limit + " emne" + (limit === 1 ? "" : "r"); }, + formatLoadMore: function (pageNumber) { return "Indlæser flere resultater…"; }, + formatSearching: function () { return "Søger…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['da']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_de.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_de.js new file mode 100644 index 00000000..e2754172 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_de.js @@ -0,0 +1,18 @@ +/** + * Select2 German translation + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['de'] = { + formatNoMatches: function () { return "Keine Übereinstimmungen gefunden"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Bitte " + n + " Zeichen mehr eingeben"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Bitte " + n + " Zeichen weniger eingeben"; }, + formatSelectionTooBig: function (limit) { return "Sie können nur " + limit + " Eintr" + (limit === 1 ? "ag" : "äge") + " auswählen"; }, + formatLoadMore: function (pageNumber) { return "Lade mehr Ergebnisse…"; }, + formatSearching: function () { return "Suche…"; }, + formatMatches: function (matches) { return matches + " Ergebnis " + (matches > 1 ? "se" : "") + " verfügbar, zum Navigieren die Hoch-/Runter-Pfeiltasten verwenden."; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['de']); +})(jQuery); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_es.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_es.js new file mode 100644 index 00000000..92a19861 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_es.js @@ -0,0 +1,19 @@ +/** + * Select2 Spanish translation + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['es'] = { + formatMatches: function (matches) { if (matches === 1) { return "Un resultado disponible, presione enter para seleccionarlo."; } return matches + " resultados disponibles, use las teclas de dirección para navegar."; }, + formatNoMatches: function () { return "No se encontraron resultados"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Por favor, introduzca " + n + " car" + (n == 1? "ácter" : "acteres"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Por favor, elimine " + n + " car" + (n == 1? "ácter" : "acteres"); }, + formatSelectionTooBig: function (limit) { return "Sólo puede seleccionar " + limit + " elemento" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "Cargando más resultados…"; }, + formatSearching: function () { return "Buscando…"; }, + formatAjaxError: function() { return "La carga falló"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['es']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_fa.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_fa.js new file mode 100644 index 00000000..b3ffd8b7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_fa.js @@ -0,0 +1,21 @@ +/** + * Select2 Persian translation. + * + * Author: Ali Choopan + * Author: Ebrahim Byagowi + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['fa'] = { + formatMatches: function (matches) { return matches + " نتیجه موجود است، کلیدهای جهت بالا و پایین را برای گشتن استفاده کنید."; }, + formatNoMatches: function () { return "نتیجه‌ای یافت نشد."; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "لطفاً " + n + " نویسه بیشتر وارد نمایید"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "لطفاً " + n + " نویسه را حذف کنید."; }, + formatSelectionTooBig: function (limit) { return "شما فقط می‌توانید " + limit + " مورد را انتخاب کنید"; }, + formatLoadMore: function (pageNumber) { return "در حال بارگیری موارد بیشتر…"; }, + formatSearching: function () { return "در حال جستجو…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['fa']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_fr.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_fr.js new file mode 100644 index 00000000..d5485d6b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_fr.js @@ -0,0 +1,18 @@ +/** + * Select2 French translation + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['fr'] = { + formatMatches: function (matches) { return matches + " résultats sont disponibles, utilisez les flèches haut et bas pour naviguer."; }, + formatNoMatches: function () { return "Aucun résultat trouvé"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Saisissez " + n + " caractère" + (n == 1? "" : "s") + " supplémentaire" + (n == 1? "" : "s") ; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Supprimez " + n + " caractère" + (n == 1? "" : "s"); }, + formatSelectionTooBig: function (limit) { return "Vous pouvez seulement sélectionner " + limit + " élément" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "Chargement de résultats supplémentaires…"; }, + formatSearching: function () { return "Recherche en cours…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['fr']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_he.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_he.js new file mode 100644 index 00000000..789dcdca --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_he.js @@ -0,0 +1,19 @@ +/** +* Select2 Hebrew translation. +* +* Author: Yakir Sitbon +*/ +(function ($) { + "use strict"; + + $.fn.select2.locales['he'] = { + formatNoMatches: function () { return "לא נמצאו התאמות"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "נא להזין עוד " + n + " תווים נוספים"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "נא להזין פחות " + n + " תווים"; }, + formatSelectionTooBig: function (limit) { return "ניתן לבחור " + limit + " פריטים"; }, + formatLoadMore: function (pageNumber) { return "טוען תוצאות נוספות…"; }, + formatSearching: function () { return "מחפש…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['he']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_hu.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_hu.js new file mode 100644 index 00000000..f431f246 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_hu.js @@ -0,0 +1,17 @@ +/** + * Select2 Hungarian translation + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['hu'] = { + formatNoMatches: function () { return "Nincs találat."; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Túl rövid. Még " + n + " karakter hiányzik."; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Túl hosszú. " + n + " karakterrel több, mint kellene."; }, + formatSelectionTooBig: function (limit) { return "Csak " + limit + " elemet lehet kiválasztani."; }, + formatLoadMore: function (pageNumber) { return "Töltés…"; }, + formatSearching: function () { return "Keresés…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['hu']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_it.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_it.js new file mode 100644 index 00000000..6e2b8e23 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_it.js @@ -0,0 +1,17 @@ +/** + * Select2 Italian translation + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['it'] = { + formatNoMatches: function () { return "Nessuna corrispondenza trovata"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Inserisci ancora " + n + " caratter" + (n == 1? "e" : "i"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Inserisci " + n + " caratter" + (n == 1? "e" : "i") + " in meno"; }, + formatSelectionTooBig: function (limit) { return "Puoi selezionare solo " + limit + " element" + (limit == 1 ? "o" : "i"); }, + formatLoadMore: function (pageNumber) { return "Caricamento in corso…"; }, + formatSearching: function () { return "Ricerca…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['it']); +})(jQuery); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ja.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ja.js new file mode 100644 index 00000000..7dbd8d7e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ja.js @@ -0,0 +1,17 @@ +/** + * Select2 Japanese translation. + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['ja'] = { + formatNoMatches: function () { return "該当なし"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "後" + n + "文字入れてください"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "検索文字列が" + n + "文字長すぎます"; }, + formatSelectionTooBig: function (limit) { return "最多で" + limit + "項目までしか選択できません"; }, + formatLoadMore: function (pageNumber) { return "読込中・・・"; }, + formatSearching: function () { return "検索中・・・"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['ja']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ka.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ka.js new file mode 100644 index 00000000..6cbe1d8f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ka.js @@ -0,0 +1,19 @@ +/** + * Select2 Georgian (Kartuli) translation. + * + * Author: Dimitri Kurashvili dimakura@gmail.com + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['ka'] = { + formatNoMatches: function () { return "ვერ მოიძებნა"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "გთხოვთ შეიყვანოთ კიდევ " + n + " სიმბოლო"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "გთხოვთ წაშალოთ " + n + " სიმბოლო"; }, + formatSelectionTooBig: function (limit) { return "თქვენ შეგიძლიათ მხოლოდ " + limit + " ჩანაწერის მონიშვნა"; }, + formatLoadMore: function (pageNumber) { return "შედეგის ჩატვირთვა…"; }, + formatSearching: function () { return "ძებნა…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['ka']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_lt.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_lt.js new file mode 100644 index 00000000..7d7040f7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_lt.js @@ -0,0 +1,26 @@ +/** + * Select2 Lithuanian translation. + * + * @author CRONUS Karmalakas + * @author Uriy Efremochkin + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['lt'] = { + formatNoMatches: function () { return "Atitikmenų nerasta"; }, + formatInputTooShort: function (input, min) { return "Įrašykite dar" + character(min - input.length); }, + formatInputTooLong: function (input, max) { return "Pašalinkite" + character(input.length - max); }, + formatSelectionTooBig: function (limit) { + return "Jūs galite pasirinkti tik " + limit + " element" + ((limit%100 > 9 && limit%100 < 21) || limit%10 == 0 ? "ų" : limit%10 > 1 ? "us" : "ą"); + }, + formatLoadMore: function (pageNumber) { return "Kraunama daugiau rezultatų…"; }, + formatSearching: function () { return "Ieškoma…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['lt']); + + function character (n) { + return " " + n + " simbol" + ((n%100 > 9 && n%100 < 21) || n%10 == 0 ? "ių" : n%10 > 1 ? "ius" : "į"); + } +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_nb.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_nb.js new file mode 100644 index 00000000..cb5dfaee --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_nb.js @@ -0,0 +1,22 @@ +/** + * Select2 Norwegian Bokmål translation. + * + * Author: Torgeir Veimo + * Author: Bjørn Johansen + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['nb'] = { + formatMatches: function (matches) { if (matches === 1) { return "Ett resultat er tilgjengelig, trykk enter for å velge det."; } return matches + " resultater er tilgjengelig. Bruk piltastene opp og ned for å navigere."; }, + formatNoMatches: function () { return "Ingen treff"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Vennligst skriv inn " + n + (n>1 ? " flere tegn" : " tegn til"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Vennligst fjern " + n + " tegn"; }, + formatSelectionTooBig: function (limit) { return "Du kan velge maks " + limit + " elementer"; }, + formatLoadMore: function (pageNumber) { return "Laster flere resultater …"; }, + formatSearching: function () { return "Søker …"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['no']); +})(jQuery); + diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_nl.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_nl.js new file mode 100644 index 00000000..985741ec --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_nl.js @@ -0,0 +1,17 @@ +/** + * Select2 Dutch translation + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['nl'] = { + formatNoMatches: function () { return "Geen resultaten gevonden"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Vul nog " + n + " karakter" + (n == 1? "" : "s") + " in"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Haal " + n + " karakter" + (n == 1? "" : "s") + " weg"; }, + formatSelectionTooBig: function (limit) { return "Maximaal " + limit + " item" + (limit == 1 ? "" : "s") + " toegestaan"; }, + formatLoadMore: function (pageNumber) { return "Meer resultaten laden…"; }, + formatSearching: function () { return "Zoeken…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['nl']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_pt-PT.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_pt-PT.js new file mode 100644 index 00000000..ae55a4fc --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_pt-PT.js @@ -0,0 +1,17 @@ +/** + * Select2 Portuguese (Portugal) translation + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['pt-PT'] = { + formatNoMatches: function () { return "Nenhum resultado encontrado"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Introduza " + n + " car" + (n == 1 ? "ácter" : "acteres"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Apague " + n + " car" + (n == 1 ? "ácter" : "acteres"); }, + formatSelectionTooBig: function (limit) { return "Só é possível selecionar " + limit + " elemento" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "A carregar mais resultados…"; }, + formatSearching: function () { return "A pesquisar…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['pt-PT']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ro.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ro.js new file mode 100644 index 00000000..21b0cf18 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_ro.js @@ -0,0 +1,17 @@ +/** + * Select2 Romanian translation. + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['ro'] = { + formatNoMatches: function () { return "Nu a fost găsit nimic"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Vă rugăm să introduceți incă " + n + " caracter" + (n == 1 ? "" : "e"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Vă rugăm să introduceți mai puțin de " + n + " caracter" + (n == 1? "" : "e"); }, + formatSelectionTooBig: function (limit) { return "Aveți voie să selectați cel mult " + limit + " element" + (limit == 1 ? "" : "e"); }, + formatLoadMore: function (pageNumber) { return "Se încarcă…"; }, + formatSearching: function () { return "Căutare…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['ro']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_rs.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_rs.js new file mode 100644 index 00000000..72c16389 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_rs.js @@ -0,0 +1,19 @@ +/** + * Select2 Serbian translation. + * + * @author Limon Monte + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['rs'] = { + formatNoMatches: function () { return "Ništa nije pronađeno"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Ukucajte bar još " + n + " simbol" + (n % 10 == 1 && n % 100 != 11 ? "" : "a"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Obrišite " + n + " simbol" + (n % 10 == 1 && n % 100 != 11 ? "" : "a"); }, + formatSelectionTooBig: function (limit) { return "Možete izabrati samo " + limit + " stavk" + (limit % 10 == 1 && limit % 100 != 11 ? "u" : (limit % 10 >= 2 && limit % 10 <= 4 && (limit % 100 < 12 || limit % 100 > 14)? "e" : "i")); }, + formatLoadMore: function (pageNumber) { return "Preuzimanje još rezultata…"; }, + formatSearching: function () { return "Pretraga…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['rs']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_sk.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_sk.js new file mode 100644 index 00000000..027530c9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_sk.js @@ -0,0 +1,50 @@ +/** + * Select2 Slovak translation. + * + * Author: David Vallner + */ +(function ($) { + "use strict"; + // use text for the numbers 2 through 4 + var smallNumbers = { + 2: function(masc) { return (masc ? "dva" : "dve"); }, + 3: function() { return "tri"; }, + 4: function() { return "štyri"; } + }; + $.fn.select2.locales['sk'] = { + formatNoMatches: function () { return "Nenašli sa žiadne položky"; }, + formatInputTooShort: function (input, min) { + var n = min - input.length; + if (n == 1) { + return "Prosím, zadajte ešte jeden znak"; + } else if (n <= 4) { + return "Prosím, zadajte ešte ďalšie "+smallNumbers[n](true)+" znaky"; + } else { + return "Prosím, zadajte ešte ďalších "+n+" znakov"; + } + }, + formatInputTooLong: function (input, max) { + var n = input.length - max; + if (n == 1) { + return "Prosím, zadajte o jeden znak menej"; + } else if (n >= 2 && n <= 4) { + return "Prosím, zadajte o "+smallNumbers[n](true)+" znaky menej"; + } else { + return "Prosím, zadajte o "+n+" znakov menej"; + } + }, + formatSelectionTooBig: function (limit) { + if (limit == 1) { + return "Môžete zvoliť len jednu položku"; + } else if (limit >= 2 && limit <= 4) { + return "Môžete zvoliť najviac "+smallNumbers[limit](false)+" položky"; + } else { + return "Môžete zvoliť najviac "+limit+" položiek"; + } + }, + formatLoadMore: function (pageNumber) { return "Načítavajú sa ďalšie výsledky…"; }, + formatSearching: function () { return "Vyhľadávanie…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['sk']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_sv.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_sv.js new file mode 100644 index 00000000..96f8c0a8 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_sv.js @@ -0,0 +1,19 @@ +/** + * Select2 Swedish translation. + * + * Author: Jens Rantil + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['sv'] = { + formatNoMatches: function () { return "Inga träffar"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Var god skriv in " + n + (n>1 ? " till tecken" : " tecken till"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Var god sudda ut " + n + " tecken"; }, + formatSelectionTooBig: function (limit) { return "Du kan max välja " + limit + " element"; }, + formatLoadMore: function (pageNumber) { return "Laddar fler resultat…"; }, + formatSearching: function () { return "Söker…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['sv']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_tr.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_tr.js new file mode 100644 index 00000000..2330bc20 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_tr.js @@ -0,0 +1,20 @@ +/** + * Select2 Turkish translation. + * + * Author: Salim KAYABAŞI + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['tr'] = { + formatMatches: function (matches) { if (matches === 1) { return "Sadece bir sonuç bulundu, seçmek için enter tuşuna basabilirsiniz."; } return matches + " sonuç bulundu, yukarı ve aşağı tuşları ile seçebilirsiniz."; }, + formatNoMatches: function () { return "Sonuç bulunamadı"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "En az " + n + " karakter daha girmelisiniz"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return n + " karakter azaltmalısınız"; }, + formatSelectionTooBig: function (limit) { return "Sadece " + limit + " seçim yapabilirsiniz"; }, + formatLoadMore: function (pageNumber) { return "Daha fazla…"; }, + formatSearching: function () { return "Aranıyor…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['tr']); +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_uk.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_uk.js new file mode 100644 index 00000000..b5bd0e02 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/select2/select2_locale_uk.js @@ -0,0 +1,25 @@ +/** + * Select2 Ukrainian translation. + * + * @author bigmihail + * @author Uriy Efremochkin + */ +(function ($) { + "use strict"; + + $.fn.select2.locales['uk'] = { + formatMatches: function (matches) { return character(matches, "результат") + " знайдено, використовуйте клавіші зі стрілками вверх та вниз для навігації."; }, + formatNoMatches: function () { return "Нічого не знайдено"; }, + formatInputTooShort: function (input, min) { return "Введіть буль ласка ще " + character(min - input.length, "символ"); }, + formatInputTooLong: function (input, max) { return "Введіть буль ласка на " + character(input.length - max, "символ") + " менше"; }, + formatSelectionTooBig: function (limit) { return "Ви можете вибрати лише " + character(limit, "елемент"); }, + formatLoadMore: function (pageNumber) { return "Завантаження даних…"; }, + formatSearching: function () { return "Пошук…"; } + }; + + $.extend($.fn.select2.defaults, $.fn.select2.locales['uk']); + + function character (n, word) { + return n + " " + word + (n%10 < 5 && n%10 > 0 && (n%100 < 5 || n%100 > 19) ? n%10 > 1 ? "и" : "" : "ів"); + } +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckan/templates/activity_streams/activity_stream_email_notifications.text b/venv/lib/python2.7/site-packages/ckan/templates/activity_streams/activity_stream_email_notifications.text new file mode 100644 index 00000000..951f3abd --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/activity_streams/activity_stream_email_notifications.text @@ -0,0 +1,7 @@ +{% set num = activities|length %}{{ ungettext("You have {num} new activity on your {site_title} dashboard", "You have {num} new activities on your {site_title} dashboard", num).format(site_title=g.site_title, num=num) }} {{ _('To view your dashboard, click on this link:') }} + +{{ g.site_url + '/dashboard' }} + +{{ _('You can turn off these email notifications in your {site_title} preferences. To change your preferences, click on this link:').format(site_title=g.site_title) }} + +{{ g.site_url + '/user/edit' }} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/admin/base.html b/venv/lib/python2.7/site-packages/ckan/templates/admin/base.html new file mode 100644 index 00000000..741c229c --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/admin/base.html @@ -0,0 +1,12 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _('Administration') }}{% endblock %} + +{% block breadcrumb_content %}{% endblock %} + +{% block content_primary_nav %} + {{ h.build_nav_icon('admin.index', _('Sysadmins'), icon='gavel') }} + {{ h.build_nav_icon('admin.config', _('Config'), icon='check-square-o') }} + {{ h.build_nav_icon('admin.trash', _('Trash'), icon='trash-o') }} + {{ h.build_extra_admin_nav() }} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/admin/config.html b/venv/lib/python2.7/site-packages/ckan/templates/admin/config.html new file mode 100644 index 00000000..2fb72909 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/admin/config.html @@ -0,0 +1,73 @@ +{% import 'macros/autoform.html' as autoform %} + +{% extends "admin/base.html" %} + +{% import 'macros/form.html' as form %} + +{% block primary_content_inner %} + + {{ form.errors(error_summary) }} + +
    + {% block admin_form %} + + {{ form.input('ckan.site_title', id='field-ckan-site-title', label=_('Site Title'), value=data['ckan.site_title'], error=error, classes=['control-medium']) }} + + {{ form.select('ckan.main_css', id='field-ckan-main-css', label=_('Style'), options=styles, selected=data['ckan.main_css'], error=error) }} + + {{ form.input('ckan.site_description', id='field-ckan-site-description', label=_('Site Tag Line'), value=data['ckan.site_description'], error=error, classes=['control-medium']) }} + + {% set field_url = 'ckan.site_logo' %} + {% set is_upload = data[field_url] and not data[field_url].startswith('http') %} + {% set is_url = data[field_url] and data[field_url].startswith('http') %} + {{ form.image_upload(data, errors, is_upload_enabled=h.uploads_enabled(), is_url=is_url, is_upload=is_upload, upload_label = _('Site logo'), url_label=_('Site logo'), field_url=field_url, field_upload='logo_upload', field_clear='clear_logo_upload' )}} + + {{ form.markdown('ckan.site_about', id='field-ckan-site-about', label=_('About'), value=data['ckan.site_about'], error=error, placeholder=_('About page text')) }} + + {{ form.markdown('ckan.site_intro_text', id='field-ckan-site-intro-text', label=_('Intro Text'), value=data['ckan.site_intro_text'], error=error, placeholder=_('Text on home page')) }} + + {{ form.textarea('ckan.site_custom_css', id='field-ckan-site-custom-css', label=_('Custom CSS'), value=data['ckan.site_custom_css'], error=error, placeholder=_('Customisable css inserted into the page header')) }} + + {{ form.select('ckan.homepage_style', id='field-homepage-style', label=_('Homepage'), options=homepages, selected=data['ckan.homepage_style'], error=error) }} + + {% endblock %} +
    + {{ _('Reset') }} + +
    +
    +{% endblock %} + +{% block secondary_content %} +
    +

    + + {{ _('CKAN config options') }} +

    +
    + {% block admin_form_help %} + {% set about_url = h.url_for(controller='home', action='about') %} + {% set home_url = h.url_for(controller='home', action='index') %} + {% set docs_url = "http://docs.ckan.org/en/{0}/theming".format(g.ckan_doc_version) %} + {% trans %} +

    Site Title: This is the title of this CKAN instance + It appears in various places throughout CKAN.

    +

    Style: Choose from a list of simple variations of + the main colour scheme to get a very quick custom theme working.

    +

    Site Tag Logo: This is the logo that appears in the + header of all the CKAN instance templates.

    +

    About: This text will appear on this CKAN instances + about page.

    +

    Intro Text: This text will appear on this CKAN instances + home page as a welcome to visitors.

    +

    Custom CSS: This is a block of CSS that appears in + <head> tag of every page. If you wish to customize + the templates more fully we recommend + reading the documentation.

    +

    Homepage: This is for choosing a predefined layout for + the modules that appear on your homepage.

    + {% endtrans %} + {% endblock %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/admin/confirm_reset.html b/venv/lib/python2.7/site-packages/ckan/templates/admin/confirm_reset.html new file mode 100644 index 00000000..9f0ba36b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/admin/confirm_reset.html @@ -0,0 +1,13 @@ +{% extends "admin/base.html" %} + +{% block subtitle %}{{ _("Confirm Reset") }}{% endblock %} + +{% block primary_content_inner %} +
    +

    {{ _('Are you sure you want to reset the config?') }}

    +

    + + +

    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/admin/index.html b/venv/lib/python2.7/site-packages/ckan/templates/admin/index.html new file mode 100644 index 00000000..c313bf32 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/admin/index.html @@ -0,0 +1,26 @@ +{% extends "admin/base.html" %} + +{% block primary_content_inner %} +
      + {% for user in sysadmins %} +
    • {{ h.linked_user(user) }}
    • + {% endfor %} +
    +{% endblock %} + +{% block secondary_content %} +
    +

    + + {{ _('Administer CKAN') }} +

    +
    + + {% set docs_url = "http://docs.ckan.org/en/{0}/sysadmin-guide.html".format(g.ckan_doc_version) %} + {% trans %} +

    As a sysadmin user you have full control over this CKAN instance. Proceed with care!

    +

    For guidance on using sysadmin features, see the CKAN sysadmin guide

    + {% endtrans %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/admin/trash.html b/venv/lib/python2.7/site-packages/ckan/templates/admin/trash.html new file mode 100644 index 00000000..108ce9e6 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/admin/trash.html @@ -0,0 +1,37 @@ +{% extends "admin/base.html" %} + +{% block primary_content_inner %} + {% set truncate = truncate or 180 %} + {% set truncate_title = truncate_title or 80 %} +
      + {% for pkg in deleted_packages %} + {% set title = pkg.title or pkg.name %} +
    • {{ h.link_to(h.truncate(title, truncate_title), h.url_for(controller='package', action='read', id=pkg.name)) }}
    • + {% endfor %} + +
    +
    + +
    +{% endblock %} + +{% block secondary_content %} +
    +

    + + {{ _('Trash') }} +

    +
    + {% trans %} +

    Purge deleted datasets forever and irreversibly.

    + {% endtrans %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/ajax_snippets/custom_fields.html b/venv/lib/python2.7/site-packages/ckan/templates/ajax_snippets/custom_fields.html new file mode 100644 index 00000000..90f73444 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/ajax_snippets/custom_fields.html @@ -0,0 +1,4 @@ +{# Snippet for unit testing custom-fields.js #} +
    + {% snippet 'snippets/custom_form_fields.html', extras=[{'key': 'key', 'value': 'value'}], errors={} %} +
    diff --git a/venv/lib/python2.7/site-packages/ckan/templates/ajax_snippets/follow_button.html b/venv/lib/python2.7/site-packages/ckan/templates/ajax_snippets/follow_button.html new file mode 100644 index 00000000..9dc09c99 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/ajax_snippets/follow_button.html @@ -0,0 +1 @@ +{{ h.follow_button(type, id) }} \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/templates/base.html b/venv/lib/python2.7/site-packages/ckan/templates/base.html new file mode 100644 index 00000000..d6267fb6 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/base.html @@ -0,0 +1,116 @@ +{# Allows the DOCTYPE to be set on a page by page basis #} +{%- block doctype %}{% endblock -%} + +{# Allows custom attributes to be added to the tag #} +{%- block htmltag -%} +{% set lang = h.lang() %} + + +{%- endblock -%} + + {# Allows custom attributes to be added to the tag #} + + {# + Add custom meta tags to the page. Call super() to get the default tags + such as charset, viewport and generator. + + Example: + + {% block meta %} + {{ super() }} + + {% endblock %} + + #} + {%- block meta -%} + + {% block meta_generator %}{% endblock %} + {% block meta_viewport %}{% endblock %} + {%- endblock -%} + + {# + Add a custom title to the page by extending the title block. Call super() + to get the default page title. + + Example: + + {% block title %}My Subtitle - {{ super() }}{% endblock %} + + #} + + {%- block title -%} + {%- block subtitle %}{% endblock -%} + {%- if self.subtitle()|trim %} {{ g.template_title_deliminater }} {% endif -%} + {{ g.site_title }} + {%- endblock -%} + + + {# + The links block allows you to add additonal content before the stylesheets + such as rss feeds and favicons in the same way as the meta block. + #} + {% block links -%} + + {% endblock -%} + + {# + The styles block allows you to add additonal stylesheets to the page in + the same way as the meta block. Use super() to include the default + stylesheets before or after your own. + + Example: + + {% block styles %} + {{ super() }} + + {% endblock %} + #} + {%- block styles %} + {% resource g.main_css[6:] %} + {% endblock %} + + {% block head_extras %} + {# defined in the config.ini under "ckan.template_head_end" #} + {{ g.template_head_end | safe }} + {% endblock %} + + {%- block custom_styles %} + {%- if g.site_custom_css -%} + + {%- endif %} + {% endblock %} + + + {# Allows custom attributes to be added to the tag #} + + + {# + The page block allows you to add content to the page. Most of the time it is + recommended that you extend one of the page.html templates in order to get + the site header and footer. If you need a clean page then this is the + block to use. + + Example: + + {% block page %} +
    Some other page content
    + {% endblock %} + #} + {%- block page %}{% endblock -%} + + {# + DO NOT USE THIS BLOCK FOR ADDING SCRIPTS + Scripts should be loaded by the {% resource %} tag except in very special + circumstances + #} + {%- block scripts %} + {% endblock -%} + + {% block body_extras -%} + {# defined in the config.ini under "ckan.template_footer_end" #} + {{ g.template_footer_end | safe }} + {%- endblock %} + + diff --git a/venv/lib/python2.7/site-packages/ckan/templates/dataviewer/base.html b/venv/lib/python2.7/site-packages/ckan/templates/dataviewer/base.html new file mode 100644 index 00000000..ad67e0f3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/dataviewer/base.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{# use of c.resource and c.package are for backwards compatibility with old previews that need them #} + +{% block subtitle %}{{ h.dataset_display_name(package or c.package) }} - {{h.resource_display_name(resource or c.resource) }}{% endblock %} + +{# remove any scripts #} +{% block scripts %} + +{% endblock %} + +{# remove any ckan styles #} +{% block styles %}{% endblock %} + +{% block custom_styles %}{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/development/markup.html b/venv/lib/python2.7/site-packages/ckan/templates/development/markup.html new file mode 100644 index 00000000..9c4fcffc --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/development/markup.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% block page %} +
    +
    + {% snippet 'development/snippets/markup.html' %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/development/primer.html b/venv/lib/python2.7/site-packages/ckan/templates/development/primer.html new file mode 100644 index 00000000..5188b434 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/development/primer.html @@ -0,0 +1,100 @@ +{% extends "page.html" %} + +{% block toolbar %} +{% snippet 'development/snippets/breadcrumb.html', stage=1 %} +{% snippet 'development/snippets/breadcrumb.html', stage=2 %} +{% snippet 'development/snippets/breadcrumb.html', stage=3 %} +{% endblock %} + +{% block actions_content %} +{% snippet 'development/snippets/actions.html' %} +{% endblock %} + +{% block secondary_content %} +{% snippet 'development/snippets/context.html' %} + +
    +

    Helper text

    +
    {{ lipsum(1) }}
    +
    + +{% snippet 'development/snippets/nav.html', heading='Navigation' %} +{% snippet 'development/snippets/nav.html', heading='Active Navigation', show_active=true %} +{% snippet 'development/snippets/nav.html', heading='Icon Navigation', show_icons=true %} +{% snippet 'development/snippets/facet.html', heading='Facet List', show_icons=true %} +{% endblock %} + +{% block primary_content %} +{% snippet 'development/snippets/page_header.html' %} + +
    +
    + + + + +
    +
    + +
    +

    Top level heading (h1)

    +

    Some Rendered Markdown (h2)

    +
    +

    Heading 1

    + {{ lipsum(1) }} +

    Heading 2

    + {{ lipsum(1) }} +

    Heading 3

    + {{ lipsum(1) }} +
    +
    + +
    +
    +

    Forms

    +
    +{% snippet 'development/snippets/form.html' %} +{% snippet 'development/snippets/form.html', error=['This field has an error'] %} + +
    +
    +

    Form stages

    +
    +{% snippet 'development/snippets/form_stages.html' %} + + +
    +

    Datasets

    +
    + {% snippet 'snippets/package_list.html', packages=[ + {'name': "test", 'title': 'Dataset #1', 'notes': lipsum(1), 'tracking_summary':{'recent': 10}}, + {'name': "test", 'title': 'Dataset #2', 'notes': lipsum(0), 'tracking_summary':{'recent': 5}}, + {'name': "test", 'title': 'Dataset #3', 'notes': lipsum(1), 'tracking_summary':{'recent': 10}}, + {'name': "test", 'title': 'Dataset #4', 'notes': lipsum(1), 'tracking_summary':{'recent': 10}} + ] %} +
    + +
    +
    +

    Media Grid

    +
    +{% snippet 'development/snippets/media_grid.html', groups=[ +{'name': "test", 'display_name': 'Group #1', 'type': 'group', 'description': lipsum(0), 'packages': 0}, +{'name': "test", 'display_name': 'Group #2', 'type': 'group', 'description': lipsum(1), 'packages': 1}, +{'name': "test", 'display_name': 'Group #3', 'type': 'group', 'description': lipsum(1), 'packages': 10}, +{'name': "test", 'display_name': 'Group #4', 'type': 'group', 'description': lipsum(1), 'packages': 200}, +{'name': "test", 'display_name': 'Group #5', 'type': 'group', 'description': lipsum(1), 'packages': 10}, +{'name': "test", 'display_name': 'Group #6', 'type': 'group', 'description': lipsum(0), 'packages': 5} +] %} + +
    +

    Pagination

    +
    +{% snippet 'development/snippets/pagination.html', total=5, current=1 %} +{% snippet 'development/snippets/pagination.html', total=5, current=3 %} +{% snippet 'development/snippets/pagination.html', total=5, current=5 %} + +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/emails/invite_user.txt b/venv/lib/python2.7/site-packages/ckan/templates/emails/invite_user.txt new file mode 100644 index 00000000..7fefc0c8 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/emails/invite_user.txt @@ -0,0 +1,17 @@ +Dear {{ user_name }}, + +You have been invited to {{ site_title }}. + +A user has already been created for you with the username {{ user_name }}. You can change it later. + +You have been added to the {{ group_type }} {{ group_title }} with the following role: {{ role_name }}. + +To accept this invite, please reset your password at: + + {{ reset_link }} + + +Have a nice day. + +-- +Message sent by {{ site_title }} ({{ site_url }}) diff --git a/venv/lib/python2.7/site-packages/ckan/templates/emails/invite_user_subject.txt b/venv/lib/python2.7/site-packages/ckan/templates/emails/invite_user_subject.txt new file mode 100644 index 00000000..6aa3184a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/emails/invite_user_subject.txt @@ -0,0 +1 @@ +Invite for {{ site_title }} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/emails/reset_password.txt b/venv/lib/python2.7/site-packages/ckan/templates/emails/reset_password.txt new file mode 100644 index 00000000..803913e2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/emails/reset_password.txt @@ -0,0 +1,12 @@ +Dear {{ user_name }}, + +You have requested your password on {{ site_title }} to be reset. + +Please click the following link to confirm this request: + + {{ reset_link }} + +Have a nice day. + +-- +Message sent by {{ site_title }} ({{ site_url }}) diff --git a/venv/lib/python2.7/site-packages/ckan/templates/emails/reset_password_subject.txt b/venv/lib/python2.7/site-packages/ckan/templates/emails/reset_password_subject.txt new file mode 100644 index 00000000..828b8dc7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/emails/reset_password_subject.txt @@ -0,0 +1 @@ +Reset your password - {{ site_title }} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/error_document_template.html b/venv/lib/python2.7/site-packages/ckan/templates/error_document_template.html new file mode 100644 index 00000000..0baecba1 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/error_document_template.html @@ -0,0 +1,21 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ gettext('Error %(error_code)s', error_code=code[0]) }}{% endblock %} + +{% block primary %} +
    +
    + {{ content}} +
    +
    +{% endblock %} + +{% block breadcrumb %} +{% endblock %} + +{% block flash %} + {# eat the flash messages caused by the 404 #} + {% set flash_messages = h.flash.pop_messages() %} +{% endblock %} + +{% block secondary %}{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/footer.html b/venv/lib/python2.7/site-packages/ckan/templates/footer.html new file mode 100644 index 00000000..de5822aa --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/footer.html @@ -0,0 +1,39 @@ +
    +
    + {% block footer_content %} +
    + +
    + {% block footer_attribution %} +

    {% trans %}Powered by {% endtrans %}

    + {% endblock %} + {% block footer_lang %} + {% snippet "snippets/language_selector.html" %} + {% endblock %} +
    +
    + {% endblock %} +
    + + {% block footer_debug %} + {% if g.debug %} + {% include 'snippets/debug.html' %} + {% endif %} + {% endblock %} +
    diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/about.html b/venv/lib/python2.7/site-packages/ckan/templates/group/about.html new file mode 100644 index 00000000..bec97ae2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/about.html @@ -0,0 +1,16 @@ +{% extends "group/read_base.html" %} + +{% block subtitle %}{{ _('About') }} - {{ super() }}{% endblock %} + +{% block primary_content_inner %} +

    {% block page_heading %}{{ c.group_dict.display_name }}{% endblock %}

    + {% block group_description %} + {% if c.group_dict.description %} + {{ h.render_markdown(c.group_dict.description) }} + {% endif %} + {% endblock %} + + {% block group_extras %} + {% snippet 'snippets/additional_info.html', extras = h.sorted_extras(c.group_dict.extras) %} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/activity_stream.html b/venv/lib/python2.7/site-packages/ckan/templates/group/activity_stream.html new file mode 100644 index 00000000..47a6d029 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/activity_stream.html @@ -0,0 +1,10 @@ +{% extends "group/read_base.html" %} + +{% block subtitle %}{{ _('Activity Stream') }} - {{ super() }}{% endblock %} + +{% block primary_content_inner %} +

    {% block page_heading %}{{ _('Activity Stream') }}{% endblock %}

    + {% block activity_stream %} + {{ c.group_activity_stream | safe }} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/admins.html b/venv/lib/python2.7/site-packages/ckan/templates/group/admins.html new file mode 100644 index 00000000..e032eb7e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/admins.html @@ -0,0 +1,10 @@ +{% extends "group/read_base.html" %} + +{% block subtitle %}{{ _('Administrators') }} - {{ c.group_dict.title or c.group_dict.name }}{% endblock %} + +{% block primary_content_inner %} +

    {% block page_heading %}{{ _('Administrators') }}{% endblock %}

    + {% block admins_list %} + {% snippet "user/snippets/followers.html", followers=c.admins %} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/base_form_page.html b/venv/lib/python2.7/site-packages/ckan/templates/group/base_form_page.html new file mode 100644 index 00000000..7d1664aa --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/base_form_page.html @@ -0,0 +1,15 @@ +{% extends "group/edit_base.html" %} + +{% block page_primary_action %}{% endblock %} + +{% block breadcrumb_content %} +
  • {{ h.nav_link(_('Groups'), controller='group', action='index') }}
  • +
  • {% block breadcrumb_link %}{{ h.nav_link(_('Add a Group'), controller='group', action='new') }}{% endblock %}
  • +{% endblock %} + +{% block primary_content_inner %} +

    {% block page_heading %}{{ _('Group Form') }}{% endblock %}

    + {% block form %} + {{ c.form | safe }} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/confirm_delete.html b/venv/lib/python2.7/site-packages/ckan/templates/group/confirm_delete.html new file mode 100644 index 00000000..1be7c8aa --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/confirm_delete.html @@ -0,0 +1,21 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _("Confirm Delete") }}{% endblock %} + +{% block maintag %}

    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 d0mRO}xo^G_%I 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 SdSQM28?+M|D(2r_;{O0|uQ74} literal 0 HcmV?d00001 diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.woff2 b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..4d13fc60404b91e398a37200c4a77b645cfd9586 GIT binary patch literal 77160 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/animated.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/animated.less new file mode 100644 index 00000000..66ad52a5 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/animated.less @@ -0,0 +1,34 @@ +// Animated Icons +// -------------------------- + +.@{fa-css-prefix}-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; +} + +.@{fa-css-prefix}-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); +} + +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} + +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/bordered-pulled.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/bordered-pulled.less new file mode 100644 index 00000000..f1c8ad75 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/bordered-pulled.less @@ -0,0 +1,25 @@ +// Bordered & Pulled +// ------------------------- + +.@{fa-css-prefix}-border { + padding: .2em .25em .15em; + border: solid .08em @fa-border-color; + border-radius: .1em; +} + +.@{fa-css-prefix}-pull-left { float: left; } +.@{fa-css-prefix}-pull-right { float: right; } + +.@{fa-css-prefix} { + &.@{fa-css-prefix}-pull-left { margin-right: .3em; } + &.@{fa-css-prefix}-pull-right { margin-left: .3em; } +} + +/* Deprecated as of 4.4.0 */ +.pull-right { float: right; } +.pull-left { float: left; } + +.@{fa-css-prefix} { + &.pull-left { margin-right: .3em; } + &.pull-right { margin-left: .3em; } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/core.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/core.less new file mode 100644 index 00000000..c577ac84 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/core.less @@ -0,0 +1,12 @@ +// Base Class Definition +// ------------------------- + +.@{fa-css-prefix} { + display: inline-block; + font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration + font-size: inherit; // can't have font-size inherit on line above, so need to override + text-rendering: auto; // optimizelegibility throws things off #1094 + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/fixed-width.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/fixed-width.less new file mode 100644 index 00000000..110289f2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/fixed-width.less @@ -0,0 +1,6 @@ +// Fixed Width Icons +// ------------------------- +.@{fa-css-prefix}-fw { + width: (18em / 14); + text-align: center; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/font-awesome.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/font-awesome.less new file mode 100644 index 00000000..c3677def --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/font-awesome.less @@ -0,0 +1,18 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ + +@import "variables.less"; +@import "mixins.less"; +@import "path.less"; +@import "core.less"; +@import "larger.less"; +@import "fixed-width.less"; +@import "list.less"; +@import "bordered-pulled.less"; +@import "animated.less"; +@import "rotated-flipped.less"; +@import "stacked.less"; +@import "icons.less"; +@import "screen-reader.less"; diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/icons.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/icons.less new file mode 100644 index 00000000..159d6004 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/icons.less @@ -0,0 +1,789 @@ +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ + +.@{fa-css-prefix}-glass:before { content: @fa-var-glass; } +.@{fa-css-prefix}-music:before { content: @fa-var-music; } +.@{fa-css-prefix}-search:before { content: @fa-var-search; } +.@{fa-css-prefix}-envelope-o:before { content: @fa-var-envelope-o; } +.@{fa-css-prefix}-heart:before { content: @fa-var-heart; } +.@{fa-css-prefix}-star:before { content: @fa-var-star; } +.@{fa-css-prefix}-star-o:before { content: @fa-var-star-o; } +.@{fa-css-prefix}-user:before { content: @fa-var-user; } +.@{fa-css-prefix}-film:before { content: @fa-var-film; } +.@{fa-css-prefix}-th-large:before { content: @fa-var-th-large; } +.@{fa-css-prefix}-th:before { content: @fa-var-th; } +.@{fa-css-prefix}-th-list:before { content: @fa-var-th-list; } +.@{fa-css-prefix}-check:before { content: @fa-var-check; } +.@{fa-css-prefix}-remove:before, +.@{fa-css-prefix}-close:before, +.@{fa-css-prefix}-times:before { content: @fa-var-times; } +.@{fa-css-prefix}-search-plus:before { content: @fa-var-search-plus; } +.@{fa-css-prefix}-search-minus:before { content: @fa-var-search-minus; } +.@{fa-css-prefix}-power-off:before { content: @fa-var-power-off; } +.@{fa-css-prefix}-signal:before { content: @fa-var-signal; } +.@{fa-css-prefix}-gear:before, +.@{fa-css-prefix}-cog:before { content: @fa-var-cog; } +.@{fa-css-prefix}-trash-o:before { content: @fa-var-trash-o; } +.@{fa-css-prefix}-home:before { content: @fa-var-home; } +.@{fa-css-prefix}-file-o:before { content: @fa-var-file-o; } +.@{fa-css-prefix}-clock-o:before { content: @fa-var-clock-o; } +.@{fa-css-prefix}-road:before { content: @fa-var-road; } +.@{fa-css-prefix}-download:before { content: @fa-var-download; } +.@{fa-css-prefix}-arrow-circle-o-down:before { content: @fa-var-arrow-circle-o-down; } +.@{fa-css-prefix}-arrow-circle-o-up:before { content: @fa-var-arrow-circle-o-up; } +.@{fa-css-prefix}-inbox:before { content: @fa-var-inbox; } +.@{fa-css-prefix}-play-circle-o:before { content: @fa-var-play-circle-o; } +.@{fa-css-prefix}-rotate-right:before, +.@{fa-css-prefix}-repeat:before { content: @fa-var-repeat; } +.@{fa-css-prefix}-refresh:before { content: @fa-var-refresh; } +.@{fa-css-prefix}-list-alt:before { content: @fa-var-list-alt; } +.@{fa-css-prefix}-lock:before { content: @fa-var-lock; } +.@{fa-css-prefix}-flag:before { content: @fa-var-flag; } +.@{fa-css-prefix}-headphones:before { content: @fa-var-headphones; } +.@{fa-css-prefix}-volume-off:before { content: @fa-var-volume-off; } +.@{fa-css-prefix}-volume-down:before { content: @fa-var-volume-down; } +.@{fa-css-prefix}-volume-up:before { content: @fa-var-volume-up; } +.@{fa-css-prefix}-qrcode:before { content: @fa-var-qrcode; } +.@{fa-css-prefix}-barcode:before { content: @fa-var-barcode; } +.@{fa-css-prefix}-tag:before { content: @fa-var-tag; } +.@{fa-css-prefix}-tags:before { content: @fa-var-tags; } +.@{fa-css-prefix}-book:before { content: @fa-var-book; } +.@{fa-css-prefix}-bookmark:before { content: @fa-var-bookmark; } +.@{fa-css-prefix}-print:before { content: @fa-var-print; } +.@{fa-css-prefix}-camera:before { content: @fa-var-camera; } +.@{fa-css-prefix}-font:before { content: @fa-var-font; } +.@{fa-css-prefix}-bold:before { content: @fa-var-bold; } +.@{fa-css-prefix}-italic:before { content: @fa-var-italic; } +.@{fa-css-prefix}-text-height:before { content: @fa-var-text-height; } +.@{fa-css-prefix}-text-width:before { content: @fa-var-text-width; } +.@{fa-css-prefix}-align-left:before { content: @fa-var-align-left; } +.@{fa-css-prefix}-align-center:before { content: @fa-var-align-center; } +.@{fa-css-prefix}-align-right:before { content: @fa-var-align-right; } +.@{fa-css-prefix}-align-justify:before { content: @fa-var-align-justify; } +.@{fa-css-prefix}-list:before { content: @fa-var-list; } +.@{fa-css-prefix}-dedent:before, +.@{fa-css-prefix}-outdent:before { content: @fa-var-outdent; } +.@{fa-css-prefix}-indent:before { content: @fa-var-indent; } +.@{fa-css-prefix}-video-camera:before { content: @fa-var-video-camera; } +.@{fa-css-prefix}-photo:before, +.@{fa-css-prefix}-image:before, +.@{fa-css-prefix}-picture-o:before { content: @fa-var-picture-o; } +.@{fa-css-prefix}-pencil:before { content: @fa-var-pencil; } +.@{fa-css-prefix}-map-marker:before { content: @fa-var-map-marker; } +.@{fa-css-prefix}-adjust:before { content: @fa-var-adjust; } +.@{fa-css-prefix}-tint:before { content: @fa-var-tint; } +.@{fa-css-prefix}-edit:before, +.@{fa-css-prefix}-pencil-square-o:before { content: @fa-var-pencil-square-o; } +.@{fa-css-prefix}-share-square-o:before { content: @fa-var-share-square-o; } +.@{fa-css-prefix}-check-square-o:before { content: @fa-var-check-square-o; } +.@{fa-css-prefix}-arrows:before { content: @fa-var-arrows; } +.@{fa-css-prefix}-step-backward:before { content: @fa-var-step-backward; } +.@{fa-css-prefix}-fast-backward:before { content: @fa-var-fast-backward; } +.@{fa-css-prefix}-backward:before { content: @fa-var-backward; } +.@{fa-css-prefix}-play:before { content: @fa-var-play; } +.@{fa-css-prefix}-pause:before { content: @fa-var-pause; } +.@{fa-css-prefix}-stop:before { content: @fa-var-stop; } +.@{fa-css-prefix}-forward:before { content: @fa-var-forward; } +.@{fa-css-prefix}-fast-forward:before { content: @fa-var-fast-forward; } +.@{fa-css-prefix}-step-forward:before { content: @fa-var-step-forward; } +.@{fa-css-prefix}-eject:before { content: @fa-var-eject; } +.@{fa-css-prefix}-chevron-left:before { content: @fa-var-chevron-left; } +.@{fa-css-prefix}-chevron-right:before { content: @fa-var-chevron-right; } +.@{fa-css-prefix}-plus-circle:before { content: @fa-var-plus-circle; } +.@{fa-css-prefix}-minus-circle:before { content: @fa-var-minus-circle; } +.@{fa-css-prefix}-times-circle:before { content: @fa-var-times-circle; } +.@{fa-css-prefix}-check-circle:before { content: @fa-var-check-circle; } +.@{fa-css-prefix}-question-circle:before { content: @fa-var-question-circle; } +.@{fa-css-prefix}-info-circle:before { content: @fa-var-info-circle; } +.@{fa-css-prefix}-crosshairs:before { content: @fa-var-crosshairs; } +.@{fa-css-prefix}-times-circle-o:before { content: @fa-var-times-circle-o; } +.@{fa-css-prefix}-check-circle-o:before { content: @fa-var-check-circle-o; } +.@{fa-css-prefix}-ban:before { content: @fa-var-ban; } +.@{fa-css-prefix}-arrow-left:before { content: @fa-var-arrow-left; } +.@{fa-css-prefix}-arrow-right:before { content: @fa-var-arrow-right; } +.@{fa-css-prefix}-arrow-up:before { content: @fa-var-arrow-up; } +.@{fa-css-prefix}-arrow-down:before { content: @fa-var-arrow-down; } +.@{fa-css-prefix}-mail-forward:before, +.@{fa-css-prefix}-share:before { content: @fa-var-share; } +.@{fa-css-prefix}-expand:before { content: @fa-var-expand; } +.@{fa-css-prefix}-compress:before { content: @fa-var-compress; } +.@{fa-css-prefix}-plus:before { content: @fa-var-plus; } +.@{fa-css-prefix}-minus:before { content: @fa-var-minus; } +.@{fa-css-prefix}-asterisk:before { content: @fa-var-asterisk; } +.@{fa-css-prefix}-exclamation-circle:before { content: @fa-var-exclamation-circle; } +.@{fa-css-prefix}-gift:before { content: @fa-var-gift; } +.@{fa-css-prefix}-leaf:before { content: @fa-var-leaf; } +.@{fa-css-prefix}-fire:before { content: @fa-var-fire; } +.@{fa-css-prefix}-eye:before { content: @fa-var-eye; } +.@{fa-css-prefix}-eye-slash:before { content: @fa-var-eye-slash; } +.@{fa-css-prefix}-warning:before, +.@{fa-css-prefix}-exclamation-triangle:before { content: @fa-var-exclamation-triangle; } +.@{fa-css-prefix}-plane:before { content: @fa-var-plane; } +.@{fa-css-prefix}-calendar:before { content: @fa-var-calendar; } +.@{fa-css-prefix}-random:before { content: @fa-var-random; } +.@{fa-css-prefix}-comment:before { content: @fa-var-comment; } +.@{fa-css-prefix}-magnet:before { content: @fa-var-magnet; } +.@{fa-css-prefix}-chevron-up:before { content: @fa-var-chevron-up; } +.@{fa-css-prefix}-chevron-down:before { content: @fa-var-chevron-down; } +.@{fa-css-prefix}-retweet:before { content: @fa-var-retweet; } +.@{fa-css-prefix}-shopping-cart:before { content: @fa-var-shopping-cart; } +.@{fa-css-prefix}-folder:before { content: @fa-var-folder; } +.@{fa-css-prefix}-folder-open:before { content: @fa-var-folder-open; } +.@{fa-css-prefix}-arrows-v:before { content: @fa-var-arrows-v; } +.@{fa-css-prefix}-arrows-h:before { content: @fa-var-arrows-h; } +.@{fa-css-prefix}-bar-chart-o:before, +.@{fa-css-prefix}-bar-chart:before { content: @fa-var-bar-chart; } +.@{fa-css-prefix}-twitter-square:before { content: @fa-var-twitter-square; } +.@{fa-css-prefix}-facebook-square:before { content: @fa-var-facebook-square; } +.@{fa-css-prefix}-camera-retro:before { content: @fa-var-camera-retro; } +.@{fa-css-prefix}-key:before { content: @fa-var-key; } +.@{fa-css-prefix}-gears:before, +.@{fa-css-prefix}-cogs:before { content: @fa-var-cogs; } +.@{fa-css-prefix}-comments:before { content: @fa-var-comments; } +.@{fa-css-prefix}-thumbs-o-up:before { content: @fa-var-thumbs-o-up; } +.@{fa-css-prefix}-thumbs-o-down:before { content: @fa-var-thumbs-o-down; } +.@{fa-css-prefix}-star-half:before { content: @fa-var-star-half; } +.@{fa-css-prefix}-heart-o:before { content: @fa-var-heart-o; } +.@{fa-css-prefix}-sign-out:before { content: @fa-var-sign-out; } +.@{fa-css-prefix}-linkedin-square:before { content: @fa-var-linkedin-square; } +.@{fa-css-prefix}-thumb-tack:before { content: @fa-var-thumb-tack; } +.@{fa-css-prefix}-external-link:before { content: @fa-var-external-link; } +.@{fa-css-prefix}-sign-in:before { content: @fa-var-sign-in; } +.@{fa-css-prefix}-trophy:before { content: @fa-var-trophy; } +.@{fa-css-prefix}-github-square:before { content: @fa-var-github-square; } +.@{fa-css-prefix}-upload:before { content: @fa-var-upload; } +.@{fa-css-prefix}-lemon-o:before { content: @fa-var-lemon-o; } +.@{fa-css-prefix}-phone:before { content: @fa-var-phone; } +.@{fa-css-prefix}-square-o:before { content: @fa-var-square-o; } +.@{fa-css-prefix}-bookmark-o:before { content: @fa-var-bookmark-o; } +.@{fa-css-prefix}-phone-square:before { content: @fa-var-phone-square; } +.@{fa-css-prefix}-twitter:before { content: @fa-var-twitter; } +.@{fa-css-prefix}-facebook-f:before, +.@{fa-css-prefix}-facebook:before { content: @fa-var-facebook; } +.@{fa-css-prefix}-github:before { content: @fa-var-github; } +.@{fa-css-prefix}-unlock:before { content: @fa-var-unlock; } +.@{fa-css-prefix}-credit-card:before { content: @fa-var-credit-card; } +.@{fa-css-prefix}-feed:before, +.@{fa-css-prefix}-rss:before { content: @fa-var-rss; } +.@{fa-css-prefix}-hdd-o:before { content: @fa-var-hdd-o; } +.@{fa-css-prefix}-bullhorn:before { content: @fa-var-bullhorn; } +.@{fa-css-prefix}-bell:before { content: @fa-var-bell; } +.@{fa-css-prefix}-certificate:before { content: @fa-var-certificate; } +.@{fa-css-prefix}-hand-o-right:before { content: @fa-var-hand-o-right; } +.@{fa-css-prefix}-hand-o-left:before { content: @fa-var-hand-o-left; } +.@{fa-css-prefix}-hand-o-up:before { content: @fa-var-hand-o-up; } +.@{fa-css-prefix}-hand-o-down:before { content: @fa-var-hand-o-down; } +.@{fa-css-prefix}-arrow-circle-left:before { content: @fa-var-arrow-circle-left; } +.@{fa-css-prefix}-arrow-circle-right:before { content: @fa-var-arrow-circle-right; } +.@{fa-css-prefix}-arrow-circle-up:before { content: @fa-var-arrow-circle-up; } +.@{fa-css-prefix}-arrow-circle-down:before { content: @fa-var-arrow-circle-down; } +.@{fa-css-prefix}-globe:before { content: @fa-var-globe; } +.@{fa-css-prefix}-wrench:before { content: @fa-var-wrench; } +.@{fa-css-prefix}-tasks:before { content: @fa-var-tasks; } +.@{fa-css-prefix}-filter:before { content: @fa-var-filter; } +.@{fa-css-prefix}-briefcase:before { content: @fa-var-briefcase; } +.@{fa-css-prefix}-arrows-alt:before { content: @fa-var-arrows-alt; } +.@{fa-css-prefix}-group:before, +.@{fa-css-prefix}-users:before { content: @fa-var-users; } +.@{fa-css-prefix}-chain:before, +.@{fa-css-prefix}-link:before { content: @fa-var-link; } +.@{fa-css-prefix}-cloud:before { content: @fa-var-cloud; } +.@{fa-css-prefix}-flask:before { content: @fa-var-flask; } +.@{fa-css-prefix}-cut:before, +.@{fa-css-prefix}-scissors:before { content: @fa-var-scissors; } +.@{fa-css-prefix}-copy:before, +.@{fa-css-prefix}-files-o:before { content: @fa-var-files-o; } +.@{fa-css-prefix}-paperclip:before { content: @fa-var-paperclip; } +.@{fa-css-prefix}-save:before, +.@{fa-css-prefix}-floppy-o:before { content: @fa-var-floppy-o; } +.@{fa-css-prefix}-square:before { content: @fa-var-square; } +.@{fa-css-prefix}-navicon:before, +.@{fa-css-prefix}-reorder:before, +.@{fa-css-prefix}-bars:before { content: @fa-var-bars; } +.@{fa-css-prefix}-list-ul:before { content: @fa-var-list-ul; } +.@{fa-css-prefix}-list-ol:before { content: @fa-var-list-ol; } +.@{fa-css-prefix}-strikethrough:before { content: @fa-var-strikethrough; } +.@{fa-css-prefix}-underline:before { content: @fa-var-underline; } +.@{fa-css-prefix}-table:before { content: @fa-var-table; } +.@{fa-css-prefix}-magic:before { content: @fa-var-magic; } +.@{fa-css-prefix}-truck:before { content: @fa-var-truck; } +.@{fa-css-prefix}-pinterest:before { content: @fa-var-pinterest; } +.@{fa-css-prefix}-pinterest-square:before { content: @fa-var-pinterest-square; } +.@{fa-css-prefix}-google-plus-square:before { content: @fa-var-google-plus-square; } +.@{fa-css-prefix}-google-plus:before { content: @fa-var-google-plus; } +.@{fa-css-prefix}-money:before { content: @fa-var-money; } +.@{fa-css-prefix}-caret-down:before { content: @fa-var-caret-down; } +.@{fa-css-prefix}-caret-up:before { content: @fa-var-caret-up; } +.@{fa-css-prefix}-caret-left:before { content: @fa-var-caret-left; } +.@{fa-css-prefix}-caret-right:before { content: @fa-var-caret-right; } +.@{fa-css-prefix}-columns:before { content: @fa-var-columns; } +.@{fa-css-prefix}-unsorted:before, +.@{fa-css-prefix}-sort:before { content: @fa-var-sort; } +.@{fa-css-prefix}-sort-down:before, +.@{fa-css-prefix}-sort-desc:before { content: @fa-var-sort-desc; } +.@{fa-css-prefix}-sort-up:before, +.@{fa-css-prefix}-sort-asc:before { content: @fa-var-sort-asc; } +.@{fa-css-prefix}-envelope:before { content: @fa-var-envelope; } +.@{fa-css-prefix}-linkedin:before { content: @fa-var-linkedin; } +.@{fa-css-prefix}-rotate-left:before, +.@{fa-css-prefix}-undo:before { content: @fa-var-undo; } +.@{fa-css-prefix}-legal:before, +.@{fa-css-prefix}-gavel:before { content: @fa-var-gavel; } +.@{fa-css-prefix}-dashboard:before, +.@{fa-css-prefix}-tachometer:before { content: @fa-var-tachometer; } +.@{fa-css-prefix}-comment-o:before { content: @fa-var-comment-o; } +.@{fa-css-prefix}-comments-o:before { content: @fa-var-comments-o; } +.@{fa-css-prefix}-flash:before, +.@{fa-css-prefix}-bolt:before { content: @fa-var-bolt; } +.@{fa-css-prefix}-sitemap:before { content: @fa-var-sitemap; } +.@{fa-css-prefix}-umbrella:before { content: @fa-var-umbrella; } +.@{fa-css-prefix}-paste:before, +.@{fa-css-prefix}-clipboard:before { content: @fa-var-clipboard; } +.@{fa-css-prefix}-lightbulb-o:before { content: @fa-var-lightbulb-o; } +.@{fa-css-prefix}-exchange:before { content: @fa-var-exchange; } +.@{fa-css-prefix}-cloud-download:before { content: @fa-var-cloud-download; } +.@{fa-css-prefix}-cloud-upload:before { content: @fa-var-cloud-upload; } +.@{fa-css-prefix}-user-md:before { content: @fa-var-user-md; } +.@{fa-css-prefix}-stethoscope:before { content: @fa-var-stethoscope; } +.@{fa-css-prefix}-suitcase:before { content: @fa-var-suitcase; } +.@{fa-css-prefix}-bell-o:before { content: @fa-var-bell-o; } +.@{fa-css-prefix}-coffee:before { content: @fa-var-coffee; } +.@{fa-css-prefix}-cutlery:before { content: @fa-var-cutlery; } +.@{fa-css-prefix}-file-text-o:before { content: @fa-var-file-text-o; } +.@{fa-css-prefix}-building-o:before { content: @fa-var-building-o; } +.@{fa-css-prefix}-hospital-o:before { content: @fa-var-hospital-o; } +.@{fa-css-prefix}-ambulance:before { content: @fa-var-ambulance; } +.@{fa-css-prefix}-medkit:before { content: @fa-var-medkit; } +.@{fa-css-prefix}-fighter-jet:before { content: @fa-var-fighter-jet; } +.@{fa-css-prefix}-beer:before { content: @fa-var-beer; } +.@{fa-css-prefix}-h-square:before { content: @fa-var-h-square; } +.@{fa-css-prefix}-plus-square:before { content: @fa-var-plus-square; } +.@{fa-css-prefix}-angle-double-left:before { content: @fa-var-angle-double-left; } +.@{fa-css-prefix}-angle-double-right:before { content: @fa-var-angle-double-right; } +.@{fa-css-prefix}-angle-double-up:before { content: @fa-var-angle-double-up; } +.@{fa-css-prefix}-angle-double-down:before { content: @fa-var-angle-double-down; } +.@{fa-css-prefix}-angle-left:before { content: @fa-var-angle-left; } +.@{fa-css-prefix}-angle-right:before { content: @fa-var-angle-right; } +.@{fa-css-prefix}-angle-up:before { content: @fa-var-angle-up; } +.@{fa-css-prefix}-angle-down:before { content: @fa-var-angle-down; } +.@{fa-css-prefix}-desktop:before { content: @fa-var-desktop; } +.@{fa-css-prefix}-laptop:before { content: @fa-var-laptop; } +.@{fa-css-prefix}-tablet:before { content: @fa-var-tablet; } +.@{fa-css-prefix}-mobile-phone:before, +.@{fa-css-prefix}-mobile:before { content: @fa-var-mobile; } +.@{fa-css-prefix}-circle-o:before { content: @fa-var-circle-o; } +.@{fa-css-prefix}-quote-left:before { content: @fa-var-quote-left; } +.@{fa-css-prefix}-quote-right:before { content: @fa-var-quote-right; } +.@{fa-css-prefix}-spinner:before { content: @fa-var-spinner; } +.@{fa-css-prefix}-circle:before { content: @fa-var-circle; } +.@{fa-css-prefix}-mail-reply:before, +.@{fa-css-prefix}-reply:before { content: @fa-var-reply; } +.@{fa-css-prefix}-github-alt:before { content: @fa-var-github-alt; } +.@{fa-css-prefix}-folder-o:before { content: @fa-var-folder-o; } +.@{fa-css-prefix}-folder-open-o:before { content: @fa-var-folder-open-o; } +.@{fa-css-prefix}-smile-o:before { content: @fa-var-smile-o; } +.@{fa-css-prefix}-frown-o:before { content: @fa-var-frown-o; } +.@{fa-css-prefix}-meh-o:before { content: @fa-var-meh-o; } +.@{fa-css-prefix}-gamepad:before { content: @fa-var-gamepad; } +.@{fa-css-prefix}-keyboard-o:before { content: @fa-var-keyboard-o; } +.@{fa-css-prefix}-flag-o:before { content: @fa-var-flag-o; } +.@{fa-css-prefix}-flag-checkered:before { content: @fa-var-flag-checkered; } +.@{fa-css-prefix}-terminal:before { content: @fa-var-terminal; } +.@{fa-css-prefix}-code:before { content: @fa-var-code; } +.@{fa-css-prefix}-mail-reply-all:before, +.@{fa-css-prefix}-reply-all:before { content: @fa-var-reply-all; } +.@{fa-css-prefix}-star-half-empty:before, +.@{fa-css-prefix}-star-half-full:before, +.@{fa-css-prefix}-star-half-o:before { content: @fa-var-star-half-o; } +.@{fa-css-prefix}-location-arrow:before { content: @fa-var-location-arrow; } +.@{fa-css-prefix}-crop:before { content: @fa-var-crop; } +.@{fa-css-prefix}-code-fork:before { content: @fa-var-code-fork; } +.@{fa-css-prefix}-unlink:before, +.@{fa-css-prefix}-chain-broken:before { content: @fa-var-chain-broken; } +.@{fa-css-prefix}-question:before { content: @fa-var-question; } +.@{fa-css-prefix}-info:before { content: @fa-var-info; } +.@{fa-css-prefix}-exclamation:before { content: @fa-var-exclamation; } +.@{fa-css-prefix}-superscript:before { content: @fa-var-superscript; } +.@{fa-css-prefix}-subscript:before { content: @fa-var-subscript; } +.@{fa-css-prefix}-eraser:before { content: @fa-var-eraser; } +.@{fa-css-prefix}-puzzle-piece:before { content: @fa-var-puzzle-piece; } +.@{fa-css-prefix}-microphone:before { content: @fa-var-microphone; } +.@{fa-css-prefix}-microphone-slash:before { content: @fa-var-microphone-slash; } +.@{fa-css-prefix}-shield:before { content: @fa-var-shield; } +.@{fa-css-prefix}-calendar-o:before { content: @fa-var-calendar-o; } +.@{fa-css-prefix}-fire-extinguisher:before { content: @fa-var-fire-extinguisher; } +.@{fa-css-prefix}-rocket:before { content: @fa-var-rocket; } +.@{fa-css-prefix}-maxcdn:before { content: @fa-var-maxcdn; } +.@{fa-css-prefix}-chevron-circle-left:before { content: @fa-var-chevron-circle-left; } +.@{fa-css-prefix}-chevron-circle-right:before { content: @fa-var-chevron-circle-right; } +.@{fa-css-prefix}-chevron-circle-up:before { content: @fa-var-chevron-circle-up; } +.@{fa-css-prefix}-chevron-circle-down:before { content: @fa-var-chevron-circle-down; } +.@{fa-css-prefix}-html5:before { content: @fa-var-html5; } +.@{fa-css-prefix}-css3:before { content: @fa-var-css3; } +.@{fa-css-prefix}-anchor:before { content: @fa-var-anchor; } +.@{fa-css-prefix}-unlock-alt:before { content: @fa-var-unlock-alt; } +.@{fa-css-prefix}-bullseye:before { content: @fa-var-bullseye; } +.@{fa-css-prefix}-ellipsis-h:before { content: @fa-var-ellipsis-h; } +.@{fa-css-prefix}-ellipsis-v:before { content: @fa-var-ellipsis-v; } +.@{fa-css-prefix}-rss-square:before { content: @fa-var-rss-square; } +.@{fa-css-prefix}-play-circle:before { content: @fa-var-play-circle; } +.@{fa-css-prefix}-ticket:before { content: @fa-var-ticket; } +.@{fa-css-prefix}-minus-square:before { content: @fa-var-minus-square; } +.@{fa-css-prefix}-minus-square-o:before { content: @fa-var-minus-square-o; } +.@{fa-css-prefix}-level-up:before { content: @fa-var-level-up; } +.@{fa-css-prefix}-level-down:before { content: @fa-var-level-down; } +.@{fa-css-prefix}-check-square:before { content: @fa-var-check-square; } +.@{fa-css-prefix}-pencil-square:before { content: @fa-var-pencil-square; } +.@{fa-css-prefix}-external-link-square:before { content: @fa-var-external-link-square; } +.@{fa-css-prefix}-share-square:before { content: @fa-var-share-square; } +.@{fa-css-prefix}-compass:before { content: @fa-var-compass; } +.@{fa-css-prefix}-toggle-down:before, +.@{fa-css-prefix}-caret-square-o-down:before { content: @fa-var-caret-square-o-down; } +.@{fa-css-prefix}-toggle-up:before, +.@{fa-css-prefix}-caret-square-o-up:before { content: @fa-var-caret-square-o-up; } +.@{fa-css-prefix}-toggle-right:before, +.@{fa-css-prefix}-caret-square-o-right:before { content: @fa-var-caret-square-o-right; } +.@{fa-css-prefix}-euro:before, +.@{fa-css-prefix}-eur:before { content: @fa-var-eur; } +.@{fa-css-prefix}-gbp:before { content: @fa-var-gbp; } +.@{fa-css-prefix}-dollar:before, +.@{fa-css-prefix}-usd:before { content: @fa-var-usd; } +.@{fa-css-prefix}-rupee:before, +.@{fa-css-prefix}-inr:before { content: @fa-var-inr; } +.@{fa-css-prefix}-cny:before, +.@{fa-css-prefix}-rmb:before, +.@{fa-css-prefix}-yen:before, +.@{fa-css-prefix}-jpy:before { content: @fa-var-jpy; } +.@{fa-css-prefix}-ruble:before, +.@{fa-css-prefix}-rouble:before, +.@{fa-css-prefix}-rub:before { content: @fa-var-rub; } +.@{fa-css-prefix}-won:before, +.@{fa-css-prefix}-krw:before { content: @fa-var-krw; } +.@{fa-css-prefix}-bitcoin:before, +.@{fa-css-prefix}-btc:before { content: @fa-var-btc; } +.@{fa-css-prefix}-file:before { content: @fa-var-file; } +.@{fa-css-prefix}-file-text:before { content: @fa-var-file-text; } +.@{fa-css-prefix}-sort-alpha-asc:before { content: @fa-var-sort-alpha-asc; } +.@{fa-css-prefix}-sort-alpha-desc:before { content: @fa-var-sort-alpha-desc; } +.@{fa-css-prefix}-sort-amount-asc:before { content: @fa-var-sort-amount-asc; } +.@{fa-css-prefix}-sort-amount-desc:before { content: @fa-var-sort-amount-desc; } +.@{fa-css-prefix}-sort-numeric-asc:before { content: @fa-var-sort-numeric-asc; } +.@{fa-css-prefix}-sort-numeric-desc:before { content: @fa-var-sort-numeric-desc; } +.@{fa-css-prefix}-thumbs-up:before { content: @fa-var-thumbs-up; } +.@{fa-css-prefix}-thumbs-down:before { content: @fa-var-thumbs-down; } +.@{fa-css-prefix}-youtube-square:before { content: @fa-var-youtube-square; } +.@{fa-css-prefix}-youtube:before { content: @fa-var-youtube; } +.@{fa-css-prefix}-xing:before { content: @fa-var-xing; } +.@{fa-css-prefix}-xing-square:before { content: @fa-var-xing-square; } +.@{fa-css-prefix}-youtube-play:before { content: @fa-var-youtube-play; } +.@{fa-css-prefix}-dropbox:before { content: @fa-var-dropbox; } +.@{fa-css-prefix}-stack-overflow:before { content: @fa-var-stack-overflow; } +.@{fa-css-prefix}-instagram:before { content: @fa-var-instagram; } +.@{fa-css-prefix}-flickr:before { content: @fa-var-flickr; } +.@{fa-css-prefix}-adn:before { content: @fa-var-adn; } +.@{fa-css-prefix}-bitbucket:before { content: @fa-var-bitbucket; } +.@{fa-css-prefix}-bitbucket-square:before { content: @fa-var-bitbucket-square; } +.@{fa-css-prefix}-tumblr:before { content: @fa-var-tumblr; } +.@{fa-css-prefix}-tumblr-square:before { content: @fa-var-tumblr-square; } +.@{fa-css-prefix}-long-arrow-down:before { content: @fa-var-long-arrow-down; } +.@{fa-css-prefix}-long-arrow-up:before { content: @fa-var-long-arrow-up; } +.@{fa-css-prefix}-long-arrow-left:before { content: @fa-var-long-arrow-left; } +.@{fa-css-prefix}-long-arrow-right:before { content: @fa-var-long-arrow-right; } +.@{fa-css-prefix}-apple:before { content: @fa-var-apple; } +.@{fa-css-prefix}-windows:before { content: @fa-var-windows; } +.@{fa-css-prefix}-android:before { content: @fa-var-android; } +.@{fa-css-prefix}-linux:before { content: @fa-var-linux; } +.@{fa-css-prefix}-dribbble:before { content: @fa-var-dribbble; } +.@{fa-css-prefix}-skype:before { content: @fa-var-skype; } +.@{fa-css-prefix}-foursquare:before { content: @fa-var-foursquare; } +.@{fa-css-prefix}-trello:before { content: @fa-var-trello; } +.@{fa-css-prefix}-female:before { content: @fa-var-female; } +.@{fa-css-prefix}-male:before { content: @fa-var-male; } +.@{fa-css-prefix}-gittip:before, +.@{fa-css-prefix}-gratipay:before { content: @fa-var-gratipay; } +.@{fa-css-prefix}-sun-o:before { content: @fa-var-sun-o; } +.@{fa-css-prefix}-moon-o:before { content: @fa-var-moon-o; } +.@{fa-css-prefix}-archive:before { content: @fa-var-archive; } +.@{fa-css-prefix}-bug:before { content: @fa-var-bug; } +.@{fa-css-prefix}-vk:before { content: @fa-var-vk; } +.@{fa-css-prefix}-weibo:before { content: @fa-var-weibo; } +.@{fa-css-prefix}-renren:before { content: @fa-var-renren; } +.@{fa-css-prefix}-pagelines:before { content: @fa-var-pagelines; } +.@{fa-css-prefix}-stack-exchange:before { content: @fa-var-stack-exchange; } +.@{fa-css-prefix}-arrow-circle-o-right:before { content: @fa-var-arrow-circle-o-right; } +.@{fa-css-prefix}-arrow-circle-o-left:before { content: @fa-var-arrow-circle-o-left; } +.@{fa-css-prefix}-toggle-left:before, +.@{fa-css-prefix}-caret-square-o-left:before { content: @fa-var-caret-square-o-left; } +.@{fa-css-prefix}-dot-circle-o:before { content: @fa-var-dot-circle-o; } +.@{fa-css-prefix}-wheelchair:before { content: @fa-var-wheelchair; } +.@{fa-css-prefix}-vimeo-square:before { content: @fa-var-vimeo-square; } +.@{fa-css-prefix}-turkish-lira:before, +.@{fa-css-prefix}-try:before { content: @fa-var-try; } +.@{fa-css-prefix}-plus-square-o:before { content: @fa-var-plus-square-o; } +.@{fa-css-prefix}-space-shuttle:before { content: @fa-var-space-shuttle; } +.@{fa-css-prefix}-slack:before { content: @fa-var-slack; } +.@{fa-css-prefix}-envelope-square:before { content: @fa-var-envelope-square; } +.@{fa-css-prefix}-wordpress:before { content: @fa-var-wordpress; } +.@{fa-css-prefix}-openid:before { content: @fa-var-openid; } +.@{fa-css-prefix}-institution:before, +.@{fa-css-prefix}-bank:before, +.@{fa-css-prefix}-university:before { content: @fa-var-university; } +.@{fa-css-prefix}-mortar-board:before, +.@{fa-css-prefix}-graduation-cap:before { content: @fa-var-graduation-cap; } +.@{fa-css-prefix}-yahoo:before { content: @fa-var-yahoo; } +.@{fa-css-prefix}-google:before { content: @fa-var-google; } +.@{fa-css-prefix}-reddit:before { content: @fa-var-reddit; } +.@{fa-css-prefix}-reddit-square:before { content: @fa-var-reddit-square; } +.@{fa-css-prefix}-stumbleupon-circle:before { content: @fa-var-stumbleupon-circle; } +.@{fa-css-prefix}-stumbleupon:before { content: @fa-var-stumbleupon; } +.@{fa-css-prefix}-delicious:before { content: @fa-var-delicious; } +.@{fa-css-prefix}-digg:before { content: @fa-var-digg; } +.@{fa-css-prefix}-pied-piper-pp:before { content: @fa-var-pied-piper-pp; } +.@{fa-css-prefix}-pied-piper-alt:before { content: @fa-var-pied-piper-alt; } +.@{fa-css-prefix}-drupal:before { content: @fa-var-drupal; } +.@{fa-css-prefix}-joomla:before { content: @fa-var-joomla; } +.@{fa-css-prefix}-language:before { content: @fa-var-language; } +.@{fa-css-prefix}-fax:before { content: @fa-var-fax; } +.@{fa-css-prefix}-building:before { content: @fa-var-building; } +.@{fa-css-prefix}-child:before { content: @fa-var-child; } +.@{fa-css-prefix}-paw:before { content: @fa-var-paw; } +.@{fa-css-prefix}-spoon:before { content: @fa-var-spoon; } +.@{fa-css-prefix}-cube:before { content: @fa-var-cube; } +.@{fa-css-prefix}-cubes:before { content: @fa-var-cubes; } +.@{fa-css-prefix}-behance:before { content: @fa-var-behance; } +.@{fa-css-prefix}-behance-square:before { content: @fa-var-behance-square; } +.@{fa-css-prefix}-steam:before { content: @fa-var-steam; } +.@{fa-css-prefix}-steam-square:before { content: @fa-var-steam-square; } +.@{fa-css-prefix}-recycle:before { content: @fa-var-recycle; } +.@{fa-css-prefix}-automobile:before, +.@{fa-css-prefix}-car:before { content: @fa-var-car; } +.@{fa-css-prefix}-cab:before, +.@{fa-css-prefix}-taxi:before { content: @fa-var-taxi; } +.@{fa-css-prefix}-tree:before { content: @fa-var-tree; } +.@{fa-css-prefix}-spotify:before { content: @fa-var-spotify; } +.@{fa-css-prefix}-deviantart:before { content: @fa-var-deviantart; } +.@{fa-css-prefix}-soundcloud:before { content: @fa-var-soundcloud; } +.@{fa-css-prefix}-database:before { content: @fa-var-database; } +.@{fa-css-prefix}-file-pdf-o:before { content: @fa-var-file-pdf-o; } +.@{fa-css-prefix}-file-word-o:before { content: @fa-var-file-word-o; } +.@{fa-css-prefix}-file-excel-o:before { content: @fa-var-file-excel-o; } +.@{fa-css-prefix}-file-powerpoint-o:before { content: @fa-var-file-powerpoint-o; } +.@{fa-css-prefix}-file-photo-o:before, +.@{fa-css-prefix}-file-picture-o:before, +.@{fa-css-prefix}-file-image-o:before { content: @fa-var-file-image-o; } +.@{fa-css-prefix}-file-zip-o:before, +.@{fa-css-prefix}-file-archive-o:before { content: @fa-var-file-archive-o; } +.@{fa-css-prefix}-file-sound-o:before, +.@{fa-css-prefix}-file-audio-o:before { content: @fa-var-file-audio-o; } +.@{fa-css-prefix}-file-movie-o:before, +.@{fa-css-prefix}-file-video-o:before { content: @fa-var-file-video-o; } +.@{fa-css-prefix}-file-code-o:before { content: @fa-var-file-code-o; } +.@{fa-css-prefix}-vine:before { content: @fa-var-vine; } +.@{fa-css-prefix}-codepen:before { content: @fa-var-codepen; } +.@{fa-css-prefix}-jsfiddle:before { content: @fa-var-jsfiddle; } +.@{fa-css-prefix}-life-bouy:before, +.@{fa-css-prefix}-life-buoy:before, +.@{fa-css-prefix}-life-saver:before, +.@{fa-css-prefix}-support:before, +.@{fa-css-prefix}-life-ring:before { content: @fa-var-life-ring; } +.@{fa-css-prefix}-circle-o-notch:before { content: @fa-var-circle-o-notch; } +.@{fa-css-prefix}-ra:before, +.@{fa-css-prefix}-resistance:before, +.@{fa-css-prefix}-rebel:before { content: @fa-var-rebel; } +.@{fa-css-prefix}-ge:before, +.@{fa-css-prefix}-empire:before { content: @fa-var-empire; } +.@{fa-css-prefix}-git-square:before { content: @fa-var-git-square; } +.@{fa-css-prefix}-git:before { content: @fa-var-git; } +.@{fa-css-prefix}-y-combinator-square:before, +.@{fa-css-prefix}-yc-square:before, +.@{fa-css-prefix}-hacker-news:before { content: @fa-var-hacker-news; } +.@{fa-css-prefix}-tencent-weibo:before { content: @fa-var-tencent-weibo; } +.@{fa-css-prefix}-qq:before { content: @fa-var-qq; } +.@{fa-css-prefix}-wechat:before, +.@{fa-css-prefix}-weixin:before { content: @fa-var-weixin; } +.@{fa-css-prefix}-send:before, +.@{fa-css-prefix}-paper-plane:before { content: @fa-var-paper-plane; } +.@{fa-css-prefix}-send-o:before, +.@{fa-css-prefix}-paper-plane-o:before { content: @fa-var-paper-plane-o; } +.@{fa-css-prefix}-history:before { content: @fa-var-history; } +.@{fa-css-prefix}-circle-thin:before { content: @fa-var-circle-thin; } +.@{fa-css-prefix}-header:before { content: @fa-var-header; } +.@{fa-css-prefix}-paragraph:before { content: @fa-var-paragraph; } +.@{fa-css-prefix}-sliders:before { content: @fa-var-sliders; } +.@{fa-css-prefix}-share-alt:before { content: @fa-var-share-alt; } +.@{fa-css-prefix}-share-alt-square:before { content: @fa-var-share-alt-square; } +.@{fa-css-prefix}-bomb:before { content: @fa-var-bomb; } +.@{fa-css-prefix}-soccer-ball-o:before, +.@{fa-css-prefix}-futbol-o:before { content: @fa-var-futbol-o; } +.@{fa-css-prefix}-tty:before { content: @fa-var-tty; } +.@{fa-css-prefix}-binoculars:before { content: @fa-var-binoculars; } +.@{fa-css-prefix}-plug:before { content: @fa-var-plug; } +.@{fa-css-prefix}-slideshare:before { content: @fa-var-slideshare; } +.@{fa-css-prefix}-twitch:before { content: @fa-var-twitch; } +.@{fa-css-prefix}-yelp:before { content: @fa-var-yelp; } +.@{fa-css-prefix}-newspaper-o:before { content: @fa-var-newspaper-o; } +.@{fa-css-prefix}-wifi:before { content: @fa-var-wifi; } +.@{fa-css-prefix}-calculator:before { content: @fa-var-calculator; } +.@{fa-css-prefix}-paypal:before { content: @fa-var-paypal; } +.@{fa-css-prefix}-google-wallet:before { content: @fa-var-google-wallet; } +.@{fa-css-prefix}-cc-visa:before { content: @fa-var-cc-visa; } +.@{fa-css-prefix}-cc-mastercard:before { content: @fa-var-cc-mastercard; } +.@{fa-css-prefix}-cc-discover:before { content: @fa-var-cc-discover; } +.@{fa-css-prefix}-cc-amex:before { content: @fa-var-cc-amex; } +.@{fa-css-prefix}-cc-paypal:before { content: @fa-var-cc-paypal; } +.@{fa-css-prefix}-cc-stripe:before { content: @fa-var-cc-stripe; } +.@{fa-css-prefix}-bell-slash:before { content: @fa-var-bell-slash; } +.@{fa-css-prefix}-bell-slash-o:before { content: @fa-var-bell-slash-o; } +.@{fa-css-prefix}-trash:before { content: @fa-var-trash; } +.@{fa-css-prefix}-copyright:before { content: @fa-var-copyright; } +.@{fa-css-prefix}-at:before { content: @fa-var-at; } +.@{fa-css-prefix}-eyedropper:before { content: @fa-var-eyedropper; } +.@{fa-css-prefix}-paint-brush:before { content: @fa-var-paint-brush; } +.@{fa-css-prefix}-birthday-cake:before { content: @fa-var-birthday-cake; } +.@{fa-css-prefix}-area-chart:before { content: @fa-var-area-chart; } +.@{fa-css-prefix}-pie-chart:before { content: @fa-var-pie-chart; } +.@{fa-css-prefix}-line-chart:before { content: @fa-var-line-chart; } +.@{fa-css-prefix}-lastfm:before { content: @fa-var-lastfm; } +.@{fa-css-prefix}-lastfm-square:before { content: @fa-var-lastfm-square; } +.@{fa-css-prefix}-toggle-off:before { content: @fa-var-toggle-off; } +.@{fa-css-prefix}-toggle-on:before { content: @fa-var-toggle-on; } +.@{fa-css-prefix}-bicycle:before { content: @fa-var-bicycle; } +.@{fa-css-prefix}-bus:before { content: @fa-var-bus; } +.@{fa-css-prefix}-ioxhost:before { content: @fa-var-ioxhost; } +.@{fa-css-prefix}-angellist:before { content: @fa-var-angellist; } +.@{fa-css-prefix}-cc:before { content: @fa-var-cc; } +.@{fa-css-prefix}-shekel:before, +.@{fa-css-prefix}-sheqel:before, +.@{fa-css-prefix}-ils:before { content: @fa-var-ils; } +.@{fa-css-prefix}-meanpath:before { content: @fa-var-meanpath; } +.@{fa-css-prefix}-buysellads:before { content: @fa-var-buysellads; } +.@{fa-css-prefix}-connectdevelop:before { content: @fa-var-connectdevelop; } +.@{fa-css-prefix}-dashcube:before { content: @fa-var-dashcube; } +.@{fa-css-prefix}-forumbee:before { content: @fa-var-forumbee; } +.@{fa-css-prefix}-leanpub:before { content: @fa-var-leanpub; } +.@{fa-css-prefix}-sellsy:before { content: @fa-var-sellsy; } +.@{fa-css-prefix}-shirtsinbulk:before { content: @fa-var-shirtsinbulk; } +.@{fa-css-prefix}-simplybuilt:before { content: @fa-var-simplybuilt; } +.@{fa-css-prefix}-skyatlas:before { content: @fa-var-skyatlas; } +.@{fa-css-prefix}-cart-plus:before { content: @fa-var-cart-plus; } +.@{fa-css-prefix}-cart-arrow-down:before { content: @fa-var-cart-arrow-down; } +.@{fa-css-prefix}-diamond:before { content: @fa-var-diamond; } +.@{fa-css-prefix}-ship:before { content: @fa-var-ship; } +.@{fa-css-prefix}-user-secret:before { content: @fa-var-user-secret; } +.@{fa-css-prefix}-motorcycle:before { content: @fa-var-motorcycle; } +.@{fa-css-prefix}-street-view:before { content: @fa-var-street-view; } +.@{fa-css-prefix}-heartbeat:before { content: @fa-var-heartbeat; } +.@{fa-css-prefix}-venus:before { content: @fa-var-venus; } +.@{fa-css-prefix}-mars:before { content: @fa-var-mars; } +.@{fa-css-prefix}-mercury:before { content: @fa-var-mercury; } +.@{fa-css-prefix}-intersex:before, +.@{fa-css-prefix}-transgender:before { content: @fa-var-transgender; } +.@{fa-css-prefix}-transgender-alt:before { content: @fa-var-transgender-alt; } +.@{fa-css-prefix}-venus-double:before { content: @fa-var-venus-double; } +.@{fa-css-prefix}-mars-double:before { content: @fa-var-mars-double; } +.@{fa-css-prefix}-venus-mars:before { content: @fa-var-venus-mars; } +.@{fa-css-prefix}-mars-stroke:before { content: @fa-var-mars-stroke; } +.@{fa-css-prefix}-mars-stroke-v:before { content: @fa-var-mars-stroke-v; } +.@{fa-css-prefix}-mars-stroke-h:before { content: @fa-var-mars-stroke-h; } +.@{fa-css-prefix}-neuter:before { content: @fa-var-neuter; } +.@{fa-css-prefix}-genderless:before { content: @fa-var-genderless; } +.@{fa-css-prefix}-facebook-official:before { content: @fa-var-facebook-official; } +.@{fa-css-prefix}-pinterest-p:before { content: @fa-var-pinterest-p; } +.@{fa-css-prefix}-whatsapp:before { content: @fa-var-whatsapp; } +.@{fa-css-prefix}-server:before { content: @fa-var-server; } +.@{fa-css-prefix}-user-plus:before { content: @fa-var-user-plus; } +.@{fa-css-prefix}-user-times:before { content: @fa-var-user-times; } +.@{fa-css-prefix}-hotel:before, +.@{fa-css-prefix}-bed:before { content: @fa-var-bed; } +.@{fa-css-prefix}-viacoin:before { content: @fa-var-viacoin; } +.@{fa-css-prefix}-train:before { content: @fa-var-train; } +.@{fa-css-prefix}-subway:before { content: @fa-var-subway; } +.@{fa-css-prefix}-medium:before { content: @fa-var-medium; } +.@{fa-css-prefix}-yc:before, +.@{fa-css-prefix}-y-combinator:before { content: @fa-var-y-combinator; } +.@{fa-css-prefix}-optin-monster:before { content: @fa-var-optin-monster; } +.@{fa-css-prefix}-opencart:before { content: @fa-var-opencart; } +.@{fa-css-prefix}-expeditedssl:before { content: @fa-var-expeditedssl; } +.@{fa-css-prefix}-battery-4:before, +.@{fa-css-prefix}-battery:before, +.@{fa-css-prefix}-battery-full:before { content: @fa-var-battery-full; } +.@{fa-css-prefix}-battery-3:before, +.@{fa-css-prefix}-battery-three-quarters:before { content: @fa-var-battery-three-quarters; } +.@{fa-css-prefix}-battery-2:before, +.@{fa-css-prefix}-battery-half:before { content: @fa-var-battery-half; } +.@{fa-css-prefix}-battery-1:before, +.@{fa-css-prefix}-battery-quarter:before { content: @fa-var-battery-quarter; } +.@{fa-css-prefix}-battery-0:before, +.@{fa-css-prefix}-battery-empty:before { content: @fa-var-battery-empty; } +.@{fa-css-prefix}-mouse-pointer:before { content: @fa-var-mouse-pointer; } +.@{fa-css-prefix}-i-cursor:before { content: @fa-var-i-cursor; } +.@{fa-css-prefix}-object-group:before { content: @fa-var-object-group; } +.@{fa-css-prefix}-object-ungroup:before { content: @fa-var-object-ungroup; } +.@{fa-css-prefix}-sticky-note:before { content: @fa-var-sticky-note; } +.@{fa-css-prefix}-sticky-note-o:before { content: @fa-var-sticky-note-o; } +.@{fa-css-prefix}-cc-jcb:before { content: @fa-var-cc-jcb; } +.@{fa-css-prefix}-cc-diners-club:before { content: @fa-var-cc-diners-club; } +.@{fa-css-prefix}-clone:before { content: @fa-var-clone; } +.@{fa-css-prefix}-balance-scale:before { content: @fa-var-balance-scale; } +.@{fa-css-prefix}-hourglass-o:before { content: @fa-var-hourglass-o; } +.@{fa-css-prefix}-hourglass-1:before, +.@{fa-css-prefix}-hourglass-start:before { content: @fa-var-hourglass-start; } +.@{fa-css-prefix}-hourglass-2:before, +.@{fa-css-prefix}-hourglass-half:before { content: @fa-var-hourglass-half; } +.@{fa-css-prefix}-hourglass-3:before, +.@{fa-css-prefix}-hourglass-end:before { content: @fa-var-hourglass-end; } +.@{fa-css-prefix}-hourglass:before { content: @fa-var-hourglass; } +.@{fa-css-prefix}-hand-grab-o:before, +.@{fa-css-prefix}-hand-rock-o:before { content: @fa-var-hand-rock-o; } +.@{fa-css-prefix}-hand-stop-o:before, +.@{fa-css-prefix}-hand-paper-o:before { content: @fa-var-hand-paper-o; } +.@{fa-css-prefix}-hand-scissors-o:before { content: @fa-var-hand-scissors-o; } +.@{fa-css-prefix}-hand-lizard-o:before { content: @fa-var-hand-lizard-o; } +.@{fa-css-prefix}-hand-spock-o:before { content: @fa-var-hand-spock-o; } +.@{fa-css-prefix}-hand-pointer-o:before { content: @fa-var-hand-pointer-o; } +.@{fa-css-prefix}-hand-peace-o:before { content: @fa-var-hand-peace-o; } +.@{fa-css-prefix}-trademark:before { content: @fa-var-trademark; } +.@{fa-css-prefix}-registered:before { content: @fa-var-registered; } +.@{fa-css-prefix}-creative-commons:before { content: @fa-var-creative-commons; } +.@{fa-css-prefix}-gg:before { content: @fa-var-gg; } +.@{fa-css-prefix}-gg-circle:before { content: @fa-var-gg-circle; } +.@{fa-css-prefix}-tripadvisor:before { content: @fa-var-tripadvisor; } +.@{fa-css-prefix}-odnoklassniki:before { content: @fa-var-odnoklassniki; } +.@{fa-css-prefix}-odnoklassniki-square:before { content: @fa-var-odnoklassniki-square; } +.@{fa-css-prefix}-get-pocket:before { content: @fa-var-get-pocket; } +.@{fa-css-prefix}-wikipedia-w:before { content: @fa-var-wikipedia-w; } +.@{fa-css-prefix}-safari:before { content: @fa-var-safari; } +.@{fa-css-prefix}-chrome:before { content: @fa-var-chrome; } +.@{fa-css-prefix}-firefox:before { content: @fa-var-firefox; } +.@{fa-css-prefix}-opera:before { content: @fa-var-opera; } +.@{fa-css-prefix}-internet-explorer:before { content: @fa-var-internet-explorer; } +.@{fa-css-prefix}-tv:before, +.@{fa-css-prefix}-television:before { content: @fa-var-television; } +.@{fa-css-prefix}-contao:before { content: @fa-var-contao; } +.@{fa-css-prefix}-500px:before { content: @fa-var-500px; } +.@{fa-css-prefix}-amazon:before { content: @fa-var-amazon; } +.@{fa-css-prefix}-calendar-plus-o:before { content: @fa-var-calendar-plus-o; } +.@{fa-css-prefix}-calendar-minus-o:before { content: @fa-var-calendar-minus-o; } +.@{fa-css-prefix}-calendar-times-o:before { content: @fa-var-calendar-times-o; } +.@{fa-css-prefix}-calendar-check-o:before { content: @fa-var-calendar-check-o; } +.@{fa-css-prefix}-industry:before { content: @fa-var-industry; } +.@{fa-css-prefix}-map-pin:before { content: @fa-var-map-pin; } +.@{fa-css-prefix}-map-signs:before { content: @fa-var-map-signs; } +.@{fa-css-prefix}-map-o:before { content: @fa-var-map-o; } +.@{fa-css-prefix}-map:before { content: @fa-var-map; } +.@{fa-css-prefix}-commenting:before { content: @fa-var-commenting; } +.@{fa-css-prefix}-commenting-o:before { content: @fa-var-commenting-o; } +.@{fa-css-prefix}-houzz:before { content: @fa-var-houzz; } +.@{fa-css-prefix}-vimeo:before { content: @fa-var-vimeo; } +.@{fa-css-prefix}-black-tie:before { content: @fa-var-black-tie; } +.@{fa-css-prefix}-fonticons:before { content: @fa-var-fonticons; } +.@{fa-css-prefix}-reddit-alien:before { content: @fa-var-reddit-alien; } +.@{fa-css-prefix}-edge:before { content: @fa-var-edge; } +.@{fa-css-prefix}-credit-card-alt:before { content: @fa-var-credit-card-alt; } +.@{fa-css-prefix}-codiepie:before { content: @fa-var-codiepie; } +.@{fa-css-prefix}-modx:before { content: @fa-var-modx; } +.@{fa-css-prefix}-fort-awesome:before { content: @fa-var-fort-awesome; } +.@{fa-css-prefix}-usb:before { content: @fa-var-usb; } +.@{fa-css-prefix}-product-hunt:before { content: @fa-var-product-hunt; } +.@{fa-css-prefix}-mixcloud:before { content: @fa-var-mixcloud; } +.@{fa-css-prefix}-scribd:before { content: @fa-var-scribd; } +.@{fa-css-prefix}-pause-circle:before { content: @fa-var-pause-circle; } +.@{fa-css-prefix}-pause-circle-o:before { content: @fa-var-pause-circle-o; } +.@{fa-css-prefix}-stop-circle:before { content: @fa-var-stop-circle; } +.@{fa-css-prefix}-stop-circle-o:before { content: @fa-var-stop-circle-o; } +.@{fa-css-prefix}-shopping-bag:before { content: @fa-var-shopping-bag; } +.@{fa-css-prefix}-shopping-basket:before { content: @fa-var-shopping-basket; } +.@{fa-css-prefix}-hashtag:before { content: @fa-var-hashtag; } +.@{fa-css-prefix}-bluetooth:before { content: @fa-var-bluetooth; } +.@{fa-css-prefix}-bluetooth-b:before { content: @fa-var-bluetooth-b; } +.@{fa-css-prefix}-percent:before { content: @fa-var-percent; } +.@{fa-css-prefix}-gitlab:before { content: @fa-var-gitlab; } +.@{fa-css-prefix}-wpbeginner:before { content: @fa-var-wpbeginner; } +.@{fa-css-prefix}-wpforms:before { content: @fa-var-wpforms; } +.@{fa-css-prefix}-envira:before { content: @fa-var-envira; } +.@{fa-css-prefix}-universal-access:before { content: @fa-var-universal-access; } +.@{fa-css-prefix}-wheelchair-alt:before { content: @fa-var-wheelchair-alt; } +.@{fa-css-prefix}-question-circle-o:before { content: @fa-var-question-circle-o; } +.@{fa-css-prefix}-blind:before { content: @fa-var-blind; } +.@{fa-css-prefix}-audio-description:before { content: @fa-var-audio-description; } +.@{fa-css-prefix}-volume-control-phone:before { content: @fa-var-volume-control-phone; } +.@{fa-css-prefix}-braille:before { content: @fa-var-braille; } +.@{fa-css-prefix}-assistive-listening-systems:before { content: @fa-var-assistive-listening-systems; } +.@{fa-css-prefix}-asl-interpreting:before, +.@{fa-css-prefix}-american-sign-language-interpreting:before { content: @fa-var-american-sign-language-interpreting; } +.@{fa-css-prefix}-deafness:before, +.@{fa-css-prefix}-hard-of-hearing:before, +.@{fa-css-prefix}-deaf:before { content: @fa-var-deaf; } +.@{fa-css-prefix}-glide:before { content: @fa-var-glide; } +.@{fa-css-prefix}-glide-g:before { content: @fa-var-glide-g; } +.@{fa-css-prefix}-signing:before, +.@{fa-css-prefix}-sign-language:before { content: @fa-var-sign-language; } +.@{fa-css-prefix}-low-vision:before { content: @fa-var-low-vision; } +.@{fa-css-prefix}-viadeo:before { content: @fa-var-viadeo; } +.@{fa-css-prefix}-viadeo-square:before { content: @fa-var-viadeo-square; } +.@{fa-css-prefix}-snapchat:before { content: @fa-var-snapchat; } +.@{fa-css-prefix}-snapchat-ghost:before { content: @fa-var-snapchat-ghost; } +.@{fa-css-prefix}-snapchat-square:before { content: @fa-var-snapchat-square; } +.@{fa-css-prefix}-pied-piper:before { content: @fa-var-pied-piper; } +.@{fa-css-prefix}-first-order:before { content: @fa-var-first-order; } +.@{fa-css-prefix}-yoast:before { content: @fa-var-yoast; } +.@{fa-css-prefix}-themeisle:before { content: @fa-var-themeisle; } +.@{fa-css-prefix}-google-plus-circle:before, +.@{fa-css-prefix}-google-plus-official:before { content: @fa-var-google-plus-official; } +.@{fa-css-prefix}-fa:before, +.@{fa-css-prefix}-font-awesome:before { content: @fa-var-font-awesome; } +.@{fa-css-prefix}-handshake-o:before { content: @fa-var-handshake-o; } +.@{fa-css-prefix}-envelope-open:before { content: @fa-var-envelope-open; } +.@{fa-css-prefix}-envelope-open-o:before { content: @fa-var-envelope-open-o; } +.@{fa-css-prefix}-linode:before { content: @fa-var-linode; } +.@{fa-css-prefix}-address-book:before { content: @fa-var-address-book; } +.@{fa-css-prefix}-address-book-o:before { content: @fa-var-address-book-o; } +.@{fa-css-prefix}-vcard:before, +.@{fa-css-prefix}-address-card:before { content: @fa-var-address-card; } +.@{fa-css-prefix}-vcard-o:before, +.@{fa-css-prefix}-address-card-o:before { content: @fa-var-address-card-o; } +.@{fa-css-prefix}-user-circle:before { content: @fa-var-user-circle; } +.@{fa-css-prefix}-user-circle-o:before { content: @fa-var-user-circle-o; } +.@{fa-css-prefix}-user-o:before { content: @fa-var-user-o; } +.@{fa-css-prefix}-id-badge:before { content: @fa-var-id-badge; } +.@{fa-css-prefix}-drivers-license:before, +.@{fa-css-prefix}-id-card:before { content: @fa-var-id-card; } +.@{fa-css-prefix}-drivers-license-o:before, +.@{fa-css-prefix}-id-card-o:before { content: @fa-var-id-card-o; } +.@{fa-css-prefix}-quora:before { content: @fa-var-quora; } +.@{fa-css-prefix}-free-code-camp:before { content: @fa-var-free-code-camp; } +.@{fa-css-prefix}-telegram:before { content: @fa-var-telegram; } +.@{fa-css-prefix}-thermometer-4:before, +.@{fa-css-prefix}-thermometer:before, +.@{fa-css-prefix}-thermometer-full:before { content: @fa-var-thermometer-full; } +.@{fa-css-prefix}-thermometer-3:before, +.@{fa-css-prefix}-thermometer-three-quarters:before { content: @fa-var-thermometer-three-quarters; } +.@{fa-css-prefix}-thermometer-2:before, +.@{fa-css-prefix}-thermometer-half:before { content: @fa-var-thermometer-half; } +.@{fa-css-prefix}-thermometer-1:before, +.@{fa-css-prefix}-thermometer-quarter:before { content: @fa-var-thermometer-quarter; } +.@{fa-css-prefix}-thermometer-0:before, +.@{fa-css-prefix}-thermometer-empty:before { content: @fa-var-thermometer-empty; } +.@{fa-css-prefix}-shower:before { content: @fa-var-shower; } +.@{fa-css-prefix}-bathtub:before, +.@{fa-css-prefix}-s15:before, +.@{fa-css-prefix}-bath:before { content: @fa-var-bath; } +.@{fa-css-prefix}-podcast:before { content: @fa-var-podcast; } +.@{fa-css-prefix}-window-maximize:before { content: @fa-var-window-maximize; } +.@{fa-css-prefix}-window-minimize:before { content: @fa-var-window-minimize; } +.@{fa-css-prefix}-window-restore:before { content: @fa-var-window-restore; } +.@{fa-css-prefix}-times-rectangle:before, +.@{fa-css-prefix}-window-close:before { content: @fa-var-window-close; } +.@{fa-css-prefix}-times-rectangle-o:before, +.@{fa-css-prefix}-window-close-o:before { content: @fa-var-window-close-o; } +.@{fa-css-prefix}-bandcamp:before { content: @fa-var-bandcamp; } +.@{fa-css-prefix}-grav:before { content: @fa-var-grav; } +.@{fa-css-prefix}-etsy:before { content: @fa-var-etsy; } +.@{fa-css-prefix}-imdb:before { content: @fa-var-imdb; } +.@{fa-css-prefix}-ravelry:before { content: @fa-var-ravelry; } +.@{fa-css-prefix}-eercast:before { content: @fa-var-eercast; } +.@{fa-css-prefix}-microchip:before { content: @fa-var-microchip; } +.@{fa-css-prefix}-snowflake-o:before { content: @fa-var-snowflake-o; } +.@{fa-css-prefix}-superpowers:before { content: @fa-var-superpowers; } +.@{fa-css-prefix}-wpexplorer:before { content: @fa-var-wpexplorer; } +.@{fa-css-prefix}-meetup:before { content: @fa-var-meetup; } diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/larger.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/larger.less new file mode 100644 index 00000000..c9d64677 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/larger.less @@ -0,0 +1,13 @@ +// Icon Sizes +// ------------------------- + +/* makes the font 33% larger relative to the icon container */ +.@{fa-css-prefix}-lg { + font-size: (4em / 3); + line-height: (3em / 4); + vertical-align: -15%; +} +.@{fa-css-prefix}-2x { font-size: 2em; } +.@{fa-css-prefix}-3x { font-size: 3em; } +.@{fa-css-prefix}-4x { font-size: 4em; } +.@{fa-css-prefix}-5x { font-size: 5em; } diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/list.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/list.less new file mode 100644 index 00000000..0b440382 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/list.less @@ -0,0 +1,19 @@ +// List Icons +// ------------------------- + +.@{fa-css-prefix}-ul { + padding-left: 0; + margin-left: @fa-li-width; + list-style-type: none; + > li { position: relative; } +} +.@{fa-css-prefix}-li { + position: absolute; + left: -@fa-li-width; + width: @fa-li-width; + top: (2em / 14); + text-align: center; + &.@{fa-css-prefix}-lg { + left: (-@fa-li-width + (4em / 14)); + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/mixins.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/mixins.less new file mode 100644 index 00000000..beef231d --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/mixins.less @@ -0,0 +1,60 @@ +// Mixins +// -------------------------- + +.fa-icon() { + display: inline-block; + font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration + font-size: inherit; // can't have font-size inherit on line above, so need to override + text-rendering: auto; // optimizelegibility throws things off #1094 + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + +} + +.fa-icon-rotate(@degrees, @rotation) { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; + -webkit-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); + transform: rotate(@degrees); +} + +.fa-icon-flip(@horiz, @vert, @rotation) { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; + -webkit-transform: scale(@horiz, @vert); + -ms-transform: scale(@horiz, @vert); + transform: scale(@horiz, @vert); +} + + +// Only display content to screen readers. A la Bootstrap 4. +// +// See: http://a11yproject.com/posts/how-to-hide-content/ + +.sr-only() { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// +// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// +// Credit: HTML5 Boilerplate + +.sr-only-focusable() { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/path.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/path.less new file mode 100644 index 00000000..835be41f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/path.less @@ -0,0 +1,15 @@ +/* FONT PATH + * -------------------------- */ + +@font-face { + font-family: 'FontAwesome'; + src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); + src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), + url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), + url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), + url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), + url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); + // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts + font-weight: normal; + font-style: normal; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/rotated-flipped.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/rotated-flipped.less new file mode 100644 index 00000000..f6ba8147 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/rotated-flipped.less @@ -0,0 +1,20 @@ +// Rotated & Flipped Icons +// ------------------------- + +.@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } +.@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } +.@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } + +.@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } +.@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } + +// Hook for IE8-9 +// ------------------------- + +:root .@{fa-css-prefix}-rotate-90, +:root .@{fa-css-prefix}-rotate-180, +:root .@{fa-css-prefix}-rotate-270, +:root .@{fa-css-prefix}-flip-horizontal, +:root .@{fa-css-prefix}-flip-vertical { + filter: none; +} diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/screen-reader.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/screen-reader.less new file mode 100644 index 00000000..11c18819 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/screen-reader.less @@ -0,0 +1,5 @@ +// Screen Readers +// ------------------------- + +.sr-only { .sr-only(); } +.sr-only-focusable { .sr-only-focusable(); } diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/stacked.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/stacked.less new file mode 100644 index 00000000..fc53fb0e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/stacked.less @@ -0,0 +1,20 @@ +// Stacked Icons +// ------------------------- + +.@{fa-css-prefix}-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.@{fa-css-prefix}-stack-1x { line-height: inherit; } +.@{fa-css-prefix}-stack-2x { font-size: 2em; } +.@{fa-css-prefix}-inverse { color: @fa-inverse; } diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/variables.less b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/variables.less new file mode 100644 index 00000000..7ddbbc01 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/font-awesome/less/variables.less @@ -0,0 +1,800 @@ +// Variables +// -------------------------- + +@fa-font-path: "../fonts"; +@fa-font-size-base: 14px; +@fa-line-height-base: 1; +//@fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.7.0/fonts"; // for referencing Bootstrap CDN font files directly +@fa-css-prefix: fa; +@fa-version: "4.7.0"; +@fa-border-color: #eee; +@fa-inverse: #fff; +@fa-li-width: (30em / 14); + +@fa-var-500px: "\f26e"; +@fa-var-address-book: "\f2b9"; +@fa-var-address-book-o: "\f2ba"; +@fa-var-address-card: "\f2bb"; +@fa-var-address-card-o: "\f2bc"; +@fa-var-adjust: "\f042"; +@fa-var-adn: "\f170"; +@fa-var-align-center: "\f037"; +@fa-var-align-justify: "\f039"; +@fa-var-align-left: "\f036"; +@fa-var-align-right: "\f038"; +@fa-var-amazon: "\f270"; +@fa-var-ambulance: "\f0f9"; +@fa-var-american-sign-language-interpreting: "\f2a3"; +@fa-var-anchor: "\f13d"; +@fa-var-android: "\f17b"; +@fa-var-angellist: "\f209"; +@fa-var-angle-double-down: "\f103"; +@fa-var-angle-double-left: "\f100"; +@fa-var-angle-double-right: "\f101"; +@fa-var-angle-double-up: "\f102"; +@fa-var-angle-down: "\f107"; +@fa-var-angle-left: "\f104"; +@fa-var-angle-right: "\f105"; +@fa-var-angle-up: "\f106"; +@fa-var-apple: "\f179"; +@fa-var-archive: "\f187"; +@fa-var-area-chart: "\f1fe"; +@fa-var-arrow-circle-down: "\f0ab"; +@fa-var-arrow-circle-left: "\f0a8"; +@fa-var-arrow-circle-o-down: "\f01a"; +@fa-var-arrow-circle-o-left: "\f190"; +@fa-var-arrow-circle-o-right: "\f18e"; +@fa-var-arrow-circle-o-up: "\f01b"; +@fa-var-arrow-circle-right: "\f0a9"; +@fa-var-arrow-circle-up: "\f0aa"; +@fa-var-arrow-down: "\f063"; +@fa-var-arrow-left: "\f060"; +@fa-var-arrow-right: "\f061"; +@fa-var-arrow-up: "\f062"; +@fa-var-arrows: "\f047"; +@fa-var-arrows-alt: "\f0b2"; +@fa-var-arrows-h: "\f07e"; +@fa-var-arrows-v: "\f07d"; +@fa-var-asl-interpreting: "\f2a3"; +@fa-var-assistive-listening-systems: "\f2a2"; +@fa-var-asterisk: "\f069"; +@fa-var-at: "\f1fa"; +@fa-var-audio-description: "\f29e"; +@fa-var-automobile: "\f1b9"; +@fa-var-backward: "\f04a"; +@fa-var-balance-scale: "\f24e"; +@fa-var-ban: "\f05e"; +@fa-var-bandcamp: "\f2d5"; +@fa-var-bank: "\f19c"; +@fa-var-bar-chart: "\f080"; +@fa-var-bar-chart-o: "\f080"; +@fa-var-barcode: "\f02a"; +@fa-var-bars: "\f0c9"; +@fa-var-bath: "\f2cd"; +@fa-var-bathtub: "\f2cd"; +@fa-var-battery: "\f240"; +@fa-var-battery-0: "\f244"; +@fa-var-battery-1: "\f243"; +@fa-var-battery-2: "\f242"; +@fa-var-battery-3: "\f241"; +@fa-var-battery-4: "\f240"; +@fa-var-battery-empty: "\f244"; +@fa-var-battery-full: "\f240"; +@fa-var-battery-half: "\f242"; +@fa-var-battery-quarter: "\f243"; +@fa-var-battery-three-quarters: "\f241"; +@fa-var-bed: "\f236"; +@fa-var-beer: "\f0fc"; +@fa-var-behance: "\f1b4"; +@fa-var-behance-square: "\f1b5"; +@fa-var-bell: "\f0f3"; +@fa-var-bell-o: "\f0a2"; +@fa-var-bell-slash: "\f1f6"; +@fa-var-bell-slash-o: "\f1f7"; +@fa-var-bicycle: "\f206"; +@fa-var-binoculars: "\f1e5"; +@fa-var-birthday-cake: "\f1fd"; +@fa-var-bitbucket: "\f171"; +@fa-var-bitbucket-square: "\f172"; +@fa-var-bitcoin: "\f15a"; +@fa-var-black-tie: "\f27e"; +@fa-var-blind: "\f29d"; +@fa-var-bluetooth: "\f293"; +@fa-var-bluetooth-b: "\f294"; +@fa-var-bold: "\f032"; +@fa-var-bolt: "\f0e7"; +@fa-var-bomb: "\f1e2"; +@fa-var-book: "\f02d"; +@fa-var-bookmark: "\f02e"; +@fa-var-bookmark-o: "\f097"; +@fa-var-braille: "\f2a1"; +@fa-var-briefcase: "\f0b1"; +@fa-var-btc: "\f15a"; +@fa-var-bug: "\f188"; +@fa-var-building: "\f1ad"; +@fa-var-building-o: "\f0f7"; +@fa-var-bullhorn: "\f0a1"; +@fa-var-bullseye: "\f140"; +@fa-var-bus: "\f207"; +@fa-var-buysellads: "\f20d"; +@fa-var-cab: "\f1ba"; +@fa-var-calculator: "\f1ec"; +@fa-var-calendar: "\f073"; +@fa-var-calendar-check-o: "\f274"; +@fa-var-calendar-minus-o: "\f272"; +@fa-var-calendar-o: "\f133"; +@fa-var-calendar-plus-o: "\f271"; +@fa-var-calendar-times-o: "\f273"; +@fa-var-camera: "\f030"; +@fa-var-camera-retro: "\f083"; +@fa-var-car: "\f1b9"; +@fa-var-caret-down: "\f0d7"; +@fa-var-caret-left: "\f0d9"; +@fa-var-caret-right: "\f0da"; +@fa-var-caret-square-o-down: "\f150"; +@fa-var-caret-square-o-left: "\f191"; +@fa-var-caret-square-o-right: "\f152"; +@fa-var-caret-square-o-up: "\f151"; +@fa-var-caret-up: "\f0d8"; +@fa-var-cart-arrow-down: "\f218"; +@fa-var-cart-plus: "\f217"; +@fa-var-cc: "\f20a"; +@fa-var-cc-amex: "\f1f3"; +@fa-var-cc-diners-club: "\f24c"; +@fa-var-cc-discover: "\f1f2"; +@fa-var-cc-jcb: "\f24b"; +@fa-var-cc-mastercard: "\f1f1"; +@fa-var-cc-paypal: "\f1f4"; +@fa-var-cc-stripe: "\f1f5"; +@fa-var-cc-visa: "\f1f0"; +@fa-var-certificate: "\f0a3"; +@fa-var-chain: "\f0c1"; +@fa-var-chain-broken: "\f127"; +@fa-var-check: "\f00c"; +@fa-var-check-circle: "\f058"; +@fa-var-check-circle-o: "\f05d"; +@fa-var-check-square: "\f14a"; +@fa-var-check-square-o: "\f046"; +@fa-var-chevron-circle-down: "\f13a"; +@fa-var-chevron-circle-left: "\f137"; +@fa-var-chevron-circle-right: "\f138"; +@fa-var-chevron-circle-up: "\f139"; +@fa-var-chevron-down: "\f078"; +@fa-var-chevron-left: "\f053"; +@fa-var-chevron-right: "\f054"; +@fa-var-chevron-up: "\f077"; +@fa-var-child: "\f1ae"; +@fa-var-chrome: "\f268"; +@fa-var-circle: "\f111"; +@fa-var-circle-o: "\f10c"; +@fa-var-circle-o-notch: "\f1ce"; +@fa-var-circle-thin: "\f1db"; +@fa-var-clipboard: "\f0ea"; +@fa-var-clock-o: "\f017"; +@fa-var-clone: "\f24d"; +@fa-var-close: "\f00d"; +@fa-var-cloud: "\f0c2"; +@fa-var-cloud-download: "\f0ed"; +@fa-var-cloud-upload: "\f0ee"; +@fa-var-cny: "\f157"; +@fa-var-code: "\f121"; +@fa-var-code-fork: "\f126"; +@fa-var-codepen: "\f1cb"; +@fa-var-codiepie: "\f284"; +@fa-var-coffee: "\f0f4"; +@fa-var-cog: "\f013"; +@fa-var-cogs: "\f085"; +@fa-var-columns: "\f0db"; +@fa-var-comment: "\f075"; +@fa-var-comment-o: "\f0e5"; +@fa-var-commenting: "\f27a"; +@fa-var-commenting-o: "\f27b"; +@fa-var-comments: "\f086"; +@fa-var-comments-o: "\f0e6"; +@fa-var-compass: "\f14e"; +@fa-var-compress: "\f066"; +@fa-var-connectdevelop: "\f20e"; +@fa-var-contao: "\f26d"; +@fa-var-copy: "\f0c5"; +@fa-var-copyright: "\f1f9"; +@fa-var-creative-commons: "\f25e"; +@fa-var-credit-card: "\f09d"; +@fa-var-credit-card-alt: "\f283"; +@fa-var-crop: "\f125"; +@fa-var-crosshairs: "\f05b"; +@fa-var-css3: "\f13c"; +@fa-var-cube: "\f1b2"; +@fa-var-cubes: "\f1b3"; +@fa-var-cut: "\f0c4"; +@fa-var-cutlery: "\f0f5"; +@fa-var-dashboard: "\f0e4"; +@fa-var-dashcube: "\f210"; +@fa-var-database: "\f1c0"; +@fa-var-deaf: "\f2a4"; +@fa-var-deafness: "\f2a4"; +@fa-var-dedent: "\f03b"; +@fa-var-delicious: "\f1a5"; +@fa-var-desktop: "\f108"; +@fa-var-deviantart: "\f1bd"; +@fa-var-diamond: "\f219"; +@fa-var-digg: "\f1a6"; +@fa-var-dollar: "\f155"; +@fa-var-dot-circle-o: "\f192"; +@fa-var-download: "\f019"; +@fa-var-dribbble: "\f17d"; +@fa-var-drivers-license: "\f2c2"; +@fa-var-drivers-license-o: "\f2c3"; +@fa-var-dropbox: "\f16b"; +@fa-var-drupal: "\f1a9"; +@fa-var-edge: "\f282"; +@fa-var-edit: "\f044"; +@fa-var-eercast: "\f2da"; +@fa-var-eject: "\f052"; +@fa-var-ellipsis-h: "\f141"; +@fa-var-ellipsis-v: "\f142"; +@fa-var-empire: "\f1d1"; +@fa-var-envelope: "\f0e0"; +@fa-var-envelope-o: "\f003"; +@fa-var-envelope-open: "\f2b6"; +@fa-var-envelope-open-o: "\f2b7"; +@fa-var-envelope-square: "\f199"; +@fa-var-envira: "\f299"; +@fa-var-eraser: "\f12d"; +@fa-var-etsy: "\f2d7"; +@fa-var-eur: "\f153"; +@fa-var-euro: "\f153"; +@fa-var-exchange: "\f0ec"; +@fa-var-exclamation: "\f12a"; +@fa-var-exclamation-circle: "\f06a"; +@fa-var-exclamation-triangle: "\f071"; +@fa-var-expand: "\f065"; +@fa-var-expeditedssl: "\f23e"; +@fa-var-external-link: "\f08e"; +@fa-var-external-link-square: "\f14c"; +@fa-var-eye: "\f06e"; +@fa-var-eye-slash: "\f070"; +@fa-var-eyedropper: "\f1fb"; +@fa-var-fa: "\f2b4"; +@fa-var-facebook: "\f09a"; +@fa-var-facebook-f: "\f09a"; +@fa-var-facebook-official: "\f230"; +@fa-var-facebook-square: "\f082"; +@fa-var-fast-backward: "\f049"; +@fa-var-fast-forward: "\f050"; +@fa-var-fax: "\f1ac"; +@fa-var-feed: "\f09e"; +@fa-var-female: "\f182"; +@fa-var-fighter-jet: "\f0fb"; +@fa-var-file: "\f15b"; +@fa-var-file-archive-o: "\f1c6"; +@fa-var-file-audio-o: "\f1c7"; +@fa-var-file-code-o: "\f1c9"; +@fa-var-file-excel-o: "\f1c3"; +@fa-var-file-image-o: "\f1c5"; +@fa-var-file-movie-o: "\f1c8"; +@fa-var-file-o: "\f016"; +@fa-var-file-pdf-o: "\f1c1"; +@fa-var-file-photo-o: "\f1c5"; +@fa-var-file-picture-o: "\f1c5"; +@fa-var-file-powerpoint-o: "\f1c4"; +@fa-var-file-sound-o: "\f1c7"; +@fa-var-file-text: "\f15c"; +@fa-var-file-text-o: "\f0f6"; +@fa-var-file-video-o: "\f1c8"; +@fa-var-file-word-o: "\f1c2"; +@fa-var-file-zip-o: "\f1c6"; +@fa-var-files-o: "\f0c5"; +@fa-var-film: "\f008"; +@fa-var-filter: "\f0b0"; +@fa-var-fire: "\f06d"; +@fa-var-fire-extinguisher: "\f134"; +@fa-var-firefox: "\f269"; +@fa-var-first-order: "\f2b0"; +@fa-var-flag: "\f024"; +@fa-var-flag-checkered: "\f11e"; +@fa-var-flag-o: "\f11d"; +@fa-var-flash: "\f0e7"; +@fa-var-flask: "\f0c3"; +@fa-var-flickr: "\f16e"; +@fa-var-floppy-o: "\f0c7"; +@fa-var-folder: "\f07b"; +@fa-var-folder-o: "\f114"; +@fa-var-folder-open: "\f07c"; +@fa-var-folder-open-o: "\f115"; +@fa-var-font: "\f031"; +@fa-var-font-awesome: "\f2b4"; +@fa-var-fonticons: "\f280"; +@fa-var-fort-awesome: "\f286"; +@fa-var-forumbee: "\f211"; +@fa-var-forward: "\f04e"; +@fa-var-foursquare: "\f180"; +@fa-var-free-code-camp: "\f2c5"; +@fa-var-frown-o: "\f119"; +@fa-var-futbol-o: "\f1e3"; +@fa-var-gamepad: "\f11b"; +@fa-var-gavel: "\f0e3"; +@fa-var-gbp: "\f154"; +@fa-var-ge: "\f1d1"; +@fa-var-gear: "\f013"; +@fa-var-gears: "\f085"; +@fa-var-genderless: "\f22d"; +@fa-var-get-pocket: "\f265"; +@fa-var-gg: "\f260"; +@fa-var-gg-circle: "\f261"; +@fa-var-gift: "\f06b"; +@fa-var-git: "\f1d3"; +@fa-var-git-square: "\f1d2"; +@fa-var-github: "\f09b"; +@fa-var-github-alt: "\f113"; +@fa-var-github-square: "\f092"; +@fa-var-gitlab: "\f296"; +@fa-var-gittip: "\f184"; +@fa-var-glass: "\f000"; +@fa-var-glide: "\f2a5"; +@fa-var-glide-g: "\f2a6"; +@fa-var-globe: "\f0ac"; +@fa-var-google: "\f1a0"; +@fa-var-google-plus: "\f0d5"; +@fa-var-google-plus-circle: "\f2b3"; +@fa-var-google-plus-official: "\f2b3"; +@fa-var-google-plus-square: "\f0d4"; +@fa-var-google-wallet: "\f1ee"; +@fa-var-graduation-cap: "\f19d"; +@fa-var-gratipay: "\f184"; +@fa-var-grav: "\f2d6"; +@fa-var-group: "\f0c0"; +@fa-var-h-square: "\f0fd"; +@fa-var-hacker-news: "\f1d4"; +@fa-var-hand-grab-o: "\f255"; +@fa-var-hand-lizard-o: "\f258"; +@fa-var-hand-o-down: "\f0a7"; +@fa-var-hand-o-left: "\f0a5"; +@fa-var-hand-o-right: "\f0a4"; +@fa-var-hand-o-up: "\f0a6"; +@fa-var-hand-paper-o: "\f256"; +@fa-var-hand-peace-o: "\f25b"; +@fa-var-hand-pointer-o: "\f25a"; +@fa-var-hand-rock-o: "\f255"; +@fa-var-hand-scissors-o: "\f257"; +@fa-var-hand-spock-o: "\f259"; +@fa-var-hand-stop-o: "\f256"; +@fa-var-handshake-o: "\f2b5"; +@fa-var-hard-of-hearing: "\f2a4"; +@fa-var-hashtag: "\f292"; +@fa-var-hdd-o: "\f0a0"; +@fa-var-header: "\f1dc"; +@fa-var-headphones: "\f025"; +@fa-var-heart: "\f004"; +@fa-var-heart-o: "\f08a"; +@fa-var-heartbeat: "\f21e"; +@fa-var-history: "\f1da"; +@fa-var-home: "\f015"; +@fa-var-hospital-o: "\f0f8"; +@fa-var-hotel: "\f236"; +@fa-var-hourglass: "\f254"; +@fa-var-hourglass-1: "\f251"; +@fa-var-hourglass-2: "\f252"; +@fa-var-hourglass-3: "\f253"; +@fa-var-hourglass-end: "\f253"; +@fa-var-hourglass-half: "\f252"; +@fa-var-hourglass-o: "\f250"; +@fa-var-hourglass-start: "\f251"; +@fa-var-houzz: "\f27c"; +@fa-var-html5: "\f13b"; +@fa-var-i-cursor: "\f246"; +@fa-var-id-badge: "\f2c1"; +@fa-var-id-card: "\f2c2"; +@fa-var-id-card-o: "\f2c3"; +@fa-var-ils: "\f20b"; +@fa-var-image: "\f03e"; +@fa-var-imdb: "\f2d8"; +@fa-var-inbox: "\f01c"; +@fa-var-indent: "\f03c"; +@fa-var-industry: "\f275"; +@fa-var-info: "\f129"; +@fa-var-info-circle: "\f05a"; +@fa-var-inr: "\f156"; +@fa-var-instagram: "\f16d"; +@fa-var-institution: "\f19c"; +@fa-var-internet-explorer: "\f26b"; +@fa-var-intersex: "\f224"; +@fa-var-ioxhost: "\f208"; +@fa-var-italic: "\f033"; +@fa-var-joomla: "\f1aa"; +@fa-var-jpy: "\f157"; +@fa-var-jsfiddle: "\f1cc"; +@fa-var-key: "\f084"; +@fa-var-keyboard-o: "\f11c"; +@fa-var-krw: "\f159"; +@fa-var-language: "\f1ab"; +@fa-var-laptop: "\f109"; +@fa-var-lastfm: "\f202"; +@fa-var-lastfm-square: "\f203"; +@fa-var-leaf: "\f06c"; +@fa-var-leanpub: "\f212"; +@fa-var-legal: "\f0e3"; +@fa-var-lemon-o: "\f094"; +@fa-var-level-down: "\f149"; +@fa-var-level-up: "\f148"; +@fa-var-life-bouy: "\f1cd"; +@fa-var-life-buoy: "\f1cd"; +@fa-var-life-ring: "\f1cd"; +@fa-var-life-saver: "\f1cd"; +@fa-var-lightbulb-o: "\f0eb"; +@fa-var-line-chart: "\f201"; +@fa-var-link: "\f0c1"; +@fa-var-linkedin: "\f0e1"; +@fa-var-linkedin-square: "\f08c"; +@fa-var-linode: "\f2b8"; +@fa-var-linux: "\f17c"; +@fa-var-list: "\f03a"; +@fa-var-list-alt: "\f022"; +@fa-var-list-ol: "\f0cb"; +@fa-var-list-ul: "\f0ca"; +@fa-var-location-arrow: "\f124"; +@fa-var-lock: "\f023"; +@fa-var-long-arrow-down: "\f175"; +@fa-var-long-arrow-left: "\f177"; +@fa-var-long-arrow-right: "\f178"; +@fa-var-long-arrow-up: "\f176"; +@fa-var-low-vision: "\f2a8"; +@fa-var-magic: "\f0d0"; +@fa-var-magnet: "\f076"; +@fa-var-mail-forward: "\f064"; +@fa-var-mail-reply: "\f112"; +@fa-var-mail-reply-all: "\f122"; +@fa-var-male: "\f183"; +@fa-var-map: "\f279"; +@fa-var-map-marker: "\f041"; +@fa-var-map-o: "\f278"; +@fa-var-map-pin: "\f276"; +@fa-var-map-signs: "\f277"; +@fa-var-mars: "\f222"; +@fa-var-mars-double: "\f227"; +@fa-var-mars-stroke: "\f229"; +@fa-var-mars-stroke-h: "\f22b"; +@fa-var-mars-stroke-v: "\f22a"; +@fa-var-maxcdn: "\f136"; +@fa-var-meanpath: "\f20c"; +@fa-var-medium: "\f23a"; +@fa-var-medkit: "\f0fa"; +@fa-var-meetup: "\f2e0"; +@fa-var-meh-o: "\f11a"; +@fa-var-mercury: "\f223"; +@fa-var-microchip: "\f2db"; +@fa-var-microphone: "\f130"; +@fa-var-microphone-slash: "\f131"; +@fa-var-minus: "\f068"; +@fa-var-minus-circle: "\f056"; +@fa-var-minus-square: "\f146"; +@fa-var-minus-square-o: "\f147"; +@fa-var-mixcloud: "\f289"; +@fa-var-mobile: "\f10b"; +@fa-var-mobile-phone: "\f10b"; +@fa-var-modx: "\f285"; +@fa-var-money: "\f0d6"; +@fa-var-moon-o: "\f186"; +@fa-var-mortar-board: "\f19d"; +@fa-var-motorcycle: "\f21c"; +@fa-var-mouse-pointer: "\f245"; +@fa-var-music: "\f001"; +@fa-var-navicon: "\f0c9"; +@fa-var-neuter: "\f22c"; +@fa-var-newspaper-o: "\f1ea"; +@fa-var-object-group: "\f247"; +@fa-var-object-ungroup: "\f248"; +@fa-var-odnoklassniki: "\f263"; +@fa-var-odnoklassniki-square: "\f264"; +@fa-var-opencart: "\f23d"; +@fa-var-openid: "\f19b"; +@fa-var-opera: "\f26a"; +@fa-var-optin-monster: "\f23c"; +@fa-var-outdent: "\f03b"; +@fa-var-pagelines: "\f18c"; +@fa-var-paint-brush: "\f1fc"; +@fa-var-paper-plane: "\f1d8"; +@fa-var-paper-plane-o: "\f1d9"; +@fa-var-paperclip: "\f0c6"; +@fa-var-paragraph: "\f1dd"; +@fa-var-paste: "\f0ea"; +@fa-var-pause: "\f04c"; +@fa-var-pause-circle: "\f28b"; +@fa-var-pause-circle-o: "\f28c"; +@fa-var-paw: "\f1b0"; +@fa-var-paypal: "\f1ed"; +@fa-var-pencil: "\f040"; +@fa-var-pencil-square: "\f14b"; +@fa-var-pencil-square-o: "\f044"; +@fa-var-percent: "\f295"; +@fa-var-phone: "\f095"; +@fa-var-phone-square: "\f098"; +@fa-var-photo: "\f03e"; +@fa-var-picture-o: "\f03e"; +@fa-var-pie-chart: "\f200"; +@fa-var-pied-piper: "\f2ae"; +@fa-var-pied-piper-alt: "\f1a8"; +@fa-var-pied-piper-pp: "\f1a7"; +@fa-var-pinterest: "\f0d2"; +@fa-var-pinterest-p: "\f231"; +@fa-var-pinterest-square: "\f0d3"; +@fa-var-plane: "\f072"; +@fa-var-play: "\f04b"; +@fa-var-play-circle: "\f144"; +@fa-var-play-circle-o: "\f01d"; +@fa-var-plug: "\f1e6"; +@fa-var-plus: "\f067"; +@fa-var-plus-circle: "\f055"; +@fa-var-plus-square: "\f0fe"; +@fa-var-plus-square-o: "\f196"; +@fa-var-podcast: "\f2ce"; +@fa-var-power-off: "\f011"; +@fa-var-print: "\f02f"; +@fa-var-product-hunt: "\f288"; +@fa-var-puzzle-piece: "\f12e"; +@fa-var-qq: "\f1d6"; +@fa-var-qrcode: "\f029"; +@fa-var-question: "\f128"; +@fa-var-question-circle: "\f059"; +@fa-var-question-circle-o: "\f29c"; +@fa-var-quora: "\f2c4"; +@fa-var-quote-left: "\f10d"; +@fa-var-quote-right: "\f10e"; +@fa-var-ra: "\f1d0"; +@fa-var-random: "\f074"; +@fa-var-ravelry: "\f2d9"; +@fa-var-rebel: "\f1d0"; +@fa-var-recycle: "\f1b8"; +@fa-var-reddit: "\f1a1"; +@fa-var-reddit-alien: "\f281"; +@fa-var-reddit-square: "\f1a2"; +@fa-var-refresh: "\f021"; +@fa-var-registered: "\f25d"; +@fa-var-remove: "\f00d"; +@fa-var-renren: "\f18b"; +@fa-var-reorder: "\f0c9"; +@fa-var-repeat: "\f01e"; +@fa-var-reply: "\f112"; +@fa-var-reply-all: "\f122"; +@fa-var-resistance: "\f1d0"; +@fa-var-retweet: "\f079"; +@fa-var-rmb: "\f157"; +@fa-var-road: "\f018"; +@fa-var-rocket: "\f135"; +@fa-var-rotate-left: "\f0e2"; +@fa-var-rotate-right: "\f01e"; +@fa-var-rouble: "\f158"; +@fa-var-rss: "\f09e"; +@fa-var-rss-square: "\f143"; +@fa-var-rub: "\f158"; +@fa-var-ruble: "\f158"; +@fa-var-rupee: "\f156"; +@fa-var-s15: "\f2cd"; +@fa-var-safari: "\f267"; +@fa-var-save: "\f0c7"; +@fa-var-scissors: "\f0c4"; +@fa-var-scribd: "\f28a"; +@fa-var-search: "\f002"; +@fa-var-search-minus: "\f010"; +@fa-var-search-plus: "\f00e"; +@fa-var-sellsy: "\f213"; +@fa-var-send: "\f1d8"; +@fa-var-send-o: "\f1d9"; +@fa-var-server: "\f233"; +@fa-var-share: "\f064"; +@fa-var-share-alt: "\f1e0"; +@fa-var-share-alt-square: "\f1e1"; +@fa-var-share-square: "\f14d"; +@fa-var-share-square-o: "\f045"; +@fa-var-shekel: "\f20b"; +@fa-var-sheqel: "\f20b"; +@fa-var-shield: "\f132"; +@fa-var-ship: "\f21a"; +@fa-var-shirtsinbulk: "\f214"; +@fa-var-shopping-bag: "\f290"; +@fa-var-shopping-basket: "\f291"; +@fa-var-shopping-cart: "\f07a"; +@fa-var-shower: "\f2cc"; +@fa-var-sign-in: "\f090"; +@fa-var-sign-language: "\f2a7"; +@fa-var-sign-out: "\f08b"; +@fa-var-signal: "\f012"; +@fa-var-signing: "\f2a7"; +@fa-var-simplybuilt: "\f215"; +@fa-var-sitemap: "\f0e8"; +@fa-var-skyatlas: "\f216"; +@fa-var-skype: "\f17e"; +@fa-var-slack: "\f198"; +@fa-var-sliders: "\f1de"; +@fa-var-slideshare: "\f1e7"; +@fa-var-smile-o: "\f118"; +@fa-var-snapchat: "\f2ab"; +@fa-var-snapchat-ghost: "\f2ac"; +@fa-var-snapchat-square: "\f2ad"; +@fa-var-snowflake-o: "\f2dc"; +@fa-var-soccer-ball-o: "\f1e3"; +@fa-var-sort: "\f0dc"; +@fa-var-sort-alpha-asc: "\f15d"; +@fa-var-sort-alpha-desc: "\f15e"; +@fa-var-sort-amount-asc: "\f160"; +@fa-var-sort-amount-desc: "\f161"; +@fa-var-sort-asc: "\f0de"; +@fa-var-sort-desc: "\f0dd"; +@fa-var-sort-down: "\f0dd"; +@fa-var-sort-numeric-asc: "\f162"; +@fa-var-sort-numeric-desc: "\f163"; +@fa-var-sort-up: "\f0de"; +@fa-var-soundcloud: "\f1be"; +@fa-var-space-shuttle: "\f197"; +@fa-var-spinner: "\f110"; +@fa-var-spoon: "\f1b1"; +@fa-var-spotify: "\f1bc"; +@fa-var-square: "\f0c8"; +@fa-var-square-o: "\f096"; +@fa-var-stack-exchange: "\f18d"; +@fa-var-stack-overflow: "\f16c"; +@fa-var-star: "\f005"; +@fa-var-star-half: "\f089"; +@fa-var-star-half-empty: "\f123"; +@fa-var-star-half-full: "\f123"; +@fa-var-star-half-o: "\f123"; +@fa-var-star-o: "\f006"; +@fa-var-steam: "\f1b6"; +@fa-var-steam-square: "\f1b7"; +@fa-var-step-backward: "\f048"; +@fa-var-step-forward: "\f051"; +@fa-var-stethoscope: "\f0f1"; +@fa-var-sticky-note: "\f249"; +@fa-var-sticky-note-o: "\f24a"; +@fa-var-stop: "\f04d"; +@fa-var-stop-circle: "\f28d"; +@fa-var-stop-circle-o: "\f28e"; +@fa-var-street-view: "\f21d"; +@fa-var-strikethrough: "\f0cc"; +@fa-var-stumbleupon: "\f1a4"; +@fa-var-stumbleupon-circle: "\f1a3"; +@fa-var-subscript: "\f12c"; +@fa-var-subway: "\f239"; +@fa-var-suitcase: "\f0f2"; +@fa-var-sun-o: "\f185"; +@fa-var-superpowers: "\f2dd"; +@fa-var-superscript: "\f12b"; +@fa-var-support: "\f1cd"; +@fa-var-table: "\f0ce"; +@fa-var-tablet: "\f10a"; +@fa-var-tachometer: "\f0e4"; +@fa-var-tag: "\f02b"; +@fa-var-tags: "\f02c"; +@fa-var-tasks: "\f0ae"; +@fa-var-taxi: "\f1ba"; +@fa-var-telegram: "\f2c6"; +@fa-var-television: "\f26c"; +@fa-var-tencent-weibo: "\f1d5"; +@fa-var-terminal: "\f120"; +@fa-var-text-height: "\f034"; +@fa-var-text-width: "\f035"; +@fa-var-th: "\f00a"; +@fa-var-th-large: "\f009"; +@fa-var-th-list: "\f00b"; +@fa-var-themeisle: "\f2b2"; +@fa-var-thermometer: "\f2c7"; +@fa-var-thermometer-0: "\f2cb"; +@fa-var-thermometer-1: "\f2ca"; +@fa-var-thermometer-2: "\f2c9"; +@fa-var-thermometer-3: "\f2c8"; +@fa-var-thermometer-4: "\f2c7"; +@fa-var-thermometer-empty: "\f2cb"; +@fa-var-thermometer-full: "\f2c7"; +@fa-var-thermometer-half: "\f2c9"; +@fa-var-thermometer-quarter: "\f2ca"; +@fa-var-thermometer-three-quarters: "\f2c8"; +@fa-var-thumb-tack: "\f08d"; +@fa-var-thumbs-down: "\f165"; +@fa-var-thumbs-o-down: "\f088"; +@fa-var-thumbs-o-up: "\f087"; +@fa-var-thumbs-up: "\f164"; +@fa-var-ticket: "\f145"; +@fa-var-times: "\f00d"; +@fa-var-times-circle: "\f057"; +@fa-var-times-circle-o: "\f05c"; +@fa-var-times-rectangle: "\f2d3"; +@fa-var-times-rectangle-o: "\f2d4"; +@fa-var-tint: "\f043"; +@fa-var-toggle-down: "\f150"; +@fa-var-toggle-left: "\f191"; +@fa-var-toggle-off: "\f204"; +@fa-var-toggle-on: "\f205"; +@fa-var-toggle-right: "\f152"; +@fa-var-toggle-up: "\f151"; +@fa-var-trademark: "\f25c"; +@fa-var-train: "\f238"; +@fa-var-transgender: "\f224"; +@fa-var-transgender-alt: "\f225"; +@fa-var-trash: "\f1f8"; +@fa-var-trash-o: "\f014"; +@fa-var-tree: "\f1bb"; +@fa-var-trello: "\f181"; +@fa-var-tripadvisor: "\f262"; +@fa-var-trophy: "\f091"; +@fa-var-truck: "\f0d1"; +@fa-var-try: "\f195"; +@fa-var-tty: "\f1e4"; +@fa-var-tumblr: "\f173"; +@fa-var-tumblr-square: "\f174"; +@fa-var-turkish-lira: "\f195"; +@fa-var-tv: "\f26c"; +@fa-var-twitch: "\f1e8"; +@fa-var-twitter: "\f099"; +@fa-var-twitter-square: "\f081"; +@fa-var-umbrella: "\f0e9"; +@fa-var-underline: "\f0cd"; +@fa-var-undo: "\f0e2"; +@fa-var-universal-access: "\f29a"; +@fa-var-university: "\f19c"; +@fa-var-unlink: "\f127"; +@fa-var-unlock: "\f09c"; +@fa-var-unlock-alt: "\f13e"; +@fa-var-unsorted: "\f0dc"; +@fa-var-upload: "\f093"; +@fa-var-usb: "\f287"; +@fa-var-usd: "\f155"; +@fa-var-user: "\f007"; +@fa-var-user-circle: "\f2bd"; +@fa-var-user-circle-o: "\f2be"; +@fa-var-user-md: "\f0f0"; +@fa-var-user-o: "\f2c0"; +@fa-var-user-plus: "\f234"; +@fa-var-user-secret: "\f21b"; +@fa-var-user-times: "\f235"; +@fa-var-users: "\f0c0"; +@fa-var-vcard: "\f2bb"; +@fa-var-vcard-o: "\f2bc"; +@fa-var-venus: "\f221"; +@fa-var-venus-double: "\f226"; +@fa-var-venus-mars: "\f228"; +@fa-var-viacoin: "\f237"; +@fa-var-viadeo: "\f2a9"; +@fa-var-viadeo-square: "\f2aa"; +@fa-var-video-camera: "\f03d"; +@fa-var-vimeo: "\f27d"; +@fa-var-vimeo-square: "\f194"; +@fa-var-vine: "\f1ca"; +@fa-var-vk: "\f189"; +@fa-var-volume-control-phone: "\f2a0"; +@fa-var-volume-down: "\f027"; +@fa-var-volume-off: "\f026"; +@fa-var-volume-up: "\f028"; +@fa-var-warning: "\f071"; +@fa-var-wechat: "\f1d7"; +@fa-var-weibo: "\f18a"; +@fa-var-weixin: "\f1d7"; +@fa-var-whatsapp: "\f232"; +@fa-var-wheelchair: "\f193"; +@fa-var-wheelchair-alt: "\f29b"; +@fa-var-wifi: "\f1eb"; +@fa-var-wikipedia-w: "\f266"; +@fa-var-window-close: "\f2d3"; +@fa-var-window-close-o: "\f2d4"; +@fa-var-window-maximize: "\f2d0"; +@fa-var-window-minimize: "\f2d1"; +@fa-var-window-restore: "\f2d2"; +@fa-var-windows: "\f17a"; +@fa-var-won: "\f159"; +@fa-var-wordpress: "\f19a"; +@fa-var-wpbeginner: "\f297"; +@fa-var-wpexplorer: "\f2de"; +@fa-var-wpforms: "\f298"; +@fa-var-wrench: "\f0ad"; +@fa-var-xing: "\f168"; +@fa-var-xing-square: "\f169"; +@fa-var-y-combinator: "\f23b"; +@fa-var-y-combinator-square: "\f1d4"; +@fa-var-yahoo: "\f19e"; +@fa-var-yc: "\f23b"; +@fa-var-yc-square: "\f1d4"; +@fa-var-yelp: "\f1e9"; +@fa-var-yen: "\f157"; +@fa-var-yoast: "\f2b1"; +@fa-var-youtube: "\f167"; +@fa-var-youtube-play: "\f16a"; +@fa-var-youtube-square: "\f166"; + diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.fileupload-ui.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.fileupload-ui.js new file mode 100644 index 00000000..e330fe16 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.fileupload-ui.js @@ -0,0 +1,702 @@ +/* + * jQuery File Upload User Interface Plugin 6.9.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window, document, URL, webkitURL, FileReader */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'tmpl', + 'load-image', + './jquery.fileupload-fp' + ], factory); + } else { + // Browser globals: + factory( + window.jQuery, + window.tmpl, + window.loadImage + ); + } +}(function ($, tmpl, loadImage) { + 'use strict'; + + // The UI version extends the FP (file processing) version or the basic + // file upload widget and adds complete user interface interaction: + var parentWidget = ($.blueimpFP || $.blueimp).fileupload; + $.widget('blueimpUI.fileupload', parentWidget, { + + options: { + // By default, files added to the widget are uploaded as soon + // as the user clicks on the start buttons. To enable automatic + // uploads, set the following option to true: + autoUpload: false, + // The following option limits the number of files that are + // allowed to be uploaded using this widget: + maxNumberOfFiles: undefined, + // The maximum allowed file size: + maxFileSize: undefined, + // The minimum allowed file size: + minFileSize: undefined, + // The regular expression for allowed file types, matches + // against either file type or file name: + acceptFileTypes: /.+$/i, + // The regular expression to define for which files a preview + // image is shown, matched against the file type: + previewSourceFileTypes: /^image\/(gif|jpeg|png)$/, + // The maximum file size of images that are to be displayed as preview: + previewSourceMaxFileSize: 5000000, // 5MB + // The maximum width of the preview images: + previewMaxWidth: 80, + // The maximum height of the preview images: + previewMaxHeight: 80, + // By default, preview images are displayed as canvas elements + // if supported by the browser. Set the following option to false + // to always display preview images as img elements: + previewAsCanvas: true, + // The ID of the upload template: + uploadTemplateId: 'template-upload', + // The ID of the download template: + downloadTemplateId: 'template-download', + // The container for the list of files. If undefined, it is set to + // an element with class "files" inside of the widget element: + filesContainer: undefined, + // By default, files are appended to the files container. + // Set the following option to true, to prepend files instead: + prependFiles: false, + // The expected data type of the upload response, sets the dataType + // option of the $.ajax upload requests: + dataType: 'json', + + // The add callback is invoked as soon as files are added to the fileupload + // widget (via file input selection, drag & drop or add API call). + // See the basic file upload widget for more information: + add: function (e, data) { + var that = $(this).data('fileupload'), + options = that.options, + files = data.files; + $(this).fileupload('process', data).done(function () { + that._adjustMaxNumberOfFiles(-files.length); + data.isAdjusted = true; + data.files.valid = data.isValidated = that._validate(files); + data.context = that._renderUpload(files).data('data', data); + options.filesContainer[ + options.prependFiles ? 'prepend' : 'append' + ](data.context); + that._renderPreviews(files, data.context); + that._forceReflow(data.context); + that._transition(data.context).done( + function () { + if ((that._trigger('added', e, data) !== false) && + (options.autoUpload || data.autoUpload) && + data.autoUpload !== false && data.isValidated) { + data.submit(); + } + } + ); + }); + }, + // Callback for the start of each file upload request: + send: function (e, data) { + var that = $(this).data('fileupload'); + if (!data.isValidated) { + if (!data.isAdjusted) { + that._adjustMaxNumberOfFiles(-data.files.length); + } + if (!that._validate(data.files)) { + return false; + } + } + if (data.context && data.dataType && + data.dataType.substr(0, 6) === 'iframe') { + // Iframe Transport does not support progress events. + // In lack of an indeterminate progress bar, we set + // the progress to 100%, showing the full animated bar: + data.context + .find('.progress').addClass( + !$.support.transition && 'progress-animated' + ) + .attr('aria-valuenow', 100) + .find('.bar').css( + 'width', + '100%' + ); + } + return that._trigger('sent', e, data); + }, + // Callback for successful uploads: + done: function (e, data) { + var that = $(this).data('fileupload'), + template; + if (data.context) { + data.context.each(function (index) { + var file = ($.isArray(data.result) && + data.result[index]) || {error: 'emptyResult'}; + if (file.error) { + that._adjustMaxNumberOfFiles(1); + } + that._transition($(this)).done( + function () { + var node = $(this); + template = that._renderDownload([file]) + .css('height', node.height()) + .replaceAll(node); + that._forceReflow(template); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('completed', e, data); + } + ); + } + ); + }); + } else { + template = that._renderDownload(data.result) + .appendTo(that.options.filesContainer); + that._forceReflow(template); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('completed', e, data); + } + ); + } + }, + // Callback for failed (abort or error) uploads: + fail: function (e, data) { + var that = $(this).data('fileupload'), + template; + that._adjustMaxNumberOfFiles(data.files.length); + if (data.context) { + data.context.each(function (index) { + if (data.errorThrown !== 'abort') { + var file = data.files[index]; + file.error = file.error || data.errorThrown || + true; + that._transition($(this)).done( + function () { + var node = $(this); + template = that._renderDownload([file]) + .replaceAll(node); + that._forceReflow(template); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('failed', e, data); + } + ); + } + ); + } else { + that._transition($(this)).done( + function () { + $(this).remove(); + that._trigger('failed', e, data); + } + ); + } + }); + } else if (data.errorThrown !== 'abort') { + that._adjustMaxNumberOfFiles(-data.files.length); + data.context = that._renderUpload(data.files) + .appendTo(that.options.filesContainer) + .data('data', data); + that._forceReflow(data.context); + that._transition(data.context).done( + function () { + data.context = $(this); + that._trigger('failed', e, data); + } + ); + } else { + that._trigger('failed', e, data); + } + }, + // Callback for upload progress events: + progress: function (e, data) { + if (data.context) { + var progress = parseInt(data.loaded / data.total * 100, 10); + data.context.find('.progress') + .attr('aria-valuenow', progress) + .find('.bar').css( + 'width', + progress + '%' + ); + } + }, + // Callback for global upload progress events: + progressall: function (e, data) { + var $this = $(this), + progress = parseInt(data.loaded / data.total * 100, 10), + globalProgressNode = $this.find('.fileupload-progress'), + extendedProgressNode = globalProgressNode + .find('.progress-extended'); + if (extendedProgressNode.length) { + extendedProgressNode.html( + $this.data('fileupload')._renderExtendedProgress(data) + ); + } + globalProgressNode + .find('.progress') + .attr('aria-valuenow', progress) + .find('.bar').css( + 'width', + progress + '%' + ); + }, + // Callback for uploads start, equivalent to the global ajaxStart event: + start: function (e) { + var that = $(this).data('fileupload'); + that._transition($(this).find('.fileupload-progress')).done( + function () { + that._trigger('started', e); + } + ); + }, + // Callback for uploads stop, equivalent to the global ajaxStop event: + stop: function (e) { + var that = $(this).data('fileupload'); + that._transition($(this).find('.fileupload-progress')).done( + function () { + $(this).find('.progress') + .attr('aria-valuenow', '0') + .find('.bar').css('width', '0%'); + $(this).find('.progress-extended').html(' '); + that._trigger('stopped', e); + } + ); + }, + // Callback for file deletion: + destroy: function (e, data) { + var that = $(this).data('fileupload'); + if (data.url) { + $.ajax(data); + that._adjustMaxNumberOfFiles(1); + } + that._transition(data.context).done( + function () { + $(this).remove(); + that._trigger('destroyed', e, data); + } + ); + } + }, + + // Link handler, that allows to download files + // by drag & drop of the links to the desktop: + _enableDragToDesktop: function () { + var link = $(this), + url = link.prop('href'), + name = link.prop('download'), + type = 'application/octet-stream'; + link.bind('dragstart', function (e) { + try { + e.originalEvent.dataTransfer.setData( + 'DownloadURL', + [type, name, url].join(':') + ); + } catch (err) {} + }); + }, + + _adjustMaxNumberOfFiles: function (operand) { + if (typeof this.options.maxNumberOfFiles === 'number') { + this.options.maxNumberOfFiles += operand; + if (this.options.maxNumberOfFiles < 1) { + this._disableFileInputButton(); + } else { + this._enableFileInputButton(); + } + } + }, + + _formatFileSize: function (bytes) { + if (typeof bytes !== 'number') { + return ''; + } + if (bytes >= 1000000000) { + return (bytes / 1000000000).toFixed(2) + ' GB'; + } + if (bytes >= 1000000) { + return (bytes / 1000000).toFixed(2) + ' MB'; + } + return (bytes / 1000).toFixed(2) + ' KB'; + }, + + _formatBitrate: function (bits) { + if (typeof bits !== 'number') { + return ''; + } + if (bits >= 1000000000) { + return (bits / 1000000000).toFixed(2) + ' Gbit/s'; + } + if (bits >= 1000000) { + return (bits / 1000000).toFixed(2) + ' Mbit/s'; + } + if (bits >= 1000) { + return (bits / 1000).toFixed(2) + ' kbit/s'; + } + return bits + ' bit/s'; + }, + + _formatTime: function (seconds) { + var date = new Date(seconds * 1000), + days = parseInt(seconds / 86400, 10); + days = days ? days + 'd ' : ''; + return days + + ('0' + date.getUTCHours()).slice(-2) + ':' + + ('0' + date.getUTCMinutes()).slice(-2) + ':' + + ('0' + date.getUTCSeconds()).slice(-2); + }, + + _formatPercentage: function (floatValue) { + return (floatValue * 100).toFixed(2) + ' %'; + }, + + _renderExtendedProgress: function (data) { + return this._formatBitrate(data.bitrate) + ' | ' + + this._formatTime( + (data.total - data.loaded) * 8 / data.bitrate + ) + ' | ' + + this._formatPercentage( + data.loaded / data.total + ) + ' | ' + + this._formatFileSize(data.loaded) + ' / ' + + this._formatFileSize(data.total); + }, + + _hasError: function (file) { + if (file.error) { + return file.error; + } + // The number of added files is subtracted from + // maxNumberOfFiles before validation, so we check if + // maxNumberOfFiles is below 0 (instead of below 1): + if (this.options.maxNumberOfFiles < 0) { + return 'maxNumberOfFiles'; + } + // Files are accepted if either the file type or the file name + // matches against the acceptFileTypes regular expression, as + // only browsers with support for the File API report the type: + if (!(this.options.acceptFileTypes.test(file.type) || + this.options.acceptFileTypes.test(file.name))) { + return 'acceptFileTypes'; + } + if (this.options.maxFileSize && + file.size > this.options.maxFileSize) { + return 'maxFileSize'; + } + if (typeof file.size === 'number' && + file.size < this.options.minFileSize) { + return 'minFileSize'; + } + return null; + }, + + _validate: function (files) { + var that = this, + valid = !!files.length; + $.each(files, function (index, file) { + file.error = that._hasError(file); + if (file.error) { + valid = false; + } + }); + return valid; + }, + + _renderTemplate: function (func, files) { + if (!func) { + return $(); + } + var result = func({ + files: files, + formatFileSize: this._formatFileSize, + options: this.options + }); + if (result instanceof $) { + return result; + } + return $(this.options.templatesContainer).html(result).children(); + }, + + _renderPreview: function (file, node) { + var that = this, + options = this.options, + dfd = $.Deferred(); + return ((loadImage && loadImage( + file, + function (img) { + node.append(img); + that._forceReflow(node); + that._transition(node).done(function () { + dfd.resolveWith(node); + }); + if (!$.contains(document.body, node[0])) { + // If the element is not part of the DOM, + // transition events are not triggered, + // so we have to resolve manually: + dfd.resolveWith(node); + } + }, + { + maxWidth: options.previewMaxWidth, + maxHeight: options.previewMaxHeight, + canvas: options.previewAsCanvas + } + )) || dfd.resolveWith(node)) && dfd; + }, + + _renderPreviews: function (files, nodes) { + var that = this, + options = this.options; + nodes.find('.preview span').each(function (index, element) { + var file = files[index]; + if (options.previewSourceFileTypes.test(file.type) && + ($.type(options.previewSourceMaxFileSize) !== 'number' || + file.size < options.previewSourceMaxFileSize)) { + that._processingQueue = that._processingQueue.pipe(function () { + var dfd = $.Deferred(); + that._renderPreview(file, $(element)).done( + function () { + dfd.resolveWith(that); + } + ); + return dfd.promise(); + }); + } + }); + return this._processingQueue; + }, + + _renderUpload: function (files) { + return this._renderTemplate( + this.options.uploadTemplate, + files + ); + }, + + _renderDownload: function (files) { + return this._renderTemplate( + this.options.downloadTemplate, + files + ).find('a[download]').each(this._enableDragToDesktop).end(); + }, + + _startHandler: function (e) { + e.preventDefault(); + var button = $(this), + template = button.closest('.template-upload'), + data = template.data('data'); + if (data && data.submit && !data.jqXHR && data.submit()) { + button.prop('disabled', true); + } + }, + + _cancelHandler: function (e) { + e.preventDefault(); + var template = $(this).closest('.template-upload'), + data = template.data('data') || {}; + if (!data.jqXHR) { + data.errorThrown = 'abort'; + e.data.fileupload._trigger('fail', e, data); + } else { + data.jqXHR.abort(); + } + }, + + _deleteHandler: function (e) { + e.preventDefault(); + var button = $(this); + e.data.fileupload._trigger('destroy', e, { + context: button.closest('.template-download'), + url: button.attr('data-url'), + type: button.attr('data-type') || 'DELETE', + dataType: e.data.fileupload.options.dataType + }); + }, + + _forceReflow: function (node) { + return $.support.transition && node.length && + node[0].offsetWidth; + }, + + _transition: function (node) { + var dfd = $.Deferred(); + if ($.support.transition && node.hasClass('fade')) { + node.bind( + $.support.transition.end, + function (e) { + // Make sure we don't respond to other transitions events + // in the container element, e.g. from button elements: + if (e.target === node[0]) { + node.unbind($.support.transition.end); + dfd.resolveWith(node); + } + } + ).toggleClass('in'); + } else { + node.toggleClass('in'); + dfd.resolveWith(node); + } + return dfd; + }, + + _initButtonBarEventHandlers: function () { + var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), + filesList = this.options.filesContainer, + ns = this.options.namespace; + fileUploadButtonBar.find('.start') + .bind('click.' + ns, function (e) { + e.preventDefault(); + filesList.find('.start button').click(); + }); + fileUploadButtonBar.find('.cancel') + .bind('click.' + ns, function (e) { + e.preventDefault(); + filesList.find('.cancel button').click(); + }); + fileUploadButtonBar.find('.delete') + .bind('click.' + ns, function (e) { + e.preventDefault(); + filesList.find('.delete input:checked') + .siblings('button').click(); + fileUploadButtonBar.find('.toggle') + .prop('checked', false); + }); + fileUploadButtonBar.find('.toggle') + .bind('change.' + ns, function (e) { + filesList.find('.delete input').prop( + 'checked', + $(this).is(':checked') + ); + }); + }, + + _destroyButtonBarEventHandlers: function () { + this.element.find('.fileupload-buttonbar button') + .unbind('click.' + this.options.namespace); + this.element.find('.fileupload-buttonbar .toggle') + .unbind('change.' + this.options.namespace); + }, + + _initEventHandlers: function () { + parentWidget.prototype._initEventHandlers.call(this); + var eventData = {fileupload: this}; + this.options.filesContainer + .delegate( + '.start button', + 'click.' + this.options.namespace, + eventData, + this._startHandler + ) + .delegate( + '.cancel button', + 'click.' + this.options.namespace, + eventData, + this._cancelHandler + ) + .delegate( + '.delete button', + 'click.' + this.options.namespace, + eventData, + this._deleteHandler + ); + this._initButtonBarEventHandlers(); + }, + + _destroyEventHandlers: function () { + var options = this.options; + this._destroyButtonBarEventHandlers(); + options.filesContainer + .undelegate('.start button', 'click.' + options.namespace) + .undelegate('.cancel button', 'click.' + options.namespace) + .undelegate('.delete button', 'click.' + options.namespace); + parentWidget.prototype._destroyEventHandlers.call(this); + }, + + _enableFileInputButton: function () { + this.element.find('.fileinput-button input') + .prop('disabled', false) + .parent().removeClass('disabled'); + }, + + _disableFileInputButton: function () { + this.element.find('.fileinput-button input') + .prop('disabled', true) + .parent().addClass('disabled'); + }, + + _initTemplates: function () { + var options = this.options; + options.templatesContainer = document.createElement( + options.filesContainer.prop('nodeName') + ); + if (tmpl) { + if (options.uploadTemplateId) { + options.uploadTemplate = tmpl(options.uploadTemplateId); + } + if (options.downloadTemplateId) { + options.downloadTemplate = tmpl(options.downloadTemplateId); + } + } + }, + + _initFilesContainer: function () { + var options = this.options; + if (options.filesContainer === undefined) { + options.filesContainer = this.element.find('.files'); + } else if (!(options.filesContainer instanceof $)) { + options.filesContainer = $(options.filesContainer); + } + }, + + _initSpecialOptions: function () { + parentWidget.prototype._initSpecialOptions.call(this); + this._initFilesContainer(); + this._initTemplates(); + }, + + _create: function () { + parentWidget.prototype._create.call(this); + this._refreshOptionsList.push( + 'filesContainer', + 'uploadTemplateId', + 'downloadTemplateId' + ); + if (!$.blueimpFP) { + this._processingQueue = $.Deferred().resolveWith(this).promise(); + this.process = function () { + return this._processingQueue; + }; + } + }, + + enable: function () { + parentWidget.prototype.enable.call(this); + this.element.find('input, button').prop('disabled', false); + this._enableFileInputButton(); + }, + + disable: function () { + this.element.find('input, button').prop('disabled', true); + this._disableFileInputButton(); + parentWidget.prototype.disable.call(this); + } + + }); + +})); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.fileupload-ui.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.fileupload-ui.min.js new file mode 100644 index 00000000..72406a00 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.fileupload-ui.min.js @@ -0,0 +1,30 @@ +(function(factory){'use strict';if(typeof define==='function'&&define.amd){define(['jquery','tmpl','load-image','./jquery.fileupload-fp'],factory);}else{factory(window.jQuery,window.tmpl,window.loadImage);}}(function($,tmpl,loadImage){'use strict';var parentWidget=($.blueimpFP||$.blueimp).fileupload;$.widget('blueimpUI.fileupload',parentWidget,{options:{autoUpload:false,maxNumberOfFiles:undefined,maxFileSize:undefined,minFileSize:undefined,acceptFileTypes:/.+$/i,previewSourceFileTypes:/^image\/(gif|jpeg|png)$/,previewSourceMaxFileSize:5000000,previewMaxWidth:80,previewMaxHeight:80,previewAsCanvas:true,uploadTemplateId:'template-upload',downloadTemplateId:'template-download',filesContainer:undefined,prependFiles:false,dataType:'json',add:function(e,data){var that=$(this).data('fileupload'),options=that.options,files=data.files;$(this).fileupload('process',data).done(function(){that._adjustMaxNumberOfFiles(-files.length);data.isAdjusted=true;data.files.valid=data.isValidated=that._validate(files);data.context=that._renderUpload(files).data('data',data);options.filesContainer[options.prependFiles?'prepend':'append'](data.context);that._renderPreviews(files,data.context);that._forceReflow(data.context);that._transition(data.context).done(function(){if((that._trigger('added',e,data)!==false)&&(options.autoUpload||data.autoUpload)&&data.autoUpload!==false&&data.isValidated){data.submit();}});});},send:function(e,data){var that=$(this).data('fileupload');if(!data.isValidated){if(!data.isAdjusted){that._adjustMaxNumberOfFiles(-data.files.length);} +if(!that._validate(data.files)){return false;}} +if(data.context&&data.dataType&&data.dataType.substr(0,6)==='iframe'){data.context.find('.progress').addClass(!$.support.transition&&'progress-animated').attr('aria-valuenow',100).find('.bar').css('width','100%');} +return that._trigger('sent',e,data);},done:function(e,data){var that=$(this).data('fileupload'),template;if(data.context){data.context.each(function(index){var file=($.isArray(data.result)&&data.result[index])||{error:'emptyResult'};if(file.error){that._adjustMaxNumberOfFiles(1);} +that._transition($(this)).done(function(){var node=$(this);template=that._renderDownload([file]).css('height',node.height()).replaceAll(node);that._forceReflow(template);that._transition(template).done(function(){data.context=$(this);that._trigger('completed',e,data);});});});}else{template=that._renderDownload(data.result).appendTo(that.options.filesContainer);that._forceReflow(template);that._transition(template).done(function(){data.context=$(this);that._trigger('completed',e,data);});}},fail:function(e,data){var that=$(this).data('fileupload'),template;that._adjustMaxNumberOfFiles(data.files.length);if(data.context){data.context.each(function(index){if(data.errorThrown!=='abort'){var file=data.files[index];file.error=file.error||data.errorThrown||true;that._transition($(this)).done(function(){var node=$(this);template=that._renderDownload([file]).replaceAll(node);that._forceReflow(template);that._transition(template).done(function(){data.context=$(this);that._trigger('failed',e,data);});});}else{that._transition($(this)).done(function(){$(this).remove();that._trigger('failed',e,data);});}});}else if(data.errorThrown!=='abort'){that._adjustMaxNumberOfFiles(-data.files.length);data.context=that._renderUpload(data.files).appendTo(that.options.filesContainer).data('data',data);that._forceReflow(data.context);that._transition(data.context).done(function(){data.context=$(this);that._trigger('failed',e,data);});}else{that._trigger('failed',e,data);}},progress:function(e,data){if(data.context){var progress=parseInt(data.loaded/data.total*100,10);data.context.find('.progress').attr('aria-valuenow',progress).find('.bar').css('width',progress+'%');}},progressall:function(e,data){var $this=$(this),progress=parseInt(data.loaded/data.total*100,10),globalProgressNode=$this.find('.fileupload-progress'),extendedProgressNode=globalProgressNode.find('.progress-extended');if(extendedProgressNode.length){extendedProgressNode.html($this.data('fileupload')._renderExtendedProgress(data));} +globalProgressNode.find('.progress').attr('aria-valuenow',progress).find('.bar').css('width',progress+'%');},start:function(e){var that=$(this).data('fileupload');that._transition($(this).find('.fileupload-progress')).done(function(){that._trigger('started',e);});},stop:function(e){var that=$(this).data('fileupload');that._transition($(this).find('.fileupload-progress')).done(function(){$(this).find('.progress').attr('aria-valuenow','0').find('.bar').css('width','0%');$(this).find('.progress-extended').html(' ');that._trigger('stopped',e);});},destroy:function(e,data){var that=$(this).data('fileupload');if(data.url){$.ajax(data);that._adjustMaxNumberOfFiles(1);} +that._transition(data.context).done(function(){$(this).remove();that._trigger('destroyed',e,data);});}},_enableDragToDesktop:function(){var link=$(this),url=link.prop('href'),name=link.prop('download'),type='application/octet-stream';link.bind('dragstart',function(e){try{e.originalEvent.dataTransfer.setData('DownloadURL',[type,name,url].join(':'));}catch(err){}});},_adjustMaxNumberOfFiles:function(operand){if(typeof this.options.maxNumberOfFiles==='number'){this.options.maxNumberOfFiles+=operand;if(this.options.maxNumberOfFiles<1){this._disableFileInputButton();}else{this._enableFileInputButton();}}},_formatFileSize:function(bytes){if(typeof bytes!=='number'){return'';} +if(bytes>=1000000000){return(bytes/1000000000).toFixed(2)+' GB';} +if(bytes>=1000000){return(bytes/1000000).toFixed(2)+' MB';} +return(bytes/1000).toFixed(2)+' KB';},_formatBitrate:function(bits){if(typeof bits!=='number'){return'';} +if(bits>=1000000000){return(bits/1000000000).toFixed(2)+' Gbit/s';} +if(bits>=1000000){return(bits/1000000).toFixed(2)+' Mbit/s';} +if(bits>=1000){return(bits/1000).toFixed(2)+' kbit/s';} +return bits+' bit/s';},_formatTime:function(seconds){var date=new Date(seconds*1000),days=parseInt(seconds/86400,10);days=days?days+'d ':'';return days+ +('0'+date.getUTCHours()).slice(-2)+':'+ +('0'+date.getUTCMinutes()).slice(-2)+':'+ +('0'+date.getUTCSeconds()).slice(-2);},_formatPercentage:function(floatValue){return(floatValue*100).toFixed(2)+' %';},_renderExtendedProgress:function(data){return this._formatBitrate(data.bitrate)+' | '+ +this._formatTime((data.total-data.loaded)*8/data.bitrate)+' | '+ +this._formatPercentage(data.loaded/data.total)+' | '+ +this._formatFileSize(data.loaded)+' / '+ +this._formatFileSize(data.total);},_hasError:function(file){if(file.error){return file.error;} +if(this.options.maxNumberOfFiles<0){return'maxNumberOfFiles';} +if(!(this.options.acceptFileTypes.test(file.type)||this.options.acceptFileTypes.test(file.name))){return'acceptFileTypes';} +if(this.options.maxFileSize&&file.size>this.options.maxFileSize){return'maxFileSize';} +if(typeof file.size==='number'&&file.size interval) { + this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; + this.loaded = loaded; + this.timestamp = now; + } + return this.bitrate; + }; + }, + + _isXHRUpload: function (options) { + return !options.forceIframeTransport && + ((!options.multipart && $.support.xhrFileUpload) || + $.support.xhrFormDataFileUpload); + }, + + _getFormData: function (options) { + var formData; + if (typeof options.formData === 'function') { + return options.formData(options.form); + } + if ($.isArray(options.formData)) { + return options.formData; + } + if (options.formData) { + formData = []; + $.each(options.formData, function (name, value) { + formData.push({name: name, value: value}); + }); + return formData; + } + return []; + }, + + _getTotal: function (files) { + var total = 0; + $.each(files, function (index, file) { + total += file.size || 1; + }); + return total; + }, + + _onProgress: function (e, data) { + if (e.lengthComputable) { + var now = +(new Date()), + total, + loaded; + if (data._time && data.progressInterval && + (now - data._time < data.progressInterval) && + e.loaded !== e.total) { + return; + } + data._time = now; + total = data.total || this._getTotal(data.files); + loaded = parseInt( + e.loaded / e.total * (data.chunkSize || total), + 10 + ) + (data.uploadedBytes || 0); + this._loaded += loaded - (data.loaded || data.uploadedBytes || 0); + data.lengthComputable = true; + data.loaded = loaded; + data.total = total; + data.bitrate = data._bitrateTimer.getBitrate( + now, + loaded, + data.bitrateInterval + ); + // Trigger a custom progress event with a total data property set + // to the file size(s) of the current upload and a loaded data + // property calculated accordingly: + this._trigger('progress', e, data); + // Trigger a global progress event for all current file uploads, + // including ajax calls queued for sequential file uploads: + this._trigger('progressall', e, { + lengthComputable: true, + loaded: this._loaded, + total: this._total, + bitrate: this._bitrateTimer.getBitrate( + now, + this._loaded, + data.bitrateInterval + ) + }); + } + }, + + _initProgressListener: function (options) { + var that = this, + xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); + // Accesss to the native XHR object is required to add event listeners + // for the upload progress event: + if (xhr.upload) { + $(xhr.upload).bind('progress', function (e) { + var oe = e.originalEvent; + // Make sure the progress event properties get copied over: + e.lengthComputable = oe.lengthComputable; + e.loaded = oe.loaded; + e.total = oe.total; + that._onProgress(e, options); + }); + options.xhr = function () { + return xhr; + }; + } + }, + + _initXHRData: function (options) { + var formData, + file = options.files[0], + // Ignore non-multipart setting if not supported: + multipart = options.multipart || !$.support.xhrFileUpload, + paramName = options.paramName[0]; + if (!multipart || options.blob) { + // For non-multipart uploads and chunked uploads, + // file meta data is not part of the request body, + // so we transmit this data as part of the HTTP headers. + // For cross domain requests, these headers must be allowed + // via Access-Control-Allow-Headers or removed using + // the beforeSend callback: + options.headers = $.extend(options.headers, { + 'X-File-Name': file.name, + 'X-File-Type': file.type, + 'X-File-Size': file.size + }); + if (!options.blob) { + // Non-chunked non-multipart upload: + options.contentType = file.type; + options.data = file; + } else if (!multipart) { + // Chunked non-multipart upload: + options.contentType = 'application/octet-stream'; + options.data = options.blob; + } + } + if (multipart && $.support.xhrFormDataFileUpload) { + if (options.postMessage) { + // window.postMessage does not allow sending FormData + // objects, so we just add the File/Blob objects to + // the formData array and let the postMessage window + // create the FormData object out of this array: + formData = this._getFormData(options); + if (options.blob) { + formData.push({ + name: paramName, + value: options.blob + }); + } else { + $.each(options.files, function (index, file) { + formData.push({ + name: options.paramName[index] || paramName, + value: file + }); + }); + } + } else { + if (options.formData instanceof FormData) { + formData = options.formData; + } else { + formData = new FormData(); + $.each(this._getFormData(options), function (index, field) { + formData.append(field.name, field.value); + }); + } + if (options.blob) { + formData.append(paramName, options.blob, file.name); + } else { + $.each(options.files, function (index, file) { + // File objects are also Blob instances. + // This check allows the tests to run with + // dummy objects: + if (file instanceof Blob) { + formData.append( + options.paramName[index] || paramName, + file, + file.name + ); + } + }); + } + } + options.data = formData; + } + // Blob reference is not needed anymore, free memory: + options.blob = null; + }, + + _initIframeSettings: function (options) { + // Setting the dataType to iframe enables the iframe transport: + options.dataType = 'iframe ' + (options.dataType || ''); + // The iframe transport accepts a serialized array as form data: + options.formData = this._getFormData(options); + // Add redirect url to form data on cross-domain uploads: + if (options.redirect && $('').prop('href', options.url) + .prop('host') !== location.host) { + options.formData.push({ + name: options.redirectParamName || 'redirect', + value: options.redirect + }); + } + }, + + _initDataSettings: function (options) { + if (this._isXHRUpload(options)) { + if (!this._chunkedUpload(options, true)) { + if (!options.data) { + this._initXHRData(options); + } + this._initProgressListener(options); + } + if (options.postMessage) { + // Setting the dataType to postmessage enables the + // postMessage transport: + options.dataType = 'postmessage ' + (options.dataType || ''); + } + } else { + this._initIframeSettings(options, 'iframe'); + } + }, + + _getParamName: function (options) { + var fileInput = $(options.fileInput), + paramName = options.paramName; + if (!paramName) { + paramName = []; + fileInput.each(function () { + var input = $(this), + name = input.prop('name') || 'files[]', + i = (input.prop('files') || [1]).length; + while (i) { + paramName.push(name); + i -= 1; + } + }); + if (!paramName.length) { + paramName = [fileInput.prop('name') || 'files[]']; + } + } else if (!$.isArray(paramName)) { + paramName = [paramName]; + } + return paramName; + }, + + _initFormSettings: function (options) { + // Retrieve missing options from the input field and the + // associated form, if available: + if (!options.form || !options.form.length) { + options.form = $(options.fileInput.prop('form')); + } + options.paramName = this._getParamName(options); + if (!options.url) { + options.url = options.form.prop('action') || location.href; + } + // The HTTP request method must be "POST" or "PUT": + options.type = (options.type || options.form.prop('method') || '') + .toUpperCase(); + if (options.type !== 'POST' && options.type !== 'PUT') { + options.type = 'POST'; + } + }, + + _getAJAXSettings: function (data) { + var options = $.extend({}, this.options, data); + this._initFormSettings(options); + this._initDataSettings(options); + return options; + }, + + // Maps jqXHR callbacks to the equivalent + // methods of the given Promise object: + _enhancePromise: function (promise) { + promise.success = promise.done; + promise.error = promise.fail; + promise.complete = promise.always; + return promise; + }, + + // Creates and returns a Promise object enhanced with + // the jqXHR methods abort, success, error and complete: + _getXHRPromise: function (resolveOrReject, context, args) { + var dfd = $.Deferred(), + promise = dfd.promise(); + context = context || this.options.context || promise; + if (resolveOrReject === true) { + dfd.resolveWith(context, args); + } else if (resolveOrReject === false) { + dfd.rejectWith(context, args); + } + promise.abort = dfd.promise; + return this._enhancePromise(promise); + }, + + // Uploads a file in multiple, sequential requests + // by splitting the file up in multiple blob chunks. + // If the second parameter is true, only tests if the file + // should be uploaded in chunks, but does not invoke any + // upload requests: + _chunkedUpload: function (options, testOnly) { + var that = this, + file = options.files[0], + fs = file.size, + ub = options.uploadedBytes = options.uploadedBytes || 0, + mcs = options.maxChunkSize || fs, + // Use the Blob methods with the slice implementation + // according to the W3C Blob API specification: + slice = file.webkitSlice || file.mozSlice || file.slice, + upload, + n, + jqXHR, + pipe; + if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || + options.data) { + return false; + } + if (testOnly) { + return true; + } + if (ub >= fs) { + file.error = 'uploadedBytes'; + return this._getXHRPromise( + false, + options.context, + [null, 'error', file.error] + ); + } + // n is the number of blobs to upload, + // calculated via filesize, uploaded bytes and max chunk size: + n = Math.ceil((fs - ub) / mcs); + // The chunk upload method accepting the chunk number as parameter: + upload = function (i) { + if (!i) { + return that._getXHRPromise(true, options.context); + } + // Upload the blobs in sequential order: + return upload(i -= 1).pipe(function () { + // Clone the options object for each chunk upload: + var o = $.extend({}, options); + o.blob = slice.call( + file, + ub + i * mcs, + ub + (i + 1) * mcs + ); + // Store the current chunk size, as the blob itself + // will be dereferenced after data processing: + o.chunkSize = o.blob.size; + // Process the upload data (the blob and potential form data): + that._initXHRData(o); + // Add progress listeners for this chunk upload: + that._initProgressListener(o); + jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context)) + .done(function () { + // Create a progress event if upload is done and + // no progress event has been invoked for this chunk: + if (!o.loaded) { + that._onProgress($.Event('progress', { + lengthComputable: true, + loaded: o.chunkSize, + total: o.chunkSize + }), o); + } + options.uploadedBytes = o.uploadedBytes += + o.chunkSize; + }); + return jqXHR; + }); + }; + // Return the piped Promise object, enhanced with an abort method, + // which is delegated to the jqXHR object of the current upload, + // and jqXHR callbacks mapped to the equivalent Promise methods: + pipe = upload(n); + pipe.abort = function () { + return jqXHR.abort(); + }; + return this._enhancePromise(pipe); + }, + + _beforeSend: function (e, data) { + if (this._active === 0) { + // the start callback is triggered when an upload starts + // and no other uploads are currently running, + // equivalent to the global ajaxStart event: + this._trigger('start'); + // Set timer for global bitrate progress calculation: + this._bitrateTimer = new this._BitrateTimer(); + } + this._active += 1; + // Initialize the global progress values: + this._loaded += data.uploadedBytes || 0; + this._total += this._getTotal(data.files); + }, + + _onDone: function (result, textStatus, jqXHR, options) { + if (!this._isXHRUpload(options)) { + // Create a progress event for each iframe load: + this._onProgress($.Event('progress', { + lengthComputable: true, + loaded: 1, + total: 1 + }), options); + } + options.result = result; + options.textStatus = textStatus; + options.jqXHR = jqXHR; + this._trigger('done', null, options); + }, + + _onFail: function (jqXHR, textStatus, errorThrown, options) { + options.jqXHR = jqXHR; + options.textStatus = textStatus; + options.errorThrown = errorThrown; + this._trigger('fail', null, options); + if (options.recalculateProgress) { + // Remove the failed (error or abort) file upload from + // the global progress calculation: + this._loaded -= options.loaded || options.uploadedBytes || 0; + this._total -= options.total || this._getTotal(options.files); + } + }, + + _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { + this._active -= 1; + options.textStatus = textStatus; + if (jqXHRorError && jqXHRorError.always) { + options.jqXHR = jqXHRorError; + options.result = jqXHRorResult; + } else { + options.jqXHR = jqXHRorResult; + options.errorThrown = jqXHRorError; + } + this._trigger('always', null, options); + if (this._active === 0) { + // The stop callback is triggered when all uploads have + // been completed, equivalent to the global ajaxStop event: + this._trigger('stop'); + // Reset the global progress values: + this._loaded = this._total = 0; + this._bitrateTimer = null; + } + }, + + _onSend: function (e, data) { + var that = this, + jqXHR, + slot, + pipe, + options = that._getAJAXSettings(data), + send = function (resolve, args) { + that._sending += 1; + // Set timer for bitrate progress calculation: + options._bitrateTimer = new that._BitrateTimer(); + jqXHR = jqXHR || ( + (resolve !== false && + that._trigger('send', e, options) !== false && + (that._chunkedUpload(options) || $.ajax(options))) || + that._getXHRPromise(false, options.context, args) + ).done(function (result, textStatus, jqXHR) { + that._onDone(result, textStatus, jqXHR, options); + }).fail(function (jqXHR, textStatus, errorThrown) { + that._onFail(jqXHR, textStatus, errorThrown, options); + }).always(function (jqXHRorResult, textStatus, jqXHRorError) { + that._sending -= 1; + that._onAlways( + jqXHRorResult, + textStatus, + jqXHRorError, + options + ); + if (options.limitConcurrentUploads && + options.limitConcurrentUploads > that._sending) { + // Start the next queued upload, + // that has not been aborted: + var nextSlot = that._slots.shift(); + while (nextSlot) { + if (!nextSlot.isRejected()) { + nextSlot.resolve(); + break; + } + nextSlot = that._slots.shift(); + } + } + }); + return jqXHR; + }; + this._beforeSend(e, options); + if (this.options.sequentialUploads || + (this.options.limitConcurrentUploads && + this.options.limitConcurrentUploads <= this._sending)) { + if (this.options.limitConcurrentUploads > 1) { + slot = $.Deferred(); + this._slots.push(slot); + pipe = slot.pipe(send); + } else { + pipe = (this._sequence = this._sequence.pipe(send, send)); + } + // Return the piped Promise object, enhanced with an abort method, + // which is delegated to the jqXHR object of the current upload, + // and jqXHR callbacks mapped to the equivalent Promise methods: + pipe.abort = function () { + var args = [undefined, 'abort', 'abort']; + if (!jqXHR) { + if (slot) { + slot.rejectWith(args); + } + return send(false, args); + } + return jqXHR.abort(); + }; + return this._enhancePromise(pipe); + } + return send(); + }, + + _onAdd: function (e, data) { + var that = this, + result = true, + options = $.extend({}, this.options, data), + limit = options.limitMultiFileUploads, + paramName = this._getParamName(options), + paramNameSet, + paramNameSlice, + fileSet, + i; + if (!(options.singleFileUploads || limit) || + !this._isXHRUpload(options)) { + fileSet = [data.files]; + paramNameSet = [paramName]; + } else if (!options.singleFileUploads && limit) { + fileSet = []; + paramNameSet = []; + for (i = 0; i < data.files.length; i += limit) { + fileSet.push(data.files.slice(i, i + limit)); + paramNameSlice = paramName.slice(i, i + limit); + if (!paramNameSlice.length) { + paramNameSlice = paramName; + } + paramNameSet.push(paramNameSlice); + } + } else { + paramNameSet = paramName; + } + data.originalFiles = data.files; + $.each(fileSet || data.files, function (index, element) { + var newData = $.extend({}, data); + newData.files = fileSet ? element : [element]; + newData.paramName = paramNameSet[index]; + newData.submit = function () { + newData.jqXHR = this.jqXHR = + (that._trigger('submit', e, this) !== false) && + that._onSend(e, this); + return this.jqXHR; + }; + return (result = that._trigger('add', e, newData)); + }); + return result; + }, + + // File Normalization for Gecko 1.9.1 (Firefox 3.5) support: + _normalizeFile: function (index, file) { + if (file.name === undefined && file.size === undefined) { + file.name = file.fileName; + file.size = file.fileSize; + } + }, + + _replaceFileInput: function (input) { + var inputClone = input.clone(true); + $('').append(inputClone)[0].reset(); + // Detaching allows to insert the fileInput on another form + // without loosing the file input value: + input.after(inputClone).detach(); + // Avoid memory leaks with the detached file input: + $.cleanData(input.unbind('remove')); + // Replace the original file input element in the fileInput + // collection with the clone, which has been copied including + // event handlers: + this.options.fileInput = this.options.fileInput.map(function (i, el) { + if (el === input[0]) { + return inputClone[0]; + } + return el; + }); + // If the widget has been initialized on the file input itself, + // override this.element with the file input clone: + if (input[0] === this.element[0]) { + this.element = inputClone; + } + }, + + _getFileInputFiles: function (fileInput) { + fileInput = $(fileInput); + var files = $.each($.makeArray(fileInput.prop('files')), this._normalizeFile), + value; + if (!files.length) { + value = fileInput.prop('value'); + if (!value) { + return []; + } + // If the files property is not available, the browser does not + // support the File API and we add a pseudo File object with + // the input value as name with path information removed: + files = [{name: value.replace(/^.*\\/, '')}]; + } + return files; + }, + + _onChange: function (e) { + var that = e.data.fileupload, + data = { + fileInput: $(e.target), + form: $(e.target.form) + }; + data.files = that._getFileInputFiles(data.fileInput); + if (that.options.replaceFileInput) { + that._replaceFileInput(data.fileInput); + } + if (that._trigger('change', e, data) === false || + that._onAdd(e, data) === false) { + return false; + } + }, + + _onPaste: function (e) { + var that = e.data.fileupload, + cbd = e.originalEvent.clipboardData, + items = (cbd && cbd.items) || [], + data = {files: []}; + $.each(items, function (index, item) { + var file = item.getAsFile && item.getAsFile(); + if (file) { + data.files.push(file); + } + }); + if (that._trigger('paste', e, data) === false || + that._onAdd(e, data) === false) { + return false; + } + }, + + _onDrop: function (e) { + var that = e.data.fileupload, + dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer, + data = { + files: $.each( + $.makeArray(dataTransfer && dataTransfer.files), + that._normalizeFile + ) + }; + if (that._trigger('drop', e, data) === false || + that._onAdd(e, data) === false) { + return false; + } + e.preventDefault(); + }, + + _onDragOver: function (e) { + var that = e.data.fileupload, + dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer; + if (that._trigger('dragover', e) === false) { + return false; + } + if (dataTransfer) { + dataTransfer.dropEffect = 'copy'; + } + e.preventDefault(); + }, + + _initEventHandlers: function () { + var ns = this.options.namespace; + if (this._isXHRUpload(this.options)) { + this.options.dropZone + .bind('dragover.' + ns, {fileupload: this}, this._onDragOver) + .bind('drop.' + ns, {fileupload: this}, this._onDrop) + .bind('paste.' + ns, {fileupload: this}, this._onPaste); + } + this.options.fileInput + .bind('change.' + ns, {fileupload: this}, this._onChange); + }, + + _destroyEventHandlers: function () { + var ns = this.options.namespace; + this.options.dropZone + .unbind('dragover.' + ns, this._onDragOver) + .unbind('drop.' + ns, this._onDrop) + .unbind('paste.' + ns, this._onPaste); + this.options.fileInput + .unbind('change.' + ns, this._onChange); + }, + + _setOption: function (key, value) { + var refresh = $.inArray(key, this._refreshOptionsList) !== -1; + if (refresh) { + this._destroyEventHandlers(); + } + $.Widget.prototype._setOption.call(this, key, value); + if (refresh) { + this._initSpecialOptions(); + this._initEventHandlers(); + } + }, + + _initSpecialOptions: function () { + var options = this.options; + if (options.fileInput === undefined) { + options.fileInput = this.element.is('input:file') ? + this.element : this.element.find('input:file'); + } else if (!(options.fileInput instanceof $)) { + options.fileInput = $(options.fileInput); + } + if (!(options.dropZone instanceof $)) { + options.dropZone = $(options.dropZone); + } + }, + + _create: function () { + var options = this.options; + // Initialize options set via HTML5 data-attributes: + $.extend(options, $(this.element[0].cloneNode(false)).data()); + options.namespace = options.namespace || this.widgetName; + this._initSpecialOptions(); + this._slots = []; + this._sequence = this._getXHRPromise(true); + this._sending = this._active = this._loaded = this._total = 0; + this._initEventHandlers(); + }, + + destroy: function () { + this._destroyEventHandlers(); + $.Widget.prototype.destroy.call(this); + }, + + enable: function () { + $.Widget.prototype.enable.call(this); + this._initEventHandlers(); + }, + + disable: function () { + this._destroyEventHandlers(); + $.Widget.prototype.disable.call(this); + }, + + // This method is exposed to the widget API and allows adding files + // using the fileupload API. The data parameter accepts an object which + // must have a files property and can contain additional options: + // .fileupload('add', {files: filesList}); + add: function (data) { + if (!data || this.options.disabled) { + return; + } + if (data.fileInput && !data.files) { + data.files = this._getFileInputFiles(data.fileInput); + } else { + data.files = $.each($.makeArray(data.files), this._normalizeFile); + } + this._onAdd(null, data); + }, + + // This method is exposed to the widget API and allows sending files + // using the fileupload API. The data parameter accepts an object which + // must have a files property and can contain additional options: + // .fileupload('send', {files: filesList}); + // The method returns a Promise object for the file upload call. + send: function (data) { + if (data && !this.options.disabled) { + if (data.fileInput && !data.files) { + data.files = this._getFileInputFiles(data.fileInput); + } else { + data.files = $.each($.makeArray(data.files), this._normalizeFile); + } + if (data.files.length) { + return this._onSend(null, data); + } + } + return this._getXHRPromise(false, data && data.context); + } + + }); + +})); diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.fileupload.min.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.fileupload.min.js new file mode 100644 index 00000000..b8666ad7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.fileupload.min.js @@ -0,0 +1,45 @@ +(function(factory){'use strict';if(typeof define==='function'&&define.amd){define(['jquery','jquery.ui.widget'],factory);}else{factory(window.jQuery);}}(function($){'use strict';$.support.xhrFileUpload=!!(window.XMLHttpRequestUpload&&window.FileReader);$.support.xhrFormDataFileUpload=!!window.FormData;$.widget('blueimp.fileupload',{options:{namespace:undefined,dropZone:$(document),fileInput:undefined,replaceFileInput:true,paramName:undefined,singleFileUploads:true,limitMultiFileUploads:undefined,sequentialUploads:false,limitConcurrentUploads:undefined,forceIframeTransport:false,redirect:undefined,redirectParamName:undefined,postMessage:undefined,multipart:true,maxChunkSize:undefined,uploadedBytes:undefined,recalculateProgress:true,progressInterval:100,bitrateInterval:500,formData:function(form){return form.serializeArray();},add:function(e,data){data.submit();},processData:false,contentType:false,cache:false},_refreshOptionsList:['namespace','dropZone','fileInput','multipart','forceIframeTransport'],_BitrateTimer:function(){this.timestamp=+(new Date());this.loaded=0;this.bitrate=0;this.getBitrate=function(now,loaded,interval){var timeDiff=now-this.timestamp;if(!this.bitrate||!interval||timeDiff>interval){this.bitrate=(loaded-this.loaded)*(1000/timeDiff)*8;this.loaded=loaded;this.timestamp=now;} +return this.bitrate;};},_isXHRUpload:function(options){return!options.forceIframeTransport&&((!options.multipart&&$.support.xhrFileUpload)||$.support.xhrFormDataFileUpload);},_getFormData:function(options){var formData;if(typeof options.formData==='function'){return options.formData(options.form);} +if($.isArray(options.formData)){return options.formData;} +if(options.formData){formData=[];$.each(options.formData,function(name,value){formData.push({name:name,value:value});});return formData;} +return[];},_getTotal:function(files){var total=0;$.each(files,function(index,file){total+=file.size||1;});return total;},_onProgress:function(e,data){if(e.lengthComputable){var now=+(new Date()),total,loaded;if(data._time&&data.progressInterval&&(now-data._time').prop('href',options.url).prop('host')!==location.host){options.formData.push({name:options.redirectParamName||'redirect',value:options.redirect});}},_initDataSettings:function(options){if(this._isXHRUpload(options)){if(!this._chunkedUpload(options,true)){if(!options.data){this._initXHRData(options);} +this._initProgressListener(options);} +if(options.postMessage){options.dataType='postmessage '+(options.dataType||'');}}else{this._initIframeSettings(options,'iframe');}},_getParamName:function(options){var fileInput=$(options.fileInput),paramName=options.paramName;if(!paramName){paramName=[];fileInput.each(function(){var input=$(this),name=input.prop('name')||'files[]',i=(input.prop('files')||[1]).length;while(i){paramName.push(name);i-=1;}});if(!paramName.length){paramName=[fileInput.prop('name')||'files[]'];}}else if(!$.isArray(paramName)){paramName=[paramName];} +return paramName;},_initFormSettings:function(options){if(!options.form||!options.form.length){options.form=$(options.fileInput.prop('form'));} +options.paramName=this._getParamName(options);if(!options.url){options.url=options.form.prop('action')||location.href;} +options.type=(options.type||options.form.prop('method')||'').toUpperCase();if(options.type!=='POST'&&options.type!=='PUT'){options.type='POST';}},_getAJAXSettings:function(data){var options=$.extend({},this.options,data);this._initFormSettings(options);this._initDataSettings(options);return options;},_enhancePromise:function(promise){promise.success=promise.done;promise.error=promise.fail;promise.complete=promise.always;return promise;},_getXHRPromise:function(resolveOrReject,context,args){var dfd=$.Deferred(),promise=dfd.promise();context=context||this.options.context||promise;if(resolveOrReject===true){dfd.resolveWith(context,args);}else if(resolveOrReject===false){dfd.rejectWith(context,args);} +promise.abort=dfd.promise;return this._enhancePromise(promise);},_chunkedUpload:function(options,testOnly){var that=this,file=options.files[0],fs=file.size,ub=options.uploadedBytes=options.uploadedBytes||0,mcs=options.maxChunkSize||fs,slice=file.webkitSlice||file.mozSlice||file.slice,upload,n,jqXHR,pipe;if(!(this._isXHRUpload(options)&&slice&&(ub||mcs=fs){file.error='uploadedBytes';return this._getXHRPromise(false,options.context,[null,'error',file.error]);} +n=Math.ceil((fs-ub)/mcs);upload=function(i){if(!i){return that._getXHRPromise(true,options.context);} +return upload(i-=1).pipe(function(){var o=$.extend({},options);o.blob=slice.call(file,ub+i*mcs,ub+(i+1)*mcs);o.chunkSize=o.blob.size;that._initXHRData(o);that._initProgressListener(o);jqXHR=($.ajax(o)||that._getXHRPromise(false,o.context)).done(function(){if(!o.loaded){that._onProgress($.Event('progress',{lengthComputable:true,loaded:o.chunkSize,total:o.chunkSize}),o);} +options.uploadedBytes=o.uploadedBytes+=o.chunkSize;});return jqXHR;});};pipe=upload(n);pipe.abort=function(){return jqXHR.abort();};return this._enhancePromise(pipe);},_beforeSend:function(e,data){if(this._active===0){this._trigger('start');this._bitrateTimer=new this._BitrateTimer();} +this._active+=1;this._loaded+=data.uploadedBytes||0;this._total+=this._getTotal(data.files);},_onDone:function(result,textStatus,jqXHR,options){if(!this._isXHRUpload(options)){this._onProgress($.Event('progress',{lengthComputable:true,loaded:1,total:1}),options);} +options.result=result;options.textStatus=textStatus;options.jqXHR=jqXHR;this._trigger('done',null,options);},_onFail:function(jqXHR,textStatus,errorThrown,options){options.jqXHR=jqXHR;options.textStatus=textStatus;options.errorThrown=errorThrown;this._trigger('fail',null,options);if(options.recalculateProgress){this._loaded-=options.loaded||options.uploadedBytes||0;this._total-=options.total||this._getTotal(options.files);}},_onAlways:function(jqXHRorResult,textStatus,jqXHRorError,options){this._active-=1;options.textStatus=textStatus;if(jqXHRorError&&jqXHRorError.always){options.jqXHR=jqXHRorError;options.result=jqXHRorResult;}else{options.jqXHR=jqXHRorResult;options.errorThrown=jqXHRorError;} +this._trigger('always',null,options);if(this._active===0){this._trigger('stop');this._loaded=this._total=0;this._bitrateTimer=null;}},_onSend:function(e,data){var that=this,jqXHR,slot,pipe,options=that._getAJAXSettings(data),send=function(resolve,args){that._sending+=1;options._bitrateTimer=new that._BitrateTimer();jqXHR=jqXHR||((resolve!==false&&that._trigger('send',e,options)!==false&&(that._chunkedUpload(options)||$.ajax(options)))||that._getXHRPromise(false,options.context,args)).done(function(result,textStatus,jqXHR){that._onDone(result,textStatus,jqXHR,options);}).fail(function(jqXHR,textStatus,errorThrown){that._onFail(jqXHR,textStatus,errorThrown,options);}).always(function(jqXHRorResult,textStatus,jqXHRorError){that._sending-=1;that._onAlways(jqXHRorResult,textStatus,jqXHRorError,options);if(options.limitConcurrentUploads&&options.limitConcurrentUploads>that._sending){var nextSlot=that._slots.shift();while(nextSlot){if(!nextSlot.isRejected()){nextSlot.resolve();break;} +nextSlot=that._slots.shift();}}});return jqXHR;};this._beforeSend(e,options);if(this.options.sequentialUploads||(this.options.limitConcurrentUploads&&this.options.limitConcurrentUploads<=this._sending)){if(this.options.limitConcurrentUploads>1){slot=$.Deferred();this._slots.push(slot);pipe=slot.pipe(send);}else{pipe=(this._sequence=this._sequence.pipe(send,send));} +pipe.abort=function(){var args=[undefined,'abort','abort'];if(!jqXHR){if(slot){slot.rejectWith(args);} +return send(false,args);} +return jqXHR.abort();};return this._enhancePromise(pipe);} +return send();},_onAdd:function(e,data){var that=this,result=true,options=$.extend({},this.options,data),limit=options.limitMultiFileUploads,paramName=this._getParamName(options),paramNameSet,paramNameSlice,fileSet,i;if(!(options.singleFileUploads||limit)||!this._isXHRUpload(options)){fileSet=[data.files];paramNameSet=[paramName];}else if(!options.singleFileUploads&&limit){fileSet=[];paramNameSet=[];for(i=0;i').append(inputClone)[0].reset();input.after(inputClone).detach();$.cleanData(input.unbind('remove'));this.options.fileInput=this.options.fileInput.map(function(i,el){if(el===input[0]){return inputClone[0];} +return el;});if(input[0]===this.element[0]){this.element=inputClone;}},_getFileInputFiles:function(fileInput){fileInput=$(fileInput);var files=$.each($.makeArray(fileInput.prop('files')),this._normalizeFile),value;if(!files.length){value=fileInput.prop('value');if(!value){return[];} +files=[{name:value.replace(/^.*\\/,'')}];} +return files;},_onChange:function(e){var that=e.data.fileupload,data={fileInput:$(e.target),form:$(e.target.form)};data.files=that._getFileInputFiles(data.fileInput);if(that.options.replaceFileInput){that._replaceFileInput(data.fileInput);} +if(that._trigger('change',e,data)===false||that._onAdd(e,data)===false){return false;}},_onPaste:function(e){var that=e.data.fileupload,cbd=e.originalEvent.clipboardData,items=(cbd&&cbd.items)||[],data={files:[]};$.each(items,function(index,item){var file=item.getAsFile&&item.getAsFile();if(file){data.files.push(file);}});if(that._trigger('paste',e,data)===false||that._onAdd(e,data)===false){return false;}},_onDrop:function(e){var that=e.data.fileupload,dataTransfer=e.dataTransfer=e.originalEvent.dataTransfer,data={files:$.each($.makeArray(dataTransfer&&dataTransfer.files),that._normalizeFile)};if(that._trigger('drop',e,data)===false||that._onAdd(e,data)===false){return false;} +e.preventDefault();},_onDragOver:function(e){var that=e.data.fileupload,dataTransfer=e.dataTransfer=e.originalEvent.dataTransfer;if(that._trigger('dragover',e)===false){return false;} +if(dataTransfer){dataTransfer.dropEffect='copy';} +e.preventDefault();},_initEventHandlers:function(){var ns=this.options.namespace;if(this._isXHRUpload(this.options)){this.options.dropZone.bind('dragover.'+ns,{fileupload:this},this._onDragOver).bind('drop.'+ns,{fileupload:this},this._onDrop).bind('paste.'+ns,{fileupload:this},this._onPaste);} +this.options.fileInput.bind('change.'+ns,{fileupload:this},this._onChange);},_destroyEventHandlers:function(){var ns=this.options.namespace;this.options.dropZone.unbind('dragover.'+ns,this._onDragOver).unbind('drop.'+ns,this._onDrop).unbind('paste.'+ns,this._onPaste);this.options.fileInput.unbind('change.'+ns,this._onChange);},_setOption:function(key,value){var refresh=$.inArray(key,this._refreshOptionsList)!==-1;if(refresh){this._destroyEventHandlers();} +$.Widget.prototype._setOption.call(this,key,value);if(refresh){this._initSpecialOptions();this._initEventHandlers();}},_initSpecialOptions:function(){var options=this.options;if(options.fileInput===undefined){options.fileInput=this.element.is('input:file')?this.element:this.element.find('input:file');}else if(!(options.fileInput instanceof $)){options.fileInput=$(options.fileInput);} +if(!(options.dropZone instanceof $)){options.dropZone=$(options.dropZone);}},_create:function(){var options=this.options;$.extend(options,$(this.element[0].cloneNode(false)).data());options.namespace=options.namespace||this.widgetName;this._initSpecialOptions();this._slots=[];this._sequence=this._getXHRPromise(true);this._sending=this._active=this._loaded=this._total=0;this._initEventHandlers();},destroy:function(){this._destroyEventHandlers();$.Widget.prototype.destroy.call(this);},enable:function(){$.Widget.prototype.enable.call(this);this._initEventHandlers();},disable:function(){this._destroyEventHandlers();$.Widget.prototype.disable.call(this);},add:function(data){if(!data||this.options.disabled){return;} +if(data.fileInput&&!data.files){data.files=this._getFileInputFiles(data.fileInput);}else{data.files=$.each($.makeArray(data.files),this._normalizeFile);} +this._onAdd(null,data);},send:function(data){if(data&&!this.options.disabled){if(data.fileInput&&!data.files){data.files=this._getFileInputFiles(data.fileInput);}else{data.files=$.each($.makeArray(data.files),this._normalizeFile);} +if(data.files.length){return this._onSend(null,data);}} +return this._getXHRPromise(false,data&&data.context);}});})); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.iframe-transport.js b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.iframe-transport.js new file mode 100644 index 00000000..04a56623 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/public/base/vendor/jquery-fileupload/jquery.iframe-transport.js @@ -0,0 +1,171 @@ +/* + * jQuery Iframe Transport Plugin 1.4 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint unparam: true, nomen: true */ +/*global define, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Helper variable to create unique names for the transport iframes: + var counter = 0; + + // The iframe transport accepts three additional options: + // options.fileInput: a jQuery collection of file input fields + // options.paramName: the parameter name for the file form data, + // overrides the name property of the file input field(s), + // can be a string or an array of strings. + // options.formData: an array of objects with name and value properties, + // equivalent to the return data of .serializeArray(), e.g.: + // [{name: 'a', value: 1}, {name: 'b', value: 2}] + $.ajaxTransport('iframe', function (options) { + if (options.async && (options.type === 'POST' || options.type === 'GET')) { + var form, + iframe; + return { + send: function (_, completeCallback) { + form = $('

    {% endblock %} + +{% block main_content %} +
    +
    + {% block form %} +

    {{ _('Are you sure you want to delete group - {name}?').format(name=c.group_dict.name) }}

    +

    +

    + + +
    +

    + {% endblock %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/confirm_delete_member.html b/venv/lib/python2.7/site-packages/ckan/templates/group/confirm_delete_member.html new file mode 100644 index 00000000..a1b9cd46 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/confirm_delete_member.html @@ -0,0 +1,22 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _("Confirm Delete") }}{% endblock %} + +{% block maintag %}
    {% endblock %} + +{% block main_content %} +
    +
    + {% block form %} +

    {{ _('Are you sure you want to delete member - {name}?').format(name=c.user_dict.name) }}

    +

    +

    + + + +
    +

    + {% endblock %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/edit.html b/venv/lib/python2.7/site-packages/ckan/templates/group/edit.html new file mode 100644 index 00000000..ff5c0b96 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/edit.html @@ -0,0 +1,12 @@ +{% extends "group/base_form_page.html" %} + +{% block breadcrumb_content %} +
  • {% link_for _('Groups'), controller='group', action='index' %}
  • + {% block breadcrumb_content_inner %} +
  • {% link_for group.display_name|truncate(35), controller='group', action='read', id=group.name %}
  • +
  • {% link_for _('Manage'), controller='group', action='edit', id=group.name %}
  • + {% endblock %} +{% endblock %} + +{% block page_heading_class %}hide-heading{% endblock %} +{% block page_heading %}{{ _('Edit Group') }}{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/edit_base.html b/venv/lib/python2.7/site-packages/ckan/templates/group/edit_base.html new file mode 100644 index 00000000..f26c04de --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/edit_base.html @@ -0,0 +1,26 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _('Manage') }} - {{ c.group_dict.display_name }} - {{ _('Groups') }}{% endblock %} + +{% set group = c.group_dict %} + +{% block breadcrumb_content %} +
  • {% link_for _('Groups'), controller='group', action='index', named_route=group_type + '_index' %}
  • + {% block breadcrumb_content_inner %} +
  • {% link_for group.display_name|truncate(35), controller='group', action='read', id=group.name, named_route=group_type + '_read' %}
  • +
  • {% link_for _('Manage'), controller='group', action='edit', id=group.name, named_route=group_type + '_edit' %}
  • + {% endblock %} +{% endblock %} + +{% block content_action %} + {% link_for _('View'), controller='group', action='read', id=c.group_dict.name, class_='btn btn-default', icon='eye', named_route=group_type + '_read' %} +{% endblock %} + +{% block content_primary_nav %} + {{ h.build_nav_icon(group_type + '_edit', _('Edit'), id=c.group_dict.name) }} + {{ h.build_nav_icon(group_type + '_members', _('Members'), id=c.group_dict.name) }} +{% endblock %} + +{% block secondary_content %} + {% snippet "group/snippets/info.html", group=c.group_dict, show_nums=false %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/followers.html b/venv/lib/python2.7/site-packages/ckan/templates/group/followers.html new file mode 100644 index 00000000..37ee035f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/followers.html @@ -0,0 +1,10 @@ +{% extends "group/read_base.html" %} + +{% block subtitle %}{{ _('Followers') }} - {{ c.group_dict.title or c.group_dict.name }}{% endblock %} + +{% block primary_content_inner %} +

    {% block page_heading %}{{ _('Followers') }}{% endblock %}

    + {% block followers_list %} + {% snippet "user/snippets/followers.html", followers=c.followers %} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/history.html b/venv/lib/python2.7/site-packages/ckan/templates/group/history.html new file mode 100644 index 00000000..021c8500 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/history.html @@ -0,0 +1,10 @@ +{% extends "group/read_base.html" %} + +{% block subtitle %}{{ _('History') }} - {{ c.group_dict.display_name }}{% endblock %} + +{% block primary_content_inner %} +

    {{ _('History') }}

    + {% block group_history_revisions %} + {% snippet "group/snippets/history_revisions.html", group_dict=c.group_dict, group_revisions=c.group_revisions %} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/index.html b/venv/lib/python2.7/site-packages/ckan/templates/group/index.html new file mode 100644 index 00000000..e03fef5b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/index.html @@ -0,0 +1,43 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _('Groups') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {% link_for _('Groups'), controller='group', action='index', named_route=group_type + '_index' %}
  • +{% endblock %} + +{% block page_header %}{% endblock %} + +{% block page_primary_action %} + {% if h.check_access('group_create') %} + {% link_for _('Add Group'), controller='group', action='new', class_='btn btn-primary', icon='plus-square', named_route=group_type + '_new' %} + {% endif %} +{% endblock %} + +{% block primary_content_inner %} +

    {{ _('Groups') }}

    + {% block groups_search_form %} + {% snippet 'snippets/search_form.html', form_id='group-search-form', type='group', query=c.q, sorting_selected=c.sort_by_selected, count=c.page.item_count, placeholder=_('Search groups...'), show_empty=request.params, no_bottom_border=true if c.page.items, sorting = [(_('Name Ascending'), 'title asc'), (_('Name Descending'), 'title desc')] %} + {% endblock %} + {% block groups_list %} + {% if c.page.items or request.params %} + {% if c.page.items %} + {% snippet "group/snippets/group_list.html", groups=c.page.items %} + {% endif %} + {% else %} +

    + {{ _('There are currently no groups for this site') }}. + {% if h.check_access('group_create') %} + {% link_for _('How about creating one?'), controller='group', action='new' %}. + {% endif %} +

    + {% endif %} + {% endblock %} + {% block page_pagination %} + {{ c.page.pager(q=c.q or '', sort=c.sort_by_selected or '') }} + {% endblock %} +{% endblock %} + +{% block secondary_content %} + {% snippet "group/snippets/helper.html" %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/member_new.html b/venv/lib/python2.7/site-packages/ckan/templates/group/member_new.html new file mode 100644 index 00000000..4f02e15d --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/member_new.html @@ -0,0 +1,93 @@ +{% extends "group/edit_base.html" %} + +{% import 'macros/form.html' as form %} + +{% set user = c.user_dict %} + +{% block primary_content_inner %} + {% link_for _('Back to all members'), controller='group', action='members', id=group.name, class_='btn btn-default pull-right', icon='arrow-left', named_route=group_type + '_members' %} +

    + {% block page_heading %}{{ _('Edit Member') if user else _('Add Member') }}{% endblock %} +

    + {% block form %} +
    +
    +
    +
    + {% if not user %} + +

    + {{ _('If you wish to add an existing user, search for their username below.') }} +

    + {% endif %} +
    + {% if user %} + + + {% else %} + + {% endif %} +
    +
    +
    + {% if not user %} +
    +
    + {{ _('or') }} +
    +
    +
    +
    + +

    + {{ _('If you wish to invite a new user, enter their email address.') }} +

    +
    + +
    +
    +
    + {% endif %} +
    + + {% set format_attrs = {'data-module': 'autocomplete'} %} + {{ form.select('role', label=_('Role'), options=c.roles, selected=c.user_role, error='', attrs=format_attrs) }} +
    + {% if user %} + {{ _('Delete') }} + + {% else %} + + {% endif %} +
    +
    + {% endblock %} +{% endblock %} + +{% block secondary_content %} + {{ super() }} +
    +

    + + {{ _('What are roles?') }} +

    +
    + {% trans %} +

    Admin: Can edit group information, as well as + manage organization members.

    +

    Member: Can add/remove datasets from groups

    + {% endtrans %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/members.html b/venv/lib/python2.7/site-packages/ckan/templates/group/members.html new file mode 100644 index 00000000..38c9f8be --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/members.html @@ -0,0 +1,38 @@ +{% extends "group/edit_base.html" %} + +{% block subtitle %}{{ _('Members') }} - {{ c.group_dict.display_name }} - {{ _('Groups') }}{% endblock %} + +{% block page_primary_action %} + {% link_for _('Add Member'), controller='group', action='member_new', id=c.group_dict.id, class_='btn btn-primary', icon='plus-square', named_route=group_type + '_member_new' %} +{% endblock %} + +{% block primary_content_inner %} +

    {{ _('{0} members'.format(c.members|length)) }}

    + + + + + + + + + + {% for user_id, user, role in c.members %} + + + + + + {% endfor %} + +
    {{ _('User') }}{{ _('Role') }}
    + {{ h.linked_user(user_id, maxlength=20) }} + {{ role }} + +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/new.html b/venv/lib/python2.7/site-packages/ckan/templates/group/new.html new file mode 100644 index 00000000..1d78ac40 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/new.html @@ -0,0 +1,13 @@ +{% extends "group/base_form_page.html" %} + +{% block subtitle %}{{ _('Create a Group') }}{% endblock %} + +{% block breadcrumb_link %}{{ h.nav_link(_('Create a Group'), controller='group', action='edit', id=c.group.name) }}{% endblock %} + +{% block page_heading %}{{ _('Create a Group') }}{% endblock %} + +{% block page_header %}{% endblock %} + +{% block secondary_content %} + {% snippet "group/snippets/helper.html" %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/new_group_form.html b/venv/lib/python2.7/site-packages/ckan/templates/group/new_group_form.html new file mode 100644 index 00000000..a330c973 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/new_group_form.html @@ -0,0 +1,25 @@ +{% extends "group/snippets/group_form.html" %} + +{# +As the form is rendered as a seperate page we take advantage of this by +overriding the form blocks depending on the current context +#} +{% block dataset_fields %} + {% if action == "edit" %}{{ super() }}{% endif %} +{% endblock %} + +{% block custom_fields %} + {% if action == "edit" %}{{ super() }}{% endif %} +{% endblock %} + +{% block save_text %} + {%- if action == "edit" -%} + {{ _('Update Group') }} + {%- else -%} + {{ _('Create Group') }} + {%- endif -%} +{% endblock %} + +{% block delete_button %} + {% if action == "edit" %}{{ super() }}{% endif %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/read.html b/venv/lib/python2.7/site-packages/ckan/templates/group/read.html new file mode 100644 index 00000000..731058e3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/read.html @@ -0,0 +1,41 @@ +{% extends "group/read_base.html" %} + +{% block primary_content_inner %} + {% block groups_search_form %} + {% set facets = { + 'fields': c.fields_grouped, + 'search': c.search_facets, + 'titles': c.facet_titles, + 'translated_fields': c.translated_fields, + 'remove_field': c.remove_field } + %} + {% set sorting = [ + (_('Relevance'), 'score desc, metadata_modified desc'), + (_('Name Ascending'), 'title_string asc'), + (_('Name Descending'), 'title_string desc'), + (_('Last Modified'), 'metadata_modified desc'), + (_('Popular'), 'views_recent desc') if g.tracking_enabled else (false, false) ] + %} + {% snippet 'snippets/search_form.html', form_id='group-datasets-search-form', type='dataset', query=c.q, sorting=sorting, sorting_selected=c.sort_by_selected, count=c.page.item_count, facets=facets, placeholder=_('Search datasets...'), show_empty=request.params, fields=c.fields %} + {% endblock %} + {% block packages_list %} + {% if c.page.items %} + {{ h.snippet('snippets/package_list.html', packages=c.page.items) }} + {% endif %} + {% endblock %} + {% block page_pagination %} + {{ c.page.pager(q=c.q) }} + {% endblock %} +{% endblock %} + +{% block secondary_content %} + {{ super() }} +
    +
    + {% for facet in c.facet_titles %} + {{ h.snippet('snippets/facet_list.html', title=c.facet_titles[facet], name=facet, extras={'id':c.group_dict.id}) }} + {% endfor %} +
    + close +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/group/read_base.html b/venv/lib/python2.7/site-packages/ckan/templates/group/read_base.html new file mode 100644 index 00000000..ae05c229 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/group/read_base.html @@ -0,0 +1,29 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ c.group_dict.display_name }} - {{ _('Groups') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {% link_for _('Groups'), controller='group', action='index', named_route=group_type + '_index' %}
  • +
  • {% link_for c.group_dict.display_name|truncate(35), controller='group', action='read', id=c.group_dict.name, named_route=group_type + '_read' %}
  • +{% endblock %} + +{% block content_action %} + {% if h.check_access('group_update', {'id': c.group_dict.id}) %} + {% link_for _('Manage'), controller='group', action='edit', id=c.group_dict.name, class_='btn btn-default', icon='wrench', named_route=group_type + '_edit' %} + {% endif %} +{% endblock %} + +{% block content_primary_nav %} + {{ h.build_nav_icon(group_type + '_read', _('Datasets'), id=c.group_dict.name) }} + {{ h.build_nav_icon(group_type + '_activity', _('Activity Stream'), id=c.group_dict.name, offset=0) }} + {{ h.build_nav_icon(group_type + '_about', _('About'), id=c.group_dict.name) }} +{% endblock %} + +{% block secondary_content %} + {% snippet "group/snippets/info.html", group=c.group_dict, show_nums=true %} +{% endblock %} + +{% block links %} + {{ super() }} + {% include "group/snippets/feeds.html" %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/header.html b/venv/lib/python2.7/site-packages/ckan/templates/header.html new file mode 100644 index 00000000..af6a87f6 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/header.html @@ -0,0 +1,103 @@ +{% block header_wrapper %} {% block header_account %} + +{% endblock %} + +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/home/about.html b/venv/lib/python2.7/site-packages/ckan/templates/home/about.html new file mode 100644 index 00000000..c00ca98c --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/home/about.html @@ -0,0 +1,24 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _('About') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {% link_for _('About'), 'home.about' %}
  • +{% endblock %} + +{% block primary %} +
    +
    + {% block about %} + {% if g.site_about %} + {{ h.render_markdown(g.site_about) }} + {% else %} +

    {{ _('About') }}

    + {% snippet 'home/snippets/about_text.html' %} + {% endif %} + {% endblock %} +
    +
    +{% endblock %} + +{% block secondary %}{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/home/index.html b/venv/lib/python2.7/site-packages/ckan/templates/home/index.html new file mode 100644 index 00000000..b28ac82c --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/home/index.html @@ -0,0 +1,18 @@ +{% extends "page.html" %} +{% set homepage_style = ( g.homepage_style or '1' ) %} + +{% block subtitle %}{{ _("Welcome") }}{% endblock %} + +{% block maintag %}{% endblock %} +{% block toolbar %}{% endblock %} + +{% block content %} +
    +
    + {{ self.flash() }} +
    + {% block primary_content %} + {% snippet "home/layout{0}.html".format(homepage_style) %} + {% endblock %} +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/home/layout1.html b/venv/lib/python2.7/site-packages/ckan/templates/home/layout1.html new file mode 100644 index 00000000..6ab3a43d --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/home/layout1.html @@ -0,0 +1,37 @@ +
    +
    +
    +
    + {% block promoted %} + {% snippet 'home/snippets/promoted.html' %} + {% endblock %} +
    +
    + {% block search %} + {% snippet 'home/snippets/search.html' %} + {% endblock %} +
    +
    +
    +
    +
    +
    +
    +
    + {# Note: this featured_group block is used as an example in the theming + tutorial in the docs! If you change this code, be sure to check + whether you need to update the docs. #} + {# Start template block example. #} + {% block featured_group %} + {% snippet 'home/snippets/featured_group.html' %} + {% endblock %} + {# End template block example. #} +
    +
    + {% block featured_organization %} + {% snippet 'home/snippets/featured_organization.html' %} + {% endblock %} +
    +
    +
    +
    diff --git a/venv/lib/python2.7/site-packages/ckan/templates/home/layout2.html b/venv/lib/python2.7/site-packages/ckan/templates/home/layout2.html new file mode 100644 index 00000000..0ad98af9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/home/layout2.html @@ -0,0 +1,35 @@ +
    +
    +
    +
    + {% block search %} + {% snippet 'home/snippets/search.html' %} + {% endblock %} + {% block stats %} + {% snippet 'home/snippets/stats.html' %} + {% endblock %} +
    +
    + {% block promoted %} + {% snippet 'home/snippets/promoted.html' %} + {% endblock %} +
    +
    +
    +
    +
    +
    +
    +
    + {% block featured_organization %} + {% snippet 'home/snippets/featured_organization.html' %} + {% endblock %} +
    +
    + {% block featured_group %} + {% snippet 'home/snippets/featured_group.html' %} + {% endblock %} +
    +
    +
    +
    diff --git a/venv/lib/python2.7/site-packages/ckan/templates/home/layout3.html b/venv/lib/python2.7/site-packages/ckan/templates/home/layout3.html new file mode 100644 index 00000000..46487014 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/home/layout3.html @@ -0,0 +1,23 @@ +
    +
    + {% block search %} + {% snippet 'home/snippets/search.html' %} + {% endblock %} +
    +
    +
    +
    +
    +
    + {% block promoted %} + {% snippet 'home/snippets/promoted.html' %} + {% endblock %} +
    +
    + {% block stats %} + {% snippet 'home/snippets/stats.html' %} + {% endblock %} +
    +
    +
    +
    diff --git a/venv/lib/python2.7/site-packages/ckan/templates/macros/autoform.html b/venv/lib/python2.7/site-packages/ckan/templates/macros/autoform.html new file mode 100644 index 00000000..7ffc4c59 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/macros/autoform.html @@ -0,0 +1,70 @@ +{# +Builds a form from the supplied form_info list/tuple. All form info dicts +can also contain an "extra_info" key which will add some help text after the +input element. + +form_info - A list of dicts describing the form field to build. +data - The form data object. +errors - The form errors object. +error_summary - A list of errors to display above the fields. + +Example + + {% set form_info = [ + {'name': 'ckan.site_title', 'control': 'input', 'label': _('Site Title'), 'placeholder': ''}, + {'name': 'ckan.main_css', 'control': 'select', 'options': styles, 'label': _('Style'), 'placeholder': ''}, + {'name': 'ckan.site_description', 'control': 'input', 'label': _('Site Tag Line'), 'placeholder': ''}, + {'name': 'ckan.site_logo', 'control': 'input', 'label': _('Site Tag Logo'), 'placeholder': ''}, + {'name': 'ckan.site_about', 'control': 'markdown', 'label': _('About'), 'placeholder': _('About page text')}, + {'name': 'ckan.site_intro_text', 'control': 'markdown', 'label': _('Intro Text'), 'placeholder': _('Text on home page')}, + {'name': 'ckan.site_custom_css', 'control': 'textarea', 'label': _('Custom CSS'), 'placeholder': _('Customisable css inserted into the page header')}, + ] %} + + {% import 'macros/autoform.html' as autoform %} + {{ autoform.generate(form_info, data, errors) }} + +#} +{% import 'macros/form.html' as form %} +{%- macro generate(form_info=[], data={}, errors={}, error_summary=[]) -%} + {{ form.errors(error_summary) if error_summary }} + + {% for item in form_info %} + {% set name = item.name %} + {% set value = data.get(name) %} + {% set error = errors.get(name) %} + {% set id = 'field-%s' % (name|lower|replace('_', '-')|replace('.', '-')) %} + + {% set control = item.control or 'input' %} + {% set label = item.label %} + {% set placeholder = item.placeholder %} + + {% set classes = item.classes or [] %} + {% set classes = ['control-medium'] if not classes and control == 'input' %} + + {% if control == 'select' %} + {% call form.select(name, id=id, label=label, options=item.options, selected=value, error=error) %} + {% if item.extra_info %}{{ form.info(item.extra_info) }}{% endif %} + {% endcall %} + {% elif control == 'html' %} +
    +
    + {{ item.html }} +
    +
    + {% elif control == 'image_upload' %} + {% set field_url = item.field_url or 'image_url' %} + {% set is_upload = data[field_url] and not data[field_url].startswith('http') %} + {% set is_url = data[field_url] and data[field_url].startswith('http') %} + + {% set field_upload = item.field_upload or 'image_upload' %} + {% set field_clear = item.field_clear or 'clear_upload' %} + + {{ form.image_upload(data, errors, is_upload_enabled=item.upload_enabled, is_url=is_url, is_upload=is_upload, upload_label = _('Site logo'), url_label=_('Site logo'), + field_url=field_url, field_upload=field_upload, field_clear=field_clear)}} + {% else %} + {% call form[control](name, id=id, label=label, placeholder=placeholder, value=value, error=error, classes=classes) %} + {% if item.extra_info %}{{ form.info(item.extra_info) }}{% endif %} + {% endcall %} + {% endif %} + {% endfor %} +{%- endmacro -%} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/macros/form.html b/venv/lib/python2.7/site-packages/ckan/templates/macros/form.html new file mode 100644 index 00000000..0a31b97b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/macros/form.html @@ -0,0 +1,444 @@ +{# +Creates all the markup required for an input element. Handles matching labels to +inputs, error messages and other useful elements. + +name - The name of the form parameter. +id - The id to use on the input and label. Convention is to prefix with 'field-'. +label - The human readable label. +value - The value of the input. +placeholder - Some placeholder text. +type - The type of input eg. email, url, date (default: text). +error - A list of error strings for the field or just true to highlight the field. +classes - An array of classes to apply to the form-group. +is_required - Boolean of whether this input is requred for the form to validate + +Examples: + +{% import 'macros/form.html' as form %} +{{ form.input('title', label=_('Title'), value=data.title, error=errors.title) }} + +#} +{% macro input(name, id='', label='', value='', placeholder='', type='text', error="", classes=[], attrs={'class': 'form-control'}, is_required=false) %} +{%- set extra_html = caller() if caller -%} + +{% call input_block(id or name, label or name, error, classes, extra_html=extra_html, is_required=is_required) %} + +{% endcall %} +{% endmacro %} + +{# +Builds a single checkbox input. + +name - The name of the form parameter. +id - The id to use on the input and label. Convention is to prefix with 'field-'. +label - The human readable label. +value - The value of the input. +checked - If true the checkbox will be checked +error - An error string for the field or just true to highlight the field. +classes - An array of classes to apply to the form-group. +is_required - Boolean of whether this input is requred for the form to validate + +Example: + +{% import 'macros/form.html' as form %} +{{ form.checkbox('remember', checked=true) }} + +#} +{% macro checkbox(name, id='', label='', value='', checked=false, placeholder='', error="", classes=[], attrs={}, is_required=false) %} +{%- set extra_html = caller() if caller -%} +
    +
    + + {{ extra_html }} +
    +
    +{% endmacro %} + +{# +Creates all the markup required for an select element. Handles matching labels to +inputs and error messages. + +A field should be a dict with a "value" key and an optional "text" key which +will be displayed to the user. We use a dict to easily allow extension in +future should extra options be required. + +name - The name of the form parameter. +id - The id to use on the input and label. Convention is to prefix with 'field-'. +label - The human readable label. +options - A list/tuple of fields to be used as . + selected - The value of the selected
    {% endblock %} + +{% block main_content %} +
    +
    + {% block form %} +

    {{ _('Are you sure you want to delete organization - {name}?').format(name=c.group_dict.name) }}

    +

    +

    + + +
    +

    + {% endblock %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/organization/confirm_delete_member.html b/venv/lib/python2.7/site-packages/ckan/templates/organization/confirm_delete_member.html new file mode 100644 index 00000000..968890a3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/organization/confirm_delete_member.html @@ -0,0 +1,22 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _("Confirm Delete") }}{% endblock %} + +{% block maintag %}
    {% endblock %} + +{% block main_content %} +
    +
    + {% block form %} +

    {{ _('Are you sure you want to delete member - {name}?').format(name=c.user_dict.name) }}

    +

    +

    + + + +
    +

    + {% endblock %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/organization/edit.html b/venv/lib/python2.7/site-packages/ckan/templates/organization/edit.html new file mode 100644 index 00000000..3fde80a2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/organization/edit.html @@ -0,0 +1,6 @@ +{% extends "organization/base_form_page.html" %} + +{% block subtitle %}{{ _('Edit') }} - {{ super() }}{% endblock %} + +{% block page_heading_class %}hide-heading{% endblock %} +{% block page_heading %}{{ _('Edit Organization') }}{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/organization/edit_base.html b/venv/lib/python2.7/site-packages/ckan/templates/organization/edit_base.html new file mode 100644 index 00000000..80215ca8 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/organization/edit_base.html @@ -0,0 +1,38 @@ +{% extends "page.html" %} + +{% set organization = c.group_dict %} + +{% block subtitle %}{{ c.group_dict.display_name }} - {{ _('Organizations') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {% link_for _('Organizations'), controller='organization', action='index', named_route=group_type + '_index' %}
  • + {% block breadcrumb_content_inner %} +
  • {% link_for organization.display_name|truncate(35), controller='organization', action='read', id=organization.name, named_route=group_type + '_read' %}
  • +
  • {% link_for _('Manage'), controller='organization', action='edit', id=organization.name, named_route=group_type + '_edit' %}
  • + {% endblock %} +{% endblock %} + +{% block content_action %} + {% if organization and h.check_access('organization_update', {'id': organization.id}) %} + {% link_for _('View'), controller='organization', action='read', id=organization.name, class_='btn btn-default', icon='eye', named_route=group_type + '_read' %} + {% endif %} +{% endblock %} + +{% block content_primary_nav %} + {{ h.build_nav_icon(group_type + '_edit', _('Edit'), id=c.group_dict.name) }} + {{ h.build_nav_icon(group_type + '_bulk_process', _('Datasets'), id=c.group_dict.name) }} + {{ h.build_nav_icon(group_type + '_members', _('Members'), id=c.group_dict.name) }} +{% endblock %} + +{% block secondary_content %} + {% if organization %} + {% snippet 'snippets/organization.html', organization=organization %} + {% else %} + {{ super() }} + {% endif %} +{% endblock %} + +{% block links %} + {{ super() }} + {% include "organization/snippets/feeds.html" %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/organization/index.html b/venv/lib/python2.7/site-packages/ckan/templates/organization/index.html new file mode 100644 index 00000000..c252f1cd --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/organization/index.html @@ -0,0 +1,43 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _('Organizations') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {% link_for _('Organizations'), controller='organization', action='index', named_route=group_type + '_index' %}
  • +{% endblock %} + +{% block page_header %}{% endblock %} + +{% block page_primary_action %} + {% if h.check_access('organization_create') %} + {% link_for _('Add Organization'), controller='organization', action='new', class_='btn btn-primary', icon='plus-square', named_route=group_type + '_new' %} + {% endif %} +{% endblock %} + +{% block primary_content_inner %} +

    {% block page_heading %}{{ _('Organizations') }}{% endblock %}

    + {% block organizations_search_form %} + {% snippet 'snippets/search_form.html', form_id='organization-search-form', type='organization', query=c.q, sorting_selected=c.sort_by_selected, count=c.page.item_count, placeholder=_('Search organizations...'), show_empty=request.params, no_bottom_border=true if c.page.items, sorting = [(_('Name Ascending'), 'title asc'), (_('Name Descending'), 'title desc')] %} + {% endblock %} + {% block organizations_list %} + {% if c.page.items or request.params %} + {% if c.page.items %} + {% snippet "organization/snippets/organization_list.html", organizations=c.page.items %} + {% endif %} + {% else %} +

    + {{ _('There are currently no organizations for this site') }}. + {% if h.check_access('organization_create') %} + {% link_for _('How about creating one?'), controller='organization', action='new' %}. + {% endif %} +

    + {% endif %} + {% endblock %} + {% block page_pagination %} + {{ c.page.pager(q=c.q or '', sort=c.sort_by_selected or '') }} + {% endblock %} +{% endblock %} + +{% block secondary_content %} + {% snippet "organization/snippets/helper.html" %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/organization/member_new.html b/venv/lib/python2.7/site-packages/ckan/templates/organization/member_new.html new file mode 100644 index 00000000..828ca693 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/organization/member_new.html @@ -0,0 +1,97 @@ +{% extends "organization/edit_base.html" %} + +{% import 'macros/form.html' as form %} + +{% set user = c.user_dict %} + +{% block subtitle %}{{ _('Edit Member') if user else _('Add Member') }} - {{ super() }}{% endblock %} + +{% block primary_content_inner %} + {% link_for _('Back to all members'), controller='organization', action='members', id=organization.name, class_='btn btn-default pull-right', icon='arrow-left', named_route=group_type + '_members' %} +

    + {% block page_heading %}{{ _('Edit Member') if user else _('Add Member') }}{% endblock %} +

    + {% block form %} +
    +
    +
    +
    + {% if not user %} + +

    + {{ _('If you wish to add an existing user, search for their username below.') }} +

    + {% endif %} +
    + {% if user %} + + + {% else %} + + {% endif %} +
    +
    +
    + {% if not user %} +
    +
    + {{ _('or') }} +
    +
    +
    +
    + +

    + {{ _('If you wish to invite a new user, enter their email address.') }} +

    +
    + +
    +
    +
    + {% endif %} +
    + + {% set format_attrs = {'data-module': 'autocomplete'} %} + {{ form.select('role', label=_('Role'), options=c.roles, selected=c.user_role, error='', attrs=format_attrs) }} +
    + {% if user %} + {{ _('Delete') }} + + {% else %} + + {% endif %} +
    +
    + {% endblock %} +{% endblock %} + +{% block secondary_content %} +{{ super() }} +
    +

    + + {{ _('What are roles?') }} +

    +
    + {% trans %} +

    Admin: Can add/edit and delete datasets, as well as + manage organization members.

    +

    Editor: Can add and edit datasets, but not manage + organization members.

    +

    Member: Can view the organization's private + datasets, but not add new datasets.

    + {% endtrans %} +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/organization/members.html b/venv/lib/python2.7/site-packages/ckan/templates/organization/members.html new file mode 100644 index 00000000..618c1f62 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/organization/members.html @@ -0,0 +1,43 @@ +{% extends "organization/edit_base.html" %} + +{% block subtitle %}{{ _('Members') }} - {{ super() }}{% endblock %} + +{% block page_primary_action %} + {% if h.check_access('organization_update', {'id': organization.id}) %} + + {% link_for _('Add Member'), controller='organization', action='member_new', id=c.group_dict.id, class_='btn btn-primary', icon='plus-square', named_route=group_type + '_member_new' %} + {% endif %} +{% endblock %} + +{% block primary_content_inner %} + {% set count = c.members|length %} + {% set members_count = ungettext('{count} member', '{count} members', count).format(count=count) %} +

    {{ members_count }}

    + + + + + + + + + + {% for user_id, user, role in c.members %} + + + + + + {% endfor %} + +
    {{ _('User') }}{{ _('Role') }}
    + {{ h.linked_user(user_id, maxlength=20) }} + {{ role }} + +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/organization/new.html b/venv/lib/python2.7/site-packages/ckan/templates/organization/new.html new file mode 100644 index 00000000..7af6a1d0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/organization/new.html @@ -0,0 +1,17 @@ +{% extends "organization/base_form_page.html" %} + +{% block subtitle %}{{ _('Create an Organization') }}{% endblock %} + +{% block breadcrumb_link %}{{ h.nav_link(_('Create an Organization'), controller='organization', action='edit', id=c.organization.name) }}{% endblock %} + +{% block page_heading %}{{ _('Create an Organization') }}{% endblock %} + +{% block page_header %}{% endblock %} + +{% block breadcrumb_content_inner %} +
  • {{ _('Create an Organization') }}
  • +{% endblock %} + +{% block secondary_content %} + {% snippet "organization/snippets/helper.html" %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/organization/new_organization_form.html b/venv/lib/python2.7/site-packages/ckan/templates/organization/new_organization_form.html new file mode 100644 index 00000000..27ae8ff5 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/organization/new_organization_form.html @@ -0,0 +1,25 @@ +{% extends "organization/snippets/organization_form.html" %} + +{# +As the form is rendered as a seperate page we take advantage of this by +overriding the form blocks depending on the current context +#} +{% block dataset_fields %} + {% if action == "edit" %}{{ super() }}{% endif %} +{% endblock %} + +{% block custom_fields %} + {% if action == "edit" %}{{ super() }}{% endif %} +{% endblock %} + +{% block save_text %} + {%- if action == "edit" -%} + {{ _('Update Organization') }} + {%- else -%} + {{ _('Create Organization') }} + {%- endif -%} +{% endblock %} + +{% block delete_button %} + {% if action == "edit" %}{{ super() }}{% endif %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/organization/read.html b/venv/lib/python2.7/site-packages/ckan/templates/organization/read.html new file mode 100644 index 00000000..12f1b55f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/organization/read.html @@ -0,0 +1,46 @@ +{% extends "organization/read_base.html" %} + +{% block page_primary_action %} + {% if h.check_access('package_create', {'owner_org': c.group_dict.id}) %} + {% snippet 'snippets/add_dataset.html', group=c.group_dict.id %} + {% endif %} +{% endblock %} + +{% block primary_content_inner %} + {% block groups_search_form %} + {% set facets = { + 'fields': c.fields_grouped, + 'search': c.search_facets, + 'titles': c.facet_titles, + 'translated_fields': c.translated_fields, + 'remove_field': c.remove_field } + %} + {% set sorting = [ + (_('Relevance'), 'score desc, metadata_modified desc'), + (_('Name Ascending'), 'title_string asc'), + (_('Name Descending'), 'title_string desc'), + (_('Last Modified'), 'metadata_modified desc'), + (_('Popular'), 'views_recent desc') if g.tracking_enabled else (false, false) ] + %} + {% snippet 'snippets/search_form.html', form_id='organization-datasets-search-form', type='dataset', query=c.q, sorting=sorting, sorting_selected=c.sort_by_selected, count=c.page.item_count, facets=facets, placeholder=_('Search datasets...'), show_empty=request.params, fields=c.fields %} + {% endblock %} + {% block packages_list %} + {% if c.page.items %} + {{ h.snippet('snippets/package_list.html', packages=c.page.items) }} + {% endif %} + {% endblock %} + {% block page_pagination %} + {{ c.page.pager(q=c.q) }} + {% endblock %} +{% endblock %} + +{% block organization_facets %} +
    +
    + {% for facet in c.facet_titles %} + {{ h.snippet('snippets/facet_list.html', title=c.facet_titles[facet], name=facet, extras={'id':c.group_dict.id}) }} + {% endfor %} +
    + close +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/organization/read_base.html b/venv/lib/python2.7/site-packages/ckan/templates/organization/read_base.html new file mode 100644 index 00000000..e2014153 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/organization/read_base.html @@ -0,0 +1,30 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ c.group_dict.display_name }} - {{ _('Organizations') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {% link_for _('Organizations'), controller='organization', action='index', named_route=group_type + '_index' %}
  • +
  • {% link_for c.group_dict.display_name|truncate(35), controller='organization', action='read', id=c.group_dict.name, named_route=group_type + '_read' %}
  • +{% endblock %} + +{% block content_action %} + {% if h.check_access('organization_update', {'id': c.group_dict.id}) %} + {% link_for _('Manage'), controller='organization', action='edit', id=c.group_dict.name, class_='btn btn-default', icon='wrench', named_route=group_type + '_edit' %} + {% endif %} +{% endblock %} + +{% block content_primary_nav %} + {{ h.build_nav_icon(group_type + '_read', _('Datasets'), id=c.group_dict.name) }} + {{ h.build_nav_icon(group_type + '_activity', _('Activity Stream'), id=c.group_dict.name, offset=0) }} + {{ h.build_nav_icon(group_type + '_about', _('About'), id=c.group_dict.name) }} +{% endblock %} + +{% block secondary_content %} + {% snippet 'snippets/organization.html', organization=c.group_dict, show_nums=true, group_type=group_type %} + {% block organization_facets %}{% endblock %} +{% endblock %} + +{% block links %} + {{ super() }} + {% include "organization/snippets/feeds.html" %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/activity.html b/venv/lib/python2.7/site-packages/ckan/templates/package/activity.html new file mode 100644 index 00000000..3941a6dd --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/activity.html @@ -0,0 +1,10 @@ +{% extends "package/read_base.html" %} + +{% block subtitle %}{{ _('Activity Stream') }} - {{ super() }}{% endblock %} + +{% block primary_content_inner %} +

    {% block page_heading %}{{ _('Activity Stream') }}{% endblock %}

    + {% block activity_stream %} + {{ c.package_activity_stream | safe }} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/base.html b/venv/lib/python2.7/site-packages/ckan/templates/package/base.html new file mode 100644 index 00000000..43622c75 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/base.html @@ -0,0 +1,25 @@ +{% extends "page.html" %} + +{% set pkg = c.pkg_dict or pkg_dict %} + +{% block breadcrumb_content_selected %} class="active"{% endblock %} + +{% block subtitle %}{{ _('Datasets') }}{% endblock %} + +{% block breadcrumb_content %} + {% if pkg %} + {% set dataset = h.dataset_display_name(pkg) %} + {% if pkg.organization %} + {% set organization = h.get_translated(pkg.organization, 'title') or pkg.organization.name %} + {% set group_type = pkg.organization.type %} +
  • {% link_for _('Organizations'), controller='organization', action='index', named_route=group_type + '_index' %}
  • +
  • {% link_for organization|truncate(30), controller='organization', action='read', id=pkg.organization.name, named_route=group_type + '_read' %}
  • + {% else %} +
  • {% link_for _('Datasets'), controller='package', action='search' %}
  • + {% endif %} + {% link_for dataset|truncate(30), controller='package', action='read', id=pkg.name %} + {% else %} +
  • {% link_for _('Datasets'), controller='package', action='search' %}
  • +
  • {{ _('Create Dataset') }}
  • + {% endif %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/base_form_page.html b/venv/lib/python2.7/site-packages/ckan/templates/package/base_form_page.html new file mode 100644 index 00000000..2aae2f3b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/base_form_page.html @@ -0,0 +1,39 @@ +{% extends "package/edit_base.html" %} + +{% block primary_content %} +
    + {% block page_header %}{% endblock %} +
    + {% block primary_content_inner %} + {% block form %} + {#- passing c to a snippet is bad but is required here + for backwards compatibility with old templates and + plugins using setup_template_variables() -#} + {{- h.snippet(form_snippet, c=c, **form_vars) -}} + {% endblock %} + {% endblock %} +
    +
    +{% endblock %} + +{% block secondary_content %} + {% block info_module %} +
    +

    {{ _('What are datasets?') }}

    +
    +

    + {% trans %} + A CKAN Dataset is a collection of data resources (such as files), + together with a description and other information, at a fixed URL. + Datasets are what users see when searching for data. + {% endtrans %} +

    +
    +
    + {% endblock %} + + {% block resources_module %} + {# TODO: Pass in a list of previously created resources and the current package dict #} + {% snippet "package/snippets/resources.html", pkg={}, action='new_resource' %} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/confirm_delete.html b/venv/lib/python2.7/site-packages/ckan/templates/package/confirm_delete.html new file mode 100644 index 00000000..1cc19928 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/confirm_delete.html @@ -0,0 +1,22 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _("Confirm Delete") }}{% endblock %} + +{% block maintag %}
    {% endblock %} + +{% block main_content %} +
    +
    + {% block form %} + {% set dataset = h.dataset_display_name(c.pkg_dict) %} +

    {{ _('Are you sure you want to delete dataset - {name}?').format(name=dataset) }}

    +

    +

    + + +
    +

    + {% endblock %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/confirm_delete_resource.html b/venv/lib/python2.7/site-packages/ckan/templates/package/confirm_delete_resource.html new file mode 100644 index 00000000..4a610cd5 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/confirm_delete_resource.html @@ -0,0 +1,21 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _("Confirm Delete") }}{% endblock %} + +{% block maintag %}
    {% endblock %} + +{% block main_content %} +
    +
    + {% block form %} +

    {{ _('Are you sure you want to delete resource - {name}?').format(name=h.resource_display_name(c.resource_dict)) }}

    +

    +

    + + +
    +

    + {% endblock %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/edit.html b/venv/lib/python2.7/site-packages/ckan/templates/package/edit.html new file mode 100644 index 00000000..a4c120b0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/edit.html @@ -0,0 +1,10 @@ +{% extends 'package/edit_base.html' %} + +{% block primary_content_inner %} + {% block form %} + {#- passing c to a snippet is bad but is required here + for backwards compatibility with old templates and + plugins using setup_template_variables() -#} + {{- h.snippet(form_snippet, c=c, **form_vars) -}} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/edit_base.html b/venv/lib/python2.7/site-packages/ckan/templates/package/edit_base.html new file mode 100644 index 00000000..9487f512 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/edit_base.html @@ -0,0 +1,26 @@ +{% extends 'package/base.html' %} + +{% set pkg = c.pkg_dict %} +{% set pkg_dict = c.pkg_dict %} + +{% block breadcrumb_content_selected %}{% endblock %} + +{% block breadcrumb_content %} + {{ super() }} + {% if pkg %} +
  • {% link_for _('Edit'), controller='package', action='edit', id=pkg.name %}
  • + {% endif %} +{% endblock %} + +{% block content_action %} + {% link_for _('View dataset'), controller='package', action='read', id=pkg.name, class_='btn btn-default', icon='eye' %} +{% endblock %} + +{% block content_primary_nav %} + {{ h.build_nav_icon('dataset_edit', _('Edit metadata'), id=pkg.name) }} + {{ h.build_nav_icon('dataset_resources', _('Resources'), id=pkg.name) }} +{% endblock %} + +{% block secondary_content %} + {% snippet 'package/snippets/info.html', pkg=pkg, hide_follow_button=true %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/edit_view.html b/venv/lib/python2.7/site-packages/ckan/templates/package/edit_view.html new file mode 100644 index 00000000..923cba50 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/edit_view.html @@ -0,0 +1,24 @@ +{% extends "package/view_edit_base.html" %} + +{% block subtitle %}{{ _('Edit view') }} - {{ h.resource_display_name(c.resource) }}{% endblock %} +{% block form_title %}{{ _('Edit view') }}{% endblock %} + +{% block breadcrumb_content %} + {{ super() }} +
  • {{ _('Edit view') }}
  • +{% endblock %} + +{% block content_primary_nav %} +
  • {{ _('Edit view') }}
  • +{% endblock %} + +{% block form %} +
    + {% include 'package/snippets/view_form.html' %} +
    + + + +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/followers.html b/venv/lib/python2.7/site-packages/ckan/templates/package/followers.html new file mode 100644 index 00000000..7ede940b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/followers.html @@ -0,0 +1,10 @@ +{% extends "package/read_base.html" %} + +{% block subtitle %}{{ _('Followers') }} - {{ h.dataset_display_name(c.pkg_dict) }}{% endblock %} + +{% block primary_content_inner %} +

    {% block page_heading %}{{ _('Followers') }}{% endblock %}

    + {% block followers_list %} + {% snippet "user/snippets/followers.html", followers=c.followers %} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/group_list.html b/venv/lib/python2.7/site-packages/ckan/templates/package/group_list.html new file mode 100644 index 00000000..ce409594 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/group_list.html @@ -0,0 +1,26 @@ +{% extends "package/read_base.html" %} +{% import 'macros/form.html' as form %} + +{% block primary_content_inner %} +

    {{ _('Groups') }}

    + + {% if c.group_dropdown %} +
    + + +
    + {% endif %} + + {% if c.pkg_dict.groups %} +
    + {% snippet 'group/snippets/group_list.html', groups=c.pkg_dict.groups %} +
    + {% else %} +

    {{ _('There are no groups associated with this dataset') }}

    + {% endif %} + +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/history.html b/venv/lib/python2.7/site-packages/ckan/templates/package/history.html new file mode 100644 index 00000000..49fbf369 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/history.html @@ -0,0 +1,10 @@ +{% extends "package/read_base.html" %} + +{% block subtitle %}{{ _('History') }} - {{ h.dataset_display_name(c.pkg_dict) }}{% endblock %} + +{% block primary_content_inner %} +

    {{ _('History') }}

    + {% block package_history_revisions %} + {% snippet "package/snippets/history_revisions.html", pkg_dict=pkg, pkg_revisions=c.pkg_revisions %} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/new.html b/venv/lib/python2.7/site-packages/ckan/templates/package/new.html new file mode 100644 index 00000000..9ef0635c --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/new.html @@ -0,0 +1,10 @@ +{% if not h.organizations_available('create_dataset') + and not h.check_config_permission('ckan.auth.create_unowned_dataset') %} + + {% include "package/snippets/cannot_create_package.html" %} + +{% else %} + {% extends "package/base_form_page.html" %} + + {% block subtitle %}{{ _('Create Dataset') }}{% endblock %} +{% endif %} \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/new_package_form.html b/venv/lib/python2.7/site-packages/ckan/templates/package/new_package_form.html new file mode 100644 index 00000000..06fd718d --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/new_package_form.html @@ -0,0 +1,29 @@ +{% extends 'package/snippets/package_form.html' %} + +{% set form_style = c.form_style or c.action %} + +{% block stages %} + {% if form_style != 'edit' %} + {{ super() }} + {% endif %} +{% endblock %} + +{% block save_button_text %} + {% if form_style != 'edit' %} + {{ super() }} + {% else %} + {{ _('Update Dataset') }} + {% endif %} +{% endblock %} + +{% block cancel_button %} + {% if form_style != 'edit' %} + {{ super() }} + {% endif %} +{% endblock %} + +{% block delete_button %} + {% if form_style == 'edit' and h.check_access('package_delete', {'id': c.pkg_dict.id}) %} + {{ super() }} + {% endif %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/new_resource.html b/venv/lib/python2.7/site-packages/ckan/templates/package/new_resource.html new file mode 100644 index 00000000..19518c74 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/new_resource.html @@ -0,0 +1,24 @@ +{% extends "package/base_form_page.html" %} + +{% set logged_in = true if c.userobj else false %} + +{% block subtitle %}{{ _('Add data to the dataset') }}{% endblock %} + +{% block breadcrumb_content_selected %}{% endblock %} +{% block breadcrumb_content %} + {{ super() }} + {% if pkg %} +
  • {{ _('Add New Resource') }}
  • + {% endif %} +{% endblock %} + +{% block form %}{% snippet resource_form_snippet, data=data, errors=errors, error_summary=error_summary, include_metadata=false, pkg_name=pkg_name, stage=stage, allow_upload=g.ofs_impl and logged_in, dataset_type=dataset_type %}{% endblock %} + +{% block secondary_content %} + {% snippet 'package/snippets/resource_help.html' %} +{% endblock %} + +{% block scripts %} + {{ super() }} + {% resource 'vendor/fileupload' %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/new_resource_not_draft.html b/venv/lib/python2.7/site-packages/ckan/templates/package/new_resource_not_draft.html new file mode 100644 index 00000000..06cdbab3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/new_resource_not_draft.html @@ -0,0 +1,21 @@ +{% extends "package/resource_edit_base.html" %} + +{% block subtitle %}{{ _('Add resource') }} - {{ h.dataset_display_name(pkg) }}{% endblock %} +{% block form_title %}{{ _('Add resource') }}{% endblock %} + +{% block breadcrumb_content %} + {{ super() }} +
  • {{ _('Add New Resource') }}
  • +{% endblock %} + +{% block form %} + {% snippet resource_form_snippet, data=data, errors=errors, error_summary=error_summary, include_metadata=false, pkg_name=pkg_name, stage=stage, allow_upload=g.ofs_impl and logged_in, dataset_type=dataset_type %} +{% endblock %} + +{% block content_primary_nav %} +
  • {{ _('New resource') }}
  • +{% endblock %} + +{% block secondary_content %} + {% snippet 'package/snippets/resource_help.html' %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/new_view.html b/venv/lib/python2.7/site-packages/ckan/templates/package/new_view.html new file mode 100644 index 00000000..b7fea825 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/new_view.html @@ -0,0 +1,32 @@ +{% extends "package/view_edit_base.html" %} + +{% block subtitle %}{{ _('Add view') }} - {{ h.resource_display_name(c.resource) }}{% endblock %} +{% block form_title %}{{ _('Add view') }}{% endblock %} + +{% block breadcrumb_content %} + {{ super() }} +
  • {{ _('Add view') }}
  • +{% endblock %} + +{% block content_primary_nav %} +
  • {{ _('Add view') }}
  • +{% endblock %} + +{% block form %} + {% if resource_view.view_type == 'recline_view' and not datastore_available %} +

    + + {% trans %} + Data Explorer views may be slow and unreliable unless the DataStore extension is enabled. For more information, please see the Data Explorer documentation. + {% endtrans %} +

    + {% endif %} + +
    + {% include 'package/snippets/view_form.html' %} +
    + + +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/read.html b/venv/lib/python2.7/site-packages/ckan/templates/package/read.html new file mode 100644 index 00000000..708e1508 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/read.html @@ -0,0 +1,48 @@ +{% extends "package/read_base.html" %} + +{% set pkg = c.pkg_dict %} + +{% block primary_content_inner %} + {{ super() }} + {% block package_description %} + {% if pkg.private %} + + + {{ _('Private') }} + + {% endif %} +

    + {% block page_heading %} + {{ h.dataset_display_name(pkg) }} + {% if pkg.state.startswith('draft') %} + [{{ _('Draft') }}] + {% endif %} + {% if pkg.state == 'deleted' %} + [{{ _('Deleted') }}] + {% endif %} + {% endblock %} +

    + {% block package_notes %} + {% if pkg.notes %} +
    + {{ h.render_markdown(h.get_translated(pkg, 'notes')) }} +
    + {% endif %} + {% endblock %} + {# FIXME why is this here? seems wrong #} + + {% endblock %} + + {% block package_resources %} + {% snippet "package/snippets/resources_list.html", pkg=pkg, resources=pkg.resources %} + {% endblock %} + + {% block package_tags %} + {% snippet "package/snippets/tags.html", tags=pkg.tags %} + {% endblock %} + + {% block package_additional_info %} + {% snippet "package/snippets/additional_info.html", pkg_dict=pkg %} + {% endblock %} + +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/read_base.html b/venv/lib/python2.7/site-packages/ckan/templates/package/read_base.html new file mode 100644 index 00000000..7012e14e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/read_base.html @@ -0,0 +1,62 @@ +{% extends "package/base.html" %} + +{% block subtitle %}{{ h.dataset_display_name(pkg) }} - {{ super() }}{% endblock %} + +{% block head_extras -%} + {{ super() }} + {% set description = h.markdown_extract(pkg.notes, extract_length=200)|forceescape %} + + +{% endblock -%} + +{% block content_action %} + {% if h.check_access('package_update', {'id':pkg.id }) %} + {% link_for _('Manage'), controller='package', action='edit', id=pkg.name, class_='btn btn-default', icon='wrench' %} + {% endif %} +{% endblock %} + +{% block content_primary_nav %} + {{ h.build_nav_icon('dataset_read', _('Dataset'), id=pkg.name) }} + {{ h.build_nav_icon('dataset_groups', _('Groups'), id=pkg.name) }} + {{ h.build_nav_icon('dataset_activity', _('Activity Stream'), id=pkg.name) }} +{% endblock %} + +{% block primary_content_inner %} + {% block package_revision_info %} + {% if c.revision_date %} +
    +

    + {% set timestamp = h.render_datetime(c.revision_date, with_hours=True) %} + {% set url = h.url_for(controller='package', action='read', id=pkg.name) %} + + {% trans timestamp=timestamp, url=url %}This is an old revision of this dataset, as edited at {{ timestamp }}. It may differ significantly from the current revision.{% endtrans %} +

    +
    + {% endif %} + {% endblock %} +{% endblock %} + +{% block secondary_content %} + + {% block secondary_help_content %}{% endblock %} + + {% block package_info %} + {% snippet 'package/snippets/info.html', pkg=pkg %} + {% endblock %} + + {% block package_organization %} + {% if pkg.organization %} + {% set org = h.get_organization(pkg.organization.name) %} + {% snippet "snippets/organization.html", organization=org, has_context_title=true %} + {% endif %} + {% endblock %} + + {% block package_social %} + {% snippet "snippets/social.html" %} + {% endblock %} + + {% block package_license %} + {% snippet "snippets/license.html", pkg_dict=pkg %} + {% endblock %} + +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/resource_edit.html b/venv/lib/python2.7/site-packages/ckan/templates/package/resource_edit.html new file mode 100644 index 00000000..c68edd27 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/resource_edit.html @@ -0,0 +1,15 @@ +{% extends "package/resource_edit_base.html" %} + +{% block subtitle %}{{ _('Edit') }} - {{ h.resource_display_name(res) }} - {{ h.dataset_display_name(pkg) }}{% endblock %} + +{% block form %} + {% snippet 'package/snippets/resource_edit_form.html', + data=data, + errors=errors, + error_summary=error_summary, + pkg_name=pkg.name, + form_action=c.form_action, + allow_upload=g.ofs_impl and logged_in, + resource_form_snippet=resource_form_snippet, + dataset_type=dataset_type %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/resource_edit_base.html b/venv/lib/python2.7/site-packages/ckan/templates/package/resource_edit_base.html new file mode 100644 index 00000000..97ccbf49 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/resource_edit_base.html @@ -0,0 +1,41 @@ +{% extends "package/base.html" %} + +{% set logged_in = true if c.userobj else false %} +{% set res = c.resource %} + +{% block breadcrumb_content_selected %}{% endblock %} + +{% block breadcrumb_content %} + {{ super() }} + {% if res %} +
  • {% link_for h.resource_display_name(res)|truncate(30), controller='package', action='resource_read', id=pkg.name, resource_id=res.id %}
  • + {{ _('Edit') }} + {% endif %} +{% endblock %} + +{% block content_action %} + {% link_for _('All resources'), controller='package', action='resources', id=pkg.name, class_='btn btn-default', icon='arrow-left' %} + {% if res %} + {% link_for _('View resource'), controller='package', action='resource_read', id=pkg.name, resource_id=res.id, class_='btn btn-default', icon='eye' %} + {% endif %} +{% endblock %} + +{% block content_primary_nav %} + {{ h.build_nav_icon('resource_edit', _('Edit resource'), id=pkg.name, resource_id=res.id) }} + {% block inner_primary_nav %}{% endblock %} + {{ h.build_nav_icon('views', _('Views'), id=pkg.name, resource_id=res.id) }} +{% endblock %} + +{% block primary_content_inner %} +

    {% block form_title %}{{ _('Edit resource') }}{% endblock %}

    + {% block form %}{% endblock %} +{% endblock %} + +{% block secondary_content %} + {% snippet 'package/snippets/resource_info.html', res=res %} +{% endblock %} + +{% block scripts %} + {{ super() }} + {% resource 'vendor/fileupload' %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/resource_read.html b/venv/lib/python2.7/site-packages/ckan/templates/package/resource_read.html new file mode 100644 index 00000000..bd2b08d0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/resource_read.html @@ -0,0 +1,213 @@ +{% extends "package/base.html" %} + +{% set res = c.resource %} + +{% block head_extras -%} + {{ super() }} + {% set description = h.markdown_extract(h.get_translated(res, 'description'), extract_length=200) if res.description else h.markdown_extract(h.get_translated(c.package, 'notes'), extract_length=200) %} + + +{% endblock -%} + +{% block subtitle %}{{ h.dataset_display_name(c.package) }} - {{ h.resource_display_name(res) }}{% endblock %} + +{% block breadcrumb_content_selected %}{% endblock %} + +{% block breadcrumb_content %} + {{ super() }} +
  • {{ h.resource_display_name(res)|truncate(30) }}
  • +{% endblock %} + +{% block pre_primary %} + {% block resource %} +
    + {% block resource_inner %} +
    +
    + {% block resource_actions %} + + {% endblock %} +
    + {% block resource_content %} + {% block resource_read_title %}

    {{ h.resource_display_name(res) | truncate(50) }}

    {% endblock %} + {% block resource_read_url %} + {% if res.url and h.is_url(res.url) %} +

    {{ _('URL:') }} {{ res.url }}

    + {% elif res.url %} +

    {{ _('URL:') }} {{ res.url }}

    + {% endif %} + {% endblock %} +
    + {% if res.description %} + {{ h.render_markdown(res.description) }} + {% endif %} + {% if not res.description and c.package.notes %} +

    {{ _('From the dataset abstract') }}

    +
    {{ h.markdown_extract(h.get_translated(c.package, 'notes')) }}
    +

    {% trans dataset=c.package.title, url=h.url_for(controller='package', action='read', id=c.package['name']) %}Source: {{ dataset }}{% endtrans %} + {% endif %} +

    + {% endblock %} + {% block data_preview %} + {% block resource_view %} + {% block resource_view_nav %} + {% set resource_preview = h.resource_preview(c.resource, c.package) %} + {% snippet "package/snippets/resource_views_list.html", + views=resource_views, + pkg=pkg, + is_edit=false, + view_id=current_resource_view['id'], + resource_preview=resource_preview, + resource=c.resource, + extra_class="nav-tabs nav-tabs-plain" + %} + {% endblock %} + {% block resource_view_content %} +
    + {% set resource_preview = h.resource_preview(c.resource, c.package) %} + {% set views_created = res.has_views or resource_preview %} + {% if views_created %} + {% if resource_preview and not current_resource_view %} + {{ h.resource_preview(c.resource, c.package) }} + {% else %} + {% for resource_view in resource_views %} + {% if resource_view == current_resource_view %} + {% snippet 'package/snippets/resource_view.html', + resource_view=resource_view, + resource=c.resource, + package=c.package + %} + {% endif %} + {% endfor %} + {% endif %} + {% else %} + {# Views not created #} +
    +

    {{ _("There are no views created for this resource yet.") }}

    + {% if h.check_access('resource_view_create', {'resource_id': c.resource.id}) %} +

    + + {{ _("Not seeing the views you were expecting?")}} + + {{ _('Click here for more information.') }} +

    +
    +

    {{ _('Here are some reasons you may not be seeing expected views:') }}

    +
      +
    • {{ _("No view has been created that is suitable for this resource")}}
    • +
    • {{ _("The site administrators may not have enabled the relevant view plugins")}}
    • +
    • {{ _("If a view requires the DataStore, the DataStore plugin may not be enabled, or the data may not have been pushed to the DataStore, or the DataStore hasn't finished processing the data yet")}}
    • +
    +
    + {% endif %} +
    + {% endif %} +
    + {% endblock %} +
    + {% endblock %} + {% endblock %} + {% endblock %} +
    + {% endblock %} +{% endblock %} + +{% block primary_content %} + {% block resource_additional_information %} + {% if res %} +
    + {% block resource_additional_information_inner %} +
    +

    {{ _('Additional Information') }}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% for key, value in h.format_resource_items(res.items()) %} + + {% endfor %} + +
    {{ _('Field') }}{{ _('Value') }}
    {{ _('Data last updated') }}{{ h.render_datetime(res.last_modified) or h.render_datetime(res.created) or _('unknown') }}
    {{ _('Metadata last updated') }}{{ h.render_datetime(res.revision_timestamp) or h.render_datetime(res.created) or _('unknown') }}
    {{ _('Created') }}{{ h.render_datetime(res.created) or _('unknown') }}
    {{ _('Format') }}{{ res.mimetype_inner or res.mimetype or res.format or _('unknown') }}
    {{ _('License') }}{% snippet "snippets/license.html", pkg_dict=pkg, text_only=True %}
    {{ key }}{{ value }}
    +
    + {% endblock %} +
    + {% endif %} + {% endblock %} +{% endblock %} + +{% block secondary_content %} + + {% block resources_list %} + {% snippet "package/snippets/resources.html", pkg=pkg, active=res.id %} + {% endblock %} + + {% block resource_license %} + {% snippet "snippets/social.html" %} + {% endblock %} +{% endblock %} + diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/resource_views.html b/venv/lib/python2.7/site-packages/ckan/templates/package/resource_views.html new file mode 100644 index 00000000..68bedb28 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/resource_views.html @@ -0,0 +1,29 @@ +{% import 'macros/form.html' as form %} +{% extends "package/resource_edit_base.html" %} + +{% block subtitle %}{{ _('View') }} - {{ h.resource_display_name(res) }}{% endblock %} + +{% block page_primary_action %} +
    + + + {{ _('New view') }} + + + +
    + {% resource 'vendor/reorder' %} +{% endblock %} + +{% block primary_content_inner %} + {% if c.views %} + {% snippet "package/snippets/resource_views_list.html", views=c.views, pkg=pkg, is_edit=true, extra_attributes='data-module = resource-view-reorder data-module-id=' + c.resource.id, extra_class='resource-view-list nav-pills nav-stacked' %} + {% else %} +

    {{ _('This resource has no views') }}

    + {% endif %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/resources.html b/venv/lib/python2.7/site-packages/ckan/templates/package/resources.html new file mode 100644 index 00000000..dcb708e6 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/resources.html @@ -0,0 +1,31 @@ +{% extends "package/edit_base.html" %} + +{% set has_reorder = c.pkg_dict and c.pkg_dict.resources and c.pkg_dict.resources|length > 0 %} + +{% block subtitle %}{{ _('Resources') }} - {{ h.dataset_display_name(pkg) }}{% endblock %} + +{% block page_primary_action %} + {% link_for _('Add new resource'), controller='package', action='new_resource', id=c.pkg_dict.name, class_='btn btn-primary', icon='plus' %} +{% endblock %} + +{% block primary_content_inner %} + {% if pkg.resources %} +
      + {% set can_edit = h.check_access('package_update', {'id':pkg.id }) %} + {% for resource in pkg.resources %} + {% snippet 'package/snippets/resource_item.html', pkg=pkg, res=resource, url_is_edit=true, can_edit=can_edit %} + {% endfor %} +
    + {% else %} + {% trans url=h.url_for(controller='package', action='new_resource', id=pkg.name) %} +

    This dataset has no data, why not add some?

    + {% endtrans %} + {% endif %} +{% endblock %} + +{% block scripts %} + {{ super() }} + {% if has_reorder %} + {% resource 'vendor/reorder' %} + {% endif %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/search.html b/venv/lib/python2.7/site-packages/ckan/templates/package/search.html new file mode 100644 index 00000000..288533ce --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/search.html @@ -0,0 +1,82 @@ +{% extends "page.html" %} +{% import 'macros/form.html' as form %} + +{% block subtitle %}{{ _("Datasets") }}{% endblock %} + +{% block breadcrumb_content %} +
  • {{ h.nav_link(_(dataset_type.title() + 's'), controller='package', action='search', named_route='search') }}
  • +{% endblock %} + +{% block primary_content %} +
    +
    + {% block page_primary_action %} + {% if h.check_access('package_create') %} +
    + {{ h.snippet ('snippets/add_dataset.html', dataset_type=dataset_type) }} +
    + {% endif %} + {% endblock %} + {% block form %} + {% set facets = { + 'fields': c.fields_grouped, + 'search': c.search_facets, + 'titles': c.facet_titles, + 'translated_fields': c.translated_fields, + 'remove_field': c.remove_field } + %} + {% set sorting = [ + (_('Relevance'), 'score desc, metadata_modified desc'), + (_('Name Ascending'), 'title_string asc'), + (_('Name Descending'), 'title_string desc'), + (_('Last Modified'), 'metadata_modified desc'), + (_('Popular'), 'views_recent desc') if g.tracking_enabled else (false, false) ] + %} + {% snippet 'snippets/search_form.html', form_id='dataset-search-form', type=dataset_type, query=c.q, sorting=sorting, sorting_selected=c.sort_by_selected, count=c.page.item_count, placeholder=_('Search ' + dataset_type + 's') + '...', facets=facets, show_empty=request.params, error=c.query_error, fields=c.fields %} + {% endblock %} + {% block package_search_results_list %} + {{ h.snippet('snippets/package_list.html', packages=c.page.items) }} + {% endblock %} +
    + + {% block page_pagination %} + {{ c.page.pager(q=c.q) }} + {% endblock %} +
    + + {% block package_search_results_api %} +
    +
    + {% block package_search_results_api_inner %} + + {% set api_link = h.link_to(_('API'), h.url_for(controller='api', action='get_api', ver=3)) %} + {% set api_doc_link = h.link_to(_('API Docs'), 'http://docs.ckan.org/en/{0}/api/'.format(g.ckan_doc_version)) %} + {% if g.dumps_url -%} + {% set dump_link = h.link_to(_('full {format} dump').format(format=g.dumps_format), g.dumps_url) %} + {% trans %} + You can also access this registry using the {{ api_link }} (see {{ api_doc_link }}) or download a {{ dump_link }}. + {% endtrans %} + {% else %} + {% trans %} + You can also access this registry using the {{ api_link }} (see {{ api_doc_link}}). + {% endtrans %} + {%- endif %} + + {% endblock %} +
    +
    + {% endblock %} +{% endblock %} + + +{% block secondary_content %} +
    +
    + {% for facet in c.facet_titles %} + {{ h.snippet('snippets/facet_list.html', title=c.facet_titles[facet], name=facet) }} + {% endfor %} +
    + close +
    +{% endblock %} + diff --git a/venv/lib/python2.7/site-packages/ckan/templates/package/view_edit_base.html b/venv/lib/python2.7/site-packages/ckan/templates/package/view_edit_base.html new file mode 100644 index 00000000..5c632c0c --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/package/view_edit_base.html @@ -0,0 +1,55 @@ +{% extends "package/resource_edit_base.html" %} + +{% set logged_in = true if c.userobj else false %} +{% set res = c.resource %} + +{% block breadcrumb_edit_selected %}{% endblock %} + +{% block content_action %} + {% link_for _('All views'), controller='package', action='resource_views', id=pkg.name, resource_id=res.id, class_='btn btn-default', icon='arrow-left' %} + {% if res %} + {% set url = h.url_for(controller='package', action='resource_read', id=pkg.name, resource_id=res.id) ~ '?view_id=' ~ resource_view.id %} + {{ _('View view') }} + {% endif %} +{% endblock %} + +{% block content_primary_nav %}{% endblock %} + +{% block primary_content_inner %} + {% block form %}{% endblock %} +{% endblock %} + +{% block main_content %} + {% if h.resource_view_full_page(resource_view) %} + {{ self.flash() }} + {{ self.toolbar() }} +
    + {{ self.primary_content() }} +
    + {% else %} + {{ super() }} + {% endif %} + + {% if to_preview and h.resource_view_display_preview(resource_view) %} +
    +
    +

    + {{ _('View preview') }} +

    +
    + {% snippet 'package/snippets/resource_view.html', resource_view=resource_view, resource=resource, package=package, to_preview=True %} +
    +
    +
    + {% endif %} +{% endblock %} + +{% block secondary %} + {% if not h.resource_view_full_page(resource_view) %} + {{ super() }} + {% endif %} +{% endblock %} + +{% block secondary_content %} + {% snippet 'package/snippets/view_help.html' %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/page.html b/venv/lib/python2.7/site-packages/ckan/templates/page.html new file mode 100644 index 00000000..2d20a00f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/page.html @@ -0,0 +1,137 @@ +{% extends "base.html" %} + +{%- block page -%} + + {% block skip %} + + {% endblock %} + + {# + Override the header on a page by page basis by extending this block. If + making sitewide header changes it is preferable to override the header.html + file. + #} + {%- block header %} + {% include "header.html" %} + {% endblock -%} + + {# The content block allows you to replace the content of the page if needed #} + {%- block content %} + {% block maintag %}
    {% endblock %} +
    + {% block main_content %} + {% block flash %} +
    + {% block flash_inner %} + {% for message in h.flash.pop_messages() | list %} +
    + {{ h.literal(message) }} +
    + {% endfor %} + {% endblock %} +
    + {% endblock %} + + {% block toolbar %} +
    + {% block breadcrumb %} + {% if self.breadcrumb_content() | trim %} + + {% endif %} + {% endblock %} +
    + {% endblock %} + +
    + {# + The pre_primary block can be used to add content to before the + rendering of the main content columns of the page. + #} + {% block pre_primary %} + {% endblock %} + + {% block secondary %} + + {% endblock %} + + {% block primary %} +
    + {# + The primary_content block can be used to add content to the page. + This is the main block that is likely to be used within a template. + + Example: + + {% block primary_content %} +

    My page content

    +

    Some content for the page

    + {% endblock %} + #} + {% block primary_content %} +
    + {% block page_header %} + + {% endblock %} +
    + {% if self.page_primary_action() | trim %} +
    + {% block page_primary_action %}{% endblock %} +
    + {% endif %} + {% block primary_content_inner %} + {% endblock %} +
    +
    + {% endblock %} +
    + {% endblock %} +
    + {% endblock %} +
    +
    + {% endblock -%} + + {# + Override the footer on a page by page basis by extending this block. If + making sitewide header changes it is preferable to override the footer.html-u + file. + #} + {%- block footer %} + {% include "footer.html" %} + {% endblock -%} +{%- endblock -%} + +{%- block scripts %} + {% resource 'base/main' %} + {% resource 'base/ckan' %} + {% if g.tracking_enabled %} + {% resource 'base/tracking.js' %} + {% endif %} + {{ super() }} +{% endblock -%} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/revision/__init__.py b/venv/lib/python2.7/site-packages/ckan/templates/revision/__init__.py new file mode 100644 index 00000000..b9445d54 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/revision/__init__.py @@ -0,0 +1,3 @@ +# encoding: utf-8 + +# empty file needed for pylons to find templates in this directory diff --git a/venv/lib/python2.7/site-packages/ckan/templates/revision/diff.html b/venv/lib/python2.7/site-packages/ckan/templates/revision/diff.html new file mode 100644 index 00000000..6909da42 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/revision/diff.html @@ -0,0 +1,56 @@ +{% extends "revision/read_base.html" %} + +{% set pkg = c.pkg %} +{% set group = c.group %} + +{% block subtitle %}{{ _('Differences')}}{% endblock %} + +{% block breadcrumb_content %} + {% if c.diff_entity == 'package' %} + {% set dataset = pkg.title or pkg.name %} +
  • {% link_for _('Datasets'), controller='package', action='search', highlight_actions = 'new index' %}
  • +
  • {% link_for dataset, controller='package', action='read', id=pkg.name %}
  • +
  • {{ _('Revision Differences') }}
  • + {% elif c.diff_entity == 'group' %} + {% set group = group.display_name or group.name %} +
  • {% link_for _('Groups'), controller='group', action='index' %}
  • +
  • {% link_for group, controller='group', action='read', id=group.name %}
  • +
  • {{ _('Revision Differences') }}
  • + {% endif %} +{% endblock %} + +{% block primary_content_inner %} +

    {{ _('Revision Differences') }} - + {% if c.diff_entity == 'package' %} + {% link_for pkg.title, controller='package', action='read', id=pkg.name %} + {% elif c.diff_entity == 'group' %} + {% link_for group.display_name, controller='group', action='read', id=group.name %} + {% endif %} +

    + +

    + From: {% link_for c.revision_from.id, controller='revision', action='read', id=c.revision_from.id %} - + {{ h.render_datetime(c.revision_from.timestamp, with_hours=True) }} +

    +

    + To: {% link_for c.revision_to.id, controller='revision', action='read', id=c.revision_to.id %} - + {{ h.render_datetime(c.revision_to.timestamp, with_hours=True) }} +

    + + {% if c.diff %} + + + + + + {% for field, diff in c.diff %} + + + + + {% endfor %} +
    {{ _('Field') }}{{ _('Difference') }}
    {{ field }}
    {{ diff }}
    + {% else %} +

    {{ _('No Differences') }}

    + {% endif %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/revision/list.html b/venv/lib/python2.7/site-packages/ckan/templates/revision/list.html new file mode 100644 index 00000000..84200a08 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/revision/list.html @@ -0,0 +1,19 @@ +{% extends "revision/read_base.html" %} + +{% block subtitle %}{{ _('Revision History') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {{ _('Revisions') }}
  • +{% endblock %} + +{% block primary_content_inner %} +

    {{ _('Revision History') }}

    + + {{ c.page.pager() }} + + {% block revisions_list %} + {% snippet "revision/snippets/revisions_list.html", revisions=c.page.items %} + {% endblock %} + + {{ c.page.pager() }} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/revision/read.html b/venv/lib/python2.7/site-packages/ckan/templates/revision/read.html new file mode 100644 index 00000000..e0223288 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/revision/read.html @@ -0,0 +1,94 @@ +{% extends "revision/read_base.html" %} + +{% set rev = c.revision %} + +{% block subtitle %}{{ _('Revision') }} {{ rev.id }}{% endblock %} + +{% block breadcrumb_content %} +
  • {% link_for _('Revisions'), controller='revision', action='index' %}
  • +
  • {{ rev.id |truncate(35) }}
  • +{% endblock %} + +{% block actions_content %} + {% if c.revision_change_state_allowed %} +
    +
  • + {% if rev.state != 'deleted' %} + + {% endif %} + {% if rev.state == 'deleted' %} + + {% endif %} +
  • +
    + {% endif %} +{% endblock %} + +{% block primary_content_inner %} +

    {{ _('Revision') }}: {{ rev.id }}

    + +
    +
    + {% if rev.state != 'active' %} +

    + {{ rev.state }} +

    + {% endif %} + +

    + {{ _('Author') }}: {{ h.linked_user(rev.author) }} +

    +

    + {{ _('Timestamp') }}: {{ h.render_datetime(rev.timestamp, with_hours=True) }} +

    +

    + {{ _('Log Message') }}: +

    +

    + {{ rev.message }} +

    +
    + +
    +

    {{ _('Changes') }}

    +

    {{ _('Datasets') }}

    +
      + {% for pkg in c.packages %} +
    • + {{ h.link_to(pkg.name, h.url_for(controller='package', action='read', id=pkg.name)) }} +
    • + {% endfor %} +
    + +

    {{ _('Datasets\' Tags') }}

    +
      + {% for pkgtag in c.pkgtags %} +
    • + Dataset - {{ h.link_to(pkgtag.package.name, h.url_for(controller='package', action='read', id=pkgtag.package.name)) }}, + Tag - {{ h.link_to(pkgtag.tag.name, h.url_for(controller='tag', action='read', id=pkgtag.tag.name)) }} +
    • + {% endfor %} +
    + +

    {{ _('Groups') }}

    +
      + {% for group in c.groups %} +
    • + {{ h.link_to(group.name, h.url_for(controller='group', action='read', id=group.name)) }} +
    • + {% endfor %} +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/templates/revision/read_base.html b/venv/lib/python2.7/site-packages/ckan/templates/revision/read_base.html new file mode 100644 index 00000000..880e4323 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/revision/read_base.html @@ -0,0 +1,19 @@ +{% extends "page.html" %} + +{% block secondary_content %} + + {% block secondary_help_content %}{% endblock %} + + {% block package_social %} + {% snippet "snippets/social.html" %} + {% endblock %} + +{% endblock %} + +{% block primary_content %} +
    +
    + {% block primary_content_inner %}{% endblock %} +
    +
    +{% endblock %} \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckan/templates/robots.txt b/venv/lib/python2.7/site-packages/ckan/templates/robots.txt new file mode 100644 index 00000000..e9457fcc --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/robots.txt @@ -0,0 +1,11 @@ +User-agent: * +{% block all_user_agents -%} +Disallow: /dataset/rate/ +Disallow: /revision/ +Disallow: /dataset/*/history +Disallow: /api/ +Crawl-Delay: 10 +{%- endblock %} + +{% block additional_user_agents -%} +{%- endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/activity_item.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/activity_item.html new file mode 100644 index 00000000..46619123 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/activity_item.html @@ -0,0 +1,10 @@ +
  • + {% if activity.is_new %} + {{ _('New activity item') }} + {% endif %} + +

    + {{ h.literal(activity.msg.format(**activity.data)) }} + {{ h.time_ago_from_timestamp(activity.timestamp) }} +

    +
  • diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/add_dataset.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/add_dataset.html new file mode 100644 index 00000000..f5f4f87a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/add_dataset.html @@ -0,0 +1,9 @@ +{# Adds 'Add Dataset' button #} + +{% set dataset_type = dataset_type if dataset_type else 'dataset' %} + +{% if group %} + {% link_for _('Add Dataset'), controller='package', action='new', group=group, class_='btn btn-primary', icon='plus-square' %} +{% else %} + {% link_for _('Add ' + dataset_type.title()), controller='package', action='new', named_route=dataset_type + '_new', class_='btn btn-primary', icon='plus-square' %} +{% endif %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/additional_info.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/additional_info.html new file mode 100644 index 00000000..a110b8d2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/additional_info.html @@ -0,0 +1,25 @@ +{# This snippet creates an Additional Info Table + +extras is a list of tuples of the form (key, value) + +#} +{% if extras %} +

    {{ _('Additional Info') }}

    + + + + + + + + + {% for extra in extras %} + {% set key, value = extra %} + + + + + {% endfor %} + +
    {{ _('Field') }}{{ _('Value') }}
    {{ _(key) }}{{ value }}
    +{% endif %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/context.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/context.html new file mode 100644 index 00000000..2ec433c4 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/context.html @@ -0,0 +1,10 @@ +{%if type == 'user' %} +

    {{ dict.name }}

    + {% snippet 'snippets/context/user.html', id=dict.id, name=dict.name, about=dict.about, is_me='false', num_followers=dict.num_followers, number_created_packages=dict.number_created_packages, number_of_edits=dict.number_of_edits %} +{%elif type == 'dataset' %} +

    {{ dict.title }}

    + {% snippet 'snippets/context/dataset.html', id=dict.id, name=dict.name, notes=dict.notes, num_resources=dict.num_resources, num_tags=dict.num_tags %} +{%elif type == 'group' %} +

    {{ dict.title }}

    + {% snippet 'snippets/context/group.html', id=dict.id, name=dict.name, description=dict.description, num_followers=dict.num_followers, package_count=dict.package_count %} +{% endif %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/custom_form_fields.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/custom_form_fields.html new file mode 100644 index 00000000..9aa2d9fd --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/custom_form_fields.html @@ -0,0 +1,42 @@ +{# +Adds a block of custom form fields. + +extras - The fields to add. +errors - A dict of errors for the fields. +limit - The total number of fields that should be output. +Example: + + {% snippet 'snippets/custom_form_fields.html', extras=data.extras, errors=errors, limit=3 %} + +#} +{% import "macros/form.html" as form %} + +
    + {% for extra in extras %} + {% set prefix = 'extras__%d__' % loop.index0 %} + {{ form.custom( + names=(prefix ~ 'key', prefix ~ 'value', prefix ~ 'deleted'), + id='field-extras-%d' % loop.index, + label=_('Custom Field'), + values=(extra.key, extra.value, extra.deleted), + error=errors[prefix ~ 'key'] or errors[prefix ~ 'value'] + ) }} + {% endfor %} + + {# Add a max of 3 empty columns #} + {% set total_extras = extras|count %} + {% set empty_extras = (limit or 3) - total_extras %} + {% if empty_extras <= 0 %}{% set empty_extras = 1 %}{% endif %} + + {% for extra in range(total_extras, total_extras + empty_extras) %} + {% set index = loop.index0 + (extras|count) %} + {% set prefix = 'extras__%d__' % index %} + {{ form.custom( + names=(prefix ~ 'key', prefix ~ 'value', prefix ~ 'deleted'), + id='field-extras-%d' % index, + label=_('Custom Field'), + values=(extra.key, extra.value, extra.deleted), + error=errors[prefix ~ 'key'] or errors[prefix ~ 'value'] + ) }} + {% endfor %} +
    diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/datapusher_status.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/datapusher_status.html new file mode 100644 index 00000000..8cd6a395 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/datapusher_status.html @@ -0,0 +1,14 @@ +{# Datapusher status indicator + +resource: the resource + +#} +{% if resource.datastore_active %} + {% set job = h.datapusher_status(resource.id) %} + {% set title = _('Datapusher status: {status}.').format(status=job.status) %} + {% if job.status == 'unknown' %} + + {% else %} + + {% endif %} +{% endif %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/debug.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/debug.html new file mode 100644 index 00000000..15bc30ea --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/debug.html @@ -0,0 +1,63 @@ +
    + Debug {{ request.environ['CKAN_DEBUG_INFO']|length }} + + +
    diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/disqus_trackback.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/disqus_trackback.html new file mode 100644 index 00000000..64a5cb73 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/disqus_trackback.html @@ -0,0 +1,4 @@ +
    +

    {{ _('Trackback URL') }}

    +
    +
    diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/facet_list.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/facet_list.html new file mode 100644 index 00000000..d5e7089a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/facet_list.html @@ -0,0 +1,97 @@ +{# +Construct a facet module populated with links to filtered results. + +name + The field name identifying the facet field, eg. "tags" + +title + The title of the facet, eg. "Tags", or "Tag Cloud" + +label_function + Renders the human-readable label for each facet value. + If defined, this should be a callable that accepts a `facet_item`. + eg. lambda facet_item: facet_item.display_name.upper() + By default it displays the facet item's display name, which should + usually be good enough + +if_empty + A string, which if defined, and the list of possible facet items is empty, + is displayed in lieu of an empty list. + +count_label + A callable which accepts an integer, and returns a string. This controls + how a facet-item's count is displayed. + +extras + Extra info passed into the add/remove params to make the url + +alternative_url + URL to use when building the necessary URLs, instead of the default + ones returned by url_for. Useful eg for dataset types. + +hide_empty + Do not show facet if there are none, Default: false. + +within_tertiary + Boolean for when a facet list should appear in the the right column of the + page and not the left column. + +#} +{% block facet_list %} + {% set hide_empty = hide_empty or false %} + {% with items = items or h.get_facet_items_dict(name) %} + {% if items or not hide_empty %} + {% if within_tertiary %} + {% set nav_class = 'nav nav-pills nav-stacked' %} + {% set nav_item_class = ' ' %} + {% set wrapper_class = 'nav-facet nav-facet-tertiary' %} + {% endif %} + {% block facet_list_item %} +
    + {% block facet_list_heading %} +

    + + {% set title = title or h.get_facet_title(name) %} + {{ title }} +

    + {% endblock %} + {% block facet_list_items %} + {% with items = items or h.get_facet_items_dict(name) %} + {% if items %} + + + + {% else %} +

    {{ _('There are no {facet_type} that match this search').format(facet_type=title) }}

    + {% endif %} + {% endwith %} + {% endblock %} +
    + {% endblock %} + {% endif %} + {% endwith %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/follow_button.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/follow_button.html new file mode 100644 index 00000000..f0e3e654 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/follow_button.html @@ -0,0 +1,16 @@ +{% set controller = obj_type %} +{% if controller == 'dataset' %} + {% set controller = 'package' %} +{% endif %} + +{% if following %} + + + {{ _('Unfollow') }} + +{% else %} + + + {{ _('Follow') }} + +{% endif %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/group.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/group.html new file mode 100644 index 00000000..6233b44e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/group.html @@ -0,0 +1,27 @@ +{# +Embeds a group within the sidebar of a page. + +group - The group dict. +truncate - A max length for the group description. If not provided the description + will be full length. + +Example: + + {% snippet 'snippets/group, group=c.group_dict %} + +#} +{% with truncate=truncate or 0, url=h.url_for(controller='group', action='read', id=group.name) %} +
    +
    + + {{ group.name }} + +
    +

    {{ group.title or group.name }}

    + {% if group.description %} +

    {{ h.markdown_extract(group.description, truncate) }}

    + {% endif %} +
    +
    +
    +{% endwith %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/group_item.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/group_item.html new file mode 100644 index 00000000..0239851f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/group_item.html @@ -0,0 +1,33 @@ +{% block group_item %} +
    + {% block group_item_header %} +
    + {% set truncate=truncate or 0 %} + {% set truncate_title = truncate_title or 0 %} + {% set title = group.title or group.name %} + {% block group_item_header_image %} + + {{ group.name }} + + {% endblock %} + {% block group_item_header_title %} +

    {{ group.title or group.name }}

    + {% endblock %} + {% block group_item_header_description %} + {% if group.description %} + {% if truncate == 0 %} +

    {{ h.markdown_extract(group.description)|urlize }}

    + {% else %} +

    {{ h.markdown_extract(group.description, truncate)|urlize }}

    + {% endif %} + {% endif %} + {% endblock %} +
    + {% endblock %} + {% block group_item_content %} + {% set list_class = "list-unstyled dataset-list" %} + {% set item_class = "dataset-item module-content" %} + {% snippet 'snippets/package_list.html', packages=group.packages, list_class=list_class, item_class=item_class, truncate=120 %} + {% endblock %} +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/home_breadcrumb_item.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/home_breadcrumb_item.html new file mode 100644 index 00000000..0e66ef13 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/home_breadcrumb_item.html @@ -0,0 +1,2 @@ +{# Used to insert the home icon into a breadcrumb #} +
  • {{ _('Home') }}
  • diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/language_selector.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/language_selector.html new file mode 100644 index 00000000..bdea7b2a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/language_selector.html @@ -0,0 +1,12 @@ +{% set current_lang = request.environ.CKAN_LANG %} +
    + + + +
    diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/license.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/license.html new file mode 100644 index 00000000..85ce763a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/license.html @@ -0,0 +1,39 @@ +{% macro license_string(pkg_dict) %} + {% if 'license_url' in pkg_dict %} + {{ pkg_dict.license_title }} + {% else %} + {{ pkg_dict.license_title }} + {% endif %} +{% endmacro %} + +{% block license %} + {% if text_only %} + {% if pkg_dict.license_id %} + {{ license_string(pkg_dict) }} + {% else %} + {{ _('No License Provided') }} + {% endif %} + {% else %} + {% if pkg_dict.license_id %} + {% block license_wrapper %} +
    + {% block license_title %} +

    {{ _('License') }}

    + {% endblock %} + {% block license_content %} +

    + {% block license_content_inner %} + {{ license_string(pkg_dict) }} + {% if pkg_dict.isopen %} + + [Open Data] + + {% endif %} + {% endblock %} +

    + {% endblock %} +
    + {% endblock %} + {% endif %} + {% endif %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/local_friendly_datetime.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/local_friendly_datetime.html new file mode 100644 index 00000000..345e74f5 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/local_friendly_datetime.html @@ -0,0 +1,14 @@ +{# +Displays a datetime that can be converted to a users timezone using JavaScript. +In the data-datetime attribute, the date is rendered in ISO 8601 format. + +datetime_obj - the datetime object to display + +Example: + + {% snippet 'snippets/local_friendly_datetime, datetime=pkg_dict.metadata_created %} + +#} + + {{ h.render_datetime(datetime_obj, with_hours=True) }} + diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/organization.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/organization.html new file mode 100644 index 00000000..765c8aea --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/organization.html @@ -0,0 +1,73 @@ +{# +Embeds a organization within the sidebar of a page. + +organization - The organization dict. +truncate - A max length for the organization description. If not provided the description + will be full length. +has_context_title - Boolean for whether to display a module-heading for when + this snippet appears within a non-organization template + (e.g. dataset page sidebar) + +Example: + + {% snippet 'snippets/organization, organization=c.group_dict %} + +#} + +{% set truncate = truncate or 0 %} +{% set url = h.url_for(organization.type + '_read', id=organization.name, ) %} + + {% block info %} +
    + {% if has_context_title %} +

    {{ _('Organization') }}

    + {% endif %} +
    + {% block inner %} + {% block image %} +
    + + {{ organization.name }} + +
    + {% endblock %} + {% block heading %} +

    {{ organization.title or organization.name }} + {% if organization.state == 'deleted' %} + [{{ _('Deleted') }}] + {% endif %} +

    + {% endblock %} + {% block description %} + {% if organization.description %} +

    + {{ h.markdown_extract(organization.description, 180) }} + {% link_for _('read more'), controller='organization', action='about', id=organization.name %} +

    + {% else %} +

    {{ _('There is no description for this organization') }}

    + {% endif %} + {% endblock %} + {% if show_nums %} + {% block nums %} +
    +
    +
    {{ _('Followers') }}
    +
    {{ h.SI_number_span(organization.num_followers) }}
    +
    +
    +
    {{ _('Datasets') }}
    +
    {{ h.SI_number_span(organization.package_count) }}
    +
    +
    + {% endblock %} + {% block follow %} + + {% endblock %} + {% endif %} + {% endblock %} +
    +
    + {% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/organization_item.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/organization_item.html new file mode 100644 index 00000000..f7f7adb5 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/organization_item.html @@ -0,0 +1,32 @@ +{% set url=h.url_for(controller='organization', action='read', id=organization.name) %} +{% set truncate=truncate or 0 %} +{% block organization_item %} +
    + {% block organization_item_header %} +
    + {% block organization_item_header_image %} + + {{ organization.name }} + + {% endblock %} + {% block organization_item_header_title %} +

    {{ organization.title or organization.name }}

    + {% endblock %} + {% block organization_item_header_description %} + {% if organization.description %} + {% if truncate == 0 %} +

    {{ h.markdown_extract(organization.description)|urlize }}

    + {% else %} +

    {{ h.markdown_extract(organization.description, truncate)|urlize }}

    + {% endif %} + {% endif %} + {% endblock %} +
    + {% endblock %} + {% block organization_item_content %} + {% set list_class = "list-unstyled dataset-list" %} + {% set item_class = "dataset-item module-content" %} + {% snippet 'snippets/package_list.html', packages=organization.packages, list_class=list_class, item_class=item_class, truncate=120 %} + {% endblock %} +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/package_grid.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/package_grid.html new file mode 100644 index 00000000..a9c45890 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/package_grid.html @@ -0,0 +1,30 @@ +{# +Displays a list of packages in a grid. + +packages - A list of packages to display. +list_class - The class name for the list item. +item_class - The class name to use on each item. +banner - If true displays a popular banner (default: false). +truncate - The length to trucate the description to (default: 120). +title_truncate - The length to truncate the title to (default: 40). + +Example: + + {% snippet 'snippets/package_grid.html', packages=c.datasets[:4] %} + +#} + +{% set truncate = truncate or 120 %} +{% set truncate_title = truncate_title or 40 %} + +{% if packages %} +
      + {% for package in packages %} + {% set full_class = item_class or 'module module-item' %} + {% if loop.index0 % 2 == 0 %}{% set full_class = full_class ~ ' first' %}{% endif %} + {% if loop.index0 % 2 == 1 %}{% set full_class = full_class ~ ' last' %}{% endif %} + + {% snippet 'snippets/package_item.html', package=package, item_class=full_class, hide_resources=hide_resources, banner=banner, truncate=truncate, truncate_title=truncate_title %} + {% endfor %} +
    +{% endif %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/package_item.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/package_item.html new file mode 100644 index 00000000..ed65bb39 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/package_item.html @@ -0,0 +1,78 @@ +{# +Displays a single of dataset. + +package - A package to display. +item_class - The class name to use on the list item. +hide_resources - If true hides the resources (default: false). +banner - If true displays a popular banner (default: false). +truncate - The length to trucate the description to (default: 180) +truncate_title - The length to truncate the title to (default: 80). + +Example: + + {% snippet 'snippets/package_item.html', package=c.datasets[0] %} + +#} +{% set truncate = truncate or 180 %} +{% set truncate_title = truncate_title or 80 %} +{% set title = package.title or package.name %} +{% set notes = h.markdown_extract(package.notes, extract_length=truncate) %} + +{% block package_item %} +
  • + {% block content %} +
    + {% block heading %} +

    + {% block heading_private %} + {% if package.private %} + + + {{ _('Private') }} + + {% endif %} + {% endblock %} + {% block heading_title %} + {{ h.link_to(h.truncate(title, truncate_title), h.url_for(package.type + '_read', controller='package', action='read', id=package.name)) }} + {% endblock %} + {% block heading_meta %} + {% if package.get('state', '').startswith('draft') %} + {{ _('Draft') }} + {% elif package.get('state', '').startswith('deleted') %} + {{ _('Deleted') }} + {% endif %} + {{ h.popular('recent views', package.tracking_summary.recent, min=10) if package.tracking_summary }} + {% endblock %} +

    + {% endblock %} + {% block banner %} + {% if banner %} + + {% endif %} + {% endblock %} + {% block notes %} + {% if notes %} +
    {{ notes|urlize }}
    + {% else %} +

    {{ _("This dataset has no description") }}

    + {% endif %} + {% endblock %} +
    + {% block resources %} + {% if package.resources and not hide_resources %} + {% block resources_outer %} +
      + {% block resources_inner %} + {% for resource in h.dict_list_reduce(package.resources, 'format') %} +
    • + {{ resource }} +
    • + {% endfor %} + {% endblock %} +
    + {% endblock %} + {% endif %} + {% endblock %} + {% endblock %} +
  • +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/package_list.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/package_list.html new file mode 100644 index 00000000..eb495f69 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/package_list.html @@ -0,0 +1,27 @@ +{# +Displays a list of datasets. + +packages - A list of packages to display. +list_class - The class name for the list item. +item_class - The class name to use on each item. +hide_resources - If true hides the resources (default: false). +banner - If true displays a popular banner (default: false). +truncate - The length to trucate the description to (default: 180) +truncate_title - The length to truncate the title to (default: 80). + +Example: + + {% snippet 'snippets/package_list.html', packages=c.datasets %} + +#} +{% block package_list %} + {% if packages %} +
      + {% block package_list_inner %} + {% for package in packages %} + {% snippet 'snippets/package_item.html', package=package, item_class=item_class, hide_resources=hide_resources, banner=banner, truncate=truncate, truncate_title=truncate_title %} + {% endfor %} + {% endblock %} +
    + {% endif %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/popular.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/popular.html new file mode 100644 index 00000000..540073c7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/popular.html @@ -0,0 +1,4 @@ +{# show the popularity icon #} +{% if number >= min %} +{{ _('Popular') }} +{% endif %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/private.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/private.html new file mode 100644 index 00000000..70c86ae2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/private.html @@ -0,0 +1,3 @@ +
    +

    {{ _('Private') }}

    +
    diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/search_form.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/search_form.html new file mode 100644 index 00000000..fed9bd48 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/search_form.html @@ -0,0 +1,90 @@ +{% import 'macros/form.html' as form %} + +{% set placeholder = placeholder if placeholder else _('Search datasets...') %} +{% set sorting = sorting if sorting else [(_('Name Ascending'), 'name asc'), (_('Name Descending'), 'name desc')] %} +{% set search_class = search_class if search_class else 'search-giant' %} +{% set no_bottom_border = no_bottom_border if no_bottom_border else false %} +{% set form_id = form_id if form_id else false %} + +
    + + {% block search_input %} +
    + + {% block search_input_button %} + + + + {% endblock %} +
    + {% endblock %} + + {% block search_search_fields %} + {% if fields -%} + {{ form.hidden_from_list(fields=fields) }} + {%- endif %} + {% endblock %} + + {% block search_sortby %} + {% if sorting %} +
    + + + {% block search_sortby_button %} + + {% endblock %} +
    + {% endif %} + {% endblock %} + + {% block search_title %} + {% if not error %} +

    {% snippet 'snippets/search_result_text.html', query=query, count=count, type=type %}

    + {% else %} +

    Error

    + {% endif %} + {% endblock %} + + {% block search_facets %} + {% if facets %} +

    + {% for field in facets.fields %} + {% set search_facets_items = facets.search.get(field)['items'] %} + {{ facets.titles.get(field) }}: + {% for value in facets.fields[field] %} + + {%- if facets.translated_fields and facets.translated_fields.has_key((field,value)) -%} + {{ facets.translated_fields[(field,value)] }} + {%- else -%} + {{ h.list_dict_filter(search_facets_items, 'name', 'display_name', value) }} + {%- endif %} + + + {% endfor %} + {% endfor %} +

    + {{ _('Filter Results') }} + {% endif %} + {% endblock %} + +
    + +{% if show_empty and count == 0 and not error %} + {% trans %} +

    Please try another search.

    + {% endtrans %} +{% endif %} + +{% if error %} + {% trans %} +

    There was an error while searching. Please try again.

    + {% endtrans %} +{% endif %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/search_result_text.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/search_result_text.html new file mode 100644 index 00000000..ffc67947 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/search_result_text.html @@ -0,0 +1,58 @@ +{# + +Displays a test for results of a search. + +query - The text that was searched for +count - The number of results for the search +type - Search result type (dataset, group, organization) + +Example: + + {% snippet 'snippets/search_result_text.html', query=query, count=count, type='dataset' %} + +#} +{% if type == 'dataset' %} + {% set text_query = ungettext('{number} dataset found for "{query}"', '{number} datasets found for "{query}"', count) %} + {% set text_query_none = _('No datasets found for "{query}"') %} + {% set text_no_query = ungettext('{number} dataset found', '{number} datasets found', count) %} + {% set text_no_query_none = _('No datasets found') %} + +{% elif type == 'group' %} + {% set text_query = ungettext('{number} group found for "{query}"', '{number} groups found for "{query}"', count) %} + {% set text_query_none = _('No groups found for "{query}"') %} + {% set text_no_query = ungettext('{number} group found', '{number} groups found', count) %} + {% set text_no_query_none = _('No groups found') %} + +{% elif type == 'organization' %} + {% set text_query = ungettext('{number} organization found for "{query}"', '{number} organizations found for "{query}"', count) %} + {% set text_query_none = _('No organizations found for "{query}"') %} + {% set text_no_query = ungettext('{number} organization found', '{number} organizations found', count) %} + {% set text_no_query_none = _('No organizations found') %} + +{% else %} + {% set text_query_singular = '{number} ' + type + ' found for "{query}"' %} + {% set text_query_plural = '{number} ' + type + 's found for "{query}"' %} + {% set text_query_none_plural = 'No ' + type + 's found for "{query}"' %} + {% set text_no_query_singular = '{number} ' + type + ' found' %} + {% set text_no_query_plural = '{number} ' + type + 's found' %} + {% set text_no_query_none_plural = 'No ' + type + 's found' %} + + {% set text_query = ungettext(text_query_singular, text_query_plural, count) %} + {% set text_query_none = _(text_query_none_plural) %} + {% set text_no_query = ungettext(text_no_query_singular, text_no_query_plural, count) %} + {% set text_no_query_none = _(text_no_query_none_plural) %} +{%- endif -%} + +{% if query %} + {%- if count -%} + {{ text_query.format(number=h.localised_number(count), query=query, type=type) }} + {%- else -%} + {{ text_query_none.format(query=query, type=type) }} + {%- endif -%} +{%- else -%} + {%- if count -%} + {{ text_no_query.format(number=h.localised_number(count), type=type) }} + {%- else -%} + {{ text_no_query_none.format(type=type) }} + {%- endif -%} +{%- endif -%} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/simple_search.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/simple_search.html new file mode 100644 index 00000000..6bc6a53e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/simple_search.html @@ -0,0 +1,17 @@ +{% set input_class = input_class or 'search-giant' %} + diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/social.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/social.html new file mode 100644 index 00000000..c4c0b1db --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/social.html @@ -0,0 +1,14 @@ +{% set current_url = h.full_current_url() %} +{% block social %} + +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/sort_by.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/sort_by.html new file mode 100644 index 00000000..877dc0c6 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/sort_by.html @@ -0,0 +1,23 @@ +{# +Displays a select box for ordering search results + +sort = The currently selected option + +Example: + + {% snippet 'snippets/sort_by.html', sort=c.sort_by_selected %} + +#} + + + + + diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/subscribe.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/subscribe.html new file mode 100644 index 00000000..bf3b180e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/subscribe.html @@ -0,0 +1,7 @@ + diff --git a/venv/lib/python2.7/site-packages/ckan/templates/snippets/tag_list.html b/venv/lib/python2.7/site-packages/ckan/templates/snippets/tag_list.html new file mode 100644 index 00000000..b0e97ccd --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/snippets/tag_list.html @@ -0,0 +1,14 @@ +{# +render a list of tags linking to the dataset search page +tags: list of tags +#} +{% set _class = _class or 'tag-list' %} +{% block tag_list %} + +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/tag/index.html b/venv/lib/python2.7/site-packages/ckan/templates/tag/index.html new file mode 100644 index 00000000..fa5ffc34 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/tag/index.html @@ -0,0 +1,39 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _('Tags') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {{ _('Tags') }}
  • +{% endblock %} + +{% block primary_content %} +
    +
    +

    {% block page_heading %}{{ _('Tags') }}{% endblock %}

    + {% block tags_list %} +
      + {% block tags_list_inner %} + {% for tag in c.page.items %} +
    • {{ h.link_to(tag.display_name, h.url_for(controller='package', action='search', tags=tag.name), class_='tag') }}
    • + {% endfor %} + {% endblock %} +
    + {% endblock %} +
    + {% block page_pagination %} + {{ c.page.pager(q=c.q) }} + {% endblock %} +
    +{% endblock %} + +{% block secondary_content %} +
    +
    +
    + + + +
    +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/tests/broken_helper_as_attribute.html b/venv/lib/python2.7/site-packages/ckan/templates/tests/broken_helper_as_attribute.html new file mode 100644 index 00000000..b592887d --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/tests/broken_helper_as_attribute.html @@ -0,0 +1,5 @@ +{% extends 'page.html' %} + +{% block page %} + {{ h.not_here() }} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/tests/broken_helper_as_item.html b/venv/lib/python2.7/site-packages/ckan/templates/tests/broken_helper_as_item.html new file mode 100644 index 00000000..d19d2e08 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/tests/broken_helper_as_item.html @@ -0,0 +1,5 @@ +{% extends 'page.html' %} + +{% block page %} + {{ h['not_here']() }} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/tests/flash_messages.html b/venv/lib/python2.7/site-packages/ckan/templates/tests/flash_messages.html new file mode 100644 index 00000000..f91e1d85 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/tests/flash_messages.html @@ -0,0 +1,14 @@ + + + + Hello + + + Flash messages: + {% for message in h.flash.pop_messages() | list %} +
    + {{ message.category }}: {{ h.literal(message) }} +
    + {% endfor %} + + diff --git a/venv/lib/python2.7/site-packages/ckan/templates/tests/helper_as_attribute.html b/venv/lib/python2.7/site-packages/ckan/templates/tests/helper_as_attribute.html new file mode 100644 index 00000000..8e22f361 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/tests/helper_as_attribute.html @@ -0,0 +1,5 @@ +{% extends 'page.html' %} + +{% block page %} + My lang is: {{ h.lang() }} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/tests/helper_as_item.html b/venv/lib/python2.7/site-packages/ckan/templates/tests/helper_as_item.html new file mode 100644 index 00000000..49074617 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/tests/helper_as_item.html @@ -0,0 +1,5 @@ +{% extends 'page.html' %} + +{% block page %} + My lang is: {{ h['lang']() }} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/tests/mock_json_resource_preview_template.html b/venv/lib/python2.7/site-packages/ckan/templates/tests/mock_json_resource_preview_template.html new file mode 100644 index 00000000..20630dec --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/tests/mock_json_resource_preview_template.html @@ -0,0 +1,17 @@ +{% extends 'dataviewer/base.html' %} + +{% block page %} +
    +
    +      
    + {{ _('Loading...') }} +
    +
    +
    +{% endblock %} + +{% block scripts %} + {{ super() }} + + +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/tests/mock_resource_preview_template.html b/venv/lib/python2.7/site-packages/ckan/templates/tests/mock_resource_preview_template.html new file mode 100644 index 00000000..9bea46da --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/tests/mock_resource_preview_template.html @@ -0,0 +1,17 @@ +{% extends 'dataviewer/base.html' %} + +{% block page %} +
    +
    +      
    + {{ _('Loading...') }} +
    +
    +
    +{% endblock %} + +{% block scripts %} + {{ super() }} + + +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/activity_stream.html b/venv/lib/python2.7/site-packages/ckan/templates/user/activity_stream.html new file mode 100644 index 00000000..729c7a43 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/activity_stream.html @@ -0,0 +1,10 @@ +{% extends "user/read.html" %} + +{% block subtitle %}{{ _('Activity Stream') }} - {{ super() }}{% endblock %} + +{% block primary_content_inner %} +

    {% block page_heading %}{{ _('Activity Stream') }}{% endblock %}

    + {% block activity_stream %} + {{ g.user_activity_stream | safe }} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/dashboard.html b/venv/lib/python2.7/site-packages/ckan/templates/user/dashboard.html new file mode 100644 index 00000000..e6c01a63 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/dashboard.html @@ -0,0 +1,48 @@ +{% extends "user/edit_base.html" %} + +{% set user = g.userobj %} + +{% block breadcrumb_content %} +
  • {{ _('Dashboard') }}
  • +{% endblock %} + +{% block secondary %}{% endblock %} + +{% block primary %} +
    + {% block page_header %} + + {% endblock %} +
    + {% if self.page_primary_action() | trim %} +
    + {% block page_primary_action %}{% endblock %} +
    + {% endif %} + {% block primary_content_inner %} +
    + {% snippet 'user/snippets/followee_dropdown.html', context=dashboard_activity_stream_context, followees=followee_list %} +

    + {% block page_heading %} + {{ _('News feed') }} + {% endblock %} + {{ _("Activity from items that I'm following") }} +

    + {% block activity_stream %} + {{ dashboard_activity_stream|safe }} + {% endblock %} +
    + {% endblock %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/dashboard_datasets.html b/venv/lib/python2.7/site-packages/ckan/templates/user/dashboard_datasets.html new file mode 100644 index 00000000..1c436331 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/dashboard_datasets.html @@ -0,0 +1,23 @@ +{% extends "user/dashboard.html" %} + +{% block dashboard_activity_stream_context %}{% endblock %} + +{% block page_primary_action %} + {% if h.check_access('package_create') %} + {% snippet 'snippets/add_dataset.html' %} + {% endif %} +{% endblock %} + +{% block primary_content_inner %} +

    {{ _('My Datasets') }}

    + {% if user_dict.datasets %} + {% snippet 'snippets/package_list.html', packages=user_dict.datasets %} + {% else %} +

    + {{ _('You haven\'t created any datasets.') }} + {% if h.check_access('package_create') %} + {% link_for _('Create one now?'), controller='package', action='new' %} + {% endif %} +

    + {% endif %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/dashboard_groups.html b/venv/lib/python2.7/site-packages/ckan/templates/user/dashboard_groups.html new file mode 100644 index 00000000..c976bdb1 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/dashboard_groups.html @@ -0,0 +1,26 @@ +{% extends "user/dashboard.html" %} + +{% block dashboard_activity_stream_context %}{% endblock %} + +{% block page_primary_action %} + {% if h.check_access('group_create') %} + {% link_for _('Add Group'), controller='group', action='new', class_="btn btn-primary", icon="plus-square" %} + {% endif %} +{% endblock %} + +{% block primary_content_inner %} +

    {{ _('My Groups') }}

    + {% set groups = h.groups_available(am_member=True) %} + {% if groups %} +
    + {% snippet "group/snippets/group_list.html", groups=groups %} +
    + {% else %} +

    + {{ _('You are not a member of any groups.') }} + {% if h.check_access('group_create') %} + {% link_for _('Create one now?'), controller='group', action='new' %} + {% endif %} +

    + {% endif %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/dashboard_organizations.html b/venv/lib/python2.7/site-packages/ckan/templates/user/dashboard_organizations.html new file mode 100644 index 00000000..cf293865 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/dashboard_organizations.html @@ -0,0 +1,27 @@ +{% extends "user/dashboard.html" %} + +{% block dashboard_activity_stream_context %}{% endblock %} + +{% block page_primary_action %} + {% if h.check_access('organization_create') %} + {% link_for _('Add Organization'), controller='organization', action='new', class_="btn btn-primary", icon="plus-square" %} + {% endif %} +{% endblock %} + +{% block primary_content_inner %} +

    {{ _('My Organizations') }}

    + {% set organizations = h.organizations_available(permission='manage_group', + include_dataset_count=True) %} + {% if organizations %} +
    + {% snippet "organization/snippets/organization_list.html", organizations=organizations, show_capacity=True %} +
    + {% else %} +

    + {{ _('You are not a member of any organizations.') }} + {% if h.check_access('organization_create') %} + {% link_for _('Create one now?'), controller='organization', action='new' %} + {% endif %} +

    + {% endif %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/edit.html b/venv/lib/python2.7/site-packages/ckan/templates/user/edit.html new file mode 100644 index 00000000..5771589a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/edit.html @@ -0,0 +1,25 @@ +{% extends 'user/edit_base.html' %} + +{% block actions_content %}{% endblock %} + +{% block breadcrumb_content %} +
  • {{ _('Users') }}
  • +
  • {{ user_dict.display_name }}
  • +
  • {{ _('Manage') }}
  • +{% endblock %} + +{% block primary_content_inner %} + {{ form | safe }} +{% endblock %} + +{% block secondary_content %} +
    +

    {{ _('Account Info') }}

    +
    + {% trans %} + Your profile lets other CKAN users know about who you are and what you + do. + {% endtrans %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/edit_base.html b/venv/lib/python2.7/site-packages/ckan/templates/user/edit_base.html new file mode 100644 index 00000000..56128037 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/edit_base.html @@ -0,0 +1,11 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _('Manage') }} - {{ user_dict.display_name }} - {{ _('Users') }}{% endblock %} + +{% block primary_content %} +
    +
    + {% block primary_content_inner %}{% endblock %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/edit_user_form.html b/venv/lib/python2.7/site-packages/ckan/templates/user/edit_user_form.html new file mode 100644 index 00000000..b11ae5b3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/edit_user_form.html @@ -0,0 +1,56 @@ +{% import 'macros/form.html' as form %} + +
    + {{ form.errors(error_summary) }} + +
    + {{ _('Change details') }} + {{ form.input('name', label=_('Username'), id='field-username', value=data.name, error=errors.name, classes=['control-medium'], attrs={'readonly': '', 'class': 'form-control'}) }} + + {{ form.input('fullname', label=_('Full name'), id='field-fullname', value=data.fullname, error=errors.fullname, placeholder=_('eg. Joe Bloggs'), classes=['control-medium']) }} + + {{ form.input('email', label=_('Email'), id='field-email', type='email', value=data.email, error=errors.email, placeholder=_('eg. joe@example.com'), classes=['control-medium'], is_required=true) }} + + {{ form.markdown('about', label=_('About'), id='field-about', value=data.about, error=errors.about, placeholder=_('A little information about yourself')) }} + + {% if show_email_notifications %} + {% call form.checkbox('activity_streams_email_notifications', label=_('Subscribe to notification emails'), id='field-activity-streams-email-notifications', value=True, checked=g.userobj.activity_streams_email_notifications) %} + {% set helper_text = _("You will receive notification emails from {site_title}, e.g. when you have new activities on your dashboard."|string) %} + {{ form.info(helper_text.format(site_title=g.site_title), classes=['info-help-tight']) }} + {% endcall %} + {% endif %} + +
    + +
    + {{ _('Change password') }} + {{ form.input('old_password', + type='password', + label=_('Sysadmin Password') if is_sysadmin else _('Old Password'), + id='field-password', + value=data.oldpassword, + error=errors.oldpassword, + classes=['control-medium'], + attrs={'autocomplete': 'off', 'class': 'form-control'} + ) }} + + {{ form.input('password1', type='password', label=_('Password'), id='field-password', value=data.password1, error=errors.password1, classes=['control-medium'], attrs={'autocomplete': 'off', 'class': 'form-control'} ) }} + + {{ form.input('password2', type='password', label=_('Confirm Password'), id='field-password-confirm', value=data.password2, error=errors.password2, classes=['control-medium'], attrs={'autocomplete': 'off', 'class': 'form-control'}) }} +
    + +
    + {% block delete_button %} + {% if h.check_access('user_delete', {'id': data.id}) %} + {% block delete_button_text %}{{ _('Delete') }}{% endblock %} + {% endif %} + {% endblock %} + {% block generate_button %} + {% if h.check_access('user_generate_apikey', {'id': data.id}) %} + {% block generate_button_text %}{{ _('Regenerate API Key') }}{% endblock %} + {% endif %} + {% endblock %} + {{ form.required_message() }} + +
    +
    diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/followers.html b/venv/lib/python2.7/site-packages/ckan/templates/user/followers.html new file mode 100644 index 00000000..d0c1b9aa --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/followers.html @@ -0,0 +1,12 @@ +{% extends "user/read.html" %} + +{% block subtitle %}{{ _('Followers') }}{% endblock %} + +{% block primary_content_inner %} +

    + {% block page_heading %}{{ _('Followers') }}{% endblock %} +

    + {% block followers_list %} + {% snippet "user/snippets/followers.html", followers=followers %} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/list.html b/venv/lib/python2.7/site-packages/ckan/templates/user/list.html new file mode 100644 index 00000000..08296fb0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/list.html @@ -0,0 +1,33 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _('All Users') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {{ h.nav_link(_('Users'), named_route='user.index') }}
  • +{% endblock %} + +{% block primary_content %} +
    +
    +

    + {% block page_heading %}{{ _('Users') }}{% endblock %} +

    + {% block users_list %} +
      + {% block users_list_inner %} + {% for user in page.items %} +
    • {{ h.linked_user(user['name'], maxlength=20) }}
    • + {% endfor %} + {% endblock %} +
    + {% endblock %} +
    + {% block page_pagination %} + {{ page.pager(q=q, order_by=order_by) }} + {% endblock %} +
    +{% endblock %} + +{% block secondary_content %} + {% snippet 'user/snippets/user_search.html', q=q %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/login.html b/venv/lib/python2.7/site-packages/ckan/templates/user/login.html new file mode 100644 index 00000000..310c7e0b --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/login.html @@ -0,0 +1,54 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _('Login') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {{ h.nav_link(_('Login'), named_route='user.login') }}
  • +{% endblock %} + +{% block primary_content %} +
    +
    +

    {% block page_heading %}{{ _('Login') }}{% endblock %}

    + {% block form %} + {% snippet "user/snippets/login_form.html", action=g.login_handler, error_summary=error_summary %} + {% endblock %} +
    +
    +{% endblock %} + +{% block secondary_content %} + {% if h.check_access('user_create') %} + {% block help_register %} +
    + {% block help_register_inner %} +

    {{ _('Need an Account?') }}

    +
    +

    {% trans %}Then sign right up, it only takes a minute.{% endtrans %}

    +

    + {% block help_register_button %} + {{ _('Create an Account') }} + {% endblock %} +

    +
    + {% endblock %} +
    + {% endblock %} + {% endif %} + + {% block help_forgotten %} +
    + {% block help_forgotten_inner %} +

    {{ _('Forgotten your password?') }}

    +
    +

    {% trans %}No problem, use our password recovery form to reset it.{% endtrans %}

    +

    + {% block help_forgotten_button %} + {{ _('Forgot your password?') }} + {% endblock %} +

    +
    + {% endblock %} +
    + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/logout.html b/venv/lib/python2.7/site-packages/ckan/templates/user/logout.html new file mode 100644 index 00000000..9d1460a2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/logout.html @@ -0,0 +1,14 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _('Logged Out') }}{% endblock %} + +{% block primary_content %} +
    +
    +

    + {% block page_heading %}{{ _('Logged Out') }}{% endblock %} +

    +

    {% trans %}You are now logged out.{% endtrans %}

    +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/logout_first.html b/venv/lib/python2.7/site-packages/ckan/templates/user/logout_first.html new file mode 100644 index 00000000..88d0da08 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/logout_first.html @@ -0,0 +1,26 @@ +{% import 'macros/form.html' as form %} +{% extends "user/login.html" %} + +{% set logout_url = h.url_for('user.logout') %} + +{% block actions %}{% endblock %} + +{% block form %} +
    {{ _("You're already logged in as {user}.").format(user=g.user) }} {{ _('Logout') }}?
    + {{ form.input('login', label=_('Username'), id='field-login', value="", error="", classes=["control-full"], attrs={"disabled": "disabled"}) }} + {{ form.input('password', label=_('Password'), id='field-password', type="password", value="", error="", classes=["control-full"], attrs={"disabled": "disabled"}) }} + {{ form.checkbox('remember', label=_('Remember me'), id='field-remember', checked=true, attrs={"disabled": "disabled"}) }} +
    + +
    +{% endblock %} + +{% block secondary_content %} +
    +

    {{ _("You're already logged in") }}

    +
    +

    {{ _("You need to log out before you can log in with another account.") }}

    +

    {{ _("Log out now") }}

    +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/new.html b/venv/lib/python2.7/site-packages/ckan/templates/user/new.html new file mode 100644 index 00000000..62d8de93 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/new.html @@ -0,0 +1,33 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _('Register') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {{ h.nav_link(_('Registration'), named_route='user.register') }}
  • +{% endblock %} + +{% block primary_content %} +
    +
    + {% block primary_content_inner %} +

    + {% block page_heading %}{{ _('Register for an Account') }}{% endblock %} +

    + {{ form | safe }} + {% endblock %} +
    +
    +{% endblock %} + +{% block secondary_content %} + {% block help %} +
    + {% block help_inner %} +

    {{ _('Why Sign Up?') }}

    +
    +

    {% trans %}Create datasets, groups and other exciting things{% endtrans %}

    +
    + {% endblock %} +
    + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/new_user_form.html b/venv/lib/python2.7/site-packages/ckan/templates/user/new_user_form.html new file mode 100644 index 00000000..7c9eae23 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/new_user_form.html @@ -0,0 +1,22 @@ +{% import "macros/form.html" as form %} + +
    + {{ form.errors(error_summary) }} + {{ form.input("name", id="field-username", label=_("Username"), placeholder=_("username"), value=data.name, error=errors.name, classes=["control-medium"], is_required=True) }} + {{ form.input("fullname", id="field-fullname", label=_("Full Name"), placeholder=_("Joe Bloggs"), value=data.fullname, error=errors.fullname, classes=["control-medium"]) }} + {{ form.input("email", id="field-email", label=_("Email"), type="email", placeholder=_("joe@example.com"), value=data.email, error=errors.email, classes=["control-medium"], is_required=True) }} + {{ form.input("password1", id="field-password", label=_("Password"), type="password", placeholder="••••••••", value=data.password1, error=errors.password1, classes=["control-medium"], is_required=True) }} + {{ form.input("password2", id="field-confirm-password", label=_("Confirm"), type="password", placeholder="••••••••", value=data.password2, error=errors.password1, classes=["control-medium"], is_required=True) }} + + {% if g.recaptcha_publickey %} + {% snippet "user/snippets/recaptcha.html", public_key=g.recaptcha_publickey %} + {% endif %} + + {{ form.required_message() }} + +
    + {% block form_actions %} + + {% endblock %} +
    +
    diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/perform_reset.html b/venv/lib/python2.7/site-packages/ckan/templates/user/perform_reset.html new file mode 100644 index 00000000..2c6c5266 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/perform_reset.html @@ -0,0 +1,50 @@ +{% extends "page.html" %} +{% import "macros/form.html" as form %} + +{% block subtitle %}{{ _('Reset Your Password') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {{ _('Password Reset') }}
  • +{% endblock %} + +{% block primary_content %} +
    + {% block primary_content_inner %} +
    +

    + {% block page_heading %}{{ _('Reset Your Password') }}{% endblock %} +

    + {% block form %} +
    + {{ form.errors(error_summary) }} + {% if user_dict['state'] == 'pending' %} +

    {{ _('You can also change username. It can not be modified later.') }}

    + {{ form.input("name", id="field-name", label=_("Username"), type="text", value=user_dict["name"], + error='', attrs={'autocomplete': 'no', 'class': 'form-control control-medium'}, classes=["form-group"]) }} + {% endif %} + {{ form.input("password1", id="field-password", label=_("Password"), type="password", value='', error='', attrs={'autocomplete': 'no', 'class': 'form-control control-medium'}, classes=["form-group"]) }} + {{ form.input("password2", id="field-confirm-password", label=_("Confirm"), type="password", value='', error='', attrs={'autocomplete': 'no', 'class': 'form-control control-medium'}, classes=["form-group"]) }} +
    + {% block form_button %} + + {% endblock %} +
    +
    + {% endblock %} +
    + {% endblock %} +
    +{% endblock %} + +{% block secondary_content %} + {% block help %} +
    + {% block help_inner %} +

    {{ _('How does this work?') }}

    +
    +

    {% trans %}Simply enter a new password and we'll update your account{% endtrans %}

    +
    + {% endblock %} +
    + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/read.html b/venv/lib/python2.7/site-packages/ckan/templates/user/read.html new file mode 100644 index 00000000..9545bf21 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/read.html @@ -0,0 +1,32 @@ +{% extends "user/read_base.html" %} + +{% block page_primary_action %} + {% if h.check_access('package_create') and user.datasets %} + {% snippet 'snippets/add_dataset.html' %} + {% endif %} +{% endblock %} + +{% block primary_content_inner %} +

    + {% block page_heading %}{{ _('Datasets') }}{% endblock %} +

    + {% block package_list %} + {% if user.datasets %} + {% snippet 'snippets/package_list.html', packages=user.datasets %} + {% else %} + + {% if is_myself %} +

    + {{ _('You haven\'t created any datasets.') }} + {% if h.check_access('package_create') %} + {% link_for _('Create one now?'), controller='package', action='new' %} + {% endif %} +

    + {% else %} +

    + {{ _('User hasn\'t created any datasets.') }} +

    + {% endif %} + {% endif %} + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/read_base.html b/venv/lib/python2.7/site-packages/ckan/templates/user/read_base.html new file mode 100644 index 00000000..86c8fc4a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/read_base.html @@ -0,0 +1,105 @@ +{% extends "page.html" %} + +{% set user = user_dict %} + +{% block subtitle %}{{ user.display_name }} - {{ _('Users') }}{% endblock %} + +{% block breadcrumb_content %} + {{ h.build_nav('user.index', _('Users')) }} + {{ h.build_nav('user.read', user.display_name|truncate(35), id=user.name) }} +{% endblock %} + +{% block content_action %} + {% if h.check_access('user_update', user) %} + {% link_for _('Manage'), named_route='user.edit', id=user.name, class_='btn btn-default', icon='wrench' %} + {% endif %} +{% endblock %} + +{% block content_primary_nav %} + {{ h.build_nav_icon('user.read', _('Datasets'), id=user.name) }} + {{ h.build_nav_icon('user.activity', _('Activity Stream'), id=user.name) }} +{% endblock %} + +{% block secondary_content %} +
    +
    + {% block secondary_content_inner %} + {% block user_image %} +
    {{ h.linked_gravatar(user.email_hash, 190) }}
    + {% endblock %} + {% block user_heading %} +

    {{ user.display_name }}

    + {% endblock %} + {% block user_about %} + {% if about_formatted %} + {{ about_formatted }} + {% else %} +

    + {% if is_myself %} + {% trans %}You have not provided a biography.{% endtrans %} + {% else %} + {% trans %}This user has no biography.{% endtrans %} + {% endif %} +

    + {% endif %} + {% endblock %} + {% block user_nums %} +
    +
    +
    {{ _('Followers') }}
    +
    {{ h.SI_number_span(user.num_followers) }}
    +
    +
    +
    {{ _('Datasets') }}
    +
    {{ h.SI_number_span(user.number_created_packages) }}
    +
    +
    +
    {{ _('Edits') }}
    +
    {{ h.SI_number_span(user.number_of_edits) }}
    +
    +
    + {% endblock %} + {% if is_myself == false %} + {% block user_follow %} + + {% endblock %} + {% endif %} + {% block user_info %} +
    +
    + {% if user.name.startswith('http://') or user.name.startswith('https://') %} +
    {{ _('Open ID') }}
    +
    {{ user.name|urlize(25) }}{# Be great if this just showed the domain #}
    + {% else %} +
    {{ _('Username') }}
    +
    {{ user.name }}
    + {% endif %} +
    + {% if is_myself %} +
    +
    {{ _('Email') }} {{ _('Private') }}
    +
    {{ user.email }}
    +
    + {% endif %} +
    +
    {{ _('Member Since') }}
    +
    {{ h.render_datetime(user.created) }}
    +
    +
    +
    {{ _('State') }}
    +
    {{ _(user.state) }}
    +
    + {% if is_myself %} +
    +
    {{ _('API Key') }} {{ _('Private') }}
    +
    {{ user.apikey }}
    +
    + {% endif %} +
    + {% endblock %} + {% endblock %} +
    +
    +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/templates/user/request_reset.html b/venv/lib/python2.7/site-packages/ckan/templates/user/request_reset.html new file mode 100644 index 00000000..c3512389 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/templates/user/request_reset.html @@ -0,0 +1,44 @@ +{% extends "page.html" %} + +{% block subtitle %}{{ _('Reset your password') }}{% endblock %} + +{% block breadcrumb_content %} +
  • {% link_for _('Password Reset'), named_route='user.request_reset' %}
  • +{% endblock %} + +{% block primary_content %} +
    +
    + {% block primary_content_inner %} +

    {{ _('Reset your password') }}

    + {% block form %} +
    +
    + + +
    +
    + {% block form_button %} + + {% endblock %} +
    +
    + {% endblock %} + {% endblock %} +
    +
    +{% endblock %} + +{% block secondary_content %} + {% block help %} +
    + {% block help_inner %} +

    {{ _('How does this work?') }}

    +
    +

    {% trans %}Enter your username into the box and we will send you + an email with a link to enter a new password.{% endtrans %}

    +
    + {% endblock %} +
    + {% endblock %} +{% endblock %} diff --git a/venv/lib/python2.7/site-packages/ckan/tests/__init__.py b/venv/lib/python2.7/site-packages/ckan/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/venv/lib/python2.7/site-packages/ckan/tests/config/__init__.py b/venv/lib/python2.7/site-packages/ckan/tests/config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/venv/lib/python2.7/site-packages/ckan/tests/config/test_environment.py b/venv/lib/python2.7/site-packages/ckan/tests/config/test_environment.py new file mode 100644 index 00000000..49501898 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/config/test_environment.py @@ -0,0 +1,113 @@ +# encoding: utf-8 + +import os +from nose import tools as nosetools + +from ckan.common import config + +import ckan.tests.helpers as h +import ckan.plugins as p +from ckan.config import environment +from ckan.exceptions import CkanConfigurationException + +from ckan.tests import helpers + + +class TestUpdateConfig(h.FunctionalTestBase): + + ''' + Tests for config settings from env vars, set in + config.environment.update_config(). + ''' + + ENV_VAR_LIST = [ + ('CKAN_SQLALCHEMY_URL', 'postgresql://mynewsqlurl/'), + ('CKAN_DATASTORE_WRITE_URL', 'http://mynewdbwriteurl/'), + ('CKAN_DATASTORE_READ_URL', 'http://mynewdbreadurl/'), + ('CKAN_SOLR_URL', 'http://mynewsolrurl/solr'), + ('CKAN_SITE_ID', 'my-site'), + ('CKAN_DB', 'postgresql://mydeprectatesqlurl/'), + ('CKAN_SMTP_SERVER', 'mail.example.com'), + ('CKAN_SMTP_STARTTLS', 'True'), + ('CKAN_SMTP_USER', 'my_user'), + ('CKAN_SMTP_PASSWORD', 'password'), + ('CKAN_SMTP_MAIL_FROM', 'server@example.com'), + ('CKAN_MAX_UPLOAD_SIZE_MB', '50') + ] + + def _setup_env_vars(self): + for env_var, value in self.ENV_VAR_LIST: + os.environ.setdefault(env_var, value) + # plugin.load() will force the config to update + p.load() + + def setup(self): + self._old_config = dict(config) + + def teardown(self): + for env_var, _ in self.ENV_VAR_LIST: + if os.environ.get(env_var, None): + del os.environ[env_var] + config.update(self._old_config) + # plugin.load() will force the config to update + p.load() + + def test_update_config_env_vars(self): + ''' + Setting an env var from the whitelist will set the appropriate option + in config object. + ''' + self._setup_env_vars() + + nosetools.assert_equal(config['solr_url'], 'http://mynewsolrurl/solr') + nosetools.assert_equal(config['sqlalchemy.url'], + 'postgresql://mynewsqlurl/') + nosetools.assert_equal(config['ckan.datastore.write_url'], + 'http://mynewdbwriteurl/') + nosetools.assert_equal(config['ckan.datastore.read_url'], + 'http://mynewdbreadurl/') + nosetools.assert_equal(config['ckan.site_id'], 'my-site') + nosetools.assert_equal(config['smtp.server'], 'mail.example.com') + nosetools.assert_equal(config['smtp.starttls'], 'True') + nosetools.assert_equal(config['smtp.user'], 'my_user') + nosetools.assert_equal(config['smtp.password'], 'password') + nosetools.assert_equal(config['smtp.mail_from'], 'server@example.com') + nosetools.assert_equal(config['ckan.max_resource_size'], '50') + + def test_update_config_db_url_precedence(self): + '''CKAN_SQLALCHEMY_URL in the env takes precedence over CKAN_DB''' + os.environ.setdefault('CKAN_DB', 'postgresql://mydeprectatesqlurl/') + os.environ.setdefault('CKAN_SQLALCHEMY_URL', + 'postgresql://mynewsqlurl/') + p.load() + + nosetools.assert_equal(config['sqlalchemy.url'], + 'postgresql://mynewsqlurl/') + + +class TestSiteUrlMandatory(object): + + @helpers.change_config('ckan.site_url', '') + def test_missing_siteurl(self): + nosetools.assert_raises(RuntimeError, environment.update_config) + + @helpers.change_config('ckan.site_url', 'demo.ckan.org') + def test_siteurl_missing_schema(self): + nosetools.assert_raises(RuntimeError, environment.update_config) + + @helpers.change_config('ckan.site_url', 'ftp://demo.ckan.org') + def test_siteurl_wrong_schema(self): + nosetools.assert_raises(RuntimeError, environment.update_config) + + @helpers.change_config('ckan.site_url', 'http://demo.ckan.org/') + def test_siteurl_removes_backslash(self): + environment.update_config() + nosetools.assert_equals(config['ckan.site_url'], + 'http://demo.ckan.org') + + +class TestDisplayTimezone(object): + + @helpers.change_config('ckan.display_timezone', 'Krypton/Argo City') + def test_missing_timezone(self): + nosetools.assert_raises(CkanConfigurationException, environment.update_config) diff --git a/venv/lib/python2.7/site-packages/ckan/tests/config/test_middleware.py b/venv/lib/python2.7/site-packages/ckan/tests/config/test_middleware.py new file mode 100644 index 00000000..5234b696 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/config/test_middleware.py @@ -0,0 +1,660 @@ +# encoding: utf-8 + +import mock +import wsgiref +from nose.tools import assert_equals, assert_not_equals, eq_, assert_raises +from ckan.lib.helpers import url_for +from flask import Blueprint +import flask + +import ckan.model as model +import ckan.plugins as p +import ckan.lib.helpers as h +import ckan.tests.helpers as helpers +import ckan.tests.factories as factories +from ckan.common import config, _ + +from ckan.config.middleware import AskAppDispatcherMiddleware +from ckan.config.middleware.flask_app import CKANFlask +from ckan.config.middleware.pylons_app import CKANPylonsApp + + +class TestPylonsResponseCleanupMiddleware(helpers.FunctionalTestBase): + @classmethod + def _apply_config_changes(cls, config): + config['ckan.use_pylons_response_cleanup_middleware'] = True + + def test_homepage_with_middleware_activated(self): + '''Test the home page renders with the middleware activated + + We are just testing the home page renders without any troubles and that + the middleware has not done anything strange to the response string''' + app = self._get_test_app() + response = app.get(url=url_for(controller='home', action='index')) + + assert_equals(200, response.status_int) + # make sure we haven't overwritten the response too early. + assert_not_equals( + 'response cleared by pylons response cleanup middleware', + response.body + ) + + +class TestAppDispatcherPlain(object): + ''' + These tests need the test app to be created at specific times to not affect + the mocks, so they don't extend FunctionalTestBase + ''' + + def test_flask_can_handle_request_is_called_with_environ(self): + + with mock.patch.object(CKANFlask, 'can_handle_request') as \ + mock_can_handle_request: + # We need set this otherwise the mock object is returned + mock_can_handle_request.return_value = (False, 'flask_app') + + app = helpers._get_test_app() + # We want our CKAN app, not the WebTest one + ckan_app = app.app + + environ = { + 'PATH_INFO': '/', + } + wsgiref.util.setup_testing_defaults(environ) + start_response = mock.MagicMock() + + ckan_app(environ, start_response) + + assert mock_can_handle_request.called_with(environ) + + def test_pylons_can_handle_request_is_called_with_environ(self): + + with mock.patch.object(CKANPylonsApp, 'can_handle_request') as \ + mock_can_handle_request: + + # We need set this otherwise the mock object is returned + mock_can_handle_request.return_value = (True, 'pylons_app', 'core') + + app = helpers._get_test_app() + # We want our CKAN app, not the WebTest one + ckan_app = app.app + + environ = { + 'PATH_INFO': '/', + } + wsgiref.util.setup_testing_defaults(environ) + start_response = mock.MagicMock() + + ckan_app(environ, start_response) + + assert mock_can_handle_request.called_with(environ) + + +class TestAppDispatcher(helpers.FunctionalTestBase): + + @classmethod + def setup_class(cls): + + super(TestAppDispatcher, cls).setup_class() + + # Add a custom route to the Flask app + app = cls._get_test_app() + + flask_app = app.flask_app + + def test_view(): + return 'This was served from Flask' + + # This endpoint is defined both in Flask and in Pylons core + flask_app.add_url_rule('/flask_core', view_func=test_view) + + # This endpoint is defined both in Flask and a Pylons extension + flask_app.add_url_rule('/pylons_and_flask', view_func=test_view) + + def test_ask_around_is_called(self): + + app = self._get_test_app() + with mock.patch.object(AskAppDispatcherMiddleware, 'ask_around') as \ + mock_ask_around: + app.get('/', status=404) + + assert mock_ask_around.called + + def test_ask_around_is_called_with_args(self): + + app = self._get_test_app() + ckan_app = app.app + + environ = {} + start_response = mock.MagicMock() + wsgiref.util.setup_testing_defaults(environ) + + with mock.patch.object(AskAppDispatcherMiddleware, 'ask_around') as \ + mock_ask_around: + + ckan_app(environ, start_response) + assert mock_ask_around.called + mock_ask_around.assert_called_with(environ) + + def test_ask_around_flask_core_route_get(self): + + app = self._get_test_app() + + # We want our CKAN app, not the WebTest one + app = app.app + + environ = { + 'PATH_INFO': '/hello', + 'REQUEST_METHOD': 'GET', + } + wsgiref.util.setup_testing_defaults(environ) + + answers = app.ask_around(environ) + + # Even though this route is defined in Flask, there is catch all route + # in Pylons for all requests to point arbitrary urls to templates with + # the same name, so we get two positive answers + eq_(answers, [(True, 'flask_app', 'core'), + (True, 'pylons_app', 'core')]) + + def test_ask_around_flask_core_route_post(self): + + app = self._get_test_app() + + # We want our CKAN app, not the WebTest one + app = app.app + + environ = { + 'PATH_INFO': '/hello', + 'REQUEST_METHOD': 'POST', + } + wsgiref.util.setup_testing_defaults(environ) + + answers = app.ask_around(environ) + + # Even though this route is defined in Flask, there is catch all route + # in Pylons for all requests to point arbitrary urls to templates with + # the same name, so we get two positive answers + eq_(answers, [(True, 'flask_app', 'core'), + (True, 'pylons_app', 'core')]) + + def test_ask_around_pylons_core_route_get(self): + + app = self._get_test_app() + + # We want our CKAN app, not the WebTest one + app = app.app + + environ = { + 'PATH_INFO': '/dataset', + 'REQUEST_METHOD': 'GET', + } + wsgiref.util.setup_testing_defaults(environ) + + answers = app.ask_around(environ) + + eq_(answers, [(False, 'flask_app'), (True, 'pylons_app', 'core')]) + + def test_ask_around_pylons_core_route_post(self): + + app = self._get_test_app() + + # We want our CKAN app, not the WebTest one + app = app.app + + environ = { + 'PATH_INFO': '/dataset/new', + 'REQUEST_METHOD': 'POST', + } + wsgiref.util.setup_testing_defaults(environ) + + answers = app.ask_around(environ) + + eq_(answers, [(False, 'flask_app'), (True, 'pylons_app', 'core')]) + + def test_ask_around_pylons_extension_route_get_before_map(self): + + if not p.plugin_loaded('test_routing_plugin'): + p.load('test_routing_plugin') + + app = self._get_test_app() + + # We want our CKAN app, not the WebTest one + app = app.app + + environ = { + 'PATH_INFO': '/from_pylons_extension_before_map', + 'REQUEST_METHOD': 'GET', + } + wsgiref.util.setup_testing_defaults(environ) + + answers = app.ask_around(environ) + + eq_(answers, [(False, 'flask_app'), (True, 'pylons_app', 'extension')]) + + p.unload('test_routing_plugin') + + def test_ask_around_pylons_extension_route_post(self): + + if not p.plugin_loaded('test_routing_plugin'): + p.load('test_routing_plugin') + + app = self._get_test_app() + + # We want our CKAN app, not the WebTest one + app = app.app + + environ = { + 'PATH_INFO': '/from_pylons_extension_before_map_post_only', + 'REQUEST_METHOD': 'POST', + } + wsgiref.util.setup_testing_defaults(environ) + + answers = app.ask_around(environ) + + eq_(answers, [(False, 'flask_app'), (True, 'pylons_app', 'extension')]) + + p.unload('test_routing_plugin') + + def test_ask_around_pylons_extension_route_post_using_get(self): + + if not p.plugin_loaded('test_routing_plugin'): + p.load('test_routing_plugin') + + app = self._get_test_app() + + # We want our CKAN app, not the WebTest one + app = app.app + + environ = { + 'PATH_INFO': '/from_pylons_extension_before_map_post_only', + 'REQUEST_METHOD': 'GET', + } + wsgiref.util.setup_testing_defaults(environ) + + answers = app.ask_around(environ) + + # We are going to get an answer from Pylons, but just because it will + # match the catch-all template route, hence the `core` origin. + eq_(answers, [(False, 'flask_app'), (True, 'pylons_app', 'core')]) + + p.unload('test_routing_plugin') + + def test_ask_around_pylons_extension_route_get_after_map(self): + + if not p.plugin_loaded('test_routing_plugin'): + p.load('test_routing_plugin') + + app = self._get_test_app() + + # We want our CKAN app, not the WebTest one + app = app.app + + environ = { + 'PATH_INFO': '/from_pylons_extension_after_map', + 'REQUEST_METHOD': 'GET', + } + wsgiref.util.setup_testing_defaults(environ) + + answers = app.ask_around(environ) + + eq_(answers, [(False, 'flask_app'), (True, 'pylons_app', 'extension')]) + + p.unload('test_routing_plugin') + + def test_ask_around_flask_core_and_pylons_extension_route(self): + + if not p.plugin_loaded('test_routing_plugin'): + p.load('test_routing_plugin') + + app = self._get_test_app() + + # We want our CKAN app, not the WebTest one + app = app.app + + environ = { + 'PATH_INFO': '/pylons_and_flask', + 'REQUEST_METHOD': 'GET', + } + wsgiref.util.setup_testing_defaults(environ) + + answers = app.ask_around(environ) + answers = sorted(answers, key=lambda a: a[1]) + + eq_(answers, [(True, 'flask_app', 'core'), + (True, 'pylons_app', 'extension')]) + + p.unload('test_routing_plugin') + + def test_flask_core_route_is_served_by_flask(self): + + app = self._get_test_app() + + res = app.get('/hello') + + eq_(res.environ['ckan.app'], 'flask_app') + + def test_flask_extension_route_is_served_by_flask(self): + + app = self._get_test_app() + + # Install plugin and register its blueprint + if not p.plugin_loaded('test_routing_plugin'): + p.load('test_routing_plugin') + plugin = p.get_plugin('test_routing_plugin') + app.flask_app.register_extension_blueprint(plugin.get_blueprint()) + + res = app.get('/simple_flask') + + eq_(res.environ['ckan.app'], 'flask_app') + + p.unload('test_routing_plugin') + + def test_pylons_core_route_is_served_by_pylons(self): + + app = self._get_test_app() + + res = app.get('/dataset') + + eq_(res.environ['ckan.app'], 'pylons_app') + + def test_pylons_extension_route_is_served_by_pylons(self): + + if not p.plugin_loaded('test_routing_plugin'): + p.load('test_routing_plugin') + + app = self._get_test_app() + + res = app.get('/from_pylons_extension_before_map') + + eq_(res.environ['ckan.app'], 'pylons_app') + eq_(res.body, 'Hello World, this is served from a Pylons extension') + + p.unload('test_routing_plugin') + + def test_flask_core_and_pylons_extension_route_is_served_by_pylons(self): + + if not p.plugin_loaded('test_routing_plugin'): + p.load('test_routing_plugin') + + app = self._get_test_app() + + res = app.get('/pylons_and_flask') + + eq_(res.environ['ckan.app'], 'pylons_app') + eq_(res.body, 'Hello World, this is served from a Pylons extension') + + p.unload('test_routing_plugin') + + def test_flask_core_and_pylons_core_route_is_served_by_flask(self): + ''' + This should never happen in core, but just in case + ''' + app = self._get_test_app() + res = app.get('/flask_core') + + eq_(res.environ['ckan.app'], 'flask_app') + eq_(res.body, 'This was served from Flask') + + +class TestFlaskUserIdentifiedInRequest(helpers.FunctionalTestBase): + + '''Flask identifies user during each request. + + Flask route provided by test.helpers.SimpleFlaskPlugin. + ''' + + @classmethod + def setup_class(cls): + super(TestFlaskUserIdentifiedInRequest, cls).setup_class() + cls.app = cls._get_test_app() + cls.flask_app = cls.app.flask_app + + if not p.plugin_loaded('test_routing_plugin'): + p.load('test_routing_plugin') + plugin = p.get_plugin('test_routing_plugin') + cls.flask_app.register_extension_blueprint( + plugin.get_blueprint()) + + @classmethod + def teardown_class(cls): + super(TestFlaskUserIdentifiedInRequest, cls).teardown_class() + p.unload('test_routing_plugin') + + def test_user_objects_in_g_normal_user(self): + ''' + A normal logged in user request will have expected user objects added + to request. + ''' + user = factories.User() + test_user_obj = model.User.by_name(user['name']) + + with self.flask_app.app_context(): + self.app.get( + '/simple_flask', + extra_environ={'REMOTE_USER': user['name'].encode('ascii')},) + eq_(flask.g.user, user['name']) + eq_(flask.g.userobj, test_user_obj) + eq_(flask.g.author, user['name']) + eq_(flask.g.remote_addr, 'Unknown IP Address') + + def test_user_objects_in_g_anon_user(self): + ''' + An anon user request will have expected user objects added to request. + ''' + with self.flask_app.app_context(): + self.app.get( + '/simple_flask', + extra_environ={'REMOTE_USER': ''},) + eq_(flask.g.user, '') + eq_(flask.g.userobj, None) + eq_(flask.g.author, 'Unknown IP Address') + eq_(flask.g.remote_addr, 'Unknown IP Address') + + def test_user_objects_in_g_sysadmin(self): + ''' + A sysadmin user request will have expected user objects added to + request. + ''' + user = factories.Sysadmin() + test_user_obj = model.User.by_name(user['name']) + + with self.flask_app.app_context(): + self.app.get( + '/simple_flask', + extra_environ={'REMOTE_USER': user['name'].encode('ascii')},) + eq_(flask.g.user, user['name']) + eq_(flask.g.userobj, test_user_obj) + eq_(flask.g.author, user['name']) + eq_(flask.g.remote_addr, 'Unknown IP Address') + + +class TestPylonsUserIdentifiedInRequest(helpers.FunctionalTestBase): + + '''Pylons identifies user during each request. + + Using a route setup via an extension to ensure we're always testing a + Pylons-flavoured request. + ''' + + def test_user_objects_in_c_normal_user(self): + ''' + A normal logged in user request will have expected user objects added + to request. + ''' + if not p.plugin_loaded('test_routing_plugin'): + p.load('test_routing_plugin') + + app = self._get_test_app() + user = factories.User() + test_user_obj = model.User.by_name(user['name']) + + resp = app.get( + '/from_pylons_extension_before_map', + extra_environ={'REMOTE_USER': user['name'].encode('ascii')}) + + # tmpl_context available on response + eq_(resp.tmpl_context.user, user['name']) + eq_(resp.tmpl_context.userobj, test_user_obj) + eq_(resp.tmpl_context.author, user['name']) + eq_(resp.tmpl_context.remote_addr, 'Unknown IP Address') + + p.unload('test_routing_plugin') + + def test_user_objects_in_c_anon_user(self): + ''' + An anon user request will have expected user objects added to request. + ''' + if not p.plugin_loaded('test_routing_plugin'): + p.load('test_routing_plugin') + + app = self._get_test_app() + + resp = app.get( + '/from_pylons_extension_before_map', + extra_environ={'REMOTE_USER': ''}) + + # tmpl_context available on response + eq_(resp.tmpl_context.user, '') + eq_(resp.tmpl_context.userobj, None) + eq_(resp.tmpl_context.author, 'Unknown IP Address') + eq_(resp.tmpl_context.remote_addr, 'Unknown IP Address') + + p.unload('test_routing_plugin') + + def test_user_objects_in_c_sysadmin(self): + ''' + A sysadmin user request will have expected user objects added to + request. + ''' + if not p.plugin_loaded('test_routing_plugin'): + p.load('test_routing_plugin') + + app = self._get_test_app() + user = factories.Sysadmin() + test_user_obj = model.User.by_name(user['name']) + + resp = app.get( + '/from_pylons_extension_before_map', + extra_environ={'REMOTE_USER': user['name'].encode('ascii')}) + + # tmpl_context available on response + eq_(resp.tmpl_context.user, user['name']) + eq_(resp.tmpl_context.userobj, test_user_obj) + eq_(resp.tmpl_context.author, user['name']) + eq_(resp.tmpl_context.remote_addr, 'Unknown IP Address') + + p.unload('test_routing_plugin') + + +_test_controller = 'ckan.tests.config.test_middleware:MockPylonsController' + + +class MockRoutingPlugin(p.SingletonPlugin): + + p.implements(p.IRoutes) + p.implements(p.IBlueprint) + + controller = _test_controller + + def before_map(self, _map): + + _map.connect('/from_pylons_extension_before_map', + controller=self.controller, action='view') + + _map.connect('/from_pylons_extension_before_map_post_only', + controller=self.controller, action='view', + conditions={'method': 'POST'}) + # This one conflicts with an extension Flask route + _map.connect('/pylons_and_flask', + controller=self.controller, action='view') + + # This one conflicts with a core Flask route + _map.connect('/hello', + controller=self.controller, action='view') + + _map.connect('/pylons_route_flask_url_for', + controller=self.controller, action='test_flask_url_for') + _map.connect('/pylons_translated', + controller=self.controller, action='test_translation') + + return _map + + def after_map(self, _map): + + _map.connect('/from_pylons_extension_after_map', + controller=self.controller, action='view') + + return _map + + def get_blueprint(self): + # Create Blueprint for plugin + blueprint = Blueprint(self.name, self.__module__) + # Add plugin url rule to Blueprint object + blueprint.add_url_rule('/pylons_and_flask', 'flask_plugin_view', + flask_plugin_view) + + blueprint.add_url_rule('/simple_flask', 'flask_plugin_view', + flask_plugin_view) + + blueprint.add_url_rule('/flask_route_pylons_url_for', + 'flask_route_pylons_url_for', + flask_plugin_view_url_for) + blueprint.add_url_rule('/flask_translated', 'flask_translated', + flask_translated_view) + + return blueprint + + +def flask_plugin_view(): + return 'Hello World, this is served from a Flask extension' + + +def flask_plugin_view_url_for(): + url = h.url_for(controller=_test_controller, action='view') + return 'This URL was generated by Pylons: {0}'.format(url) + + +def flask_translated_view(): + return _('Dataset') + + +class MockPylonsController(p.toolkit.BaseController): + + def view(self): + return 'Hello World, this is served from a Pylons extension' + + def test_flask_url_for(self): + url = h.url_for('api.get_api', ver=3) + return 'This URL was generated by Flask: {0}'.format(url) + + def test_translation(self): + return _('Groups') + + +class TestSecretKey(object): + + @helpers.change_config('SECRET_KEY', 'super_secret_stuff') + def test_secret_key_is_used_if_present(self): + + app = helpers._get_test_app() + + eq_(app.flask_app.config['SECRET_KEY'], + u'super_secret_stuff') + + @helpers.change_config('SECRET_KEY', None) + def test_beaker_secret_is_used_by_default(self): + + app = helpers._get_test_app() + + eq_(app.flask_app.config['SECRET_KEY'], + config['beaker.session.secret']) + + @helpers.change_config('SECRET_KEY', None) + @helpers.change_config('beaker.session.secret', None) + def test_no_beaker_secret_crashes(self): + + assert_raises(ValueError, helpers._get_test_app) + + # TODO: When Pylons is finally removed, we should test for + # RuntimeError instead (thrown on `make_flask_stack`) diff --git a/venv/lib/python2.7/site-packages/ckan/tests/config/test_sessions.py b/venv/lib/python2.7/site-packages/ckan/tests/config/test_sessions.py new file mode 100644 index 00000000..45fc05d1 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/config/test_sessions.py @@ -0,0 +1,127 @@ +# encoding: utf-8 + +from nose.tools import ok_ + +from flask import Blueprint +from flask import render_template +from ckan.lib.base import render as pylons_render + +import ckan.plugins as p +import ckan.tests.helpers as helpers +import ckan.lib.helpers as h + + +class TestCrossFlaskPylonsFlashMessages(helpers.FunctionalTestBase): + u''' + Test that flash message set in the Pylons controller can be accessed by + Flask views, and visa versa. + ''' + + def setup(self): + self.app = helpers._get_test_app() + + # Install plugin and register its blueprint + if not p.plugin_loaded(u'test_flash_plugin'): + p.load(u'test_flash_plugin') + plugin = p.get_plugin(u'test_flash_plugin') + self.app.flask_app.register_extension_blueprint( + plugin.get_blueprint()) + + def test_flash_populated_by_flask_redirect_to_flask(self): + u''' + Flash store is populated by Flask view is accessible by another Flask + view. + ''' + res = self.app.get( + u'/flask_add_flash_message_redirect_to_flask').follow() + + ok_(u'This is a success message populated by Flask' in res.body) + + def test_flash_populated_in_pylons_action_redirect_to_flask(self): + u''' + Flash store is populated by pylons action is accessible by Flask view. + ''' + res = self.app.get(u'/pylons_add_flash_message_redirect_view').follow() + + ok_(u'This is a success message populated by Pylons' in res.body) + + def test_flash_populated_in_flask_view_redirect_to_pylons(self): + u''' + Flash store is populated by flask view is accessible by pylons action. + ''' + res = self.app.get( + u'/flask_add_flash_message_redirect_pylons').follow() + + ok_(u'This is a success message populated by Flask' in res.body) + + +class FlashMessagePlugin(p.SingletonPlugin): + u''' + A Flask and Pylons compatible IRoutes/IBlueprint plugin to add Flask views + and Pylons actions to display flash messages. + ''' + + p.implements(p.IRoutes, inherit=True) + p.implements(p.IBlueprint) + + def flash_message_view(self): + u'''Flask view that renders the flash message html template.''' + return render_template(u'tests/flash_messages.html') + + def add_flash_message_view_redirect_to_flask(self): + u'''Add flash message, then redirect to Flask view to render it.''' + h.flash_success(u'This is a success message populated by Flask') + return h.redirect_to( + h.url_for(u'test_flash_plugin.flash_message_view')) + + def add_flash_message_view_redirect_to_pylons(self): + u'''Add flash message, then redirect to view that renders it''' + h.flash_success(u'This is a success message populated by Flask') + return h.redirect_to(u'/pylons_view_flash_message') + + def get_blueprint(self): + u'''Return Flask Blueprint object to be registered by the Flask app.''' + + # Create Blueprint for plugin + blueprint = Blueprint(self.name, self.__module__) + # Add plugin url rules to Blueprint object + rules = [ + (u'/flask_add_flash_message_redirect_to_flask', + u'add_flash_message', + self.add_flash_message_view_redirect_to_flask), + (u'/flask_add_flash_message_redirect_pylons', + u'add_flash_message_view_redirect_to_pylons', + self.add_flash_message_view_redirect_to_pylons), + (u'/flask_view_flash_message', u'flash_message_view', + self.flash_message_view), + ] + for rule in rules: + blueprint.add_url_rule(*rule) + + return blueprint + + controller = \ + u'ckan.tests.config.test_sessions:PylonsAddFlashMessageController' + + def before_map(self, _map): + u'''Update the pylons route map to be used by the Pylons app.''' + _map.connect(u'/pylons_add_flash_message_redirect_view', + controller=self.controller, + action=u'add_flash_message_redirect') + + _map.connect(u'/pylons_view_flash_message', + controller=self.controller, + action=u'flash_message_action') + return _map + + +class PylonsAddFlashMessageController(p.toolkit.BaseController): + + def flash_message_action(self): + u'''Pylons view to render flash messages in a template.''' + return pylons_render(u'tests/flash_messages.html') + + def add_flash_message_redirect(self): + # Adds a flash message and redirects to flask view + h.flash_success(u'This is a success message populated by Pylons') + return h.redirect_to(u'/flask_view_flash_message') diff --git a/venv/lib/python2.7/site-packages/ckan/tests/controllers/__init__.py b/venv/lib/python2.7/site-packages/ckan/tests/controllers/__init__.py new file mode 100644 index 00000000..4f48ec7a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/controllers/__init__.py @@ -0,0 +1,55 @@ +# encoding: utf-8 + +''' +Controller tests probably shouldn't use mocking. + +.. todo:: + + Write the tests for one controller, figuring out the best way to write + controller tests. Then fill in this guidelines section, using the first set + of controller tests as an example. + + Some things have been decided already: + + * All controller methods should have tests + + * Controller tests should be high-level tests that work by posting simulated + HTTP requests to CKAN URLs and testing the response. So the controller + tests are also testing CKAN's templates and rendering - these are CKAN's + front-end tests. + + For example, maybe we use a webtests testapp and then use beautiful soup + to parse the HTML? + + * In general the tests for a controller shouldn't need to be too detailed, + because there shouldn't be a lot of complicated logic and code in + controller classes. The logic should be handled in other places such as + :mod:`ckan.logic` and :mod:`ckan.lib`, where it can be tested easily and + also shared with other code. + + * The tests for a controller should: + + * Make sure that the template renders without crashing. + + * Test that the page contents seem basically correct, or test certain + important elements in the page contents (but don't do too much HTML + parsing). + + * Test that submitting any forms on the page works without crashing and + has the expected side-effects. + + * When asserting side-effects after submitting a form, controller tests + should user the :func:`ckan.tests.helpers.call_action` function. For + example after creating a new user by submitting the new user form, a + test could call the :func:`~ckan.logic.action.get.user_show` action + function to verify that the user was created with the correct values. + +.. warning:: + + Some CKAN controllers *do* contain a lot of complicated logic code. These + controllers should be refactored to move the logic into :mod:`ckan.logic` or + :mod:`ckan.lib` where it can be tested easily. Unfortunately in cases like + this it may be necessary to write a lot of controller tests to get this + code's behavior into a test harness before it can be safely refactored. + +''' diff --git a/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_admin.py b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_admin.py new file mode 100644 index 00000000..ad292e31 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_admin.py @@ -0,0 +1,418 @@ +# encoding: utf-8 + +from nose.tools import assert_true, assert_equal + +from bs4 import BeautifulSoup +from ckan.lib.helpers import url_for +from ckan.common import config + +import ckan.model as model +import ckan.tests.helpers as helpers +import ckan.tests.factories as factories +from ckan.model.system_info import get_system_info + + +submit_and_follow = helpers.submit_and_follow +webtest_submit = helpers.webtest_submit + + +def _get_admin_config_page(app): + user = factories.Sysadmin() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url=url_for(controller='admin', action='config'), + extra_environ=env, + ) + return env, response + + +def _reset_config(app): + '''Reset config via action''' + user = factories.Sysadmin() + env = {'REMOTE_USER': user['name'].encode('ascii')} + app.post( + url=url_for('admin.reset_config'), + extra_environ=env, + ) + + +class TestConfig(helpers.FunctionalTestBase): + '''View tests to go along with 'Customizing look and feel' docs.''' + + def teardown(self): + helpers.reset_db() + + def test_form_renders(self): + '''admin-config-form in the response''' + app = self._get_test_app() + env, response = _get_admin_config_page(app) + assert_true('admin-config-form' in response.forms) + + def test_site_title(self): + '''Configure the site title''' + # current site title + app = self._get_test_app() + + index_response = app.get('/') + assert_true('Welcome - CKAN' in index_response) + + # change site title + env, config_response = _get_admin_config_page(app) + config_form = config_response.forms['admin-config-form'] + config_form['ckan.site_title'] = 'Test Site Title' + webtest_submit(config_form, 'save', status=302, extra_environ=env) + + # new site title + new_index_response = app.get('/') + assert_true('Welcome - Test Site Title' in new_index_response) + + # reset config value + _reset_config(app) + reset_index_response = app.get('/') + assert_true('Welcome - CKAN' in reset_index_response) + + def test_main_css_list(self): + '''Style list contains pre-configured styles''' + + STYLE_NAMES = [ + 'Default', + 'Red', + 'Green', + 'Maroon', + 'Fuchsia' + ] + + app = self._get_test_app() + + env, config_response = _get_admin_config_page(app) + config_response_html = BeautifulSoup(config_response.body) + style_select_options = \ + config_response_html.select('#field-ckan-main-css option') + for option in style_select_options: + assert_true(option.string in STYLE_NAMES) + + def test_main_css(self): + '''Select a colour style''' + app = self._get_test_app() + + # current style + index_response = app.get('/') + assert_true('main.css' in index_response or + 'main.min.css' in index_response) + + # set new style css + env, config_response = _get_admin_config_page(app) + config_form = config_response.forms['admin-config-form'] + config_form['ckan.main_css'] = '/base/css/red.css' + webtest_submit(config_form, 'save', status=302, extra_environ=env) + + # new style + new_index_response = app.get('/') + assert_true('red.css' in new_index_response or + 'red.min.css' in new_index_response) + assert_true('main.css' not in new_index_response) + assert_true('main.min.css' not in new_index_response) + + # reset config value + _reset_config(app) + reset_index_response = app.get('/') + assert_true('main.css' in reset_index_response or + 'main.min.css' in reset_index_response) + + def test_tag_line(self): + '''Add a tag line (only when no logo)''' + app = self._get_test_app() + + # current tagline + index_response = app.get('/') + assert_true('Special Tagline' not in index_response) + + # set new tagline css + env, config_response = _get_admin_config_page(app) + config_form = config_response.forms['admin-config-form'] + config_form['ckan.site_description'] = 'Special Tagline' + webtest_submit(config_form, 'save', status=302, extra_environ=env) + + # new tagline not visible yet + new_index_response = app.get('/') + assert_true('Special Tagline' not in new_index_response) + + # remove logo + env, config_response = _get_admin_config_page(app) + config_form = config_response.forms['admin-config-form'] + config_form['ckan.site_logo'] = '' + webtest_submit(config_form, 'save', status=302, extra_environ=env) + + # new tagline + new_index_response = app.get('/') + assert_true('Special Tagline' in new_index_response) + + # reset config value + _reset_config(app) + reset_index_response = app.get('/') + assert_true('Special Tagline' not in reset_index_response) + + def test_about(self): + '''Add some About tag text''' + app = self._get_test_app() + + # current about + about_response = app.get('/about') + assert_true('My special about text' not in about_response) + + # set new about + env, config_response = _get_admin_config_page(app) + config_form = config_response.forms['admin-config-form'] + config_form['ckan.site_about'] = 'My special about text' + webtest_submit(config_form, 'save', status=302, extra_environ=env) + + # new about + new_about_response = app.get('/about') + assert_true('My special about text' in new_about_response) + + # reset config value + _reset_config(app) + reset_about_response = app.get('/about') + assert_true('My special about text' not in reset_about_response) + + def test_intro(self): + '''Add some Intro tag text''' + app = self._get_test_app() + + # current intro + intro_response = app.get('/') + assert_true('My special intro text' not in intro_response) + + # set new intro + env, config_response = _get_admin_config_page(app) + config_form = config_response.forms['admin-config-form'] + config_form['ckan.site_intro_text'] = 'My special intro text' + webtest_submit(config_form, 'save', status=302, extra_environ=env) + + # new intro + new_intro_response = app.get('/') + assert_true('My special intro text' in new_intro_response) + + # reset config value + _reset_config(app) + reset_intro_response = app.get('/') + assert_true('My special intro text' not in reset_intro_response) + + def test_custom_css(self): + '''Add some custom css to the head element''' + app = self._get_test_app() + + # current tagline + intro_response_html = BeautifulSoup(app.get('/').body) + style_tag = intro_response_html.select('head style') + assert_equal(len(style_tag), 0) + + # set new tagline css + env, config_response = _get_admin_config_page(app) + config_form = config_response.forms['admin-config-form'] + config_form['ckan.site_custom_css'] = 'body {background-color:red}' + webtest_submit(config_form, 'save', status=302, extra_environ=env) + + # new tagline not visible yet + new_intro_response_html = BeautifulSoup(app.get('/').body) + style_tag = new_intro_response_html.select('head style') + assert_equal(len(style_tag), 1) + assert_equal(style_tag[0].text.strip(), 'body {background-color:red}') + + # reset config value + _reset_config(app) + reset_intro_response_html = BeautifulSoup(app.get('/').body) + style_tag = reset_intro_response_html.select('head style') + assert_equal(len(style_tag), 0) + + @helpers.change_config('debug', True) + def test_homepage_style(self): + '''Select a homepage style''' + app = self._get_test_app() + + # current style + index_response = app.get('/') + assert_true('' + in index_response) + + # set new style css + env, config_response = _get_admin_config_page(app) + config_form = config_response.forms['admin-config-form'] + config_form['ckan.homepage_style'] = '2' + webtest_submit(config_form, 'save', status=302, extra_environ=env) + + # new style + new_index_response = app.get('/') + assert_true('' + not in new_index_response) + assert_true('' + in new_index_response) + + # reset config value + _reset_config(app) + reset_index_response = app.get('/') + assert_true('' + in reset_index_response) + + +class TestTrashView(helpers.FunctionalTestBase): + '''View tests for permanently deleting datasets with Admin Trash.''' + + @helpers.change_config('debug', True) + def test_trash_view_anon_user(self): + '''An anon user shouldn't be able to access trash view.''' + app = self._get_test_app() + + trash_url = url_for(controller='admin', action='trash') + trash_response = app.get(trash_url, status=403) + + def test_trash_view_normal_user(self): + '''A normal logged in user shouldn't be able to access trash view.''' + user = factories.User() + app = self._get_test_app() + + env = {'REMOTE_USER': user['name'].encode('ascii')} + trash_url = url_for(controller='admin', action='trash') + trash_response = app.get(trash_url, extra_environ=env, status=403) + assert_true('Need to be system administrator to administer' + in trash_response) + + def test_trash_view_sysadmin(self): + '''A sysadmin should be able to access trash view.''' + user = factories.Sysadmin() + app = self._get_test_app() + + env = {'REMOTE_USER': user['name'].encode('ascii')} + trash_url = url_for(controller='admin', action='trash') + trash_response = app.get(trash_url, extra_environ=env, status=200) + # On the purge page + assert_true('form-purge-packages' in trash_response) + + def test_trash_no_datasets(self): + '''Getting the trash view with no 'deleted' datasets should list no + datasets.''' + factories.Dataset() + user = factories.Sysadmin() + app = self._get_test_app() + + env = {'REMOTE_USER': user['name'].encode('ascii')} + trash_url = url_for(controller='admin', action='trash') + trash_response = app.get(trash_url, extra_environ=env, status=200) + + trash_response_html = BeautifulSoup(trash_response.body) + # it's called a 'user list' for some reason + trash_pkg_list = trash_response_html.select('ul.user-list li') + # no packages available to purge + assert_equal(len(trash_pkg_list), 0) + + def test_trash_with_deleted_datasets(self): + '''Getting the trash view with 'deleted' datasets should list the + datasets.''' + user = factories.Sysadmin() + factories.Dataset(state='deleted') + factories.Dataset(state='deleted') + factories.Dataset() + app = self._get_test_app() + + env = {'REMOTE_USER': user['name'].encode('ascii')} + trash_url = url_for(controller='admin', action='trash') + trash_response = app.get(trash_url, extra_environ=env, status=200) + + trash_response_html = BeautifulSoup(trash_response.body) + # it's called a 'user list' for some reason + trash_pkg_list = trash_response_html.select('ul.user-list li') + # Two packages in the list to purge + assert_equal(len(trash_pkg_list), 2) + + def test_trash_purge_deleted_datasets(self): + '''Posting the trash view with 'deleted' datasets, purges the + datasets.''' + user = factories.Sysadmin() + factories.Dataset(state='deleted') + factories.Dataset(state='deleted') + factories.Dataset() + app = self._get_test_app() + + # how many datasets before purge + pkgs_before_purge = model.Session.query(model.Package).count() + assert_equal(pkgs_before_purge, 3) + + env = {'REMOTE_USER': user['name'].encode('ascii')} + trash_url = url_for(controller='admin', action='trash') + trash_response = app.get(trash_url, extra_environ=env, status=200) + + # submit the purge form + purge_form = trash_response.forms['form-purge-packages'] + purge_response = webtest_submit(purge_form, 'purge-packages', + status=302, extra_environ=env) + purge_response = purge_response.follow(extra_environ=env) + # redirected back to trash page + assert_true('Purge complete' in purge_response) + + # how many datasets after purge + pkgs_before_purge = model.Session.query(model.Package).count() + assert_equal(pkgs_before_purge, 1) + + +class TestAdminConfigUpdate(helpers.FunctionalTestBase): + + def teardown(self): + '''Reset the database and clear the search indexes.''' + helpers.reset_db() + + def _update_config_option(self): + sysadmin = factories.Sysadmin() + env = {'REMOTE_USER': sysadmin['name'].encode('ascii')} + app = self._get_test_app() + url = url_for(controller='admin', action='config') + + response = app.get(url=url, extra_environ=env) + form = response.forms[1] + form['ckan.site_title'] = 'My Updated Site Title' + + webtest_submit(form, 'save', status=302, extra_environ=env) + + def test_admin_config_update(self): + '''Changing a config option using the admin interface appropriately + updates value returned by config_option_show, + system_info.get_system_info and in the title tag in templates.''' + + # test value before update + # config_option_show returns default value + before_update = helpers.call_action('config_option_show', + key='ckan.site_title') + assert_equal(before_update, 'CKAN') + + # system_info.get_system_info returns None, or default + # test value before update + before_update = get_system_info('ckan.site_title') + assert_equal(before_update, None) + # test value before update with default + before_update_default = get_system_info('ckan.site_title', + config['ckan.site_title']) + assert_equal(before_update_default, 'CKAN') + + # title tag contains default value + app = self._get_test_app() + home_page_before = app.get('/', status=200) + assert_true('Welcome - CKAN' in home_page_before) + + # update the option + self._update_config_option() + + # test config_option_show returns new value after update + after_update = helpers.call_action('config_option_show', + key='ckan.site_title') + assert_equal(after_update, 'My Updated Site Title') + + # system_info.get_system_info returns new value + after_update = get_system_info('ckan.site_title') + assert_equal(after_update, 'My Updated Site Title') + # test value after update with default + after_update_default = get_system_info('ckan.site_title', + config['ckan.site_title']) + assert_equal(after_update_default, 'My Updated Site Title') + + # title tag contains new value + home_page_after = app.get('/', status=200) + assert_true('Welcome - My Updated Site Title' in home_page_after) diff --git a/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_api.py b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_api.py new file mode 100644 index 00000000..b36ec48c --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_api.py @@ -0,0 +1,400 @@ +# encoding: utf-8 + +''' +NB Don't test logic functions here. This is just for the mechanics of the API +controller itself. +''' +import json +import re +import mock +import __builtin__ as builtins +from StringIO import StringIO + +from nose.tools import assert_equal, assert_in, eq_ +from pyfakefs import fake_filesystem + +from six import text_type +from six.moves import xrange + +from ckan.lib.helpers import url_for +import ckan.tests.helpers as helpers +from ckan.tests import factories +from ckan.lib import helpers as template_helpers, uploader as ckan_uploader +import ckan.plugins as p +from ckan import model + +fs = fake_filesystem.FakeFilesystem() +fake_os = fake_filesystem.FakeOsModule(fs) +fake_open = fake_filesystem.FakeFileOpen(fs) +real_open = open + + +def mock_open_if_open_fails(*args, **kwargs): + try: + return real_open(*args, **kwargs) + except (OSError, IOError): + return fake_open(*args, **kwargs) + + +class TestApiController(helpers.FunctionalTestBase): + + @helpers.change_config('ckan.storage_path', '/doesnt_exist') + @mock.patch.object(builtins, 'open', side_effect=mock_open_if_open_fails) + @mock.patch.object(ckan_uploader, 'os', fake_os) + @mock.patch.object(ckan_uploader, '_storage_path', new='/doesnt_exist') + def test_resource_create_upload_file(self, _): + user = factories.User() + pkg = factories.Dataset(creator_user_id=user['id']) + # upload_content = StringIO() + # upload_content.write('test-content') + + url = url_for( + controller='api', + action='action', + logic_function='resource_create', ver='/3') + env = {'REMOTE_USER': user['name'].encode('ascii')} + postparams = { + 'name': 'test-flask-upload', + 'package_id': pkg['id'] + } + upload_content = 'test-content' + upload_info = ('upload', 'test-upload.txt', upload_content) + app = self._get_test_app() + resp = app.post( + url, params=postparams, + upload_files=[upload_info], + extra_environ=env + # content_type= 'application/json' + ) + result = resp.json['result'] + eq_('upload', result['url_type']) + eq_(len(upload_content), result['size']) + + def test_unicode_in_error_message_works_ok(self): + # Use tag_delete to echo back some unicode + app = self._get_test_app() + org_url = '/api/action/tag_delete' + data_dict = {'id': u'Delta symbol: \u0394'} # unicode gets rec'd ok + postparams = '%s=1' % json.dumps(data_dict) + response = app.post(url=org_url, params=postparams, status=404) + # The unicode is backslash encoded (because that is the default when + # you do str(exception) ) + assert 'Delta symbol: \\u0394' in response.body + + def test_dataset_autocomplete_name(self): + dataset = factories.Dataset(name='rivers') + url = url_for(controller='api', action='dataset_autocomplete', ver='/2') + assert_equal(url, '/api/2/util/dataset/autocomplete') + app = self._get_test_app() + + response = app.get( + url=url, + params={ + 'incomplete': u'rive', + }, + status=200, + ) + + results = json.loads(response.body) + assert_equal(results, {"ResultSet": {"Result": [{ + 'match_field': 'name', + "name": "rivers", + 'match_displayed': 'rivers', + 'title': dataset['title'], + }]}}) + assert_equal(response.headers['Content-Type'], + 'application/json;charset=utf-8') + + def test_dataset_autocomplete_title(self): + dataset = factories.Dataset(name='test_ri', title='Rivers') + url = url_for(controller='api', action='dataset_autocomplete', ver='/2') + assert_equal(url, '/api/2/util/dataset/autocomplete') + app = self._get_test_app() + + response = app.get( + url=url, + params={ + 'incomplete': u'riv', + }, + status=200, + ) + + results = json.loads(response.body) + assert_equal(results, {"ResultSet": {"Result": [{ + 'match_field': 'title', + "name": dataset['name'], + 'match_displayed': 'Rivers (test_ri)', + 'title': 'Rivers', + }]}}) + assert_equal(response.headers['Content-Type'], + 'application/json;charset=utf-8') + + def test_tag_autocomplete(self): + factories.Dataset(tags=[{'name': 'rivers'}]) + url = url_for(controller='api', action='tag_autocomplete', ver='/2') + assert_equal(url, '/api/2/util/tag/autocomplete') + app = self._get_test_app() + + response = app.get( + url=url, + params={ + 'incomplete': u'rive', + }, + status=200, + ) + + results = json.loads(response.body) + assert_equal(results, {"ResultSet": {"Result": [{"Name": "rivers"}]}}) + assert_equal(response.headers['Content-Type'], + 'application/json;charset=utf-8') + + def test_group_autocomplete_by_name(self): + org = factories.Group(name='rivers', title='Bridges') + url = url_for(controller='api', action='group_autocomplete', ver='/2') + assert_equal(url, '/api/2/util/group/autocomplete') + app = self._get_test_app() + + response = app.get( + url=url, + params={ + 'q': u'rive', + }, + status=200, + ) + + results = json.loads(response.body) + assert_equal(len(results), 1) + assert_equal(results[0]['name'], 'rivers') + assert_equal(results[0]['title'], 'Bridges') + assert_equal(response.headers['Content-Type'], + 'application/json;charset=utf-8') + + def test_group_autocomplete_by_title(self): + org = factories.Group(name='frogs', title='Bugs') + url = url_for(controller='api', action='group_autocomplete', ver='/2') + app = self._get_test_app() + + response = app.get( + url=url, + params={ + 'q': u'bug', + }, + status=200, + ) + + results = json.loads(response.body) + assert_equal(len(results), 1) + assert_equal(results[0]['name'], 'frogs') + + def test_organization_autocomplete_by_name(self): + org = factories.Organization(name='simple-dummy-org') + url = url_for(controller='api', action='organization_autocomplete', ver='/2') + assert_equal(url, '/api/2/util/organization/autocomplete') + app = self._get_test_app() + + response = app.get( + url=url, + params={ + 'q': u'simple', + }, + status=200, + ) + + results = json.loads(response.body) + assert_equal(len(results), 1) + assert_equal(results[0]['name'], 'simple-dummy-org') + assert_equal(results[0]['title'], org['title']) + assert_equal(response.headers['Content-Type'], + 'application/json;charset=utf-8') + + def test_organization_autocomplete_by_title(self): + org = factories.Organization(title='Simple dummy org') + url = url_for(controller='api', action='organization_autocomplete', ver='/2') + app = self._get_test_app() + + response = app.get( + url=url, + params={ + 'q': u'simple dum', + }, + status=200, + ) + + results = json.loads(response.body) + assert_equal(len(results), 1) + assert_equal(results[0]['title'], 'Simple dummy org') + + def test_config_option_list_access_sysadmin(self): + user = factories.Sysadmin() + url = url_for( + controller='api', + action='action', + logic_function='config_option_list', + ver='/3') + app = self._get_test_app() + + app.get( + url=url, + params={}, + extra_environ={'REMOTE_USER': user['name'].encode('ascii')}, + status=200, + ) + + def test_config_option_list_access_sysadmin_jsonp(self): + user = factories.Sysadmin() + url = url_for( + controller='api', + action='action', + logic_function='config_option_list', + ver='/3') + app = self._get_test_app() + + app.get( + url=url, + params={'callback': 'myfn'}, + extra_environ={'REMOTE_USER': user['name'].encode('ascii')}, + status=403, + ) + + def test_jsonp_works_on_get_requests(self): + + dataset1 = factories.Dataset() + dataset2 = factories.Dataset() + + url = url_for( + controller='api', + action='action', + logic_function='package_list', + ver='/3') + app = self._get_test_app() + res = app.get( + url=url, + params={'callback': 'my_callback'}, + ) + assert re.match('my_callback\(.*\);', res.body), res + # Unwrap JSONP callback (we want to look at the data). + msg = res.body[len('my_callback') + 1:-2] + res_dict = json.loads(msg) + eq_(res_dict['success'], True) + eq_(sorted(res_dict['result']), + sorted([dataset1['name'], dataset2['name']])) + + def test_jsonp_returns_javascript_content_type(self): + url = url_for( + controller='api', + action='action', + logic_function='status_show', + ver='/3') + app = self._get_test_app() + res = app.get( + url=url, + params={'callback': 'my_callback'}, + ) + assert_in('application/javascript', res.headers.get('Content-Type')) + + def test_jsonp_does_not_work_on_post_requests(self): + + dataset1 = factories.Dataset() + dataset2 = factories.Dataset() + + url = url_for( + controller='api', + action='action', + logic_function='package_list', + ver='/3', + callback='my_callback', + ) + app = self._get_test_app() + res = app.post( + url=url, + ) + # The callback param is ignored and the normal response is returned + assert not res.body.startswith('my_callback') + res_dict = json.loads(res.body) + eq_(res_dict['success'], True) + eq_(sorted(res_dict['result']), + sorted([dataset1['name'], dataset2['name']])) + + +class TestRevisionSearch(helpers.FunctionalTestBase): + + # Error cases + + def test_no_search_term(self): + app = self._get_test_app() + response = app.get('/api/search/revision', status=400) + assert_in('Bad request - Missing search term', response.body) + + def test_no_search_term_api_v2(self): + app = self._get_test_app() + response = app.get('/api/2/search/revision', status=400) + assert_in('Bad request - Missing search term', response.body) + + def test_date_instead_of_revision(self): + app = self._get_test_app() + response = app.get('/api/search/revision' + '?since_id=2010-01-01T00:00:00', status=404) + assert_in('Not found - There is no revision', response.body) + + def test_date_invalid(self): + app = self._get_test_app() + response = app.get('/api/search/revision' + '?since_time=2010-02-31T00:00:00', status=400) + assert_in('Bad request - ValueError: day is out of range for month', + response.body) + + def test_no_value(self): + app = self._get_test_app() + response = app.get('/api/search/revision?since_id=', status=400) + assert_in('Bad request - No revision specified', response.body) + + def test_revision_doesnt_exist(self): + app = self._get_test_app() + response = app.get('/api/search/revision?since_id=1234', status=404) + assert_in('Not found - There is no revision', response.body) + + def test_revision_doesnt_exist_api_v2(self): + app = self._get_test_app() + response = app.get('/api/2/search/revision?since_id=1234', status=404) + assert_in('Not found - There is no revision', response.body) + + # Normal usage + + @classmethod + def _create_revisions(cls, num_revisions): + rev_ids = [] + for i in xrange(num_revisions): + rev = model.repo.new_revision() + rev.id = text_type(i) + model.Session.commit() + rev_ids.append(rev.id) + return rev_ids + + def test_revision_since_id(self): + rev_ids = self._create_revisions(4) + app = self._get_test_app() + + response = app.get('/api/2/search/revision?since_id=%s' % rev_ids[1]) + + res = json.loads(response.body) + assert_equal(res, rev_ids[2:]) + + def test_revision_since_time(self): + rev_ids = self._create_revisions(4) + app = self._get_test_app() + + rev1 = model.Session.query(model.Revision).get(rev_ids[1]) + response = app.get('/api/2/search/revision?since_time=%s' + % rev1.timestamp.isoformat()) + + res = json.loads(response.body) + assert_equal(res, rev_ids[2:]) + + def test_revisions_returned_are_limited(self): + rev_ids = self._create_revisions(55) + app = self._get_test_app() + + response = app.get('/api/2/search/revision?since_id=%s' % rev_ids[1]) + + res = json.loads(response.body) + assert_equal(res, rev_ids[2:52]) # i.e. limited to 50 diff --git a/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_feed.py b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_feed.py new file mode 100644 index 00000000..f34fa273 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_feed.py @@ -0,0 +1,152 @@ +# encoding: utf-8 + +from ckan.lib.helpers import url_for + +import ckan.tests.helpers as helpers +import ckan.tests.factories as factories +import ckan.plugins as plugins +from webhelpers.feedgenerator import GeoAtom1Feed + + +class TestFeedNew(helpers.FunctionalTestBase): + @classmethod + def teardown_class(cls): + helpers.reset_db() + + def test_atom_feed_page_zero_gives_error(self): + group = factories.Group() + offset = url_for(u'feeds.group', id=group['name']) + '?page=0' + app = self._get_test_app() + offset = url_for(u'feeds.group', id=group['name']) + u'?page=0' + + res = app.get(offset, status=400) + assert '"page" parameter must be a positive integer' in res, res + + def test_atom_feed_page_negative_gives_error(self): + group = factories.Group() + offset = url_for(u'feeds.group', id=group['name']) + '?page=-2' + app = self._get_test_app() + offset = url_for(u'feeds.group', id=group['name']) + '?page=-2' + res = app.get(offset, status=400) + assert '"page" parameter must be a positive integer' in res, res + + def test_atom_feed_page_not_int_gives_error(self): + group = factories.Group() + offset = url_for(u'feeds.group', id=group['name']) + '?page=abc' + app = self._get_test_app() + offset = url_for(u'feeds.group', id=group['name']) + '?page=abc' + res = app.get(offset, status=400) + assert '"page" parameter must be a positive integer' in res, res + + def test_general_atom_feed_works(self): + dataset = factories.Dataset() + offset = url_for(u'feeds.general') + app = self._get_test_app() + offset = url_for(u'feeds.general') + res = app.get(offset) + + assert u'{0}'.format( + dataset['title']) in res.body + + def test_group_atom_feed_works(self): + group = factories.Group() + dataset = factories.Dataset(groups=[{'id': group['id']}]) + offset = url_for(u'feeds.group', id=group['name']) + app = self._get_test_app() + offset = url_for(u'feeds.group', id=group['name']) + res = app.get(offset) + + assert u'{0}'.format( + dataset['title']) in res.body + + def test_organization_atom_feed_works(self): + group = factories.Organization() + dataset = factories.Dataset(owner_org=group['id']) + offset = url_for(u'feeds.organization', id=group['name']) + app = self._get_test_app() + offset = url_for(u'feeds.organization', id=group['name']) + res = app.get(offset) + + assert u'{0}'.format( + dataset['title']) in res.body + + def test_custom_atom_feed_works(self): + dataset1 = factories.Dataset( + title=u'Test weekly', + extras=[{ + 'key': 'frequency', + 'value': 'weekly' + }]) + dataset2 = factories.Dataset( + title=u'Test daily', + extras=[{ + 'key': 'frequency', + 'value': 'daily' + }]) + + offset = url_for(u'feeds.custom') + params = {'q': 'frequency:weekly'} + app = self._get_test_app() + res = app.get(offset, params=params) + + assert u'{0}'.format( + dataset1['title']) in res.body + + assert u'{0}'.format( + dataset2['title']) not in res.body + + +class TestFeedInterface(helpers.FunctionalTestBase): + @classmethod + def setup_class(cls): + super(TestFeedInterface, cls).setup_class() + + if not plugins.plugin_loaded('test_feed_plugin'): + plugins.load('test_feed_plugin') + + @classmethod + def teardown_class(cls): + helpers.reset_db() + plugins.unload('test_feed_plugin') + + def test_custom_class_used(self): + + app = self._get_test_app() + offset = url_for(u'feeds.general') + app = self._get_test_app() + res = app.get(offset) + + assert 'xmlns:georss="http://www.georss.org/georss"' in res.body, res.body + + def test_additional_fields_added(self): + metadata = { + 'ymin': '-2373790', + 'xmin': '2937940', + 'ymax': '-1681290', + 'xmax': '3567770', + } + + extras = [{'key': k, 'value': v} for (k, v) in metadata.items()] + + factories.Dataset(extras=extras) + + app = self._get_test_app() + offset = url_for(u'feeds.general') + app = self._get_test_app() + res = app.get(offset) + + assert '-2373790.000000 2937940.000000 -1681290.000000 3567770.000000' in res.body, res.body + + +class MockFeedPlugin(plugins.SingletonPlugin): + plugins.implements(plugins.IFeed) + + def get_feed_class(self): + return GeoAtom1Feed + + def get_item_additional_fields(self, dataset_dict): + extras = {e['key']: e['value'] for e in dataset_dict['extras']} + + box = tuple( + float(extras.get(n)) for n in ('ymin', 'xmin', 'ymax', 'xmax')) + return {'geometry': box} diff --git a/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_group.py b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_group.py new file mode 100644 index 00000000..29a3b790 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_group.py @@ -0,0 +1,782 @@ +# encoding: utf-8 + +from bs4 import BeautifulSoup +from nose.tools import assert_equal, assert_true, assert_in + +from six.moves import xrange + +from ckan.lib.helpers import url_for + +import ckan.tests.helpers as helpers +import ckan.model as model +from ckan.tests import factories + +webtest_submit = helpers.webtest_submit +submit_and_follow = helpers.submit_and_follow + + +class TestGroupController(helpers.FunctionalTestBase): + + def setup(self): + model.repo.rebuild_db() + + def test_bulk_process_throws_404_for_nonexistent_org(self): + app = self._get_test_app() + bulk_process_url = url_for(controller='organization', + action='bulk_process', id='does-not-exist') + app.get(url=bulk_process_url, status=404) + + def test_page_thru_list_of_orgs_preserves_sort_order(self): + orgs = [factories.Organization() for _ in range(35)] + app = self._get_test_app() + org_url = url_for(controller='organization', + action='index', + sort='name desc') + response = app.get(url=org_url) + assert orgs[-1]['name'] in response + assert orgs[0]['name'] not in response + + response2 = response.click('2') + assert orgs[-1]['name'] not in response2 + assert orgs[0]['name'] in response2 + + def test_page_thru_list_of_groups_preserves_sort_order(self): + groups = [factories.Group() for _ in range(35)] + app = self._get_test_app() + group_url = url_for(controller='group', + action='index', + sort='title desc') + + response = app.get(url=group_url) + assert groups[-1]['title'] in response + assert groups[0]['title'] not in response + + response2 = response.click(r'^2$') + assert groups[-1]['title'] not in response2 + assert groups[0]['title'] in response2 + + def test_invalid_sort_param_does_not_crash(self): + app = self._get_test_app() + + with app.flask_app.test_request_context(): + group_url = url_for(controller='group', + action='index', + sort='title desc nope') + + app.get(url=group_url) + + group_url = url_for(controller='group', + action='index', + sort='title nope desc nope') + + app.get(url=group_url) + + +def _get_group_new_page(app): + user = factories.User() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url=url_for(controller='group', action='new'), + extra_environ=env, + ) + return env, response + + +class TestGroupControllerNew(helpers.FunctionalTestBase): + def test_not_logged_in(self): + app = self._get_test_app() + app.get(url=url_for(controller='group', action='new'), + status=403) + + def test_form_renders(self): + app = self._get_test_app() + env, response = _get_group_new_page(app) + assert_in('group-edit', response.forms) + + def test_name_required(self): + app = self._get_test_app() + env, response = _get_group_new_page(app) + form = response.forms['group-edit'] + + response = webtest_submit(form, 'save', status=200, extra_environ=env) + assert_true('group-edit' in response.forms) + assert_true('Name: Missing value' in response) + + def test_saved(self): + app = self._get_test_app() + env, response = _get_group_new_page(app) + form = response.forms['group-edit'] + form['name'] = u'saved' + + response = submit_and_follow(app, form, env, 'save') + group = model.Group.by_name(u'saved') + assert_equal(group.title, u'') + assert_equal(group.type, 'group') + assert_equal(group.state, 'active') + + def test_all_fields_saved(self): + app = self._get_test_app() + env, response = _get_group_new_page(app) + form = response.forms['group-edit'] + form['name'] = u'all-fields-saved' + form['title'] = 'Science' + form['description'] = 'Sciencey datasets' + form['image_url'] = 'http://example.com/image.png' + + response = submit_and_follow(app, form, env, 'save') + group = model.Group.by_name(u'all-fields-saved') + assert_equal(group.title, u'Science') + assert_equal(group.description, 'Sciencey datasets') + + +def _get_group_edit_page(app, group_name=None): + user = factories.User() + if group_name is None: + group = factories.Group(user=user) + group_name = group['name'] + env = {'REMOTE_USER': user['name'].encode('ascii')} + url = url_for(controller='group', + action='edit', + id=group_name) + response = app.get(url=url, extra_environ=env) + return env, response, group_name + + +class TestGroupControllerEdit(helpers.FunctionalTestBase): + def test_not_logged_in(self): + app = self._get_test_app() + app.get(url=url_for(controller='group', action='new'), + status=403) + + def test_group_doesnt_exist(self): + app = self._get_test_app() + user = factories.User() + env = {'REMOTE_USER': user['name'].encode('ascii')} + url = url_for(controller='group', + action='edit', + id='doesnt_exist') + app.get(url=url, extra_environ=env, + status=404) + + def test_form_renders(self): + app = self._get_test_app() + env, response, group_name = _get_group_edit_page(app) + assert_in('group-edit', response.forms) + + def test_saved(self): + app = self._get_test_app() + env, response, group_name = _get_group_edit_page(app) + form = response.forms['group-edit'] + + response = submit_and_follow(app, form, env, 'save') + group = model.Group.by_name(group_name) + assert_equal(group.state, 'active') + + def test_all_fields_saved(self): + app = self._get_test_app() + env, response, group_name = _get_group_edit_page(app) + form = response.forms['group-edit'] + form['name'] = u'all-fields-edited' + form['title'] = 'Science' + form['description'] = 'Sciencey datasets' + form['image_url'] = 'http://example.com/image.png' + + response = submit_and_follow(app, form, env, 'save') + group = model.Group.by_name(u'all-fields-edited') + assert_equal(group.title, u'Science') + assert_equal(group.description, 'Sciencey datasets') + assert_equal(group.image_url, 'http://example.com/image.png') + + +class TestGroupRead(helpers.FunctionalTestBase): + def setup(self): + super(TestGroupRead, self).setup() + self.app = helpers._get_test_app() + self.user = factories.User() + self.user_env = {'REMOTE_USER': self.user['name'].encode('ascii')} + self.group = factories.Group(user=self.user) + + def test_group_read(self): + response = self.app.get(url=url_for(controller='group', + action='read', + id=self.group['id']), + status=200, + extra_environ=self.user_env) + assert_in(self.group['title'], response) + assert_in(self.group['description'], response) + + +class TestGroupDelete(helpers.FunctionalTestBase): + def setup(self): + super(TestGroupDelete, self).setup() + self.app = helpers._get_test_app() + self.user = factories.User() + self.user_env = {'REMOTE_USER': self.user['name'].encode('ascii')} + self.group = factories.Group(user=self.user) + + def test_owner_delete(self): + response = self.app.get(url=url_for(controller='group', + action='delete', + id=self.group['id']), + status=200, + extra_environ=self.user_env) + + form = response.forms['group-confirm-delete-form'] + response = submit_and_follow(self.app, form, name='delete', + extra_environ=self.user_env) + group = helpers.call_action('group_show', + id=self.group['id']) + assert_equal(group['state'], 'deleted') + + def test_sysadmin_delete(self): + sysadmin = factories.Sysadmin() + extra_environ = {'REMOTE_USER': sysadmin['name'].encode('ascii')} + response = self.app.get(url=url_for(controller='group', + action='delete', + id=self.group['id']), + status=200, + extra_environ=extra_environ) + + form = response.forms['group-confirm-delete-form'] + response = submit_and_follow(self.app, form, name='delete', + extra_environ=self.user_env) + group = helpers.call_action('group_show', + id=self.group['id']) + assert_equal(group['state'], 'deleted') + + def test_non_authorized_user_trying_to_delete_fails(self): + user = factories.User() + extra_environ = {'REMOTE_USER': user['name'].encode('ascii')} + self.app.get(url=url_for(controller='group', + action='delete', + id=self.group['id']), + status=403, + extra_environ=extra_environ) + + group = helpers.call_action('group_show', + id=self.group['id']) + assert_equal(group['state'], 'active') + + def test_anon_user_trying_to_delete_fails(self): + self.app.get(url=url_for(controller='group', + action='delete', + id=self.group['id']), + status=403) + + group = helpers.call_action('group_show', + id=self.group['id']) + assert_equal(group['state'], 'active') + + +class TestGroupMembership(helpers.FunctionalTestBase): + + def _create_group(self, owner_username, users=None): + '''Create a group with the owner defined by owner_username and + optionally with a list of other users.''' + if users is None: + users = [] + context = {'user': owner_username, 'ignore_auth': True, } + group = helpers.call_action('group_create', context=context, + name='test-group', users=users) + return group + + def _get_group_add_member_page(self, app, user, group_name): + env = {'REMOTE_USER': user['name'].encode('ascii')} + url = url_for(controller='group', + action='member_new', + id=group_name) + response = app.get(url=url, extra_environ=env) + return env, response + + def test_membership_list(self): + '''List group admins and members''' + app = self._get_test_app() + user_one = factories.User(fullname='User One', name='user-one') + user_two = factories.User(fullname='User Two') + + other_users = [ + {'name': user_two['id'], 'capacity': 'member'} + ] + + group = self._create_group(user_one['name'], other_users) + + member_list_url = url_for(controller='group', action='members', + id=group['id']) + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + + member_list_response = app.get( + member_list_url, extra_environ=env) + + assert_true('2 members' in member_list_response) + + member_response_html = BeautifulSoup(member_list_response.body) + user_names = [u.string for u in + member_response_html.select('#member-table td.media a')] + roles = [r.next_sibling.next_sibling.string + for r + in member_response_html.select('#member-table td.media')] + + user_roles = dict(zip(user_names, roles)) + + assert_equal(user_roles['User One'], 'Admin') + assert_equal(user_roles['User Two'], 'Member') + + def test_membership_add(self): + '''Member can be added via add member page''' + app = self._get_test_app() + owner = factories.User(fullname='My Owner') + factories.User(fullname="My Fullname", name='my-user') + group = self._create_group(owner['name']) + + env, response = self._get_group_add_member_page(app, + owner, + group['name']) + + add_form = response.forms['add-member-form'] + add_form['username'] = 'my-user' + add_response = submit_and_follow(app, add_form, env, 'save') + + assert_true('2 members' in add_response) + + add_response_html = BeautifulSoup(add_response.body) + user_names = [u.string for u in + add_response_html.select('#member-table td.media a')] + roles = [r.next_sibling.next_sibling.string + for r in add_response_html.select('#member-table td.media')] + + user_roles = dict(zip(user_names, roles)) + + assert_equal(user_roles['My Owner'], 'Admin') + assert_equal(user_roles['My Fullname'], 'Member') + + def test_admin_add(self): + '''Admin can be added via add member page''' + app = self._get_test_app() + owner = factories.User(fullname='My Owner') + factories.User(fullname="My Fullname", name='my-user') + group = self._create_group(owner['name']) + + env, response = self._get_group_add_member_page(app, + owner, + group['name']) + + add_form = response.forms['add-member-form'] + add_form['username'] = 'my-user' + add_form['role'] = 'admin' + add_response = submit_and_follow(app, add_form, env, 'save') + + assert_true('2 members' in add_response) + + add_response_html = BeautifulSoup(add_response.body) + user_names = [u.string for u in + add_response_html.select('#member-table td.media a')] + roles = [r.next_sibling.next_sibling.string + for r in add_response_html.select('#member-table td.media')] + + user_roles = dict(zip(user_names, roles)) + + assert_equal(user_roles['My Owner'], 'Admin') + assert_equal(user_roles['My Fullname'], 'Admin') + + def test_remove_member(self): + '''Member can be removed from group''' + app = self._get_test_app() + user_one = factories.User(fullname='User One', name='user-one') + user_two = factories.User(fullname='User Two') + + other_users = [ + {'name': user_two['id'], 'capacity': 'member'} + ] + + group = self._create_group(user_one['name'], other_users) + + remove_url = url_for(controller='group', action='member_delete', + user=user_two['id'], id=group['id']) + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + remove_response = app.post(remove_url, extra_environ=env, status=302) + # redirected to member list after removal + remove_response = remove_response.follow(extra_environ=env) + + assert_true('Group member has been deleted.' in remove_response) + assert_true('1 members' in remove_response) + + remove_response_html = BeautifulSoup(remove_response.body) + user_names = [u.string for u in + remove_response_html.select('#member-table td.media a')] + roles = [r.next_sibling.next_sibling.string + for r in + remove_response_html.select('#member-table td.media')] + + user_roles = dict(zip(user_names, roles)) + + assert_equal(len(user_roles.keys()), 1) + assert_equal(user_roles['User One'], 'Admin') + + def test_member_users_cannot_add_members(self): + + user = factories.User() + group = factories.Group( + users=[{'name': user['name'], 'capacity': 'member'}] + ) + + app = helpers._get_test_app() + + env = {'REMOTE_USER': user['name'].encode('ascii')} + + with app.flask_app.test_request_context(): + app.get( + url_for( + controller='group', + action='member_new', + id=group['id'], + ), + extra_environ=env, + status=403, + ) + + app.post( + url_for( + controller='group', + action='member_new', + id=group['id'], + ), + {'id': 'test', 'username': 'test', 'save': 'save', 'role': 'test'}, + extra_environ=env, + status=403, + ) + + def test_anonymous_users_cannot_add_members(self): + group = factories.Group() + + app = helpers._get_test_app() + + with app.flask_app.test_request_context(): + app.get( + url_for( + controller='group', + action='member_new', + id=group['id'], + ), + status=403, + ) + + app.post( + url_for( + controller='group', + action='member_new', + id=group['id'], + ), + {'id': 'test', 'username': 'test', 'save': 'save', 'role': 'test'}, + status=403, + ) + + +class TestGroupFollow(helpers.FunctionalTestBase): + + def test_group_follow(self): + app = self._get_test_app() + + user = factories.User() + group = factories.Group() + + env = {'REMOTE_USER': user['name'].encode('ascii')} + follow_url = url_for(controller='group', + action='follow', + id=group['id']) + response = app.post(follow_url, extra_environ=env, status=302) + response = response.follow() + assert_true('You are now following {0}' + .format(group['display_name']) + in response) + + def test_group_follow_not_exist(self): + '''Pass an id for a group that doesn't exist''' + app = self._get_test_app() + + user_one = factories.User() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + follow_url = url_for(controller='group', + action='follow', + id='not-here') + response = app.post(follow_url, extra_environ=env, status=404) + assert_true('Group not found' in response) + + def test_group_unfollow(self): + app = self._get_test_app() + + user_one = factories.User() + group = factories.Group() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + follow_url = url_for(controller='group', + action='follow', + id=group['id']) + app.post(follow_url, extra_environ=env, status=302) + + unfollow_url = url_for(controller='group', action='unfollow', + id=group['id']) + unfollow_response = app.post(unfollow_url, extra_environ=env, + status=302) + unfollow_response = unfollow_response.follow() + + assert_true('You are no longer following {0}' + .format(group['display_name']) + in unfollow_response) + + def test_group_unfollow_not_following(self): + '''Unfollow a group not currently following''' + app = self._get_test_app() + + user_one = factories.User() + group = factories.Group() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + unfollow_url = url_for(controller='group', action='unfollow', + id=group['id']) + unfollow_response = app.post(unfollow_url, extra_environ=env, + status=302) + unfollow_response = unfollow_response.follow() + + assert_true('You are not following {0}'.format(group['id']) + in unfollow_response) + + def test_group_unfollow_not_exist(self): + '''Unfollow a group that doesn't exist.''' + app = self._get_test_app() + + user_one = factories.User() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + unfollow_url = url_for(controller='group', action='unfollow', + id='not-here') + unfollow_response = app.post(unfollow_url, extra_environ=env, + status=404) + assert_true('Group not found' in unfollow_response) + + def test_group_follower_list(self): + '''Following users appear on followers list page.''' + app = self._get_test_app() + + user_one = factories.Sysadmin() + group = factories.Group() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + follow_url = url_for(controller='group', + action='follow', + id=group['id']) + app.post(follow_url, extra_environ=env, status=302) + + followers_url = url_for(controller='group', action='followers', + id=group['id']) + + # Only sysadmins can view the followers list pages + followers_response = app.get(followers_url, extra_environ=env, + status=200) + assert_true(user_one['display_name'] in followers_response) + + +class TestGroupSearch(helpers.FunctionalTestBase): + + '''Test searching for groups.''' + + def setup(self): + super(TestGroupSearch, self).setup() + self.app = self._get_test_app() + factories.Group(name='grp-one', title='AGrp One') + factories.Group(name='grp-two', title='AGrp Two') + factories.Group(name='grp-three', title='Grp Three') + self.search_url = url_for(controller='group', action='index') + + def test_group_search(self): + '''Requesting group search (index) returns list of groups and search + form.''' + + index_response = self.app.get(self.search_url) + index_response_html = BeautifulSoup(index_response.body) + grp_names = index_response_html.select('ul.media-grid ' + 'li.media-item ' + 'h3.media-heading') + grp_names = [n.string for n in grp_names] + + assert_equal(len(grp_names), 3) + assert_true('AGrp One' in grp_names) + assert_true('AGrp Two' in grp_names) + assert_true('Grp Three' in grp_names) + + def test_group_search_results(self): + '''Searching via group search form returns list of expected groups.''' + + index_response = self.app.get(self.search_url) + search_form = index_response.forms['group-search-form'] + search_form['q'] = 'AGrp' + search_response = webtest_submit(search_form) + + search_response_html = BeautifulSoup(search_response.body) + grp_names = search_response_html.select('ul.media-grid ' + 'li.media-item ' + 'h3.media-heading') + grp_names = [n.string for n in grp_names] + + assert_equal(len(grp_names), 2) + assert_true('AGrp One' in grp_names) + assert_true('AGrp Two' in grp_names) + assert_true('Grp Three' not in grp_names) + + def test_group_search_no_results(self): + '''Searching with a term that doesn't apply returns no results.''' + + index_response = self.app.get(self.search_url) + search_form = index_response.forms['group-search-form'] + search_form['q'] = 'No Results Here' + search_response = webtest_submit(search_form) + + search_response_html = BeautifulSoup(search_response.body) + grp_names = search_response_html.select('ul.media-grid ' + 'li.media-item ' + 'h3.media-heading') + grp_names = [n.string for n in grp_names] + + assert_equal(len(grp_names), 0) + assert_true("No groups found for "No Results Here"" + in search_response) + + +class TestGroupInnerSearch(helpers.FunctionalTestBase): + + '''Test searching within an group.''' + + def test_group_search_within_org(self): + '''Group read page request returns list of datasets owned by group.''' + app = self._get_test_app() + + grp = factories.Group() + factories.Dataset(name="ds-one", title="Dataset One", + groups=[{'id': grp['id']}]) + factories.Dataset(name="ds-two", title="Dataset Two", + groups=[{'id': grp['id']}]) + factories.Dataset(name="ds-three", title="Dataset Three", + groups=[{'id': grp['id']}]) + + grp_url = url_for(controller='group', action='read', + id=grp['id']) + grp_response = app.get(grp_url) + grp_response_html = BeautifulSoup(grp_response.body) + + ds_titles = grp_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + ds_titles = [t.string for t in ds_titles] + + assert_true('3 datasets found' in grp_response) + assert_equal(len(ds_titles), 3) + assert_true('Dataset One' in ds_titles) + assert_true('Dataset Two' in ds_titles) + assert_true('Dataset Three' in ds_titles) + + def test_group_search_within_org_results(self): + '''Searching within an group returns expected dataset results.''' + app = self._get_test_app() + + grp = factories.Group() + factories.Dataset(name="ds-one", title="Dataset One", + groups=[{'id': grp['id']}]) + factories.Dataset(name="ds-two", title="Dataset Two", + groups=[{'id': grp['id']}]) + factories.Dataset(name="ds-three", title="Dataset Three", + groups=[{'id': grp['id']}]) + + grp_url = url_for(controller='group', action='read', + id=grp['id']) + grp_response = app.get(grp_url) + search_form = grp_response.forms['group-datasets-search-form'] + search_form['q'] = 'One' + search_response = webtest_submit(search_form) + assert_true('1 dataset found for "One"' in search_response) + + search_response_html = BeautifulSoup(search_response.body) + + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + ds_titles = [t.string for t in ds_titles] + + assert_equal(len(ds_titles), 1) + assert_true('Dataset One' in ds_titles) + assert_true('Dataset Two' not in ds_titles) + assert_true('Dataset Three' not in ds_titles) + + def test_group_search_within_org_no_results(self): + '''Searching for non-returning phrase within an group returns no + results.''' + app = self._get_test_app() + + grp = factories.Group() + factories.Dataset(name="ds-one", title="Dataset One", + groups=[{'id': grp['id']}]) + factories.Dataset(name="ds-two", title="Dataset Two", + groups=[{'id': grp['id']}]) + factories.Dataset(name="ds-three", title="Dataset Three", + groups=[{'id': grp['id']}]) + + grp_url = url_for(controller='group', action='read', + id=grp['id']) + grp_response = app.get(grp_url) + search_form = grp_response.forms['group-datasets-search-form'] + search_form['q'] = 'Nout' + search_response = webtest_submit(search_form) + + assert_true('No datasets found for "Nout"' in search_response) + + search_response_html = BeautifulSoup(search_response.body) + + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + ds_titles = [t.string for t in ds_titles] + + assert_equal(len(ds_titles), 0) + + +class TestGroupIndex(helpers.FunctionalTestBase): + + def test_group_index(self): + app = self._get_test_app() + + for i in xrange(1, 26): + _i = '0' + str(i) if i < 10 else i + factories.Group( + name='test-group-{0}'.format(_i), + title='Test Group {0}'.format(_i)) + + url = url_for(controller='group', + action='index') + response = app.get(url) + + for i in xrange(1, 22): + _i = '0' + str(i) if i < 10 else i + assert_in('Test Group {0}'.format(_i), response) + + assert 'Test Group 22' not in response + + url = url_for(controller='group', + action='index', + page=1) + response = app.get(url) + + for i in xrange(1, 22): + _i = '0' + str(i) if i < 10 else i + assert_in('Test Group {0}'.format(_i), response) + + assert 'Test Group 22' not in response + + url = url_for(controller='group', + action='index', + page=2) + response = app.get(url) + + for i in xrange(22, 26): + assert_in('Test Group {0}'.format(i), response) + + assert 'Test Group 21' not in response diff --git a/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_home.py b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_home.py new file mode 100644 index 00000000..038aa926 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_home.py @@ -0,0 +1,120 @@ +# encoding: utf-8 + +from nose.tools import eq_ +from ckan.lib.helpers import url_for +from bs4 import BeautifulSoup + +from ckan.tests import factories +import ckan.tests.helpers as helpers + + +class TestHome(helpers.FunctionalTestBase): + + def test_home_renders(self): + app = self._get_test_app() + response = app.get(url_for('home.index')) + assert 'Welcome to CKAN' in response.body + + def test_template_head_end(self): + app = self._get_test_app() + # test-core.ini sets ckan.template_head_end to this: + test_link = '' + response = app.get(url_for('home.index')) + assert test_link in response.body + + def test_template_footer_end(self): + app = self._get_test_app() + # test-core.ini sets ckan.template_footer_end to this: + test_html = 'TEST TEMPLATE_FOOTER_END TEST' + response = app.get(url_for('home.index')) + assert test_html in response.body + + def test_email_address_nag(self): + # before CKAN 1.6, users were allowed to have no email addresses + app = self._get_test_app() + # can't use factory to create user as without email it fails validation + from ckan import model + model.repo.new_revision() + user = model.user.User(name='has-no-email') + model.Session.add(user) + model.Session.commit() + env = {'REMOTE_USER': user.name.encode('ascii')} + + response = app.get(url=url_for('home.index'), extra_environ=env) + + assert 'update your profile' in response.body + assert url_for('user.edit') in response.body + assert ' and add your email address.' in response.body + + def test_email_address_no_nag(self): + app = self._get_test_app() + user = factories.User(email='filled_in@nicely.com') + env = {'REMOTE_USER': user['name'].encode('ascii')} + + response = app.get(url=url_for('home.index'), extra_environ=env) + + assert 'add your email address' not in response + + @helpers.change_config('ckan.legacy_route_mappings', + '{"my_home_route": "home.index"}') + def test_map_pylons_to_flask_route(self): + app = self._get_test_app() + response = app.get(url_for('my_home_route')) + assert 'Welcome to CKAN' in response.body + + response = app.get(url_for('home')) + assert 'Welcome to CKAN' in response.body + + @helpers.change_config('ckan.legacy_route_mappings', + {'my_home_route': 'home.index'}) + def test_map_pylons_to_flask_route_using_dict(self): + app = self._get_test_app() + response = app.get(url_for('my_home_route')) + assert 'Welcome to CKAN' in response.body + + response = app.get(url_for('home')) + assert 'Welcome to CKAN' in response.body + + +class TestI18nURLs(helpers.FunctionalTestBase): + + def test_right_urls_are_rendered_on_language_selector(self): + app = self._get_test_app() + response = app.get(url_for('home.index')) + html = BeautifulSoup(response.body) + + select = html.find(id='field-lang-select') + for option in select.find_all('option'): + if option.text.strip() == u'English': + eq_(option['value'], '/en/') + elif option.text.strip() == u'čeština (Česká republika)': + eq_(option['value'], '/cs_CZ/') + elif option.text.strip() == u'português (Brasil)': + eq_(option['value'], '/pt_BR/') + elif option.text.strip() == u'srpski (latinica)': + eq_(option['value'], '/sr_Latn/') + + def test_default_english_option_is_selected_on_language_selector(self): + app = self._get_test_app() + response = app.get(url_for('home.index')) + html = BeautifulSoup(response.body) + + select = html.find(id='field-lang-select') + for option in select.find_all('option'): + if option['value'] == '/en/': + eq_(option['selected'], 'selected') + else: + assert not option.has_attr('selected') + + def test_right_option_is_selected_on_language_selector(self): + app = self._get_test_app() + response = app.get(url_for('home.index', locale='ca')) + html = BeautifulSoup(response.body) + + select = html.find(id='field-lang-select') + for option in select.find_all('option'): + if option['value'] == '/ca/': + eq_(option['selected'], 'selected') + else: + assert not option.has_attr('selected') diff --git a/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_organization.py b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_organization.py new file mode 100644 index 00000000..07a9d63e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_organization.py @@ -0,0 +1,571 @@ +# encoding: utf-8 + +from ckan.common import config +from bs4 import BeautifulSoup +from nose.tools import assert_equal, assert_true, assert_in +from ckan.lib.helpers import url_for +from mock import patch + +from ckan.tests import factories, helpers +from ckan.tests.helpers import webtest_submit, submit_and_follow + + +class TestOrganizationNew(helpers.FunctionalTestBase): + def setup(self): + super(TestOrganizationNew, self).setup() + self.app = helpers._get_test_app() + self.user = factories.User() + self.user_env = {'REMOTE_USER': self.user['name'].encode('ascii')} + self.organization_new_url = url_for(controller='organization', + action='new') + + def test_not_logged_in(self): + self.app.get(url=url_for(controller='group', action='new'), + status=403) + + def test_name_required(self): + response = self.app.get(url=self.organization_new_url, + extra_environ=self.user_env) + form = response.forms['organization-edit-form'] + response = webtest_submit(form, name='save', + extra_environ=self.user_env) + + assert_true('organization-edit-form' in response.forms) + assert_true('Name: Missing value' in response) + + def test_saved(self): + response = self.app.get(url=self.organization_new_url, + extra_environ=self.user_env) + + form = response.forms['organization-edit-form'] + form['name'] = u'saved' + + response = submit_and_follow(self.app, form, name='save', + extra_environ=self.user_env) + group = helpers.call_action('organization_show', id='saved') + assert_equal(group['title'], u'') + assert_equal(group['type'], 'organization') + assert_equal(group['state'], 'active') + + def test_all_fields_saved(self): + app = helpers._get_test_app() + response = app.get(url=self.organization_new_url, + extra_environ=self.user_env) + + form = response.forms['organization-edit-form'] + form['name'] = u'all-fields-saved' + form['title'] = 'Science' + form['description'] = 'Sciencey datasets' + form['image_url'] = 'http://example.com/image.png' + + response = submit_and_follow(self.app, form, name='save', + extra_environ=self.user_env) + group = helpers.call_action('organization_show', id='all-fields-saved') + assert_equal(group['title'], u'Science') + assert_equal(group['description'], 'Sciencey datasets') + + +class TestOrganizationList(helpers.FunctionalTestBase): + def setup(self): + super(TestOrganizationList, self).setup() + self.app = helpers._get_test_app() + self.user = factories.User() + self.user_env = {'REMOTE_USER': self.user['name'].encode('ascii')} + self.organization_list_url = url_for(controller='organization', + action='index') + + @patch('ckan.logic.auth.get.organization_list', return_value={'success': False}) + def test_error_message_shown_when_no_organization_list_permission(self, mock_check_access): + response = self.app.get(url=self.organization_list_url, + extra_environ=self.user_env, + status=403) + + +class TestOrganizationRead(helpers.FunctionalTestBase): + def setup(self): + super(TestOrganizationRead, self).setup() + self.app = helpers._get_test_app() + self.user = factories.User() + self.user_env = {'REMOTE_USER': self.user['name'].encode('ascii')} + self.organization = factories.Organization(user=self.user) + + def test_organization_read(self): + response = self.app.get(url=url_for(controller='organization', + action='read', + id=self.organization['id']), + status=200, + extra_environ=self.user_env) + assert_in(self.organization['title'], response) + assert_in(self.organization['description'], response) + + +class TestOrganizationEdit(helpers.FunctionalTestBase): + def setup(self): + super(TestOrganizationEdit, self).setup() + self.app = helpers._get_test_app() + self.user = factories.User() + self.user_env = {'REMOTE_USER': self.user['name'].encode('ascii')} + self.organization = factories.Organization(user=self.user) + self.organization_edit_url = url_for(controller='organization', + action='edit', + id=self.organization['id']) + + def test_group_doesnt_exist(self): + url = url_for(controller='organization', + action='edit', + id='doesnt_exist') + self.app.get(url=url, extra_environ=self.user_env, + status=404) + + def test_saved(self): + response = self.app.get(url=self.organization_edit_url, + extra_environ=self.user_env) + + form = response.forms['organization-edit-form'] + response = webtest_submit(form, name='save', + extra_environ=self.user_env) + group = helpers.call_action('organization_show', + id=self.organization['id']) + assert_equal(group['title'], u'Test Organization') + assert_equal(group['type'], 'organization') + assert_equal(group['state'], 'active') + + def test_all_fields_saved(self): + response = self.app.get(url=self.organization_edit_url, + extra_environ=self.user_env) + + form = response.forms['organization-edit-form'] + form['name'] = u'all-fields-edited' + form['title'] = 'Science' + form['description'] = 'Sciencey datasets' + form['image_url'] = 'http://example.com/image.png' + response = webtest_submit(form, name='save', + extra_environ=self.user_env) + + group = helpers.call_action('organization_show', + id=self.organization['id']) + assert_equal(group['title'], u'Science') + assert_equal(group['description'], 'Sciencey datasets') + assert_equal(group['image_url'], 'http://example.com/image.png') + + +class TestOrganizationDelete(helpers.FunctionalTestBase): + def setup(self): + super(TestOrganizationDelete, self).setup() + self.app = helpers._get_test_app() + self.user = factories.User() + self.user_env = {'REMOTE_USER': self.user['name'].encode('ascii')} + self.organization = factories.Organization(user=self.user) + + def test_owner_delete(self): + response = self.app.get(url=url_for(controller='organization', + action='delete', + id=self.organization['id']), + status=200, + extra_environ=self.user_env) + + form = response.forms['organization-confirm-delete-form'] + response = submit_and_follow(self.app, form, name='delete', + extra_environ=self.user_env) + organization = helpers.call_action('organization_show', + id=self.organization['id']) + assert_equal(organization['state'], 'deleted') + + def test_sysadmin_delete(self): + sysadmin = factories.Sysadmin() + extra_environ = {'REMOTE_USER': sysadmin['name'].encode('ascii')} + response = self.app.get(url=url_for(controller='organization', + action='delete', + id=self.organization['id']), + status=200, + extra_environ=extra_environ) + + form = response.forms['organization-confirm-delete-form'] + response = submit_and_follow(self.app, form, name='delete', + extra_environ=self.user_env) + organization = helpers.call_action('organization_show', + id=self.organization['id']) + assert_equal(organization['state'], 'deleted') + + def test_non_authorized_user_trying_to_delete_fails(self): + user = factories.User() + extra_environ = {'REMOTE_USER': user['name'].encode('ascii')} + self.app.get(url=url_for(controller='organization', + action='delete', + id=self.organization['id']), + status=403, + extra_environ=extra_environ) + + organization = helpers.call_action('organization_show', + id=self.organization['id']) + assert_equal(organization['state'], 'active') + + def test_anon_user_trying_to_delete_fails(self): + self.app.get(url=url_for(controller='organization', + action='delete', + id=self.organization['id']), + status=403) + + organization = helpers.call_action('organization_show', + id=self.organization['id']) + assert_equal(organization['state'], 'active') + + @helpers.change_config('ckan.auth.create_unowned_dataset', False) + def test_delete_organization_with_datasets(self): + ''' Test deletion of organization that has datasets''' + text = 'Organization cannot be deleted while it still has datasets' + datasets = [factories.Dataset(owner_org=self.organization['id']) + for i in range(0, 5)] + response = self.app.get( + url=url_for( + controller='organization', + action='delete', + id=self.organization['id']), + status=200, + extra_environ=self.user_env) + + form = response.forms['organization-confirm-delete-form'] + response = submit_and_follow( + self.app, form, name='delete', extra_environ=self.user_env) + assert text in response.body + + def test_delete_organization_with_unknown_dataset_true(self): + ''' Test deletion of organization that has datasets and unknown + datasets are set to true''' + dataset = factories.Dataset(owner_org=self.organization['id']) + assert_equal(dataset['owner_org'], self.organization['id']) + helpers.call_action('organization_delete', id=self.organization['id']) + + dataset = helpers.call_action('package_show', id=dataset['id']) + assert_equal(dataset['owner_org'], None) + + +class TestOrganizationBulkProcess(helpers.FunctionalTestBase): + def setup(self): + super(TestOrganizationBulkProcess, self).setup() + self.app = helpers._get_test_app() + self.user = factories.User() + self.user_env = {'REMOTE_USER': self.user['name'].encode('ascii')} + self.organization = factories.Organization(user=self.user) + self.organization_bulk_url = url_for( + controller='organization', + action='bulk_process', + id=self.organization['id']) + + def test_make_private(self): + datasets = [factories.Dataset(owner_org=self.organization['id']) + for i in range(0, 5)] + response = self.app.get(url=self.organization_bulk_url, + extra_environ=self.user_env) + form = response.forms[1] + for v in form.fields.values(): + try: + v[0].checked = True + except AttributeError: + pass + response = webtest_submit(form, name='bulk_action.private', + value='private', + extra_environ=self.user_env) + + for dataset in datasets: + d = helpers.call_action('package_show', id=dataset['id']) + assert_equal(d['private'], True) + + def test_make_public(self): + datasets = [factories.Dataset(owner_org=self.organization['id'], + private=True) + for i in range(0, 5)] + response = self.app.get(url=self.organization_bulk_url, + extra_environ=self.user_env) + form = response.forms[1] + for v in form.fields.values(): + try: + v[0].checked = True + except AttributeError: + pass + response = webtest_submit(form, name='bulk_action.public', + value='public', + extra_environ=self.user_env) + + for dataset in datasets: + d = helpers.call_action('package_show', id=dataset['id']) + assert_equal(d['private'], False) + + def test_delete(self): + datasets = [factories.Dataset(owner_org=self.organization['id'], + private=True) + for i in range(0, 5)] + response = self.app.get(url=self.organization_bulk_url, + extra_environ=self.user_env) + form = response.forms[1] + for v in form.fields.values(): + try: + v[0].checked = True + except AttributeError: + pass + response = webtest_submit(form, name='bulk_action.delete', + value='delete', + extra_environ=self.user_env) + + for dataset in datasets: + d = helpers.call_action('package_show', id=dataset['id']) + assert_equal(d['state'], 'deleted') + + +class TestOrganizationSearch(helpers.FunctionalTestBase): + + '''Test searching for organizations.''' + + def setup(self): + super(TestOrganizationSearch, self).setup() + self.app = self._get_test_app() + factories.Organization(name='org-one', title='AOrg One') + factories.Organization(name='org-two', title='AOrg Two') + factories.Organization(name='org-three', title='Org Three') + self.search_url = url_for(controller='organization', action='index') + + def test_organization_search(self): + '''Requesting organization search (index) returns list of + organizations and search form.''' + + index_response = self.app.get(self.search_url) + index_response_html = BeautifulSoup(index_response.body) + org_names = index_response_html.select('ul.media-grid ' + 'li.media-item ' + 'h3.media-heading') + org_names = [n.string for n in org_names] + + assert_equal(len(org_names), 3) + assert_true('AOrg One' in org_names) + assert_true('AOrg Two' in org_names) + assert_true('Org Three' in org_names) + + def test_organization_search_results(self): + '''Searching via organization search form returns list of expected + organizations.''' + + index_response = self.app.get(self.search_url) + search_form = index_response.forms['organization-search-form'] + search_form['q'] = 'AOrg' + search_response = webtest_submit(search_form) + + search_response_html = BeautifulSoup(search_response.body) + org_names = search_response_html.select('ul.media-grid ' + 'li.media-item ' + 'h3.media-heading') + org_names = [n.string for n in org_names] + + assert_equal(len(org_names), 2) + assert_true('AOrg One' in org_names) + assert_true('AOrg Two' in org_names) + assert_true('Org Three' not in org_names) + + def test_organization_search_no_results(self): + '''Searching with a term that doesn't apply returns no results.''' + + index_response = self.app.get(self.search_url) + search_form = index_response.forms['organization-search-form'] + search_form['q'] = 'No Results Here' + search_response = webtest_submit(search_form) + + search_response_html = BeautifulSoup(search_response.body) + org_names = search_response_html.select('ul.media-grid ' + 'li.media-item ' + 'h3.media-heading') + org_names = [n.string for n in org_names] + + assert_equal(len(org_names), 0) + assert_true("No organizations found for "No Results Here"" + in search_response) + + +class TestOrganizationInnerSearch(helpers.FunctionalTestBase): + + '''Test searching within an organization.''' + + def test_organization_search_within_org(self): + '''Organization read page request returns list of datasets owned by + organization.''' + app = self._get_test_app() + + org = factories.Organization() + factories.Dataset(name="ds-one", title="Dataset One", + owner_org=org['id']) + factories.Dataset(name="ds-two", title="Dataset Two", + owner_org=org['id']) + factories.Dataset(name="ds-three", title="Dataset Three", + owner_org=org['id']) + + org_url = url_for(controller='organization', action='read', + id=org['id']) + org_response = app.get(org_url) + org_response_html = BeautifulSoup(org_response.body) + + ds_titles = org_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + ds_titles = [t.string for t in ds_titles] + + assert_true('3 datasets found' in org_response) + assert_equal(len(ds_titles), 3) + assert_true('Dataset One' in ds_titles) + assert_true('Dataset Two' in ds_titles) + assert_true('Dataset Three' in ds_titles) + + def test_organization_search_within_org_results(self): + '''Searching within an organization returns expected dataset + results.''' + app = self._get_test_app() + + org = factories.Organization() + factories.Dataset(name="ds-one", title="Dataset One", + owner_org=org['id']) + factories.Dataset(name="ds-two", title="Dataset Two", + owner_org=org['id']) + factories.Dataset(name="ds-three", title="Dataset Three", + owner_org=org['id']) + + org_url = url_for(controller='organization', action='read', + id=org['id']) + org_response = app.get(org_url) + search_form = org_response.forms['organization-datasets-search-form'] + search_form['q'] = 'One' + search_response = webtest_submit(search_form) + assert_true('1 dataset found for "One"' in search_response) + + search_response_html = BeautifulSoup(search_response.body) + + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + ds_titles = [t.string for t in ds_titles] + + assert_equal(len(ds_titles), 1) + assert_true('Dataset One' in ds_titles) + assert_true('Dataset Two' not in ds_titles) + assert_true('Dataset Three' not in ds_titles) + + def test_organization_search_within_org_no_results(self): + '''Searching for non-returning phrase within an organization returns + no results.''' + app = self._get_test_app() + + org = factories.Organization() + factories.Dataset(name="ds-one", title="Dataset One", + owner_org=org['id']) + factories.Dataset(name="ds-two", title="Dataset Two", + owner_org=org['id']) + factories.Dataset(name="ds-three", title="Dataset Three", + owner_org=org['id']) + + org_url = url_for(controller='organization', action='read', + id=org['id']) + org_response = app.get(org_url) + search_form = org_response.forms['organization-datasets-search-form'] + search_form['q'] = 'Nout' + search_response = webtest_submit(search_form) + + assert_true('No datasets found for "Nout"' in search_response) + + search_response_html = BeautifulSoup(search_response.body) + + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + ds_titles = [t.string for t in ds_titles] + + assert_equal(len(ds_titles), 0) + + +class TestOrganizationMembership(helpers.FunctionalTestBase): + + def test_editor_users_cannot_add_members(self): + + user = factories.User() + organization = factories.Organization( + users=[{'name': user['name'], 'capacity': 'editor'}] + ) + + app = helpers._get_test_app() + + env = {'REMOTE_USER': user['name'].encode('ascii')} + + with app.flask_app.test_request_context(): + app.get( + url_for( + controller='organization', + action='member_new', + id=organization['id'], + ), + extra_environ=env, + status=403, + ) + + app.post( + url_for( + controller='organization', + action='member_new', + id=organization['id'], + ), + {'id': 'test', 'username': 'test', 'save': 'save', 'role': 'test'}, + extra_environ=env, + status=403, + ) + + def test_member_users_cannot_add_members(self): + + user = factories.User() + organization = factories.Organization( + users=[{'name': user['name'], 'capacity': 'member'}] + ) + + app = helpers._get_test_app() + + env = {'REMOTE_USER': user['name'].encode('ascii')} + + with app.flask_app.test_request_context(): + app.get( + url_for( + controller='organization', + action='member_new', + id=organization['id'], + ), + extra_environ=env, + status=403, + ) + + app.post( + url_for( + controller='organization', + action='member_new', + id=organization['id'], + ), + {'id': 'test', 'username': 'test', 'save': 'save', 'role': 'test'}, + extra_environ=env, + status=403, + ) + + def test_anonymous_users_cannot_add_members(self): + organization = factories.Organization() + + app = helpers._get_test_app() + + with app.flask_app.test_request_context(): + app.get( + url_for( + controller='organization', + action='member_new', + id=organization['id'], + ), + status=403, + ) + + app.post( + url_for( + controller='organization', + action='member_new', + id=organization['id'], + ), + {'id': 'test', 'username': 'test', 'save': 'save', 'role': 'test'}, + status=403, + ) diff --git a/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_package.py b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_package.py new file mode 100644 index 00000000..162f9d41 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_package.py @@ -0,0 +1,1749 @@ +# encoding: utf-8 + +from bs4 import BeautifulSoup +from nose.tools import ( + assert_equal, + assert_not_equal, + assert_raises, + assert_true, + assert_in +) + +from mock import patch, MagicMock +from ckan.lib.helpers import url_for + +import ckan.model as model +import ckan.plugins as p +from ckan.lib import search + +import ckan.tests.helpers as helpers +import ckan.tests.factories as factories + + +webtest_submit = helpers.webtest_submit +submit_and_follow = helpers.submit_and_follow + + +def _get_package_new_page(app): + user = factories.User() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url=url_for(controller='package', action='new'), + extra_environ=env, + ) + return env, response + + +class TestPackageNew(helpers.FunctionalTestBase): + def test_form_renders(self): + app = self._get_test_app() + env, response = _get_package_new_page(app) + assert_true('dataset-edit' in response.forms) + + @helpers.change_config('ckan.auth.create_unowned_dataset', 'false') + def test_needs_organization_but_no_organizations_has_button(self): + ''' Scenario: The settings say every dataset needs an organization + but there are no organizations. If the user is allowed to create an + organization they should be prompted to do so when they try to create + a new dataset''' + app = self._get_test_app() + sysadmin = factories.Sysadmin() + + env = {'REMOTE_USER': sysadmin['name'].encode('ascii')} + response = app.get( + url=url_for(controller='package', action='new'), + extra_environ=env + ) + assert 'dataset-edit' not in response.forms + assert url_for(controller='organization', action='new') in response + + @helpers.mock_auth('ckan.logic.auth.create.package_create') + @helpers.change_config('ckan.auth.create_unowned_dataset', 'false') + @helpers.change_config('ckan.auth.user_create_organizations', 'false') + def test_needs_organization_but_no_organizations_no_button(self, + mock_p_create): + ''' Scenario: The settings say every dataset needs an organization + but there are no organizations. If the user is not allowed to create an + organization they should be told to ask the admin but no link should be + presented. Note: This cannot happen with the default ckan and requires + a plugin to overwrite the package_create behavior''' + mock_p_create.return_value = {'success': True} + + app = self._get_test_app() + user = factories.User() + + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url=url_for(controller='package', action='new'), + extra_environ=env + ) + + assert 'dataset-edit' not in response.forms + assert url_for(controller='organization', action='new') not in response + assert 'Ask a system administrator' in response + + def test_name_required(self): + app = self._get_test_app() + env, response = _get_package_new_page(app) + form = response.forms['dataset-edit'] + + response = webtest_submit(form, 'save', status=200, extra_environ=env) + assert_true('dataset-edit' in response.forms) + assert_true('Name: Missing value' in response) + + def test_resource_form_renders(self): + app = self._get_test_app() + env, response = _get_package_new_page(app) + form = response.forms['dataset-edit'] + form['name'] = u'resource-form-renders' + + response = submit_and_follow(app, form, env, 'save') + assert_true('resource-edit' in response.forms) + + def test_first_page_creates_draft_package(self): + app = self._get_test_app() + env, response = _get_package_new_page(app) + form = response.forms['dataset-edit'] + form['name'] = u'first-page-creates-draft' + + webtest_submit(form, 'save', status=302, extra_environ=env) + pkg = model.Package.by_name(u'first-page-creates-draft') + assert_equal(pkg.state, 'draft') + + def test_resource_required(self): + app = self._get_test_app() + env, response = _get_package_new_page(app) + form = response.forms['dataset-edit'] + form['name'] = u'one-resource-required' + + response = submit_and_follow(app, form, env, 'save') + form = response.forms['resource-edit'] + + response = webtest_submit(form, 'save', value='go-metadata', + status=200, extra_environ=env) + assert_true('resource-edit' in response.forms) + assert_true('You must add at least one data resource' in response) + + def test_complete_package_with_one_resource(self): + app = self._get_test_app() + env, response = _get_package_new_page(app) + form = response.forms['dataset-edit'] + form['name'] = u'complete-package-with-one-resource' + + response = submit_and_follow(app, form, env, 'save') + form = response.forms['resource-edit'] + form['url'] = u'http://example.com/resource' + + submit_and_follow(app, form, env, 'save', 'go-metadata') + pkg = model.Package.by_name(u'complete-package-with-one-resource') + assert_equal(pkg.resources[0].url, u'http://example.com/resource') + assert_equal(pkg.state, 'active') + + def test_complete_package_with_two_resources(self): + app = self._get_test_app() + env, response = _get_package_new_page(app) + form = response.forms['dataset-edit'] + form['name'] = u'complete-package-with-two-resources' + + response = submit_and_follow(app, form, env, 'save') + form = response.forms['resource-edit'] + form['url'] = u'http://example.com/resource0' + + response = submit_and_follow(app, form, env, 'save', 'again') + form = response.forms['resource-edit'] + form['url'] = u'http://example.com/resource1' + + submit_and_follow(app, form, env, 'save', 'go-metadata') + pkg = model.Package.by_name(u'complete-package-with-two-resources') + assert_equal(pkg.resources[0].url, u'http://example.com/resource0') + assert_equal(pkg.resources[1].url, u'http://example.com/resource1') + assert_equal(pkg.state, 'active') + + # def test_resource_uploads(self): + # app = self._get_test_app() + # env, response = _get_package_new_page(app) + # form = response.forms['dataset-edit'] + # form['name'] = u'complete-package-with-two-resources' + + # response = submit_and_follow(app, form, env, 'save') + # form = response.forms['resource-edit'] + # form['upload'] = ('README.rst', b'data') + + # response = submit_and_follow(app, form, env, 'save', 'go-metadata') + # pkg = model.Package.by_name(u'complete-package-with-two-resources') + # assert_equal(pkg.resources[0].url_type, u'upload') + # assert_equal(pkg.state, 'active') + # response = app.get( + # url_for( + # controller='package', + # action='resource_download', + # id=pkg.id, + # resource_id=pkg.resources[0].id + # ), + # ) + # assert_equal('data', response.body) + + def test_previous_button_works(self): + app = self._get_test_app() + env, response = _get_package_new_page(app) + form = response.forms['dataset-edit'] + form['name'] = u'previous-button-works' + + response = submit_and_follow(app, form, env, 'save') + form = response.forms['resource-edit'] + + response = submit_and_follow(app, form, env, 'save', 'go-dataset') + assert_true('dataset-edit' in response.forms) + + def test_previous_button_populates_form(self): + app = self._get_test_app() + env, response = _get_package_new_page(app) + form = response.forms['dataset-edit'] + form['name'] = u'previous-button-populates-form' + + response = submit_and_follow(app, form, env, 'save') + form = response.forms['resource-edit'] + + response = submit_and_follow(app, form, env, 'save', 'go-dataset') + form = response.forms['dataset-edit'] + assert_true('title' in form.fields) + assert_equal(form['name'].value, u'previous-button-populates-form') + + def test_previous_next_maintains_draft_state(self): + app = self._get_test_app() + env, response = _get_package_new_page(app) + form = response.forms['dataset-edit'] + form['name'] = u'previous-next-maintains-draft' + + response = submit_and_follow(app, form, env, 'save') + form = response.forms['resource-edit'] + + response = submit_and_follow(app, form, env, 'save', 'go-dataset') + form = response.forms['dataset-edit'] + + webtest_submit(form, 'save', status=302, extra_environ=env) + pkg = model.Package.by_name(u'previous-next-maintains-draft') + assert_equal(pkg.state, 'draft') + + def test_dataset_edit_org_dropdown_visible_to_normal_user_with_orgs_available(self): + ''' + The 'Organization' dropdown is available on the dataset create/edit + page to normal (non-sysadmin) users who have organizations available + to them. + ''' + user = factories.User() + # user is admin of org. + org = factories.Organization( + name="my-org", + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + + app = self._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url=url_for(controller='package', action='new'), + extra_environ=env, + ) + + # organization dropdown available in create page. + form = response.forms['dataset-edit'] + assert 'owner_org' in form.fields + + # create dataset + form['name'] = u'my-dataset' + form['owner_org'] = org['id'] + response = submit_and_follow(app, form, env, 'save') + + # add a resource to make the pkg active + resource_form = response.forms['resource-edit'] + resource_form['url'] = u'http://example.com/resource' + submit_and_follow(app, resource_form, env, 'save', 'go-metadata') + pkg = model.Package.by_name(u'my-dataset') + assert_equal(pkg.state, 'active') + + # edit package page response + url = url_for(controller='package', + action='edit', + id=pkg.id) + pkg_edit_response = app.get(url=url, extra_environ=env) + # A field with the correct id is in the response + form = pkg_edit_response.forms['dataset-edit'] + assert 'owner_org' in form.fields + # The organization id is in the response in a value attribute + owner_org_options = [value for (value, _) in form['owner_org'].options] + assert org['id'] in owner_org_options + + def test_dataset_edit_org_dropdown_normal_user_can_remove_org(self): + ''' + A normal user (non-sysadmin) can remove an organization from a dataset + have permissions on. + ''' + user = factories.User() + # user is admin of org. + org = factories.Organization(name="my-org", + users=[{'name': user['id'], 'capacity': 'admin'}]) + + app = self._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url=url_for(controller='package', action='new'), + extra_environ=env, + ) + + # create dataset with owner_org + form = response.forms['dataset-edit'] + form['name'] = u'my-dataset' + form['owner_org'] = org['id'] + response = submit_and_follow(app, form, env, 'save') + + # add a resource to make the pkg active + resource_form = response.forms['resource-edit'] + resource_form['url'] = u'http://example.com/resource' + submit_and_follow(app, resource_form, env, 'save', 'go-metadata') + pkg = model.Package.by_name(u'my-dataset') + assert_equal(pkg.state, 'active') + assert_equal(pkg.owner_org, org['id']) + assert_not_equal(pkg.owner_org, None) + + # edit package page response + url = url_for(controller='package', + action='edit', + id=pkg.id) + pkg_edit_response = app.get(url=url, extra_environ=env) + + # edit dataset + edit_form = pkg_edit_response.forms['dataset-edit'] + edit_form['owner_org'] = '' + submit_and_follow(app, edit_form, env, 'save') + post_edit_pkg = model.Package.by_name(u'my-dataset') + assert_equal(post_edit_pkg.owner_org, None) + assert_not_equal(post_edit_pkg.owner_org, org['id']) + + def test_dataset_edit_org_dropdown_not_visible_to_normal_user_with_no_orgs_available(self): + ''' + The 'Organization' dropdown is not available on the dataset + create/edit page to normal (non-sysadmin) users who have no + organizations available to them. + ''' + user = factories.User() + # user isn't admin of org. + org = factories.Organization(name="my-org") + + app = self._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url=url_for(controller='package', action='new'), + extra_environ=env, + ) + + # organization dropdown not in create page. + form = response.forms['dataset-edit'] + assert 'owner_org' not in form.fields + + # create dataset + form['name'] = u'my-dataset' + response = submit_and_follow(app, form, env, 'save') + + # add a resource to make the pkg active + resource_form = response.forms['resource-edit'] + resource_form['url'] = u'http://example.com/resource' + submit_and_follow(app, resource_form, env, 'save', 'go-metadata') + pkg = model.Package.by_name(u'my-dataset') + assert_equal(pkg.state, 'active') + + # edit package response + url = url_for(controller='package', + action='edit', + id=model.Package.by_name(u'my-dataset').id) + pkg_edit_response = app.get(url=url, extra_environ=env) + # A field with the correct id is in the response + form = pkg_edit_response.forms['dataset-edit'] + assert 'owner_org' not in form.fields + # The organization id is in the response in a value attribute + assert 'value="{0}"'.format(org['id']) not in pkg_edit_response + + def test_dataset_edit_org_dropdown_visible_to_sysadmin_with_no_orgs_available(self): + ''' + The 'Organization' dropdown is available to sysadmin users regardless + of whether they personally have an organization they administrate. + ''' + user = factories.User() + sysadmin = factories.Sysadmin() + # user is admin of org. + org = factories.Organization(name="my-org", + users=[{'name': user['id'], 'capacity': 'admin'}]) + + app = self._get_test_app() + # user in env is sysadmin + env = {'REMOTE_USER': sysadmin['name'].encode('ascii')} + response = app.get( + url=url_for(controller='package', action='new'), + extra_environ=env, + ) + + # organization dropdown available in create page. + assert 'id="field-organizations"' in response + + # create dataset + form = response.forms['dataset-edit'] + form['name'] = u'my-dataset' + form['owner_org'] = org['id'] + response = submit_and_follow(app, form, env, 'save') + + # add a resource to make the pkg active + resource_form = response.forms['resource-edit'] + resource_form['url'] = u'http://example.com/resource' + submit_and_follow(app, resource_form, env, 'save', 'go-metadata') + pkg = model.Package.by_name(u'my-dataset') + assert_equal(pkg.state, 'active') + + # edit package page response + url = url_for(controller='package', + action='edit', + id=pkg.id) + pkg_edit_response = app.get(url=url, extra_environ=env) + # A field with the correct id is in the response + assert 'id="field-organizations"' in pkg_edit_response + # The organization id is in the response in a value attribute + assert 'value="{0}"'.format(org['id']) in pkg_edit_response + + def test_unauthed_user_creating_dataset(self): + app = self._get_test_app() + + # provide REMOTE_ADDR to idenfity as remote user, see + # ckan.views.identify_user() for details + response = app.post(url=url_for(controller='package', action='new'), + extra_environ={'REMOTE_ADDR': '127.0.0.1'}, + status=403) + + +class TestPackageEdit(helpers.FunctionalTestBase): + def test_organization_admin_can_edit(self): + user = factories.User() + organization = factories.Organization( + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=organization['id']) + app = helpers._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url_for(controller='package', + action='edit', + id=dataset['name']), + extra_environ=env, + ) + form = response.forms['dataset-edit'] + form['notes'] = u'edited description' + submit_and_follow(app, form, env, 'save') + + result = helpers.call_action('package_show', id=dataset['id']) + assert_equal(u'edited description', result['notes']) + + def test_organization_editor_can_edit(self): + user = factories.User() + organization = factories.Organization( + users=[{'name': user['id'], 'capacity': 'editor'}] + ) + dataset = factories.Dataset(owner_org=organization['id']) + app = helpers._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url_for(controller='package', + action='edit', + id=dataset['name']), + extra_environ=env, + ) + form = response.forms['dataset-edit'] + form['notes'] = u'edited description' + submit_and_follow(app, form, env, 'save') + + result = helpers.call_action('package_show', id=dataset['id']) + assert_equal(u'edited description', result['notes']) + + def test_organization_member_cannot_edit(self): + user = factories.User() + organization = factories.Organization( + users=[{'name': user['id'], 'capacity': 'member'}] + ) + dataset = factories.Dataset(owner_org=organization['id']) + app = helpers._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url_for(controller='package', + action='edit', + id=dataset['name']), + extra_environ=env, + status=403, + ) + + def test_user_not_in_organization_cannot_edit(self): + user = factories.User() + organization = factories.Organization() + dataset = factories.Dataset(owner_org=organization['id']) + app = helpers._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url_for(controller='package', + action='edit', + id=dataset['name']), + extra_environ=env, + status=403, + ) + + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.post( + url_for(controller='package', + action='edit', + id=dataset['name']), + {'notes': 'edited description'}, + extra_environ=env, + status=403, + ) + + def test_anonymous_user_cannot_edit(self): + organization = factories.Organization() + dataset = factories.Dataset(owner_org=organization['id']) + app = helpers._get_test_app() + response = app.get( + url_for(controller='package', + action='edit', + id=dataset['name']), + status=403, + ) + + response = app.post( + url_for(controller='package', + action='edit', + id=dataset['name']), + {'notes': 'edited description'}, + status=403, + ) + + def test_validation_errors_for_dataset_name_appear(self): + '''fill out a bad dataset set name and make sure errors appear''' + user = factories.User() + organization = factories.Organization( + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=organization['id']) + app = helpers._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url_for(controller='package', + action='edit', + id=dataset['name']), + extra_environ=env, + ) + form = response.forms['dataset-edit'] + form['name'] = u'this is not a valid name' + response = webtest_submit(form, 'save', status=200, extra_environ=env) + assert_in('The form contains invalid entries', response.body) + + assert_in('Name: Must be purely lowercase alphanumeric (ascii) ' + 'characters and these symbols: -_', response.body) + + def test_edit_a_dataset_that_does_not_exist_404s(self): + user = factories.User() + app = helpers._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url_for(controller='package', + action='edit', + id='does-not-exist'), + extra_environ=env, + expect_errors=True + ) + assert_equal(404, response.status_int) + + +class TestPackageRead(helpers.FunctionalTestBase): + def test_read(self): + dataset = factories.Dataset() + app = helpers._get_test_app() + response = app.get(url_for(controller='package', action='read', + id=dataset['name'])) + response.mustcontain('Test Dataset') + response.mustcontain('Just another test dataset') + + def test_organization_members_can_read_private_datasets(self): + members = { + 'member': factories.User(), + 'editor': factories.User(), + 'admin': factories.User(), + 'sysadmin': factories.Sysadmin() + } + organization = factories.Organization( + users=[ + {'name': members['member']['id'], 'capacity': 'member'}, + {'name': members['editor']['id'], 'capacity': 'editor'}, + {'name': members['admin']['id'], 'capacity': 'admin'}, + ] + ) + dataset = factories.Dataset( + owner_org=organization['id'], + private=True, + ) + app = helpers._get_test_app() + + for user, user_dict in members.items(): + response = app.get( + url_for( + controller='package', + action='read', + id=dataset['name'] + ), + extra_environ={ + 'REMOTE_USER': user_dict['name'].encode('ascii'), + }, + ) + assert_in('Test Dataset', response.body) + assert_in('Just another test dataset', response.body) + + def test_anonymous_users_cannot_read_private_datasets(self): + organization = factories.Organization() + dataset = factories.Dataset( + owner_org=organization['id'], + private=True, + ) + app = helpers._get_test_app() + response = app.get( + url_for(controller='package', action='read', id=dataset['name']), + status=404 + ) + assert_equal(404, response.status_int) + + def test_user_not_in_organization_cannot_read_private_datasets(self): + user = factories.User() + organization = factories.Organization() + dataset = factories.Dataset( + owner_org=organization['id'], + private=True, + ) + app = helpers._get_test_app() + response = app.get( + url_for(controller='package', action='read', id=dataset['name']), + extra_environ={'REMOTE_USER': user['name'].encode('ascii')}, + status=404 + ) + assert_equal(404, response.status_int) + + def test_read_rdf(self): + ''' The RDF outputs now live in ckanext-dcat''' + dataset1 = factories.Dataset() + + offset = url_for(controller='package', action='read', + id=dataset1['name']) + ".rdf" + app = self._get_test_app() + app.get(offset, status=404) + + def test_read_n3(self): + ''' The RDF outputs now live in ckanext-dcat''' + dataset1 = factories.Dataset() + + offset = url_for(controller='package', action='read', + id=dataset1['name']) + ".n3" + app = self._get_test_app() + app.get(offset, status=404) + + +class TestPackageDelete(helpers.FunctionalTestBase): + def test_owner_delete(self): + user = factories.User() + owner_org = factories.Organization( + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=owner_org['id']) + + app = helpers._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.post( + url_for(controller='package', action='delete', id=dataset['name']), + extra_environ=env, + ) + response = response.follow() + assert_equal(200, response.status_int) + + deleted = helpers.call_action('package_show', id=dataset['id']) + assert_equal('deleted', deleted['state']) + + def test_delete_on_non_existing_dataset(self): + app = helpers._get_test_app() + response = app.post( + url_for(controller='package', action='delete', + id='schrodingersdatset'), + expect_errors=True, + ) + assert_equal(404, response.status_int) + + def test_sysadmin_can_delete_any_dataset(self): + owner_org = factories.Organization() + dataset = factories.Dataset(owner_org=owner_org['id']) + app = helpers._get_test_app() + + user = factories.Sysadmin() + env = {'REMOTE_USER': user['name'].encode('ascii')} + + response = app.post( + url_for(controller='package', action='delete', id=dataset['name']), + extra_environ=env, + ) + response = response.follow() + assert_equal(200, response.status_int) + + deleted = helpers.call_action('package_show', id=dataset['id']) + assert_equal('deleted', deleted['state']) + + def test_anon_user_cannot_delete_owned_dataset(self): + user = factories.User() + owner_org = factories.Organization( + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=owner_org['id']) + + app = helpers._get_test_app() + response = app.post( + url_for(controller='package', action='delete', id=dataset['name']), + status=403, + ) + response.mustcontain('Unauthorized to delete package') + + deleted = helpers.call_action('package_show', id=dataset['id']) + assert_equal('active', deleted['state']) + + def test_logged_in_user_cannot_delete_owned_dataset(self): + owner = factories.User() + owner_org = factories.Organization( + users=[{'name': owner['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=owner_org['id']) + + app = helpers._get_test_app() + user = factories.User() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.post( + url_for(controller='package', action='delete', id=dataset['name']), + extra_environ=env, + expect_errors=True + ) + assert_equal(403, response.status_int) + response.mustcontain('Unauthorized to delete package') + + def test_confirm_cancel_delete(self): + '''Test confirmation of deleting datasets + + When package_delete is made as a get request, it should return a + 'do you want to delete this dataset? confirmation page''' + user = factories.User() + owner_org = factories.Organization( + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=owner_org['id']) + + app = helpers._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url_for(controller='package', action='delete', id=dataset['name']), + extra_environ=env, + ) + assert_equal(200, response.status_int) + message = 'Are you sure you want to delete dataset - {name}?' + response.mustcontain(message.format(name=dataset['title'])) + + form = response.forms['confirm-dataset-delete-form'] + response = form.submit('cancel') + response = helpers.webtest_maybe_follow( + response, + extra_environ=env, + ) + assert_equal(200, response.status_int) + + +class TestResourceNew(helpers.FunctionalTestBase): + def test_manage_dataset_resource_listing_page(self): + user = factories.User() + organization = factories.Organization(user=user) + dataset = factories.Dataset(owner_org=organization['id']) + resource = factories.Resource(package_id=dataset['id']) + app = helpers._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url_for( + controller='package', + action='resources', + id=dataset['name'], + ), + extra_environ=env + ) + assert_in(resource['name'], response) + assert_in(resource['description'], response) + assert_in(resource['format'], response) + + def test_unauth_user_cannot_view_manage_dataset_resource_listing_page(self): + user = factories.User() + organization = factories.Organization(user=user) + dataset = factories.Dataset(owner_org=organization['id']) + resource = factories.Resource(package_id=dataset['id']) + app = helpers._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url_for( + controller='package', + action='resources', + id=dataset['name'], + ), + extra_environ=env + ) + assert_in(resource['name'], response) + assert_in(resource['description'], response) + assert_in(resource['format'], response) + + def test_404_on_manage_dataset_resource_listing_page_that_does_not_exist(self): + user = factories.User() + app = helpers._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url_for( + controller='package', + action='resources', + id='does-not-exist' + ), + extra_environ=env, + expect_errors=True + ) + assert_equal(404, response.status_int) + + def test_add_new_resource_with_link_and_download(self): + user = factories.User() + dataset = factories.Dataset() + env = {'REMOTE_USER': user['name'].encode('ascii')} + app = helpers._get_test_app() + + response = app.get( + url_for( + controller='package', + action='new_resource', + id=dataset['id'], + ), + extra_environ=env + ) + + form = response.forms['resource-edit'] + form['url'] = u'http://test.com/' + response = submit_and_follow(app, form, env, 'save', + 'go-dataset-complete') + + result = helpers.call_action('package_show', id=dataset['id']) + response = app.get( + url_for( + controller='package', + action='resource_download', + id=dataset['id'], + resource_id=result['resources'][0]['id'] + ), + extra_environ=env, + ) + assert_equal(302, response.status_int) + + def test_editor_can_add_new_resource(self): + user = factories.User() + organization = factories.Organization( + users=[{'name': user['id'], 'capacity': 'editor'}] + ) + dataset = factories.Dataset( + owner_org=organization['id'], + ) + env = {'REMOTE_USER': user['name'].encode('ascii')} + app = helpers._get_test_app() + + response = app.get( + url_for( + controller='package', + action='new_resource', + id=dataset['id'], + ), + extra_environ=env + ) + + form = response.forms['resource-edit'] + form['name'] = u'test resource' + form['url'] = u'http://test.com/' + response = submit_and_follow(app, form, env, 'save', + 'go-dataset-complete') + + result = helpers.call_action('package_show', id=dataset['id']) + assert_equal(1, len(result['resources'])) + assert_equal(u'test resource', result['resources'][0]['name']) + + def test_admin_can_add_new_resource(self): + user = factories.User() + organization = factories.Organization( + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset( + owner_org=organization['id'], + ) + env = {'REMOTE_USER': user['name'].encode('ascii')} + app = helpers._get_test_app() + + response = app.get( + url_for( + controller='package', + action='new_resource', + id=dataset['id'], + ), + extra_environ=env + ) + + form = response.forms['resource-edit'] + form['name'] = u'test resource' + form['url'] = u'http://test.com/' + response = submit_and_follow(app, form, env, 'save', + 'go-dataset-complete') + + result = helpers.call_action('package_show', id=dataset['id']) + assert_equal(1, len(result['resources'])) + assert_equal(u'test resource', result['resources'][0]['name']) + + def test_member_cannot_add_new_resource(self): + user = factories.User() + organization = factories.Organization( + users=[{'name': user['id'], 'capacity': 'member'}] + ) + dataset = factories.Dataset( + owner_org=organization['id'], + ) + env = {'REMOTE_USER': user['name'].encode('ascii')} + app = helpers._get_test_app() + + response = app.get( + url_for( + controller='package', + action='new_resource', + id=dataset['id'], + ), + extra_environ=env, + status=403, + ) + + response = app.post( + url_for( + controller='package', + action='new_resource', + id=dataset['id'], + ), + {'name': 'test', 'url': 'test', 'save': 'save', 'id': ''}, + extra_environ=env, + status=403, + ) + + def test_non_organization_users_cannot_add_new_resource(self): + '''on an owned dataset''' + user = factories.User() + organization = factories.Organization() + dataset = factories.Dataset( + owner_org=organization['id'], + ) + env = {'REMOTE_USER': user['name'].encode('ascii')} + app = helpers._get_test_app() + + response = app.get( + url_for( + controller='package', + action='new_resource', + id=dataset['id'], + ), + extra_environ=env, + status=403, + ) + + response = app.post( + url_for( + controller='package', + action='new_resource', + id=dataset['id'], + ), + {'name': 'test', 'url': 'test', 'save': 'save', 'id': ''}, + extra_environ=env, + status=403, + ) + + def test_anonymous_users_cannot_add_new_resource(self): + organization = factories.Organization() + dataset = factories.Dataset( + owner_org=organization['id'], + ) + app = helpers._get_test_app() + + response = app.get( + url_for( + controller='package', + action='new_resource', + id=dataset['id'], + ), + status=403, + ) + + response = app.post( + url_for( + controller='package', + action='new_resource', + id=dataset['id'], + ), + {'name': 'test', 'url': 'test', 'save': 'save', 'id': ''}, + status=403, + ) + + def test_anonymous_users_cannot_edit_resource(self): + organization = factories.Organization() + dataset = factories.Dataset( + owner_org=organization['id'], + ) + resource = factories.Resource(package_id=dataset['id']) + app = helpers._get_test_app() + + with app.flask_app.test_request_context(): + response = app.get( + url_for( + controller='package', + action='resource_edit', + id=dataset['id'], + resource_id=resource['id'], + ), + status=403, + ) + + response = app.post( + url_for( + controller='package', + action='resource_edit', + id=dataset['id'], + resource_id=resource['id'], + ), + {'name': 'test', 'url': 'test', 'save': 'save', 'id': ''}, + status=403, + ) + + +class TestResourceView(helpers.FunctionalTestBase): + @classmethod + def setup_class(cls): + super(cls, cls).setup_class() + + if not p.plugin_loaded('image_view'): + p.load('image_view') + + helpers.reset_db() + + @classmethod + def teardown_class(cls): + p.unload('image_view') + + def test_existent_resource_view_page_returns_ok_code(self): + resource_view = factories.ResourceView() + + url = url_for(controller='package', + action='resource_read', + id=resource_view['package_id'], + resource_id=resource_view['resource_id'], + view_id=resource_view['id']) + + app = self._get_test_app() + app.get(url, status=200) + + def test_inexistent_resource_view_page_returns_not_found_code(self): + resource_view = factories.ResourceView() + + url = url_for(controller='package', + action='resource_read', + id=resource_view['package_id'], + resource_id=resource_view['resource_id'], + view_id='inexistent-view-id') + + app = self._get_test_app() + app.get(url, status=404) + + def test_resource_view_description_is_rendered_as_markdown(self): + resource_view = factories.ResourceView(description="Some **Markdown**") + url = url_for(controller='package', + action='resource_read', + id=resource_view['package_id'], + resource_id=resource_view['resource_id'], + view_id=resource_view['id']) + app = self._get_test_app() + response = app.get(url) + response.mustcontain('Some Markdown') + + +class TestResourceRead(helpers.FunctionalTestBase): + def test_existing_resource_with_not_associated_dataset(self): + + dataset = factories.Dataset() + resource = factories.Resource() + + url = url_for(controller='package', + action='resource_read', + id=dataset['id'], + resource_id=resource['id']) + + app = self._get_test_app() + app.get(url, status=404) + + def test_resource_read_logged_in_user(self): + ''' + A logged-in user can view resource page. + ''' + user = factories.User() + env = {'REMOTE_USER': user['name'].encode('ascii')} + dataset = factories.Dataset() + resource = factories.Resource(package_id=dataset['id']) + + url = url_for(controller='package', + action='resource_read', + id=dataset['id'], + resource_id=resource['id']) + + app = self._get_test_app() + app.get(url, status=200, extra_environ=env) + + def test_resource_read_anon_user(self): + ''' + An anon user can view resource page. + ''' + dataset = factories.Dataset() + resource = factories.Resource(package_id=dataset['id']) + + url = url_for(controller='package', + action='resource_read', + id=dataset['id'], + resource_id=resource['id']) + + app = self._get_test_app() + app.get(url, status=200) + + def test_resource_read_sysadmin(self): + ''' + A sysadmin can view resource page. + ''' + sysadmin = factories.Sysadmin() + env = {'REMOTE_USER': sysadmin['name'].encode('ascii')} + dataset = factories.Dataset() + resource = factories.Resource(package_id=dataset['id']) + + url = url_for(controller='package', + action='resource_read', + id=dataset['id'], + resource_id=resource['id']) + + app = self._get_test_app() + app.get(url, status=200, extra_environ=env) + + def test_user_not_in_organization_cannot_read_private_dataset(self): + user = factories.User() + env = {'REMOTE_USER': user['name'].encode('ascii')} + organization = factories.Organization() + dataset = factories.Dataset( + owner_org=organization['id'], + private=True, + ) + resource = factories.Resource(package_id=dataset['id']) + + url = url_for(controller='package', + action='resource_read', + id=dataset['id'], + resource_id=resource['id']) + + app = self._get_test_app() + response = app.get(url, + status=404, + extra_environ=env) + + def test_organization_members_can_read_resources_in_private_datasets(self): + members = { + 'member': factories.User(), + 'editor': factories.User(), + 'admin': factories.User(), + 'sysadmin': factories.Sysadmin() + } + organization = factories.Organization( + users=[ + {'name': members['member']['id'], 'capacity': 'member'}, + {'name': members['editor']['id'], 'capacity': 'editor'}, + {'name': members['admin']['id'], 'capacity': 'admin'}, + ] + ) + dataset = factories.Dataset( + owner_org=organization['id'], + private=True, + ) + resource = factories.Resource(package_id=dataset['id']) + + app = helpers._get_test_app() + + for user, user_dict in members.items(): + response = app.get( + url_for( + controller='package', + action='resource_read', + id=dataset['name'], + resource_id=resource['id'], + ), + extra_environ={ + 'REMOTE_USER': user_dict['name'].encode('ascii'), + }, + ) + assert_in('Just another test resource', response.body) + + def test_anonymous_users_cannot_read_private_datasets(self): + organization = factories.Organization() + dataset = factories.Dataset( + owner_org=organization['id'], + private=True, + ) + app = helpers._get_test_app() + response = app.get( + url_for(controller='package', action='read', id=dataset['name']), + status=404 + ) + assert_equal(404, response.status_int) + + +class TestResourceDelete(helpers.FunctionalTestBase): + def test_dataset_owners_can_delete_resources(self): + user = factories.User() + owner_org = factories.Organization( + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=owner_org['id']) + resource = factories.Resource(package_id=dataset['id']) + app = helpers._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.post( + url_for(controller='package', action='resource_delete', + id=dataset['name'], resource_id=resource['id']), + extra_environ=env, + ) + response = response.follow() + assert_equal(200, response.status_int) + response.mustcontain('This dataset has no data') + + assert_raises(p.toolkit.ObjectNotFound, helpers.call_action, + 'resource_show', id=resource['id']) + + def test_deleting_non_existing_resource_404s(self): + user = factories.User() + owner_org = factories.Organization( + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=owner_org['id']) + env = {'REMOTE_USER': user['name'].encode('ascii')} + app = helpers._get_test_app() + response = app.post( + url_for(controller='package', action='resource_delete', + id=dataset['name'], resource_id='doesnotexist'), + extra_environ=env, + expect_errors=True + ) + assert_equal(404, response.status_int) + + def test_anon_users_cannot_delete_owned_resources(self): + user = factories.User() + owner_org = factories.Organization( + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=owner_org['id']) + resource = factories.Resource(package_id=dataset['id']) + + app = helpers._get_test_app() + response = app.post( + url_for(controller='package', action='resource_delete', + id=dataset['name'], resource_id=resource['id']), + status=403, + ) + response.mustcontain('Unauthorized to delete package') + + def test_logged_in_users_cannot_delete_resources_they_do_not_own(self): + # setup our dataset + owner = factories.User() + owner_org = factories.Organization( + users=[{'name': owner['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=owner_org['id']) + resource = factories.Resource(package_id=dataset['id']) + + # access as another user + user = factories.User() + env = {'REMOTE_USER': user['name'].encode('ascii')} + app = helpers._get_test_app() + response = app.post( + url_for(controller='package', action='resource_delete', + id=dataset['name'], resource_id=resource['id']), + extra_environ=env, + expect_errors=True + ) + assert_equal(403, response.status_int) + response.mustcontain('Unauthorized to delete package') + + def test_sysadmins_can_delete_any_resource(self): + owner_org = factories.Organization() + dataset = factories.Dataset(owner_org=owner_org['id']) + resource = factories.Resource(package_id=dataset['id']) + + sysadmin = factories.Sysadmin() + app = helpers._get_test_app() + env = {'REMOTE_USER': sysadmin['name'].encode('ascii')} + response = app.post( + url_for(controller='package', action='resource_delete', + id=dataset['name'], resource_id=resource['id']), + extra_environ=env, + ) + response = response.follow() + assert_equal(200, response.status_int) + response.mustcontain('This dataset has no data') + + assert_raises(p.toolkit.ObjectNotFound, helpers.call_action, + 'resource_show', id=resource['id']) + + def test_confirm_and_cancel_deleting_a_resource(self): + '''Test confirmation of deleting resources + + When resource_delete is made as a get request, it should return a + 'do you want to delete this reource? confirmation page''' + user = factories.User() + owner_org = factories.Organization( + users=[{'name': user['id'], 'capacity': 'admin'}] + ) + dataset = factories.Dataset(owner_org=owner_org['id']) + resource = factories.Resource(package_id=dataset['id']) + app = helpers._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url_for(controller='package', action='resource_delete', + id=dataset['name'], resource_id=resource['id']), + extra_environ=env, + ) + assert_equal(200, response.status_int) + message = 'Are you sure you want to delete resource - {name}?' + response.mustcontain(message.format(name=resource['name'])) + + # cancelling sends us back to the resource edit page + form = response.forms['confirm-resource-delete-form'] + response = form.submit('cancel') + response = response.follow(extra_environ=env) + assert_equal(200, response.status_int) + + +class TestSearch(helpers.FunctionalTestBase): + def test_search_basic(self): + dataset1 = factories.Dataset() + + offset = url_for(controller='package', action='search') + app = self._get_test_app() + page = app.get(offset) + + assert dataset1['name'] in page.body.decode('utf8') + + def test_search_language_toggle(self): + dataset1 = factories.Dataset() + + app = self._get_test_app() + with app.flask_app.test_request_context(): + offset = url_for( + controller='package', action='search', q=dataset1['name']) + page = app.get(offset) + + assert dataset1['name'] in page.body.decode('utf8') + assert ('q=' + dataset1['name']) in page.body.decode('utf8') + + def test_search_sort_by_blank(self): + factories.Dataset() + + # ?sort has caused an exception in the past + offset = url_for(controller='package', action='search') + '?sort' + app = self._get_test_app() + app.get(offset) + + def test_search_sort_by_bad(self): + factories.Dataset() + + # bad spiders try all sorts of invalid values for sort. They should get + # a 400 error with specific error message. No need to alert the + # administrator. + offset = url_for(controller='package', action='search') + \ + '?sort=gvgyr_fgevat+nfp' + app = self._get_test_app() + response = app.get(offset, status=[200, 400]) + if response.status == 200: + import sys + sys.stdout.write(response.body) + raise Exception("Solr returned an unknown error message. " + "Please check the error handling " + "in ckan/lib/search/query.py:run") + + def test_search_solr_syntax_error(self): + factories.Dataset() + + # SOLR raises SyntaxError when it can't parse q (or other fields?). + # Whilst this could be due to a bad user input, it could also be + # because CKAN mangled things somehow and therefore we flag it up to + # the administrator and give a meaningless error, just in case + offset = url_for(controller='package', action='search') + \ + '?q=--included' + app = self._get_test_app() + search_response = app.get(offset) + + search_response_html = BeautifulSoup(search_response.body) + err_msg = search_response_html.select('#search-error') + err_msg = ''.join([n.text for n in err_msg]) + assert_in('error while searching', err_msg) + + def test_search_plugin_hooks(self): + with p.use_plugin('test_package_controller_plugin') as plugin: + + offset = url_for(controller='package', action='search') + app = self._get_test_app() + app.get(offset) + + # get redirected ... + assert plugin.calls['before_search'] == 1, plugin.calls + assert plugin.calls['after_search'] == 1, plugin.calls + + def test_search_page_request(self): + '''Requesting package search page returns list of datasets.''' + app = self._get_test_app() + factories.Dataset(name="dataset-one", title='Dataset One') + factories.Dataset(name="dataset-two", title='Dataset Two') + factories.Dataset(name="dataset-three", title='Dataset Three') + + search_url = url_for(controller='package', action='search') + search_response = app.get(search_url) + + assert_true('3 datasets found' in search_response) + + search_response_html = BeautifulSoup(search_response.body) + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + ds_titles = [n.string for n in ds_titles] + + assert_equal(len(ds_titles), 3) + assert_true('Dataset One' in ds_titles) + assert_true('Dataset Two' in ds_titles) + assert_true('Dataset Three' in ds_titles) + + def test_search_page_results(self): + '''Searching for datasets returns expected results.''' + app = self._get_test_app() + factories.Dataset(name="dataset-one", title='Dataset One') + factories.Dataset(name="dataset-two", title='Dataset Two') + factories.Dataset(name="dataset-three", title='Dataset Three') + + search_url = url_for(controller='package', action='search') + search_response = app.get(search_url) + + search_form = search_response.forms['dataset-search-form'] + search_form['q'] = 'One' + search_results = webtest_submit(search_form) + + assert_true('1 dataset found' in search_results) + + search_response_html = BeautifulSoup(search_results.body) + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + ds_titles = [n.string for n in ds_titles] + + assert_equal(len(ds_titles), 1) + assert_true('Dataset One' in ds_titles) + + def test_search_page_no_results(self): + '''Search with non-returning phrase returns no results.''' + app = self._get_test_app() + factories.Dataset(name="dataset-one", title='Dataset One') + factories.Dataset(name="dataset-two", title='Dataset Two') + factories.Dataset(name="dataset-three", title='Dataset Three') + + search_url = url_for(controller='package', action='search') + search_response = app.get(search_url) + + search_form = search_response.forms['dataset-search-form'] + search_form['q'] = 'Nout' + search_results = webtest_submit(search_form) + + assert_true('No datasets found for "Nout"' in search_results) + + search_response_html = BeautifulSoup(search_results.body) + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + ds_titles = [n.string for n in ds_titles] + + assert_equal(len(ds_titles), 0) + + def test_search_page_results_tag(self): + '''Searching with a tag returns expected results.''' + app = self._get_test_app() + factories.Dataset(name="dataset-one", title='Dataset One', + tags=[{'name': 'my-tag'}]) + factories.Dataset(name="dataset-two", title='Dataset Two') + factories.Dataset(name="dataset-three", title='Dataset Three') + + search_url = url_for(controller='package', action='search') + search_response = app.get(search_url) + + assert_true('/dataset?tags=my-tag' in search_response) + + tag_search_response = app.get('/dataset?tags=my-tag') + + assert_true('1 dataset found' in tag_search_response) + + search_response_html = BeautifulSoup(tag_search_response.body) + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + ds_titles = [n.string for n in ds_titles] + + assert_equal(len(ds_titles), 1) + assert_true('Dataset One' in ds_titles) + + def test_search_page_results_private(self): + '''Private datasets don't show up in dataset search results.''' + app = self._get_test_app() + org = factories.Organization() + + factories.Dataset(name="dataset-one", title='Dataset One', + owner_org=org['id'], private=True) + factories.Dataset(name="dataset-two", title='Dataset Two') + factories.Dataset(name="dataset-three", title='Dataset Three') + + search_url = url_for(controller='package', action='search') + search_response = app.get(search_url) + + search_response_html = BeautifulSoup(search_response.body) + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + ds_titles = [n.string for n in ds_titles] + + assert_equal(len(ds_titles), 2) + assert_true('Dataset One' not in ds_titles) + assert_true('Dataset Two' in ds_titles) + assert_true('Dataset Three' in ds_titles) + + def test_user_not_in_organization_cannot_search_private_datasets(self): + app = helpers._get_test_app() + user = factories.User() + organization = factories.Organization() + dataset = factories.Dataset( + owner_org=organization['id'], + private=True, + ) + env = {'REMOTE_USER': user['name'].encode('ascii')} + search_url = url_for(controller='package', action='search') + search_response = app.get(search_url, extra_environ=env) + + search_response_html = BeautifulSoup(search_response.body) + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + assert_equal([n.string for n in ds_titles], []) + + def test_user_in_organization_can_search_private_datasets(self): + app = helpers._get_test_app() + user = factories.User() + organization = factories.Organization( + users=[{'name': user['id'], 'capacity': 'member'}]) + dataset = factories.Dataset( + title='A private dataset', + owner_org=organization['id'], + private=True, + ) + env = {'REMOTE_USER': user['name'].encode('ascii')} + search_url = url_for(controller='package', action='search') + search_response = app.get(search_url, extra_environ=env) + + search_response_html = BeautifulSoup(search_response.body) + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + assert_equal([n.string for n in ds_titles], ['A private dataset']) + + def test_user_in_different_organization_cannot_search_private_datasets(self): + app = helpers._get_test_app() + user = factories.User() + org1 = factories.Organization( + users=[{'name': user['id'], 'capacity': 'member'}]) + org2 = factories.Organization() + dataset = factories.Dataset( + title='A private dataset', + owner_org=org2['id'], + private=True, + ) + env = {'REMOTE_USER': user['name'].encode('ascii')} + search_url = url_for(controller='package', action='search') + search_response = app.get(search_url, extra_environ=env) + + search_response_html = BeautifulSoup(search_response.body) + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + assert_equal([n.string for n in ds_titles], []) + + @helpers.change_config('ckan.search.default_include_private', 'false') + def test_search_default_include_private_false(self): + app = helpers._get_test_app() + user = factories.User() + organization = factories.Organization( + users=[{'name': user['id'], 'capacity': 'member'}]) + dataset = factories.Dataset( + owner_org=organization['id'], + private=True, + ) + env = {'REMOTE_USER': user['name'].encode('ascii')} + search_url = url_for(controller='package', action='search') + search_response = app.get(search_url, extra_environ=env) + + search_response_html = BeautifulSoup(search_response.body) + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + assert_equal([n.string for n in ds_titles], []) + + def test_sysadmin_can_search_private_datasets(self): + app = helpers._get_test_app() + user = factories.Sysadmin() + organization = factories.Organization() + dataset = factories.Dataset( + title='A private dataset', + owner_org=organization['id'], + private=True, + ) + env = {'REMOTE_USER': user['name'].encode('ascii')} + search_url = url_for(controller='package', action='search') + search_response = app.get(search_url, extra_environ=env) + + search_response_html = BeautifulSoup(search_response.body) + ds_titles = search_response_html.select('.dataset-list ' + '.dataset-item ' + '.dataset-heading a') + assert_equal([n.string for n in ds_titles], ['A private dataset']) + + +class TestPackageFollow(helpers.FunctionalTestBase): + + def test_package_follow(self): + app = self._get_test_app() + + user = factories.User() + package = factories.Dataset() + + env = {'REMOTE_USER': user['name'].encode('ascii')} + follow_url = url_for(controller='package', + action='follow', + id=package['id']) + response = app.post(follow_url, extra_environ=env, status=302) + response = response.follow() + assert_true('You are now following {0}' + .format(package['title']) + in response) + + def test_package_follow_not_exist(self): + '''Pass an id for a package that doesn't exist''' + app = self._get_test_app() + + user_one = factories.User() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + follow_url = url_for(controller='package', + action='follow', + id='not-here') + response = app.post(follow_url, extra_environ=env, status=302) + response = response.follow(status=404) + assert_true('Dataset not found' in response) + + def test_package_unfollow(self): + app = self._get_test_app() + + user_one = factories.User() + package = factories.Dataset() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + follow_url = url_for(controller='package', + action='follow', + id=package['id']) + app.post(follow_url, extra_environ=env, status=302) + + unfollow_url = url_for(controller='package', action='unfollow', + id=package['id']) + unfollow_response = app.post(unfollow_url, extra_environ=env, + status=302) + unfollow_response = unfollow_response.follow() + + assert_true('You are no longer following {0}' + .format(package['title']) + in unfollow_response) + + def test_package_unfollow_not_following(self): + '''Unfollow a package not currently following''' + app = self._get_test_app() + + user_one = factories.User() + package = factories.Dataset() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + unfollow_url = url_for(controller='package', action='unfollow', + id=package['id']) + unfollow_response = app.post(unfollow_url, extra_environ=env, + status=302) + unfollow_response = unfollow_response.follow() + + assert_true('You are not following {0}'.format(package['id']) + in unfollow_response) + + def test_package_unfollow_not_exist(self): + '''Unfollow a package that doesn't exist.''' + app = self._get_test_app() + + user_one = factories.User() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + unfollow_url = url_for(controller='package', action='unfollow', + id='not-here') + unfollow_response = app.post(unfollow_url, extra_environ=env, + status=302) + unfollow_response = unfollow_response.follow(status=404) + assert_true('Dataset not found' in unfollow_response) + + def test_package_follower_list(self): + '''Following users appear on followers list page.''' + app = self._get_test_app() + + user_one = factories.Sysadmin() + package = factories.Dataset() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + follow_url = url_for(controller='package', + action='follow', + id=package['id']) + app.post(follow_url, extra_environ=env, status=302) + + followers_url = url_for(controller='package', action='followers', + id=package['id']) + + # Only sysadmins can view the followers list pages + followers_response = app.get(followers_url, extra_environ=env, + status=200) + assert_true(user_one['display_name'] in followers_response) + + +class TestDatasetRead(helpers.FunctionalTestBase): + + def test_dataset_read(self): + app = self._get_test_app() + + dataset = factories.Dataset() + + url = url_for(controller='package', + action='read', + id=dataset['id']) + response = app.get(url) + assert_in(dataset['title'], response) diff --git a/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_tags.py b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_tags.py new file mode 100644 index 00000000..93499e79 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_tags.py @@ -0,0 +1,135 @@ +# encoding: utf-8 + +import math +import string + +from nose.tools import assert_equal, assert_true, assert_false, assert_in +from bs4 import BeautifulSoup + +from ckan.lib.helpers import url_for + +import ckan.tests.helpers as helpers +from ckan.tests import factories + +webtest_submit = helpers.webtest_submit +submit_and_follow = helpers.submit_and_follow + + +def _make_tag_list(n=26): + '''Returns a list of tag dicts, starting with 'aa, bb, ..., zz', then + 'aaa, bbb, ..., zzz', etc. Tags must be at least 2 characters.''' + lc = string.lowercase + lc_len = len(lc) + return [{'name': lc[i % lc_len] * int(math.ceil(i / lc_len) + 2)} + for i in range(0, n)] + + +class TestTagIndex(helpers.FunctionalTestBase): + + def test_tags_listed_under_50(self): + '''Tag index lists tags under 50 tags.''' + app = self._get_test_app() + expected_tags = _make_tag_list(49) + factories.Dataset(tags=expected_tags) + + tag_index_url = url_for(controller='tag', action='index') + tag_response = app.get(tag_index_url) + + tag_response_html = BeautifulSoup(tag_response.body) + tags = [t.string for t in tag_response_html.select('.tag')] + + expected_tag_values = [t['name'] for t in expected_tags] + + assert_equal(len(tags), 49) + for t in expected_tag_values: + assert_true(t in tags) + + # no pagination + assert_false(tag_response_html.select('.pagination')) + + def test_tags_listed_over_50(self): + '''Tag index lists tags over 50 tags.''' + app = self._get_test_app() + expected_tags = _make_tag_list(51) + factories.Dataset(tags=expected_tags) + + tag_index_url = url_for(controller='tag', action='index') + tag_response = app.get(tag_index_url) + + tag_response_html = BeautifulSoup(tag_response.body) + tags = [t.string for t in tag_response_html.select('.tag')] + + expected_tag_values = [t['name'] for t in expected_tags] + + assert_equal(len(tags), 2) + assert_true(expected_tag_values) + for t in [u'aa', u'aaa']: + assert_true(t in tags) + + # has pagination + assert_true(tag_response_html.select('.pagination')) + + def test_tag_search(self): + '''Tag search returns expected results''' + app = self._get_test_app() + expected_tags = _make_tag_list(50) + expected_tags.append({'name': 'find-me'}) + factories.Dataset(tags=expected_tags) + + tag_index_url = url_for(controller='tag', action='index') + tag_response = app.get(tag_index_url) + + search_form = tag_response.forms[1] + search_form['q'] = 'find-me' + search_response = webtest_submit(search_form, status=200) + + search_response_html = BeautifulSoup(search_response.body) + tags = [t.string for t in search_response_html.select('.tag')] + + assert_equal(len(tags), 1) + assert_true('find-me' in tags) + + # no pagination + assert_false(search_response_html.select('.pagination')) + + def test_tag_search_no_results(self): + '''Searching for tags yielding no results''' + app = self._get_test_app() + expected_tags = _make_tag_list(50) + factories.Dataset(tags=expected_tags) + + tag_index_url = url_for(controller='tag', action='index') + tag_response = app.get(tag_index_url) + + search_form = tag_response.forms[1] + search_form['q'] = 'find-me' + search_response = webtest_submit(search_form, status=200) + + search_response_html = BeautifulSoup(search_response.body) + tags = [t.string for t in search_response_html.select('.tag')] + + assert_equal(len(tags), 0) + assert_true('find-me' not in tags) + + # no pagination + assert_false(search_response_html.select('.pagination')) + + +class TestTagRead(helpers.FunctionalTestBase): + + def test_tag_read_redirects_to_dataset_search(self): + app = self._get_test_app() + factories.Dataset(title='My Other Dataset', tags=[{'name': 'find-me'}]) + + tag_url = url_for(controller='tag', action='read', id='find-me') + tag_response = app.get(tag_url, status=302) + assert_equal(tag_response.headers['Location'], + 'http://test.ckan.net/dataset?tags=find-me') + + def test_tag_read_not_found(self): + '''Attempting access to non-existing tag returns a 404''' + app = self._get_test_app() + factories.Dataset(title='My Other Dataset', tags=[{'name': 'find-me'}]) + + tag_url = url_for(controller='tag', action='read', id='not-here') + app.get(tag_url, status=404) diff --git a/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_template.py b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_template.py new file mode 100644 index 00000000..9e5400e2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_template.py @@ -0,0 +1,19 @@ +# encoding: utf-8 + +from nose.tools import assert_equal + +import ckan.tests.helpers as helpers + + +class TestTemplateController(helpers.FunctionalTestBase): + + def test_content_type(self): + cases = { + u'/robots.txt': u'text/plain; charset=utf-8', + u'/page': u'text/html; charset=utf-8', + u'/page.html': u'text/html; charset=utf-8', + } + app = self._get_test_app() + for url, expected in cases.iteritems(): + response = app.get(url, status=200) + assert_equal(response.headers.get(u'Content-Type'), expected) diff --git a/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_user.py b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_user.py new file mode 100644 index 00000000..0d610cc8 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_user.py @@ -0,0 +1,691 @@ +# encoding: utf-8 +from bs4 import BeautifulSoup +from nose.tools import assert_true, assert_false, assert_equal, assert_in + +import ckan.tests.helpers as helpers +import ckan.tests.factories as factories +import ckan.tests.helpers as helpers + +from ckan.lib.helpers import url_for +from ckan import model +from ckan.lib.mailer import create_reset_key + +webtest_submit = helpers.webtest_submit +submit_and_follow = helpers.submit_and_follow + + +def _get_user_edit_page(app): + user = factories.User() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url=url_for('user.edit'), + extra_environ=env, + ) + return env, response, user + + +class TestRegisterUser(helpers.FunctionalTestBase): + def test_register_a_user(self): + app = helpers._get_test_app() + response = app.get(url=url_for('user.register')) + + form = response.forms['user-register-form'] + form['name'] = 'newuser' + form['fullname'] = 'New User' + form['email'] = 'test@test.com' + form['password1'] = 'TestPassword1' + form['password2'] = 'TestPassword1' + response = submit_and_follow(app, form, name='save') + response = response.follow() + assert_equal(200, response.status_int) + + user = helpers.call_action('user_show', id='newuser') + assert_equal(user['name'], 'newuser') + assert_equal(user['fullname'], 'New User') + assert_false(user['sysadmin']) + + def test_register_user_bad_password(self): + app = helpers._get_test_app() + response = app.get(url=url_for('user.register')) + + form = response.forms['user-register-form'] + form['name'] = 'newuser' + form['fullname'] = 'New User' + form['email'] = 'test@test.com' + form['password1'] = 'TestPassword1' + form['password2'] = '' + + response = form.submit('save') + assert_true('The passwords you entered do not match' in response) + + def test_create_user_as_sysadmin(self): + admin_pass = 'RandomPassword123' + sysadmin = factories.Sysadmin(password=admin_pass) + app = self._get_test_app() + + # Have to do an actual login as this test relies on repoze + # cookie handling. + + # get the form + response = app.get('/user/login') + # ...it's the second one + login_form = response.forms[1] + # fill it in + login_form['login'] = sysadmin['name'] + login_form['password'] = admin_pass + # submit it + login_form.submit('save') + + response = app.get( + url=url_for('user.register'), + ) + assert "user-register-form" in response.forms + form = response.forms['user-register-form'] + form['name'] = 'newestuser' + form['fullname'] = 'Newest User' + form['email'] = 'test@test.com' + form['password1'] = 'NewPassword1' + form['password2'] = 'NewPassword1' + response2 = form.submit('save') + assert '/user/activity' in response2.location + + +class TestLoginView(helpers.FunctionalTestBase): + def test_registered_user_login(self): + ''' + Registered user can submit valid login details at /user/login and + be returned to appropriate place. + ''' + app = helpers._get_test_app() + + # make a user + user = factories.User() + + # get the form + response = app.get('/user/login') + # ...it's the second one + login_form = response.forms[1] + + # fill it in + login_form['login'] = user['name'] + login_form['password'] = 'RandomPassword123' + + # submit it + submit_response = login_form.submit() + # let's go to the last redirect in the chain + final_response = helpers.webtest_maybe_follow(submit_response) + + # the response is the user dashboard, right? + final_response.mustcontain('Dashboard', + '{0}' + .format(user['fullname'])) + # and we're definitely not back on the login page. + final_response.mustcontain(no='

    Login

    ') + + def test_registered_user_login_bad_password(self): + ''' + Registered user is redirected to appropriate place if they submit + invalid login details at /user/login. + ''' + app = helpers._get_test_app() + + # make a user + user = factories.User() + + # get the form + response = app.get('/user/login') + # ...it's the second one + login_form = response.forms[1] + + # fill it in + login_form['login'] = user['name'] + login_form['password'] = 'BadPass1' + + # submit it + submit_response = login_form.submit() + # let's go to the last redirect in the chain + final_response = helpers.webtest_maybe_follow(submit_response) + + # the response is the login page again + final_response.mustcontain('

    Login

    ', + 'Login failed. Bad username or password.') + # and we're definitely not on the dashboard. + final_response.mustcontain(no='Dashboard'), + final_response.mustcontain(no='{0}' + .format(user['fullname'])) + + +class TestLogout(helpers.FunctionalTestBase): + + def test_user_logout_url_redirect(self): + '''_logout url redirects to logged out page. + + Note: this doesn't test the actual logout of a logged in user, just + the associated redirect. + ''' + app = self._get_test_app() + + logout_url = url_for('user.logout') + logout_response = app.get(logout_url, status=302) + final_response = helpers.webtest_maybe_follow(logout_response) + + assert_true('You are now logged out.' in final_response) + + @helpers.change_config('ckan.root_path', '/my/prefix') + def test_non_root_user_logout_url_redirect(self): + ''' + _logout url redirects to logged out page with `ckan.root_path` + prefixed. + + Note: this doesn't test the actual logout of a logged in user, just + the associated redirect. + ''' + app = self._get_test_app() + + logout_url = url_for('user.logout') + # Remove the prefix otherwise the test app won't find the correct route + logout_url = logout_url.replace('/my/prefix', '') + logout_response = app.get(logout_url, status=302) + assert_equal(logout_response.status_int, 302) + assert_true('/my/prefix/user/logout' in logout_response.location) + + +class TestUser(helpers.FunctionalTestBase): + + def test_not_logged_in_dashboard(self): + app = self._get_test_app() + + for route in ['index', 'organizations', 'datasets', 'groups']: + app.get( + url=url_for(u'dashboard.{}'.format(route)), + status=403 + ) + + def test_own_datasets_show_up_on_user_dashboard(self): + user = factories.User() + dataset_title = 'My very own dataset' + factories.Dataset(user=user, + name='my-own-dataset', + title=dataset_title) + + app = self._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url=url_for('dashboard.datasets'), + extra_environ=env, + ) + + assert_true(dataset_title in response) + + def test_other_datasets_dont_show_up_on_user_dashboard(self): + user1 = factories.User() + user2 = factories.User() + dataset_title = 'Someone else\'s dataset' + factories.Dataset(user=user1, + name='someone-elses-dataset', + title=dataset_title) + + app = self._get_test_app() + env = {'REMOTE_USER': user2['name'].encode('ascii')} + response = app.get( + url=url_for('dashboard.datasets'), + extra_environ=env, + ) + + assert_false(dataset_title in response) + + +class TestUserEdit(helpers.FunctionalTestBase): + + def test_user_edit_no_user(self): + app = self._get_test_app() + response = app.get( + url_for('user.edit', id=None), + status=400 + ) + assert_true('No user specified' in response) + + def test_user_edit_unknown_user(self): + '''Attempt to read edit user for an unknown user redirects to login + page.''' + app = self._get_test_app() + response = app.get( + url_for('user.edit', id='unknown_person'), + status=403) + + def test_user_edit_not_logged_in(self): + '''Attempt to read edit user for an existing, not-logged in user + redirects to login page.''' + app = self._get_test_app() + user = factories.User() + username = user['name'] + response = app.get( + url_for('user.edit', id=username), + status=403 + ) + + def test_edit_user(self): + user = factories.User(password='TestPassword1') + app = self._get_test_app() + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = app.get( + url=url_for('user.edit'), + extra_environ=env, + ) + # existing values in the form + form = response.forms['user-edit-form'] + assert_equal(form['name'].value, user['name']) + assert_equal(form['fullname'].value, user['fullname']) + assert_equal(form['email'].value, user['email']) + assert_equal(form['about'].value, user['about']) + assert_equal(form['activity_streams_email_notifications'].value, None) + assert_equal(form['password1'].value, '') + assert_equal(form['password2'].value, '') + + # new values + # form['name'] = 'new-name' + form['fullname'] = 'new full name' + form['email'] = 'new@example.com' + form['about'] = 'new about' + form['activity_streams_email_notifications'] = True + form['old_password'] = 'TestPassword1' + form['password1'] = 'NewPass1' + form['password2'] = 'NewPass1' + response = submit_and_follow(app, form, env, 'save') + + user = model.Session.query(model.User).get(user['id']) + # assert_equal(user.name, 'new-name') + assert_equal(user.fullname, 'new full name') + assert_equal(user.email, 'new@example.com') + assert_equal(user.about, 'new about') + assert_equal(user.activity_streams_email_notifications, True) + + def test_email_change_without_password(self): + + app = self._get_test_app() + env, response, user = _get_user_edit_page(app) + + form = response.forms['user-edit-form'] + + # new values + form['email'] = 'new@example.com' + + # factory returns user with password 'pass' + form.fields['old_password'][0].value = 'Wrong-pass1' + + response = webtest_submit(form, 'save', status=200, extra_environ=env) + assert_true('Old Password: incorrect password' in response) + + def test_email_change_with_password(self): + app = self._get_test_app() + env, response, user = _get_user_edit_page(app) + + form = response.forms['user-edit-form'] + + # new values + form['email'] = 'new@example.com' + + # factory returns user with password 'pass' + form.fields['old_password'][0].value = 'RandomPassword123' + + response = submit_and_follow(app, form, env, 'save') + assert_true('Profile updated' in response) + + def test_edit_user_logged_in_username_change(self): + + user_pass = 'TestPassword1' + user = factories.User(password=user_pass) + app = self._get_test_app() + + # Have to do an actual login as this test relys on repoze cookie handling. + # get the form + response = app.get('/user/login') + # ...it's the second one + login_form = response.forms[1] + # fill it in + login_form['login'] = user['name'] + login_form['password'] = user_pass + # submit it + login_form.submit() + + # Now the cookie is set, run the test + response = app.get( + url=url_for('user.edit'), + ) + # existing values in the form + form = response.forms['user-edit-form'] + + # new values + form['name'] = 'new-name' + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = webtest_submit(form, 'save', status=200, extra_environ=env) + assert_true('That login name can not be modified' in response) + + def test_edit_user_logged_in_username_change_by_name(self): + user_pass = 'TestPassword1' + user = factories.User(password=user_pass) + app = self._get_test_app() + + # Have to do an actual login as this test relys on repoze cookie handling. + # get the form + response = app.get('/user/login') + # ...it's the second one + login_form = response.forms[1] + # fill it in + login_form['login'] = user['name'] + login_form['password'] = user_pass + # submit it + login_form.submit() + + # Now the cookie is set, run the test + response = app.get( + url=url_for('user.edit', id=user['name']), + ) + # existing values in the form + form = response.forms['user-edit-form'] + + # new values + form['name'] = 'new-name' + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = webtest_submit(form, 'save', status=200, extra_environ=env) + assert_true('That login name can not be modified' in response) + + def test_edit_user_logged_in_username_change_by_id(self): + user_pass = 'TestPassword1' + user = factories.User(password=user_pass) + app = self._get_test_app() + + # Have to do an actual login as this test relys on repoze cookie handling. + # get the form + response = app.get('/user/login') + # ...it's the second one + login_form = response.forms[1] + # fill it in + login_form['login'] = user['name'] + login_form['password'] = user_pass + # submit it + login_form.submit() + + # Now the cookie is set, run the test + response = app.get( + url=url_for('user.edit', id=user['id']), + ) + # existing values in the form + form = response.forms['user-edit-form'] + + # new values + form['name'] = 'new-name' + env = {'REMOTE_USER': user['name'].encode('ascii')} + response = webtest_submit(form, 'save', status=200, extra_environ=env) + assert_true('That login name can not be modified' in response) + + def test_perform_reset_for_key_change(self): + password = 'TestPassword1' + params = {'password1': password, 'password2': password} + user = factories.User() + user_obj = helpers.model.User.by_name(user['name']) + create_reset_key(user_obj) + key = user_obj.reset_key + + app = self._get_test_app() + offset = url_for(controller='user', + action='perform_reset', + id=user_obj.id, + key=user_obj.reset_key) + response = app.post(offset, params=params, status=302) + user_obj = helpers.model.User.by_name(user['name']) # Update user_obj + + assert_true(key != user_obj.reset_key) + + def test_password_reset_correct_password(self): + """ + user password reset attempted with correct old password + """ + app = self._get_test_app() + env, response, user = _get_user_edit_page(app) + + form = response.forms['user-edit-form'] + + # factory returns user with password 'RandomPassword123' + form.fields['old_password'][0].value = 'RandomPassword123' + form.fields['password1'][0].value = 'NewPassword1' + form.fields['password2'][0].value = 'NewPassword1' + + response = submit_and_follow(app, form, env, 'save') + assert_true('Profile updated' in response) + + def test_password_reset_incorrect_password(self): + """ + user password reset attempted with invalid old password + """ + + app = self._get_test_app() + env, response, user = _get_user_edit_page(app) + + form = response.forms['user-edit-form'] + + # factory returns user with password 'RandomPassword123' + form.fields['old_password'][0].value = 'Wrong-Pass1' + form.fields['password1'][0].value = 'NewPassword1' + form.fields['password2'][0].value = 'NewPassword1' + + response = webtest_submit(form, 'save', status=200, extra_environ=env) + assert_true('Old Password: incorrect password' in response) + + +class TestUserFollow(helpers.FunctionalTestBase): + + def test_user_follow(self): + app = self._get_test_app() + + user_one = factories.User() + user_two = factories.User() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + follow_url = url_for(controller='user', + action='follow', + id=user_two['id']) + response = app.post(follow_url, extra_environ=env, status=302) + response = response.follow() + assert_true('You are now following {0}' + .format(user_two['display_name']) + in response) + + def test_user_follow_not_exist(self): + '''Pass an id for a user that doesn't exist''' + app = self._get_test_app() + + user_one = factories.User() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + follow_url = url_for(controller='user', + action='follow', + id='not-here') + response = app.post(follow_url, extra_environ=env, status=302) + response = response.follow(status=302) + assert_in('user/login', response.headers['location']) + + def test_user_unfollow(self): + app = self._get_test_app() + + user_one = factories.User() + user_two = factories.User() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + follow_url = url_for(controller='user', + action='follow', + id=user_two['id']) + app.post(follow_url, extra_environ=env, status=302) + + unfollow_url = url_for('user.unfollow', + id=user_two['id']) + unfollow_response = app.post(unfollow_url, extra_environ=env, + status=302) + unfollow_response = unfollow_response.follow() + + assert_true('You are no longer following {0}' + .format(user_two['display_name']) + in unfollow_response) + + def test_user_unfollow_not_following(self): + '''Unfollow a user not currently following''' + app = self._get_test_app() + + user_one = factories.User() + user_two = factories.User() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + unfollow_url = url_for('user.unfollow', + id=user_two['id']) + unfollow_response = app.post(unfollow_url, extra_environ=env, + status=302) + unfollow_response = unfollow_response.follow() + + assert_true('You are not following {0}'.format(user_two['id']) + in unfollow_response) + + def test_user_unfollow_not_exist(self): + '''Unfollow a user that doesn't exist.''' + app = self._get_test_app() + + user_one = factories.User() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + unfollow_url = url_for('user.unfollow', + id='not-here') + unfollow_response = app.post(unfollow_url, extra_environ=env, + status=302) + unfollow_response = unfollow_response.follow(status=302) + assert_in('user/login', unfollow_response.headers['location']) + + def test_user_follower_list(self): + '''Following users appear on followers list page.''' + app = self._get_test_app() + + user_one = factories.Sysadmin() + user_two = factories.User() + + env = {'REMOTE_USER': user_one['name'].encode('ascii')} + follow_url = url_for(controller='user', + action='follow', + id=user_two['id']) + app.post(follow_url, extra_environ=env, status=302) + + followers_url = url_for('user.followers', + id=user_two['id']) + + # Only sysadmins can view the followers list pages + followers_response = app.get(followers_url, extra_environ=env, + status=200) + assert_true(user_one['display_name'] in followers_response) + + +class TestUserSearch(helpers.FunctionalTestBase): + + def test_user_page_anon_access(self): + '''Anon users can access the user list page''' + app = self._get_test_app() + + user_url = url_for('user.index') + user_response = app.get(user_url, status=200) + assert_true('All Users - CKAN' + in user_response) + + def test_user_page_lists_users(self): + '''/users/ lists registered users''' + app = self._get_test_app() + factories.User(fullname='User One') + factories.User(fullname='User Two') + factories.User(fullname='User Three') + + user_url = url_for('user.index') + user_response = app.get(user_url, status=200) + + user_response_html = BeautifulSoup(user_response.body) + user_list = user_response_html.select('ul.user-list li') + assert_equal(len(user_list), 3) + + user_names = [u.text.strip() for u in user_list] + assert_true('User One' in user_names) + assert_true('User Two' in user_names) + assert_true('User Three' in user_names) + + def test_user_page_doesnot_list_deleted_users(self): + '''/users/ doesn't list deleted users''' + app = self._get_test_app() + factories.User(fullname='User One', state='deleted') + factories.User(fullname='User Two') + factories.User(fullname='User Three') + + user_url = url_for('user.index') + user_response = app.get(user_url, status=200) + + user_response_html = BeautifulSoup(user_response.body) + user_list = user_response_html.select('ul.user-list li') + assert_equal(len(user_list), 2) + + user_names = [u.text.strip() for u in user_list] + assert_true('User One' not in user_names) + assert_true('User Two' in user_names) + assert_true('User Three' in user_names) + + def test_user_page_anon_search(self): + '''Anon users can search for users by username.''' + app = self._get_test_app() + factories.User(fullname='User One', email='useroneemail@example.com') + factories.User(fullname='Person Two') + factories.User(fullname='Person Three') + + user_url = url_for('user.index') + user_response = app.get(user_url, status=200) + search_form = user_response.forms['user-search-form'] + search_form['q'] = 'Person' + search_response = webtest_submit(search_form, status=200) + + search_response_html = BeautifulSoup(search_response.body) + user_list = search_response_html.select('ul.user-list li') + assert_equal(len(user_list), 2) + + user_names = [u.text.strip() for u in user_list] + assert_true('Person Two' in user_names) + assert_true('Person Three' in user_names) + assert_true('User One' not in user_names) + + def test_user_page_anon_search_not_by_email(self): + '''Anon users can not search for users by email.''' + app = self._get_test_app() + factories.User(fullname='User One', email='useroneemail@example.com') + factories.User(fullname='Person Two') + factories.User(fullname='Person Three') + + user_url = url_for('user.index') + user_response = app.get(user_url, status=200) + search_form = user_response.forms['user-search-form'] + search_form['q'] = 'useroneemail@example.com' + search_response = webtest_submit(search_form, status=200) + + search_response_html = BeautifulSoup(search_response.body) + user_list = search_response_html.select('ul.user-list li') + assert_equal(len(user_list), 0) + + def test_user_page_sysadmin_user(self): + '''Sysadmin can search for users by email.''' + app = self._get_test_app() + sysadmin = factories.Sysadmin() + + factories.User(fullname='User One', email='useroneemail@example.com') + factories.User(fullname='Person Two') + factories.User(fullname='Person Three') + + env = {'REMOTE_USER': sysadmin['name'].encode('ascii')} + user_url = url_for('user.index') + user_response = app.get(user_url, status=200, extra_environ=env) + search_form = user_response.forms['user-search-form'] + search_form['q'] = 'useroneemail@example.com' + search_response = webtest_submit(search_form, status=200, + extra_environ=env) + + search_response_html = BeautifulSoup(search_response.body) + user_list = search_response_html.select('ul.user-list li') + assert_equal(len(user_list), 1) + assert_equal(user_list[0].text.strip(), 'User One') diff --git a/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_util.py b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_util.py new file mode 100644 index 00000000..8ed8a12d --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/controllers/test_util.py @@ -0,0 +1,45 @@ +# encoding: utf-8 + +from nose.tools import assert_equal +from pylons.test import pylonsapp +import paste.fixture + +from ckan.lib.helpers import url_for as url_for + +import ckan.tests.helpers as helpers + + +class TestUtil(helpers.FunctionalTestBase): + def test_redirect_ok(self): + app = self._get_test_app() + response = app.get( + url=url_for(controller='util', action='redirect'), + params={'url': '/dataset'}, + status=302, + ) + assert_equal(response.headers.get('Location'), + 'http://test.ckan.net/dataset') + + def test_redirect_external(self): + app = self._get_test_app() + response = app.get( + url=url_for(controller='util', action='redirect'), + params={'url': 'http://nastysite.com'}, + status=403, + ) + + def test_redirect_no_params(self): + app = self._get_test_app() + response = app.get( + url=url_for(controller='util', action='redirect'), + params={}, + status=400, + ) + + def test_redirect_no_params_2(self): + app = self._get_test_app() + response = app.get( + url=url_for(controller='util', action='redirect'), + params={'url': ''}, + status=400, + ) diff --git a/venv/lib/python2.7/site-packages/ckan/tests/factories.py b/venv/lib/python2.7/site-packages/ckan/tests/factories.py new file mode 100644 index 00000000..122adf4c --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/factories.py @@ -0,0 +1,423 @@ +# encoding: utf-8 + +'''This is a collection of factory classes for building CKAN users, datasets, +etc. + +These are meant to be used by tests to create any objects that are needed for +the tests. They're written using ``factory_boy``: + +http://factoryboy.readthedocs.org/en/latest/ + +These are not meant to be used for the actual testing, e.g. if you're writing +a test for the :py:func:`~ckan.logic.action.create.user_create` function then +call :py:func:`~ckan.tests.helpers.call_action`, don't test it via the +:py:class:`~ckan.tests.factories.User` factory below. + +Usage:: + + # Create a user with the factory's default attributes, and get back a + # user dict: + user_dict = factories.User() + + # You can create a second user the same way. For attributes that can't be + # the same (e.g. you can't have two users with the same name) a new value + # will be generated each time you use the factory: + another_user_dict = factories.User() + + # Create a user and specify your own user name and email (this works + # with any params that CKAN's user_create() accepts): + custom_user_dict = factories.User(name='bob', email='bob@bob.com') + + # Get a user dict containing the attributes (name, email, password, etc.) + # that the factory would use to create a user, but without actually + # creating the user in CKAN: + user_attributes_dict = factories.User.attributes() + + # If you later want to create a user using these attributes, just pass them + # to the factory: + user = factories.User(**user_attributes_dict) + +''' +import random +import string +import factory +import mock + +import ckan.model +import ckan.logic +import ckan.tests.helpers as helpers + + +def _get_action_user_name(kwargs): + '''Return the name of the user in kwargs, defaulting to the site user + + It can be overriden by explictly setting {'user': None} in the keyword + arguments. In that case, this method will return None. + ''' + + if 'user' in kwargs: + user = kwargs['user'] + else: + user = helpers.call_action('get_site_user') + + if user is None: + user_name = None + else: + user_name = user['name'] + + return user_name + + +def _generate_email(user): + '''Return an email address for the given User factory stub object.''' + + return '{0}@ckan.org'.format(user.name).lower() + + +def _generate_reset_key(user): + '''Return a reset key for the given User factory stub object.''' + + return '{0}_reset_key'.format(user.name).lower() + + +def _generate_user_id(user): + '''Return a user id for the given User factory stub object.''' + + return '{0}_user_id'.format(user.name).lower() + + +def _generate_group_title(group): + '''Return a title for the given Group factory stub object.''' + + return group.name.replace('_', ' ').title() + + +def _generate_random_string(length=6): + '''Return a random string of the defined length.''' + + return ''.join(random.sample(string.ascii_lowercase, length)) + + +class User(factory.Factory): + '''A factory class for creating CKAN users.''' + + # This is the class that UserFactory will create and return instances + # of. + FACTORY_FOR = ckan.model.User + + # These are the default params that will be used to create new users. + fullname = 'Mr. Test User' + password = 'RandomPassword123' + about = 'Just another test user.' + + # Generate a different user name param for each user that gets created. + name = factory.Sequence(lambda n: 'test_user_{0:02d}'.format(n)) + + # Compute the email param for each user based on the values of the other + # params above. + email = factory.LazyAttribute(_generate_email) + + # I'm not sure how to support factory_boy's .build() feature in CKAN, + # so I've disabled it here. + @classmethod + def _build(cls, target_class, *args, **kwargs): + raise NotImplementedError(".build() isn't supported in CKAN") + + # To make factory_boy work with CKAN we override _create() and make it call + # a CKAN action function. + # We might also be able to do this by using factory_boy's direct SQLAlchemy + # support: http://factoryboy.readthedocs.org/en/latest/orms.html#sqlalchemy + @classmethod + def _create(cls, target_class, *args, **kwargs): + if args: + assert False, "Positional args aren't supported, use keyword args." + user_dict = helpers.call_action('user_create', **kwargs) + return user_dict + + +class Resource(factory.Factory): + '''A factory class for creating CKAN resources.''' + + FACTORY_FOR = ckan.model.Resource + + name = factory.Sequence(lambda n: 'test_resource_{0:02d}'.format(n)) + description = 'Just another test resource.' + format = 'res_format' + url = 'http://link.to.some.data' + package_id = factory.LazyAttribute(lambda _: Dataset()['id']) + + @classmethod + def _build(cls, target_class, *args, **kwargs): + raise NotImplementedError(".build() isn't supported in CKAN") + + @classmethod + def _create(cls, target_class, *args, **kwargs): + if args: + assert False, "Positional args aren't supported, use keyword args." + + context = {'user': _get_action_user_name(kwargs)} + + resource_dict = helpers.call_action('resource_create', context=context, + **kwargs) + return resource_dict + + +class ResourceView(factory.Factory): + '''A factory class for creating CKAN resource views. + + Note: if you use this factory, you need to load the `image_view` plugin + on your test class (and unload it later), otherwise you will get an error. + + Example:: + + class TestSomethingWithResourceViews(object): + @classmethod + def setup_class(cls): + if not p.plugin_loaded('image_view'): + p.load('image_view') + + @classmethod + def teardown_class(cls): + p.unload('image_view') + + ''' + + FACTORY_FOR = ckan.model.ResourceView + + title = factory.Sequence(lambda n: 'test_resource_view_{0:02d}'.format(n)) + description = 'Just another test resource view.' + view_type = 'image_view' + resource_id = factory.LazyAttribute(lambda _: Resource()['id']) + + @classmethod + def _build(cls, target_class, *args, **kwargs): + raise NotImplementedError(".build() isn't supported in CKAN") + + @classmethod + def _create(cls, target_class, *args, **kwargs): + if args: + assert False, "Positional args aren't supported, use keyword args." + + context = {'user': _get_action_user_name(kwargs)} + + resource_dict = helpers.call_action('resource_view_create', + context=context, **kwargs) + return resource_dict + + +class Sysadmin(factory.Factory): + '''A factory class for creating sysadmin users.''' + + FACTORY_FOR = ckan.model.User + + fullname = 'Mr. Test Sysadmin' + password = 'RandomPassword123' + about = 'Just another test sysadmin.' + + name = factory.Sequence(lambda n: 'test_sysadmin_{0:02d}'.format(n)) + + email = factory.LazyAttribute(_generate_email) + sysadmin = True + + @classmethod + def _build(cls, target_class, *args, **kwargs): + raise NotImplementedError(".build() isn't supported in CKAN") + + @classmethod + def _create(cls, target_class, *args, **kwargs): + if args: + assert False, "Positional args aren't supported, use keyword args." + + user = target_class(**dict(kwargs, sysadmin=True)) + ckan.model.Session.add(user) + ckan.model.Session.commit() + ckan.model.Session.remove() + + # We want to return a user dict not a model object, so call user_show + # to get one. We pass the user's name in the context because we want + # the API key and other sensitive data to be returned in the user + # dict. + user_dict = helpers.call_action('user_show', id=user.id, + context={'user': user.name}) + return user_dict + + +class Group(factory.Factory): + '''A factory class for creating CKAN groups.''' + + FACTORY_FOR = ckan.model.Group + + name = factory.Sequence(lambda n: 'test_group_{0:02d}'.format(n)) + title = factory.LazyAttribute(_generate_group_title) + description = 'A test description for this test group.' + + user = factory.LazyAttribute(lambda _: + helpers.call_action('get_site_user')) + + @classmethod + def _build(cls, target_class, *args, **kwargs): + raise NotImplementedError(".build() isn't supported in CKAN") + + @classmethod + def _create(cls, target_class, *args, **kwargs): + if args: + assert False, "Positional args aren't supported, use keyword args." + + context = {'user': _get_action_user_name(kwargs)} + + group_dict = helpers.call_action('group_create', + context=context, + **kwargs) + return group_dict + + +class Organization(factory.Factory): + '''A factory class for creating CKAN organizations.''' + + # This is the class that OrganizationFactory will create and return + # instances of. + FACTORY_FOR = ckan.model.Group + + # These are the default params that will be used to create new + # organizations. + is_organization = True + + title = 'Test Organization' + description = 'Just another test organization.' + image_url = 'http://placekitten.com/g/200/100' + + # Generate a different group name param for each user that gets created. + name = factory.Sequence(lambda n: 'test_org_{0:02d}'.format(n)) + + @classmethod + def _build(cls, target_class, *args, **kwargs): + raise NotImplementedError(".build() isn't supported in CKAN") + + @classmethod + def _create(cls, target_class, *args, **kwargs): + if args: + assert False, "Positional args aren't supported, use keyword args." + + context = {'user': _get_action_user_name(kwargs)} + + kwargs.setdefault('type', 'organization') + + group_dict = helpers.call_action('organization_create', + context=context, + **kwargs) + return group_dict + + +class Dataset(factory.Factory): + '''A factory class for creating CKAN datasets.''' + + FACTORY_FOR = ckan.model.Package + + # These are the default params that will be used to create new groups. + title = 'Test Dataset' + notes = 'Just another test dataset.' + + # Generate a different group name param for each user that gets created. + name = factory.Sequence(lambda n: 'test_dataset_{0:02d}'.format(n)) + + @classmethod + def _build(cls, target_class, *args, **kwargs): + raise NotImplementedError(".build() isn't supported in CKAN") + + @classmethod + def _create(cls, target_class, *args, **kwargs): + if args: + assert False, "Positional args aren't supported, use keyword args." + + context = {'user': _get_action_user_name(kwargs)} + + dataset_dict = helpers.call_action('package_create', + context=context, + **kwargs) + return dataset_dict + + +class MockUser(factory.Factory): + '''A factory class for creating mock CKAN users using the mock library.''' + + FACTORY_FOR = mock.MagicMock + + fullname = 'Mr. Mock User' + password = 'pass' + about = 'Just another mock user.' + name = factory.Sequence(lambda n: 'mock_user_{0:02d}'.format(n)) + email = factory.LazyAttribute(_generate_email) + reset_key = factory.LazyAttribute(_generate_reset_key) + id = factory.LazyAttribute(_generate_user_id) + + @classmethod + def _build(cls, target_class, *args, **kwargs): + raise NotImplementedError(".build() isn't supported in CKAN") + + @classmethod + def _create(cls, target_class, *args, **kwargs): + if args: + assert False, "Positional args aren't supported, use keyword args." + mock_user = mock.MagicMock() + for name, value in kwargs.items(): + setattr(mock_user, name, value) + return mock_user + + +class SystemInfo(factory.Factory): + '''A factory class for creating SystemInfo objects (config objects + stored in the DB).''' + + FACTORY_FOR = ckan.model.SystemInfo + + key = factory.Sequence(lambda n: 'test_config_{0:02d}'.format(n)) + value = _generate_random_string() + + @classmethod + def _build(cls, target_class, *args, **kwargs): + raise NotImplementedError(".build() isn't supported in CKAN") + + @classmethod + def _create(cls, target_class, *args, **kwargs): + if args: + assert False, "Positional args aren't supported, use keyword args." + + ckan.model.system_info.set_system_info(kwargs['key'], + kwargs['value']) + obj = ckan.model.Session.query(ckan.model.system_info.SystemInfo) \ + .filter_by(key=kwargs['key']).first() + + return obj + + +def validator_data_dict(): + '''Return a data dict with some arbitrary data in it, suitable to be passed + to validator functions for testing. + + ''' + return {('other key',): 'other value'} + + +def validator_errors_dict(): + '''Return an errors dict with some arbitrary errors in it, suitable to be + passed to validator functions for testing. + + ''' + return {('other key',): ['other error']} + + +class Vocabulary(factory.Factory): + '''A factory class for creating tag vocabularies.''' + + FACTORY_FOR = ckan.model.Vocabulary + name = factory.Sequence(lambda n: 'test_vocabulary_{0:02d}'.format(n)) + + @classmethod + def _build(cls, target_class, *args, **kwargs): + raise NotImplementedError(".build() isn't supported in CKAN") + + @classmethod + def _create(cls, target_class, *args, **kwargs): + if args: + assert False, "Positional args aren't supported, use keyword args." + return helpers.call_action('vocabulary_create', **kwargs) diff --git a/venv/lib/python2.7/site-packages/ckan/tests/helpers.py b/venv/lib/python2.7/site-packages/ckan/tests/helpers.py new file mode 100644 index 00000000..49d099f9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/helpers.py @@ -0,0 +1,678 @@ +# encoding: utf-8 + +'''This is a collection of helper functions for use in tests. + +We want to avoid sharing test helper functions between test modules as +much as possible, and we definitely don't want to share test fixtures between +test modules, or to introduce a complex hierarchy of test class subclasses, +etc. + +We want to reduce the amount of "travel" that a reader needs to undertake to +understand a test method -- reducing the number of other files they need to go +and read to understand what the test code does. And we want to avoid tightly +coupling test modules to each other by having them share code. + +But some test helper functions just increase the readability of tests so much +and make writing tests so much easier, that it's worth having them despite the +potential drawbacks. + +This module is reserved for these very useful functions. + +''' + +import collections +import contextlib +import errno +import functools +import logging +import os +import re + +import webtest +import nose.tools +from nose.tools import assert_in, assert_not_in +import mock +import rq + +from ckan.common import config +import ckan.lib.jobs as jobs +from ckan.lib.redis import connect_to_redis +import ckan.lib.search as search +import ckan.config.middleware +import ckan.model as model +import ckan.logic as logic + + +def reset_db(): + '''Reset CKAN's database. + + If a test class uses the database, then it should call this function in its + ``setup()`` method to make sure that it has a clean database to start with + (nothing left over from other test classes or from previous test runs). + + If a test class doesn't use the database (and most test classes shouldn't + need to) then it doesn't need to call this function. + + :returns: ``None`` + + ''' + # Close any database connections that have been left open. + # This prevents CKAN from hanging waiting for some unclosed connection. + model.Session.close_all() + + model.repo.rebuild_db() + + +def call_action(action_name, context=None, **kwargs): + '''Call the named ``ckan.logic.action`` function and return the result. + + This is just a nicer way for user code to call action functions, nicer than + either calling the action function directly or via + :py:func:`ckan.logic.get_action`. + + For example:: + + user_dict = call_action('user_create', name='seanh', + email='seanh@seanh.com', password='pass') + + Any keyword arguments given will be wrapped in a dict and passed to the + action function as its ``data_dict`` argument. + + Note: this skips authorization! It passes 'ignore_auth': True to action + functions in their ``context`` dicts, so the corresponding authorization + functions will not be run. + This is because ckan.tests.logic.action tests only the actions, the + authorization functions are tested separately in + ckan.tests.logic.auth. + See the :doc:`testing guidelines ` for more info. + + This function should eventually be moved to + :py:func:`ckan.logic.call_action` and the current + :py:func:`ckan.logic.get_action` function should be + deprecated. The tests may still need their own wrapper function for + :py:func:`ckan.logic.call_action`, e.g. to insert ``'ignore_auth': True`` + into the ``context`` dict. + + :param action_name: the name of the action function to call, e.g. + ``'user_update'`` + :type action_name: string + :param context: the context dict to pass to the action function + (optional, if no context is given a default one will be supplied) + :type context: dict + :returns: the dict or other value that the action function returns + + ''' + if context is None: + context = {} + context.setdefault('user', '127.0.0.1') + context.setdefault('ignore_auth', True) + return logic.get_action(action_name)(context=context, data_dict=kwargs) + + +def call_auth(auth_name, context, **kwargs): + '''Call the named ``ckan.logic.auth`` function and return the result. + + This is just a convenience function for tests in + :py:mod:`ckan.tests.logic.auth` to use. + + Usage:: + + result = helpers.call_auth('user_update', context=context, + id='some_user_id', + name='updated_user_name') + + :param auth_name: the name of the auth function to call, e.g. + ``'user_update'`` + :type auth_name: string + + :param context: the context dict to pass to the auth function, must + contain ``'user'`` and ``'model'`` keys, + e.g. ``{'user': 'fred', 'model': my_mock_model_object}`` + :type context: dict + + :returns: the dict that the auth function returns, e.g. + ``{'success': True}`` or ``{'success': False, msg: '...'}`` + or just ``{'success': False}`` + :rtype: dict + + ''' + assert 'user' in context, ('Test methods must put a user name in the ' + 'context dict') + assert 'model' in context, ('Test methods must put a model in the ' + 'context dict') + + return logic.check_access(auth_name, context, data_dict=kwargs) + + +class CKANTestApp(webtest.TestApp): + '''A wrapper around webtest.TestApp + + It adds some convenience methods for CKAN + ''' + + _flask_app = None + + @property + def flask_app(self): + if not self._flask_app: + self._flask_app = self.app.apps['flask_app']._wsgi_app + return self._flask_app + + +def _get_test_app(): + '''Return a webtest.TestApp for CKAN, with legacy templates disabled. + + For functional tests that need to request CKAN pages or post to the API. + Unit tests shouldn't need this. + + ''' + config['ckan.legacy_templates'] = False + config['testing'] = True + app = ckan.config.middleware.make_app(config['global_conf'], **config) + app = CKANTestApp(app) + return app + + +class FunctionalTestBase(object): + '''A base class for functional test classes to inherit from. + + Allows configuration changes by overriding _apply_config_changes and + resetting the CKAN config after your test class has run. It creates a + webtest.TestApp at self.app for your class to use to make HTTP requests + to the CKAN web UI or API. Also loads plugins defined by + _load_plugins in the class definition. + + If you're overriding methods that this class provides, like setup_class() + and teardown_class(), make sure to use super() to call this class's methods + at the top of yours! + + ''' + @classmethod + def _get_test_app(cls): # leading _ because nose is terrible + # FIXME: remove this method and switch to using helpers.get_test_app + # in each test once the old functional tests are fixed or removed + if not hasattr(cls, '_test_app'): + cls._test_app = _get_test_app() + return cls._test_app + + @classmethod + def setup_class(cls): + import ckan.plugins as p + # Make a copy of the Pylons config, so we can restore it in teardown. + cls._original_config = dict(config) + cls._apply_config_changes(config) + cls._get_test_app() + for plugin in getattr(cls, '_load_plugins', []): + p.load(plugin) + + @classmethod + def _apply_config_changes(cls, cfg): + pass + + def setup(self): + '''Reset the database and clear the search indexes.''' + reset_db() + if hasattr(self, '_test_app'): + self._test_app.reset() + search.clear_all() + + @classmethod + def teardown_class(cls): + import ckan.plugins as p + for plugin in reversed(getattr(cls, '_load_plugins', [])): + p.unload(plugin) + # Restore the Pylons config to its original values, in case any tests + # changed any config settings. + config.clear() + config.update(cls._original_config) + + +class RQTestBase(object): + ''' + Base class for tests of RQ functionality. + ''' + def setup(self): + u''' + Delete all RQ queues and jobs. + ''' + # See https://github.com/nvie/rq/issues/731 + redis_conn = connect_to_redis() + for queue in rq.Queue.all(connection=redis_conn): + queue.empty() + redis_conn.srem(rq.Queue.redis_queues_keys, queue._key) + redis_conn.delete(queue._key) + + def all_jobs(self): + u''' + Get a list of all RQ jobs. + ''' + jobs = [] + redis_conn = connect_to_redis() + for queue in rq.Queue.all(connection=redis_conn): + jobs.extend(queue.jobs) + return jobs + + def enqueue(self, job=None, *args, **kwargs): + u''' + Enqueue a test job. + ''' + if job is None: + job = jobs.test_job + return jobs.enqueue(job, *args, **kwargs) + + +class FunctionalRQTestBase(FunctionalTestBase, RQTestBase): + ''' + Base class for functional tests of RQ functionality. + ''' + def setup(self): + FunctionalTestBase.setup(self) + RQTestBase.setup(self) + + +def submit_and_follow(app, form, extra_environ=None, name=None, + value=None, **args): + ''' + Call webtest_submit with name/value passed expecting a redirect + and return the response from following that redirect. + ''' + response = webtest_submit(form, name, value=value, status=302, + extra_environ=extra_environ, **args) + return app.get(url=response.headers['Location'], + extra_environ=extra_environ) + + +# FIXME: remove webtest_* functions below when we upgrade webtest + +def webtest_submit(form, name=None, index=None, value=None, **args): + ''' + backported version of webtest.Form.submit that actually works + for submitting with different submit buttons. + + We're stuck on an old version of webtest because we're stuck + on an old version of webob because we're stuck on an old version + of Pylons. This prolongs our suffering, but on the bright side + it lets us have functional tests that work. + ''' + fields = webtest_submit_fields(form, name, index=index, submit_value=value) + if form.method.upper() != "GET": + args.setdefault("content_type", form.enctype) + return form.response.goto(form.action, method=form.method, + params=fields, **args) + + +def webtest_submit_fields(form, name=None, index=None, submit_value=None): + ''' + backported version of webtest.Form.submit_fields that actually works + for submitting with different submit buttons. + ''' + from webtest.app import File + submit = [] + # Use another name here so we can keep function param the same for BWC. + submit_name = name + if index is not None and submit_value is not None: + raise ValueError("Can't specify both submit_value and index.") + + # If no particular button was selected, use the first one + if index is None and submit_value is None: + index = 0 + + # This counts all fields with the submit name not just submit fields. + current_index = 0 + for name, field in form.field_order: + if name is None: # pragma: no cover + continue + if submit_name is not None and name == submit_name: + if index is not None and current_index == index: + submit.append((name, field.value_if_submitted())) + if submit_value is not None and \ + field.value_if_submitted() == submit_value: + submit.append((name, field.value_if_submitted())) + current_index += 1 + else: + value = field.value + if value is None: + continue + if isinstance(field, File): + submit.append((name, field)) + continue + if isinstance(value, list): + for item in value: + submit.append((name, item)) + else: + submit.append((name, value)) + return submit + + +def webtest_maybe_follow(response, **kw): + """ + Follow all redirects. If this response is not a redirect, do nothing. + Returns another response object. + + (backported from WebTest 2.0.1) + """ + remaining_redirects = 100 # infinite loops protection + + while 300 <= response.status_int < 400 and remaining_redirects: + response = response.follow(**kw) + remaining_redirects -= 1 + + assert remaining_redirects > 0, "redirects chain looks infinite" + return response + + +def change_config(key, value): + '''Decorator to temporarily change CKAN's config to a new value + + This allows you to easily create tests that need specific config values to + be set, making sure it'll be reverted to what it was originally, after your + test is run. + + Usage:: + + @helpers.change_config('ckan.site_title', 'My Test CKAN') + def test_ckan_site_title(self): + assert config['ckan.site_title'] == 'My Test CKAN' + + :param key: the config key to be changed, e.g. ``'ckan.site_title'`` + :type key: string + + :param value: the new config key's value, e.g. ``'My Test CKAN'`` + :type value: string + + .. seealso:: The context manager :py:func:`changed_config` + ''' + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + with changed_config(key, value): + return func(*args, **kwargs) + return wrapper + return decorator + + +@contextlib.contextmanager +def changed_config(key, value): + ''' + Context manager for temporarily changing a config value. + + Allows you to temporarily change the value of a CKAN configuration + option. The original value is restored once the context manager is + left. + + Usage:: + + with changed_config(u'ckan.site_title', u'My Test CKAN'): + assert config[u'ckan.site_title'] == u'My Test CKAN' + + .. seealso:: The decorator :py:func:`change_config` + ''' + _original_config = config.copy() + config[key] = value + try: + yield + finally: + config.clear() + config.update(_original_config) + + +def mock_auth(auth_function_path): + ''' + Decorator to easily mock a CKAN auth method in the context of a test + function + + It adds a mock object for the provided auth_function_path as a parameter to + the test function. + + Essentially it makes sure that `ckan.authz.clear_auth_functions_cache` is + called before and after to make sure that the auth functions pick up + the newly changed values. + + Usage:: + + @helpers.mock_auth('ckan.logic.auth.create.package_create') + def test_mock_package_create(self, mock_package_create): + from ckan import logic + mock_package_create.return_value = {'success': True} + + # package_create is mocked + eq_(logic.check_access('package_create', {}), True) + + assert mock_package_create.called + + :param action_name: the full path to the auth function to be mocked, + e.g. ``ckan.logic.auth.create.package_create`` + :type action_name: string + + ''' + from ckan.authz import clear_auth_functions_cache + + def decorator(func): + def wrapper(*args, **kwargs): + + try: + with mock.patch(auth_function_path) as mocked_auth: + clear_auth_functions_cache() + new_args = args + tuple([mocked_auth]) + return_value = func(*new_args, **kwargs) + finally: + clear_auth_functions_cache() + return return_value + + return nose.tools.make_decorator(func)(wrapper) + return decorator + + +def mock_action(action_name): + ''' + Decorator to easily mock a CKAN action in the context of a test function + + It adds a mock object for the provided action as a parameter to the test + function. The mock is discarded at the end of the function, even if there + is an exception raised. + + Note that this mocks the action both when it's called directly via + ``ckan.logic.get_action`` and via ``ckan.plugins.toolkit.get_action``. + + Usage:: + + @mock_action('user_list') + def test_mock_user_list(self, mock_user_list): + + mock_user_list.return_value = 'hi' + + # user_list is mocked + eq_(helpers.call_action('user_list', {}), 'hi') + + assert mock_user_list.called + + :param action_name: the name of the action to be mocked, + e.g. ``package_create`` + :type action_name: string + + ''' + def decorator(func): + def wrapper(*args, **kwargs): + mock_action = mock.MagicMock() + + from ckan.logic import get_action as original_get_action + + def side_effect(called_action_name): + if called_action_name == action_name: + return mock_action + else: + return original_get_action(called_action_name) + try: + with mock.patch('ckan.logic.get_action') as mock_get_action, \ + mock.patch('ckan.plugins.toolkit.get_action') \ + as mock_get_action_toolkit: + mock_get_action.side_effect = side_effect + mock_get_action_toolkit.side_effect = side_effect + + new_args = args + tuple([mock_action]) + return_value = func(*new_args, **kwargs) + finally: + # Make sure to stop the mock, even with an exception + mock_action.stop() + return return_value + + return nose.tools.make_decorator(func)(wrapper) + return decorator + + +def set_extra_environ(key, value): + '''Decorator to temporarily changes a single request environemnt value + + Create a new test app and use the a side effect of making a request + to set an extra_environ value. Reset the value to '' after the test. + + Usage:: + + @helpers.extra_environ('SCRIPT_NAME', '/myscript') + def test_ckan_thing_affected_by_script_name(self): + # ... + + :param key: the extra_environ key to be changed, e.g. ``'SCRIPT_NAME'`` + :type key: string + + :param value: the new extra_environ key's value, e.g. ``'/myscript'`` + :type value: string + ''' + def decorator(func): + def wrapper(*args, **kwargs): + app = _get_test_app() + app.get('/', extra_environ={key: value}) + + try: + return_value = func(*args, **kwargs) + finally: + app.get('/', extra_environ={key: ''}) + + return return_value + return nose.tools.make_decorator(func)(wrapper) + return decorator + + +@contextlib.contextmanager +def recorded_logs(logger=None, level=logging.DEBUG, + override_disabled=True, override_global_level=True): + u''' + Context manager for recording log messages. + + :param logger: The logger to record messages from. Can either be a + :py:class:`logging.Logger` instance or a string with the + logger's name. Defaults to the root logger. + + :param int level: Temporary log level for the target logger while + the context manager is active. Pass ``None`` if you don't want + the level to be changed. The level is automatically reset to its + original value when the context manager is left. + + :param bool override_disabled: A logger can be disabled by setting + its ``disabled`` attribute. By default, this context manager + sets that attribute to ``False`` at the beginning of its + execution and resets it when the context manager is left. Set + ``override_disabled`` to ``False`` to keep the current value + of the attribute. + + :param bool override_global_level: The ``logging.disable`` function + allows one to install a global minimum log level that takes + precedence over a logger's own level. By default, this context + manager makes sure that the global limit is at most ``level``, + and reduces it if necessary during its execution. Set + ``override_global_level`` to ``False`` to keep the global limit. + + :returns: A recording log handler that listens to ``logger`` during + the execution of the context manager. + :rtype: :py:class:`RecordingLogHandler` + + Example:: + + import logging + + logger = logging.getLogger(__name__) + + with recorded_logs(logger) as logs: + logger.info(u'Hello, world!') + + logs.assert_log(u'info', u'world') + ''' + if logger is None: + logger = logging.getLogger() + elif not isinstance(logger, logging.Logger): + logger = logging.getLogger(logger) + handler = RecordingLogHandler() + old_level = logger.level + manager_level = logger.manager.disable + disabled = logger.disabled + logger.addHandler(handler) + try: + if level is not None: + logger.setLevel(level) + if override_disabled: + logger.disabled = False + if override_global_level: + if (level is None) and (manager_level > old_level): + logger.manager.disable = old_level + elif (level is not None) and (manager_level > level): + logger.manager.disable = level + yield handler + finally: + logger.handlers.remove(handler) + logger.setLevel(old_level) + logger.disabled = disabled + logger.manager.disable = manager_level + + +class RecordingLogHandler(logging.Handler): + u''' + Log handler that records log messages for later inspection. + + You can inspect the recorded messages via the ``messages`` attribute + (a dict that maps log levels to lists of messages) or by using + ``assert_log``. + + This class is rarely useful on its own, instead use + :py:func:`recorded_logs` to temporarily record log messages. + ''' + def __init__(self, *args, **kwargs): + super(RecordingLogHandler, self).__init__(*args, **kwargs) + self.clear() + + def emit(self, record): + self.messages[record.levelname.lower()].append(record.getMessage()) + + def assert_log(self, level, pattern, msg=None): + u''' + Assert that a certain message has been logged. + + :param string pattern: A regex which the message has to match. + The match is done using ``re.search``. + + :param string level: The message level (``'debug'``, ...). + + :param string msg: Optional failure message in case the expected + log message was not logged. + + :raises AssertionError: If the expected message was not logged. + ''' + compiled_pattern = re.compile(pattern) + for log_msg in self.messages[level]: + if compiled_pattern.search(log_msg): + return + if not msg: + if self.messages[level]: + lines = u'\n '.join(self.messages[level]) + msg = (u'Pattern "{}" was not found in the log messages for ' + + u'level "{}":\n {}').format(pattern, level, lines) + else: + msg = (u'Pattern "{}" was not found in the log messages for ' + + u'level "{}" (no messages were recorded for that ' + + u'level).').format(pattern, level) + raise AssertionError(msg) + + def clear(self): + u''' + Clear all captured log messages. + ''' + self.messages = collections.defaultdict(list) diff --git a/venv/lib/python2.7/site-packages/ckan/tests/i18n/__init__.py b/venv/lib/python2.7/site-packages/ckan/tests/i18n/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/venv/lib/python2.7/site-packages/ckan/tests/i18n/test_check_po_files.py b/venv/lib/python2.7/site-packages/ckan/tests/i18n/test_check_po_files.py new file mode 100644 index 00000000..5243aeea --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/i18n/test_check_po_files.py @@ -0,0 +1,125 @@ +# encoding: utf-8 + +import nose + +from ckan.i18n.check_po_files import (check_po_file, + simple_conv_specs, + mapping_keys, + replacement_fields) + +eq_ = nose.tools.eq_ + + +PO_OK = ''' +#: ckan/lib/formatters.py:57 +msgid "November" +msgstr "Noiembrie" + +#: ckan/lib/formatters.py:61 +msgid "December" +msgstr "Decembrie" +''' + +PO_WRONG = ''' +#: ckan/templates/snippets/search_result_text.html:15 +msgid "{number} dataset found for {query}" +msgstr "צביר נתונים אחד נמצא עבור {query}" +''' + +PO_PLURALS_OK = ''' +#: ckan/lib/formatters.py:114 +msgid "{hours} hour ago" +msgid_plural "{hours} hours ago" +msgstr[0] "Fa {hours} hora" +msgstr[1] "Fa {hours} hores" +''' + +PO_WRONG_PLURALS = ''' +#: ckan/lib/formatters.py:114 +msgid "{hours} hour ago" +msgid_plural "{hours} hours ago" +msgstr[0] "o oră în urmă" +msgstr[1] "cîteva ore în urmă" +msgstr[2] "{hours} ore în urmă" +''' + + +class TestCheckPoFiles(object): + + def test_basic(self): + + errors = check_po_file(PO_OK) + + eq_(errors, []) + + def test_wrong(self): + + errors = check_po_file(PO_WRONG) + + eq_(len(errors), 1) + + eq_(errors[0][0], '{number} dataset found for {query}') + + def test_plurals_ok(self): + + errors = check_po_file(PO_PLURALS_OK) + + eq_(errors, []) + + def test_wrong_plurals(self): + + errors = check_po_file(PO_WRONG_PLURALS) + + eq_(len(errors), 2) + + for error in errors: + assert error[0] in ('{hours} hour ago', '{hours} hours ago') + + +class TestValidators(object): + + def test_simple_conv_specs(self): + eq_(simple_conv_specs("Authorization function not found: %s"), + (['%s'])) + eq_(simple_conv_specs("Problem purging revision %s: %s"), + (['%s', '%s'])) + eq_(simple_conv_specs("Cannot create new entity of this type: %s %s"), + ['%s', '%s']) + eq_(simple_conv_specs("Could not read parameters: %r"), ['%r']) + eq_(simple_conv_specs("User %r not authorized to edit %r"), + (['%r', '%r'])) + eq_(simple_conv_specs( + "Please update your profile and add your email " + "address and your full name. " + "%s uses your email address if you need to reset your password."), + (['%s', '%s'])) + eq_(simple_conv_specs("You can use %sMarkdown formatting%s here."), + ['%s', '%s']) + eq_(simple_conv_specs("Name must be a maximum of %i characters long"), + ['%i']) + eq_(simple_conv_specs("Blah blah %s blah %(key)s blah %i"), + (['%s', '%i'])) + + def test_replacement_fields(self): + eq_(replacement_fields( + "{actor} added the tag {object} to the dataset {target}"), + (['{actor}', '{object}', '{target}'])) + eq_(replacement_fields("{actor} updated their profile"), ['{actor}']) + + def test_mapping_keys(self): + eq_(mapping_keys( + "You have requested your password on %(site_title)s to be reset.\n" + "\n" + "Please click the following link to confirm this request:\n" + "\n" + " %(reset_link)s\n"), + ['%(reset_link)s', '%(site_title)s']) + eq_(mapping_keys( + "The input field %(name)s was not expected."), + ['%(name)s']) + eq_(mapping_keys( + "[1:You searched for \"%(query)s\". ]%(number_of_results)s " + "datasets found."), + ['%(number_of_results)s', '%(query)s']) + eq_(mapping_keys("Blah blah %s blah %(key)s blah %i"), + (['%(key)s']), mapping_keys("Blah blah %s blah %(key)s blah %i")) diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/__init__.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/__init__.py new file mode 100644 index 00000000..c6084b4d --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/__init__.py @@ -0,0 +1,427 @@ +# encoding: utf-8 + +"""Pylons application test package + +When the test runner finds and executes tests within this directory, +this file will be loaded to setup the test environment. + +It registers the root directory of the project in sys.path and +pkg_resources, in case the project hasn't been installed with +setuptools. It also initializes the application via websetup (paster +setup-app) with the project's test.ini configuration file. +""" +import os +import sys +import re +from unittest import TestCase +from nose.tools import assert_equal, assert_not_equal, make_decorator +from nose.plugins.skip import SkipTest +import time + +from ckan.common import config +from pylons.test import pylonsapp +from paste.script.appinstall import SetupCommand +from six import text_type + +import pkg_resources +import paste.fixture +import paste.script.appinstall +from paste.deploy import loadapp + +from ckan.lib.create_test_data import CreateTestData +from ckan.lib import search +import ckan.lib.helpers as h +from ckan.logic import get_action +from ckan.logic.action import get_domain_object +import ckan.model as model +from ckan import ckan_nose_plugin +from ckan.common import json + +# evil hack as url_for is passed out +url_for = h.url_for + +__all__ = ['url_for', + 'TestController', + 'CreateTestData', + 'TestSearchIndexer', + 'CheckMethods', + 'CommonFixtureMethods', + 'TestCase', + 'SkipTest', + 'CkanServerCase', + 'call_action_api', + 'BaseCase', + 'here_dir', + 'conf_dir', + 'is_datastore_supported', + ] + +here_dir = os.path.dirname(os.path.abspath(__file__)) +conf_dir = os.path.dirname(os.path.dirname(here_dir)) + +# Invoke websetup with the current config file +SetupCommand('setup-app').run([config['__file__']]) + +# monkey patch paste.fixtures.TestRespose +# webtest (successor library) already has this +# http://pythonpaste.org/webtest/#parsing-the-body +def _getjson(self): + return json.loads(self.body) +paste.fixture.TestResponse.json = property(_getjson) + +# Check config is correct for sqlite +if model.engine_is_sqlite(): + assert ckan_nose_plugin.CkanNose.settings.is_ckan, \ + 'You forgot the "--ckan" nosetest setting - see doc/test.rst' + +class BaseCase(object): + + def setup(self): + pass + + def teardown(self): + pass + + @staticmethod + def _system(cmd): + import commands + (status, output) = commands.getstatusoutput(cmd) + if status: + raise Exception("Couldn't execute cmd: %s: %s" % (cmd, output)) + + @classmethod + def _paster(cls, cmd, config_path_rel): + config_path = os.path.join(config['here'], config_path_rel) + cls._system('paster --plugin ckan %s --config=%s' % (cmd, config_path)) + + +class CommonFixtureMethods(BaseCase): + + @classmethod + def create_package(self, data={}, **kwds): + # Todo: A simpler method for just creating a package. + CreateTestData.create_arbitrary(package_dicts=[data or kwds]) + + @classmethod + def create_user(cls, **kwds): + user = model.User(name=kwds['name']) + model.Session.add(user) + model.Session.commit() + model.Session.remove() + return user + + @staticmethod + def get_package_by_name(package_name): + return model.Package.by_name(package_name) + + @staticmethod + def get_group_by_name(group_name): + return model.Group.by_name(group_name) + + @staticmethod + def get_user_by_name(name): + return model.User.by_name(name) + + @staticmethod + def get_tag_by_name(name): + return model.Tag.by_name(name) + + def purge_package_by_name(self, package_name): + package = self.get_package_by_name(package_name) + if package: + package.purge() + model.repo.commit_and_remove() + + @classmethod + def purge_packages(cls, pkg_names): + for pkg_name in pkg_names: + pkg = model.Package.by_name(text_type(pkg_name)) + if pkg: + pkg.purge() + model.repo.commit_and_remove() + + @classmethod + def purge_all_packages(self): + all_pkg_names = [pkg.name for pkg in model.Session.query(model.Package)] + self.purge_packages(all_pkg_names) + + def purge_group_by_name(self, group_name): + group = self.get_group_by_name(group_name) + if group: + group.purge() + model.repo.commit_and_remove() + + @classmethod + def clear_all_tst_ratings(self): + ratings = model.Session.query(model.Rating).filter_by(package=model.Package.by_name(u'annakarenina')).all() + ratings += model.Session.query(model.Rating).filter_by(package=model.Package.by_name(u'warandpeace')).all() + for rating in ratings[:]: + model.Session.delete(rating) + model.repo.commit_and_remove() + + @property + def war(self): + return self.get_package_by_name(u'warandpeace') + + @property + def anna(self): + return self.get_package_by_name(u'annakarenina') + + @property + def roger(self): + return self.get_group_by_name(u'roger') + + @property + def david(self): + return self.get_group_by_name(u'david') + + @property + def russian(self): + return self.get_tag_by_name(u'russian') + + @property + def tolstoy(self): + return self.get_tag_by_name(u'tolstoy') + + @property + def flexible_tag(self): + return self.get_tag_by_name(u'Flexible \u30a1') + +class CheckMethods(BaseCase): + + def assert_true(self, value): + assert value, "Not true: '%s'" % value + + def assert_false(self, value): + assert not value, "Not false: '%s'" % value + + def assert_equal(self, value1, value2): + assert value1 == value2, 'Not equal: %s' % ((value1, value2),) + + def assert_isinstance(self, value, check): + assert isinstance(value, check), 'Not an instance: %s' % ((value, check),) + + def assert_raises(self, exception_class, callable, *args, **kwds): + try: + callable(*args, **kwds) + except exception_class: + pass + else: + assert False, "Didn't raise '%s' when calling: %s with %s" % (exception_class, callable, (args, kwds)) + + def assert_contains(self, sequence, item): + assert item in sequence, "Sequence %s does not contain item: %s" % (sequence, item) + + def assert_missing(self, sequence, item): + assert item not in sequence, "Sequence %s does contain item: %s" % (sequence, item) + + def assert_len(self, sequence, count): + assert len(sequence) == count, "Length of sequence %s was not %s." % (sequence, count) + + def assert_isinstance(self, object, kind): + assert isinstance(object, kind), "Object %s is not an instance of %s." % (object, kind) + + +class TestCase(CommonFixtureMethods, CheckMethods, BaseCase): + def setup(self): + super(TestCase, self).setup() + self.conditional_create_common_fixtures() + + def teardown(self): + self.reuse_or_delete_common_fixtures() + super(TestCase, self).setup() + + +class WsgiAppCase(BaseCase): + wsgiapp = pylonsapp + assert wsgiapp, 'You need to run nose with --with-pylons' + # Either that, or this file got imported somehow before the tests started + # running, meaning the pylonsapp wasn't setup yet (which is done in + # pylons.test.py:begin()) + app = paste.fixture.TestApp(wsgiapp) + + +def config_abspath(file_path): + if os.path.isabs(file_path): + return file_path + return os.path.join(conf_dir, file_path) + +class CkanServerCase(BaseCase): + @classmethod + def _recreate_ckan_server_testdata(cls, config_path): + cls._paster('db clean', config_path) + cls._paster('db init', config_path) + cls._paster('create-test-data', config_path) + cls._paster('search-index rebuild', config_path) + + @staticmethod + def _start_ckan_server(config_file=None): + if not config_file: + config_file = config['__file__'] + config_path = config_abspath(config_file) + import subprocess + process = subprocess.Popen(['paster', 'serve', config_path]) + return process + + @staticmethod + def _wait_for_url(url='http://127.0.0.1:5000/', timeout=15): + for i in range(int(timeout)*100): + import urllib2 + import time + try: + response = urllib2.urlopen(url) + except urllib2.URLError: + time.sleep(0.01) + else: + break + + @staticmethod + def _stop_ckan_server(process): + pid = process.pid + pid = int(pid) + if os.system("kill -9 %d" % pid): + raise Exception("Can't kill foreign CKAN instance (pid: %d)." % pid) + + +class TestController(CommonFixtureMethods, CkanServerCase, WsgiAppCase, BaseCase): + + def assert_equal(self, *args, **kwds): + assert_equal(*args, **kwds) + + def assert_not_equal(self, *args, **kwds): + assert_not_equal(*args, **kwds) + + def clear_language_setting(self): + self.app.cookies = {} + + +class TestSearchIndexer: + ''' + Tests which use search can use this object to provide indexing + Usage: + self.tsi = TestSearchIndexer() + (create packages) + self.tsi.index() + (do searching) + ''' + + def __init__(self): + from ckan import plugins + if not is_search_supported(): + raise SkipTest("Search not supported") + plugins.load('synchronous_search') + + @classmethod + def index(cls): + pass + + @classmethod + def list(cls): + return [model.Package.get(pkg_index.package_id).name for pkg_index in model.Session.query(model.PackageSearch)] + +def setup_test_search_index(): + #from ckan import plugins + if not is_search_supported(): + raise SkipTest("Search not supported") + search.clear_all() + #plugins.load('synchronous_search') + +def is_search_supported(): + is_supported_db = not model.engine_is_sqlite() + return is_supported_db + +def are_foreign_keys_supported(): + return not model.engine_is_sqlite() + +def is_regex_supported(): + is_supported_db = not model.engine_is_sqlite() + return is_supported_db + +def is_migration_supported(): + is_supported_db = not model.engine_is_sqlite() + return is_supported_db + +def is_datastore_supported(): + # we assume that the datastore uses the same db engine that ckan uses + is_supported_db = model.engine_is_pg() + return is_supported_db + +def regex_related(test): + def skip_test(*args): + raise SkipTest("Regex not supported") + if not is_regex_supported(): + return make_decorator(test)(skip_test) + return test + +def clear_flash(res=None): + messages = h._flash.pop_messages() + +class StatusCodes: + STATUS_200_OK = 200 + STATUS_201_CREATED = 201 + STATUS_400_BAD_REQUEST = 400 + STATUS_403_ACCESS_DENIED = 403 + STATUS_404_NOT_FOUND = 404 + STATUS_409_CONFLICT = 409 + + +def call_action_api(app, action, apikey=None, status=200, **kwargs): + '''POST an HTTP request to the CKAN API and return the result. + + Any additional keyword arguments that you pass to this function as **kwargs + are posted as params to the API. + + Usage: + + package_dict = post(app, 'package_create', apikey=apikey, + name='my_package') + assert package_dict['name'] == 'my_package' + + num_followers = post(app, 'user_follower_count', id='annafan') + + If you are expecting an error from the API and want to check the contents + of the error dict, you have to use the status param otherwise an exception + will be raised: + + error_dict = post(app, 'group_activity_list', status=403, + id='invalid_id') + assert error_dict['message'] == 'Access Denied' + + :param app: the test app to post to + :type app: paste.fixture.TestApp + + :param action: the action to post to, e.g. 'package_create' + :type action: string + + :param apikey: the API key to put in the Authorization header of the post + (optional, default: None) + :type apikey: string + + :param status: the HTTP status code expected in the response from the CKAN + API, e.g. 403, if a different status code is received an exception will + be raised (optional, default: 200) + :type status: int + + :param **kwargs: any other keyword arguments passed to this function will + be posted to the API as params + + :raises paste.fixture.AppError: if the HTTP status code of the response + from the CKAN API is different from the status param passed to this + function + + :returns: the 'result' or 'error' dictionary from the CKAN API response + :rtype: dictionary + + ''' + params = json.dumps(kwargs) + response = app.post('/api/action/{0}'.format(action), params=params, + extra_environ={'Authorization': str(apikey)}, status=status) + assert '/api/3/action/help_show?name={0}'.format(action) \ + in response.json['help'] + + if status in (200,): + assert response.json['success'] is True + return response.json['result'] + else: + assert response.json['success'] is False + return response.json['error'] diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/ckantestplugins.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/ckantestplugins.py new file mode 100644 index 00000000..a2cc8fa2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/ckantestplugins.py @@ -0,0 +1,200 @@ +# encoding: utf-8 + +from collections import defaultdict + +import ckan.plugins as p +import ckan.tests.legacy.mock_plugin as mock_plugin + + +class MapperPlugin(p.SingletonPlugin): + p.implements(p.IMapper, inherit=True) + + def __init__(self, *args, **kw): + self.calls = [] + + def before_insert(self, mapper, conn, instance): + self.calls.append(('before_insert', instance.name)) + + def after_insert(self, mapper, conn, instance): + self.calls.append(('after_insert', instance.name)) + + def before_delete(self, mapper, conn, instance): + self.calls.append(('before_delete', instance.name)) + + def after_delete(self, mapper, conn, instance): + self.calls.append(('after_delete', instance.name)) + + +class MapperPlugin2(MapperPlugin): + p.implements(p.IMapper) + + +class SessionPlugin(p.SingletonPlugin): + p.implements(p.ISession, inherit=True) + + def __init__(self, *args, **kw): + self.added = [] + self.deleted = [] + + def before_insert(self, mapper, conn, instance): + self.added.append(instance) + + def before_delete(self, mapper, conn, instance): + self.deleted.append(instance) + +class RoutesPlugin(p.SingletonPlugin): + p.implements(p.IRoutes, inherit=True) + + def __init__(self, *args, **kw): + self.calls_made = [] + + def before_map(self, map): + self.calls_made.append('before_map') + return map + + def after_map(self, map): + self.calls_made.append('after_map') + return map + + +class PluginObserverPlugin(mock_plugin.MockSingletonPlugin): + p.implements(p.IPluginObserver) + +class ActionPlugin(p.SingletonPlugin): + p.implements(p.IActions) + + def get_actions(self): + return {'status_show': lambda context, data_dict: {}} + +class AuthPlugin(p.SingletonPlugin): + p.implements(p.IAuthFunctions) + + def get_auth_functions(self): + return {'package_list': lambda context, data_dict: {}} + +class MockGroupControllerPlugin(p.SingletonPlugin): + p.implements(p.IGroupController) + + def __init__(self, *args, **kw): + self.calls = defaultdict(int) + + def read(self, entity): + self.calls['read'] += 1 + + def create(self, entity): + self.calls['create'] += 1 + + def edit(self, entity): + self.calls['edit'] += 1 + + def delete(self, entity): + self.calls['delete'] += 1 + + def before_view(self, data_dict): + self.calls['before_view'] += 1 + return data_dict + + +class MockPackageControllerPlugin(p.SingletonPlugin): + p.implements(p.IPackageController) + + def __init__(self, *args, **kw): + self.calls = defaultdict(int) + + def read(self, entity): + self.calls['read'] += 1 + + def create(self, entity): + self.calls['create'] += 1 + + def edit(self, entity): + self.calls['edit'] += 1 + + def delete(self, entity): + self.calls['delete'] += 1 + + def before_search(self, search_params): + self.calls['before_search'] += 1 + return search_params + + def after_search(self, search_results, search_params): + self.calls['after_search'] += 1 + return search_results + + def before_index(self, data_dict): + self.calls['before_index'] += 1 + return data_dict + + def before_view(self, data_dict): + self.calls['before_view'] += 1 + return data_dict + + def after_create(self, context, data_dict): + self.calls['after_create'] += 1 + self.id_in_dict = 'id' in data_dict + + return data_dict + + def after_update(self, context, data_dict): + self.calls['after_update'] += 1 + return data_dict + + def after_delete(self, context, data_dict): + self.calls['after_delete'] += 1 + return data_dict + + def after_show(self, context, data_dict): + self.calls['after_show'] += 1 + return data_dict + + def update_facet_titles(self, facet_titles): + return facet_titles + + + +class MockResourcePreviewExtension(mock_plugin.MockSingletonPlugin): + p.implements(p.IResourcePreview) + + def __init__(self, *args, **kw): + self.calls = defaultdict(int) + + def setup_template_variables(self, context, data_dict): + self.calls['setup_template_variables'] += 1 + + def can_preview(self, data_dict): + assert(isinstance(data_dict['resource'], dict)) + assert(isinstance(data_dict['package'], dict)) + assert('on_same_domain' in data_dict['resource']) + + self.calls['can_preview'] += 1 + return data_dict['resource']['format'].lower() == 'mock' + + def preview_template(self, context, data_dict): + assert(isinstance(data_dict['resource'], dict)) + assert(isinstance(data_dict['package'], dict)) + + self.calls['preview_templates'] += 1 + return 'tests/mock_resource_preview_template.html' + + +class JsonMockResourcePreviewExtension(mock_plugin.MockSingletonPlugin): + p.implements(p.IResourcePreview) + + def __init__(self, *args, **kw): + self.calls = defaultdict(int) + + def setup_template_variables(self, context, data_dict): + self.calls['setup_template_variables'] += 1 + + def can_preview(self, data_dict): + self.calls['can_preview'] += 1 + return data_dict['resource']['format'].lower() == 'json' + + def preview_template(self, context, data_dict): + self.calls['preview_templates'] += 1 + return 'tests/mock_json_resource_preview_template.html' + + +# importing this file loads all these extensions by default +# so clean up the extensions +p.plugins_update() diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/__init__.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_group.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_group.py new file mode 100644 index 00000000..a1c2b549 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_group.py @@ -0,0 +1,184 @@ +# encoding: utf-8 + +import copy + +from ckan import model +from ckan.lib.create_test_data import CreateTestData +from ckan.lib import search + +from nose.tools import assert_equal + +from ckan.tests.legacy.functional.api.base import BaseModelApiTestCase + + +class GroupsTestCase(BaseModelApiTestCase): + + @classmethod + def setup_class(cls): + search.clear_all() + CreateTestData.create() + cls.user_name = u'russianfan' # created in CreateTestData + cls.init_extra_environ(cls.user_name) + + @classmethod + def teardown_class(cls): + model.repo.rebuild_db() + + def teardown(self): + self.purge_group_by_name(self.testgroupvalues['name']) + + def test_register_get_ok(self): + offset = self.group_offset() + res = self.app.get(offset, status=self.STATUS_200_OK) + assert self.ref_group(self.roger) in res, res + assert self.ref_group(self.david) in res, res + + def test_register_post_ok(self): + data = self.testgroupvalues + postparams = '%s=1' % self.dumps(data) + offset = self.group_offset() + res = self.app.post(offset, params=postparams, + status=self.STATUS_201_CREATED, + extra_environ=self.extra_environ) + # check group object + group = self.get_group_by_name(self.testgroupvalues['name']) + assert group + assert group.title == self.testgroupvalues['title'], group + assert group.description == self.testgroupvalues['description'], group + pkg_ids = [member.table_id for member in group.member_all] + pkgs = model.Session.query(model.Package).filter(model.Package.id.in_(pkg_ids)).all() + pkg_names = [pkg.name for pkg in pkgs] + + assert set(pkg_names) == set(('annakarenina', 'warandpeace')), pkg_names + + # check register updated + res = self.app.get(offset, status=self.STATUS_200_OK) + data = self.loads(res.body) + assert isinstance(data, list), data + assert self._ref_group(group) in data, data + + # check entity + offset = self.group_offset(self.testgroupvalues['name']) + res = self.app.get(offset, status=self.STATUS_200_OK) + group = self.loads(res.body) + expected_group = copy.deepcopy(self.testgroupvalues) + expected_group['packages'] = \ + sorted([self.ref_package(self.get_package_by_name(pkg_name)) \ + for pkg_name in expected_group['packages']]) + for expected_key, expected_value in expected_group.items(): + assert_equal(group.get(expected_key), expected_value) + + # Test Group Register Post 409 (conflict - create duplicate group). + offset = self.group_offset() + postparams = '%s=1' % self.dumps(self.testgroupvalues) + res = self.app.post(offset, params=postparams, + status=self.STATUS_409_CONFLICT, + extra_environ=self.extra_environ) + self.assert_json_response(res, 'Group name already exists') + + def test_entity_get_ok(self): + offset = self.group_offset(self.roger.name) + res = self.app.get(offset, status=self.STATUS_200_OK) + + self.assert_msg_represents_roger(msg=res.body) + assert self.package_ref_from_name('annakarenina') in res, res + assert self.group_ref_from_name('roger') in res, res + assert not self.package_ref_from_name('warandpeace') in res, res + + def test_entity_get_then_post(self): + # (ticket 662) Ensure an entity you 'get' from a register can be + # returned by posting it back + offset = self.group_offset(self.david.name) + res = self.app.get(offset, status=self.STATUS_200_OK) + data = self.loads(res.body) + postparams = '%s=1' % self.dumps(data) + res = self.app.post(offset, params=postparams, + status=self.STATUS_200_OK, + extra_environ=self.admin_extra_environ) + res = self.set_env(self.extra_environ) + + def test_10_edit_group_name_duplicate(self): + # create a group with testgroupvalues + if not model.Group.by_name(self.testgroupvalues['name']): + rev = model.repo.new_revision() + group = model.Group() + model.Session.add(group) + group.name = self.testgroupvalues['name'] + model.Session.commit() + + group = model.Group.by_name(self.testgroupvalues['name']) + rev = model.repo.new_revision() + model.repo.commit_and_remove() + assert model.Group.by_name(self.testgroupvalues['name']) + + # create a group with name 'dupname' + dupname = u'dupname' + if not model.Group.by_name(dupname): + rev = model.repo.new_revision() + group = model.Group() + model.Session.add(group) + group.name = dupname + model.Session.commit() + assert model.Group.by_name(dupname) + + # edit first group to have dupname + group_vals = {'name':dupname} + offset = self.group_offset(self.testgroupvalues['name']) + postparams = '%s=1' % self.dumps(group_vals) + res = self.app.post(offset, params=postparams, status=[409], + extra_environ=self.admin_extra_environ) + self.assert_json_response(res, 'Group name already exists') + res = self.set_env(self.extra_environ) + + def test_11_delete_group(self): + # Test Groups Entity Delete 200. + + # create a group with testgroupvalues + group = model.Group.by_name(self.testgroupvalues['name']) + if not group: + rev = model.repo.new_revision() + group = model.Group() + model.Session.add(group) + group.name = self.testgroupvalues['name'] + model.repo.commit_and_remove() + + rev = model.repo.new_revision() + group = model.Group.by_name(self.testgroupvalues['name']) + model.repo.commit_and_remove() + assert group + user = model.User.by_name(self.user_name) + + # delete it + offset = self.group_offset(self.testgroupvalues['name']) + res = self.app.delete(offset, status=[200], + extra_environ=self.admin_extra_environ) + + res = self.set_env(self.extra_environ) + + group = model.Group.by_name(self.testgroupvalues['name']) + assert group + assert group.state == 'deleted', group.state + + # Anyone can see groups especially sysadmins + # maybe we want to do something different with + # deleted groups but that would be a new requirement + #res = self.app.get(offset, status=[403]) + #self.assert_json_response(res, 'Access denied') + res = self.app.get(offset, status=[200], + extra_environ=self.admin_extra_environ) + res = self.set_env(self.extra_environ) + + def test_12_get_group_404(self): + # Test Package Entity Get 404. + assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count() + offset = self.group_offset(self.testgroupvalues['name']) + res = self.app.get(offset, status=404) + self.assert_json_response(res, 'Not found') + + def test_13_delete_group_404(self): + # Test Packages Entity Delete 404. + assert not model.Session.query(model.Group).filter_by(name=self.testgroupvalues['name']).count() + offset = self.group_offset(self.testgroupvalues['name']) + res = self.app.delete(offset, status=[404], + extra_environ=self.extra_environ) + self.assert_json_response(res, 'not found') diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_package.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_package.py new file mode 100644 index 00000000..1f0f08c7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_package.py @@ -0,0 +1,564 @@ +# encoding: utf-8 + +from __future__ import print_function + +import copy + +from nose.tools import assert_equal, assert_raises + +from ckan.lib.create_test_data import CreateTestData +import ckan.lib.search as search +from ckan.lib.search.common import SolrSettings + +from ckan.tests.legacy.functional.api.base import BaseModelApiTestCase + +import ckan.tests.legacy as tests + +# Todo: Remove this ckan.model stuff. +import ckan.model as model + +class PackagesTestCase(BaseModelApiTestCase): + + @classmethod + def setup_class(cls): + CreateTestData.create() + cls.user_name = u'annafan' # created in CreateTestData + cls.init_extra_environ(cls.user_name) + + @classmethod + def teardown_class(cls): + model.repo.rebuild_db() + + def teardown(self): + self.purge_package_by_name(self.package_fixture_data['name']) + + def get_groups_identifiers(self, test_groups, users=[]): + groups = [] + for grp in test_groups: + group = model.Group.get(grp) + if self.get_expected_api_version() == 1: + groups.append(group.name) + else: + groups.append(group.id) + return groups + + def test_register_get_ok(self): + offset = self.package_offset() + res = self.app.get(offset, status=self.STATUS_200_OK) + assert self.ref_package(self.anna) in res, res + assert self.ref_package(self.war) in res, res + + def test_register_post_ok(self): + assert not self.get_package_by_name(self.package_fixture_data['name']) + offset = self.package_offset() + postparams = '%s=1' % self.dumps(self.package_fixture_data) + res = self.app.post(offset, params=postparams, + status=self.STATUS_201_CREATED, + extra_environ=self.admin_extra_environ) + + # Check the returned package is as expected + pkg = self.loads(res.body) + assert_equal(pkg['name'], self.package_fixture_data['name']) + assert_equal(pkg['title'], self.package_fixture_data['title']) + assert_equal(set(pkg['tags']), set(self.package_fixture_data['tags'])) + assert_equal(len(pkg['resources']), len(self.package_fixture_data['resources'])) + assert_equal(pkg['extras'], self.package_fixture_data['extras']) + + # Check the value of the Location header. + location = res.header('Location') + + assert offset in location + res = self.app.get(location, status=self.STATUS_200_OK) + # Check the database record. + model.Session.remove() + package = self.get_package_by_name(self.package_fixture_data['name']) + assert package + self.assert_equal(package.title, self.package_fixture_data['title']) + self.assert_equal(package.url, self.package_fixture_data['url']) + self.assert_equal(package.license_id, self.testpackage_license_id) + self.assert_equal(len(package.get_tags()), 2) + self.assert_equal(len(package.extras), 2) + for key, value in self.package_fixture_data['extras'].items(): + self.assert_equal(package.extras[key], value) + self.assert_equal(len(package.resources), len(self.package_fixture_data['resources'])) + for (i, expected_resource) in enumerate(self.package_fixture_data['resources']): + package_resource = package.resources[i] + for key in expected_resource.keys(): + if key == 'extras': + package_resource_extras = getattr(package_resource, key) + expected_resource_extras = expected_resource[key].items() + for expected_extras_key, expected_extras_value in expected_resource_extras: + package_resource_value = package_resource_extras[expected_extras_key],\ + 'Package:%r Extras:%r Expected_extras:%r' % \ + (self.package_fixture_data['name'], + package_resource_extras, expected_resource) + else: + package_resource_value = getattr(package_resource, key, None) + if not package_resource_value: + package_resource_value = package_resource.extras[key] + + expected_resource_value = expected_resource[key] + self.assert_equal(package_resource_value, expected_resource_value) + + # Test Package Entity Get 200. + offset = self.package_offset(self.package_fixture_data['name']) + res = self.app.get(offset, status=self.STATUS_200_OK) + # Todo: Instead loads() the data and then check actual values. + assert self.package_fixture_data['name'] in res, res + assert '"license_id": "%s"' % self.package_fixture_data['license_id'] in res, res + assert self.package_fixture_data['tags'][0] in res, res + assert self.package_fixture_data['tags'][1] in res, res + assert '"extras": {' in res, res + for key, value in self.package_fixture_data['extras'].items(): + assert '"%s": "%s"' % (key, value) in res, res + + model.Session.remove() + + # Test Packages Register Post 409 (conflict - create duplicate package). + offset = self.package_offset() + postparams = '%s=1' % self.dumps(self.package_fixture_data) + res = self.app.post(offset, params=postparams, status=self.STATUS_409_CONFLICT, + extra_environ=self.admin_extra_environ) + model.Session.remove() + + def test_register_post_with_group(self): + assert not self.get_package_by_name(self.package_fixture_data['name']) + offset = self.package_offset() + + test_groups = [u'david'] + user = model.User.by_name(u'testsysadmin') + + groups = self.get_groups_identifiers(test_groups,[user]) + + package_fixture_data = self.package_fixture_data + package_fixture_data['groups'] = groups + data = self.dumps(package_fixture_data) + res = self.post_json(offset, data, status=self.STATUS_201_CREATED, + extra_environ={'Authorization':str(user.apikey)}) + + # Check the database record. + model.Session.remove() + package = self.get_package_by_name(self.package_fixture_data['name']) + assert package + pkg_groups = model.Session.query(model.Group).\ + join(model.Member, model.Member.group_id == model.Group.id).\ + filter(model.Member.table_id == package.id).all() + if self.get_expected_api_version() == 1: + self.assert_equal([g.name for g in pkg_groups], groups) + else: + self.assert_equal([g.id for g in pkg_groups], groups) + del package_fixture_data['groups'] + + def test_register_post_with_group_not_authorized(self): + assert not self.get_package_by_name(self.package_fixture_data['name']) + offset = self.package_offset() + + test_groups = [u'david'] + groups = self.get_groups_identifiers(test_groups) + + package_fixture_data = self.package_fixture_data + package_fixture_data['groups'] = groups + data = self.dumps(package_fixture_data) + res = self.post_json(offset, data, status=self.STATUS_403_ACCESS_DENIED, + extra_environ=self.extra_environ) + del package_fixture_data['groups'] + + def test_register_post_with_group_not_found(self): + assert not self.get_package_by_name(self.package_fixture_data['name']) + offset = self.package_offset() + + test_groups = [u'this-group-does-not-exist'] + groups = test_groups + + package_fixture_data = self.package_fixture_data + package_fixture_data['groups'] = groups + data = self.dumps(package_fixture_data) + res = self.post_json(offset, data, status=self.STATUS_404_NOT_FOUND, + extra_environ=self.extra_environ) + del package_fixture_data['groups'] + + def test_register_post_with_group_sysadmin(self): + assert not self.get_package_by_name(self.package_fixture_data['name']) + offset = self.package_offset() + user = model.User.by_name(u'testsysadmin') + test_groups = [u'david'] + groups = self.get_groups_identifiers(test_groups) + + package_fixture_data = self.package_fixture_data + package_fixture_data['groups'] = groups + data = self.dumps(package_fixture_data) + res = self.post_json(offset, data, status=self.STATUS_201_CREATED, + extra_environ={'Authorization':str(user.apikey)}) + # Check the database record. + model.Session.remove() + package = self.get_package_by_name(self.package_fixture_data['name']) + assert package + pkg_groups = model.Session.query(model.Group).\ + join(model.Member, model.Member.group_id == model.Group.id).\ + filter(model.Member.table_id == package.id).all() + if self.get_expected_api_version() == 1: + self.assert_equal([g.name for g in pkg_groups], groups) + else: + self.assert_equal([g.id for g in pkg_groups], groups) + + del package_fixture_data['groups'] + + def test_register_post_json(self): + assert not self.get_package_by_name(self.package_fixture_data['name']) + offset = self.package_offset() + data = self.dumps(self.package_fixture_data) + res = self.post_json(offset, data, status=self.STATUS_201_CREATED, + extra_environ=self.admin_extra_environ) + # Check the database record. + model.Session.remove() + package = self.get_package_by_name(self.package_fixture_data['name']) + assert package + self.assert_equal(package.title, self.package_fixture_data['title']) + + def test_register_post_indexerror(self): + """ + Test that we can't add a package if Solr is down. + """ + bad_solr_url = 'http://example.com/badsolrurl' + original_settings = SolrSettings.get()[0] + try: + SolrSettings.init(bad_solr_url) + + assert not self.get_package_by_name(self.package_fixture_data['name']) + offset = self.package_offset() + data = self.dumps(self.package_fixture_data) + + self.post_json(offset, data, status=500, extra_environ=self.admin_extra_environ) + model.Session.remove() + finally: + SolrSettings.init(original_settings) + + def test_register_post_tag_too_long(self): + pkg = {'name': 'test_tag_too_long', + 'tags': ['tagok', 't'*101]} + assert not self.get_package_by_name(pkg['name']) + offset = self.package_offset() + data = self.dumps(pkg) + res = self.post_json(offset, data, status=self.STATUS_409_CONFLICT, + extra_environ=self.admin_extra_environ) + assert 'length is more than maximum 100' in res.body, res.body + assert 'tagok' not in res.body + + def test_entity_get_ok_jsonp(self): + offset = self.anna_offset(postfix='?callback=jsoncallback') + res = self.app.get(offset, status=self.STATUS_200_OK) + import re + assert re.match('jsoncallback\(.*\);', res.body), res + # Unwrap JSONP callback (we want to look at the data). + msg = res.body[len('jsoncallback')+1:-2] + self.assert_msg_represents_anna(msg=msg) + + def test_entity_get_then_post(self): + # (ticket 662) Ensure an entity you 'get' from a register can be + # returned by posting it back + offset = self.package_offset(self.war.name) + res = self.app.get(offset, status=self.STATUS_200_OK) + data = self.loads(res.body) + + postparams = '%s=1' % self.dumps(data) + res = self.app.post(offset, params=postparams, + status=self.STATUS_200_OK, + extra_environ=self.admin_extra_environ) + data_returned = self.loads(res.body) + assert_equal(data['name'], data_returned['name']) + assert_equal(data['license_id'], data_returned['license_id']) + + def test_entity_get_then_post_new(self): + offset = self.package_offset(self.war.name) + res = self.app.get(offset, status=self.STATUS_200_OK) + data = self.loads(res.body) + + # change name and create a new package + data['name'] = u'newpkg' + data['id'] = None # ensure this doesn't clash or you get 409 error + postparams = '%s=1' % self.dumps(data) + # use russianfan now because he has rights to add this package to + # the 'david' group. + extra_environ = {'REMOTE_USER': 'testsysadmin'} + res = self.app.post(self.package_offset(), params=postparams, + status=self.STATUS_201_CREATED, + extra_environ=extra_environ) + try: + data_returned = self.loads(res.body) + assert_equal(data['name'], data_returned['name']) + assert_equal(data['license_id'], data_returned['license_id']) + finally: + self.purge_package_by_name(data['name']) + + def test_entity_post_changed_readonly(self): + # (ticket 662) Edit a readonly field gives error + offset = self.package_offset(self.war.name) + res = self.app.get(offset, status=self.STATUS_200_OK) + data = self.loads(res.body) + data['id'] = 'illegally changed value' + postparams = '%s=1' % self.dumps(data) + res = self.app.post(offset, params=postparams, + status=self.STATUS_409_CONFLICT, + extra_environ=self.admin_extra_environ) + assert "Cannot change value of key from" in res.body, res.body + assert "to illegally changed value. This key is read-only" in res.body, res.body + + def test_entity_update_denied(self): + offset = self.anna_offset() + postparams = '%s=1' % self.dumps(self.package_fixture_data) + res = self.app.post(offset, params=postparams, status=self.STATUS_403_ACCESS_DENIED) + + def test_entity_delete_denied(self): + offset = self.anna_offset() + res = self.app.delete(offset, status=self.STATUS_403_ACCESS_DENIED) + + def create_package_with_admin_user(self, package_data): + '''Creates a package with self.user as admin and provided package_data. + ''' + self.create_package(data=package_data) + + def test_package_update_ok_by_id(self): + self.assert_package_update_ok('id', 'post') + + def test_entity_update_ok_by_name(self): + self.assert_package_update_ok('name', 'post') + + def test_package_update_ok_by_id_by_put(self): + self.assert_package_update_ok('id', 'put') + + def test_entity_update_ok_by_name_by_put(self): + self.assert_package_update_ok('name', 'put') + + def test_package_update_delete_last_extra(self): + old_fixture_data = { + 'name': self.package_fixture_data['name'], + 'extras': { + u'key1': u'val1', + }, + } + new_fixture_data = { + 'name':u'somethingnew', + 'extras': { + u'key1': None, + }, + } + self.create_package_with_admin_user(old_fixture_data) + offset = self.package_offset(old_fixture_data['name']) + params = '%s=1' % self.dumps(new_fixture_data) + res = self.app.post(offset, params=params, status=self.STATUS_200_OK, + extra_environ=self.admin_extra_environ) + + try: + # Check the returned package is as expected + pkg = self.loads(res.body) + assert_equal(pkg['name'], new_fixture_data['name']) + expected_extras = copy.deepcopy(new_fixture_data['extras']) + del expected_extras['key1'] + assert_equal(pkg['extras'], expected_extras) + + # Check extra was deleted + model.Session.remove() + package = self.get_package_by_name(new_fixture_data['name']) + # - title + self.assert_equal(package.extras, {}) + finally: + self.purge_package_by_name(new_fixture_data['name']) + + def test_package_update_do_not_delete_last_extra(self): + old_fixture_data = { + 'name': self.package_fixture_data['name'], + 'extras': { + u'key1': u'val1', + }, + } + new_fixture_data = { + 'name':u'somethingnew', + 'extras': {}, # no extras specified, but existing + # ones should be left alone + } + self.create_package_with_admin_user(old_fixture_data) + offset = self.package_offset(old_fixture_data['name']) + params = '%s=1' % self.dumps(new_fixture_data) + res = self.app.post(offset, params=params, status=self.STATUS_200_OK, + extra_environ=self.admin_extra_environ) + + try: + # Check the returned package is as expected + pkg = self.loads(res.body) + assert_equal(pkg['name'], new_fixture_data['name']) + expected_extras = {u'key1': u'val1'} # should not be deleted + assert_equal(pkg['extras'], expected_extras) + + # Check extra was not deleted + model.Session.remove() + package = self.get_package_by_name(new_fixture_data['name']) + # - title + assert len(package.extras) == 1, package.extras + finally: + self.purge_package_by_name(new_fixture_data['name']) + + def test_entity_update_readd_tag(self): + name = self.package_fixture_data['name'] + old_fixture_data = { + 'name': name, + 'tags': ['tag 1.', 'tag2'] + } + new_fixture_data = { + 'name': name, + 'tags': ['tag 1.'] + } + self.create_package_with_admin_user(old_fixture_data) + offset = self.package_offset(name) + params = '%s=1' % self.dumps(new_fixture_data) + res = self.app.post(offset, params=params, status=self.STATUS_200_OK, + extra_environ=self.admin_extra_environ) + + # Check the returned package is as expected + pkg = self.loads(res.body) + assert_equal(pkg['name'], new_fixture_data['name']) + assert_equal(pkg['tags'], ['tag 1.']) + + package = self.get_package_by_name(new_fixture_data['name']) + assert len(package.get_tags()) == 1, package.get_tags() + + # now reinstate the tag + params = '%s=1' % self.dumps(old_fixture_data) + res = self.app.post(offset, params=params, status=self.STATUS_200_OK, + extra_environ=self.admin_extra_environ) + pkg = self.loads(res.body) + assert_equal(pkg['tags'], ['tag 1.', 'tag2']) + + def test_entity_update_conflict(self): + package1_name = self.package_fixture_data['name'] + package1_data = {'name': package1_name} + package1 = self.create_package_with_admin_user(package1_data) + package2_name = u'somethingnew' + package2_data = {'name': package2_name} + package2 = self.create_package_with_admin_user(package2_data) + try: + package1_offset = self.package_offset(package1_name) + # trying to rename package 1 to package 2's name + print(package1_offset, package2_data) + self.post(package1_offset, package2_data, self.STATUS_409_CONFLICT, extra_environ=self.admin_extra_environ) + finally: + self.purge_package_by_name(package2_name) + + def test_entity_update_empty(self): + package1_name = self.package_fixture_data['name'] + package1_data = {'name': package1_name} + package1 = self.create_package_with_admin_user(package1_data) + package2_data = '' # this is the error + package1_offset = self.package_offset(package1_name) + self.app.put(package1_offset, package2_data, + status=self.STATUS_400_BAD_REQUEST) + + def test_entity_update_indexerror(self): + """ + Test that we can't update a package if Solr is down. + """ + bad_solr_url = 'http://example.com/badsolrurl' + original_settings = SolrSettings.get()[0] + try: + SolrSettings.init(bad_solr_url) + + assert_raises( + search.SearchIndexError, self.assert_package_update_ok, 'name', 'post' + ) + finally: + SolrSettings.init(original_settings) + + def test_package_update_delete_resource(self): + old_fixture_data = { + 'name': self.package_fixture_data['name'], + 'resources': [{ + u'url':u'http://blah.com/file2.xml', + u'format':u'XML', + u'description':u'Appendix 1', + u'hash':u'def123', + u'alt_url':u'alt123', + },{ + u'url':u'http://blah.com/file3.xml', + u'format':u'XML', + u'description':u'Appenddic 2', + u'hash':u'ghi123', + u'alt_url':u'alt123', + }], + } + new_fixture_data = { + 'name':u'somethingnew', + 'resources': [], + } + self.create_package_with_admin_user(old_fixture_data) + offset = self.package_offset(old_fixture_data['name']) + params = '%s=1' % self.dumps(new_fixture_data) + res = self.app.post(offset, params=params, status=self.STATUS_200_OK, + extra_environ=self.admin_extra_environ) + + try: + # Check the returned package is as expected + pkg = self.loads(res.body) + assert_equal(pkg['name'], new_fixture_data['name']) + assert_equal(pkg['resources'], []) + + # Check resources were deleted + model.Session.remove() + package = self.get_package_by_name(new_fixture_data['name']) + self.assert_equal(len(package.resources), 0) + finally: + self.purge_package_by_name(new_fixture_data['name']) + + def test_entity_delete_ok(self): + # create a package with package_fixture_data + if not self.get_package_by_name(self.package_fixture_data['name']): + self.create_package(name=self.package_fixture_data['name']) + assert self.get_package_by_name(self.package_fixture_data['name']) + # delete it + offset = self.package_offset(self.package_fixture_data['name']) + res = self.app.delete(offset, status=self.STATUS_200_OK, + extra_environ=self.admin_extra_environ) + package = self.get_package_by_name(self.package_fixture_data['name']) + self.assert_equal(package.state, 'deleted') + model.Session.remove() + + def test_entity_delete_ok_without_request_headers(self): + # create a package with package_fixture_data + if not self.get_package_by_name(self.package_fixture_data['name']): + self.create_package(name=self.package_fixture_data['name']) + assert self.get_package_by_name(self.package_fixture_data['name']) + # delete it + offset = self.package_offset(self.package_fixture_data['name']) + res = self.delete_request(offset, status=self.STATUS_200_OK, + extra_environ=self.admin_extra_environ) + package = self.get_package_by_name(self.package_fixture_data['name']) + self.assert_equal(package.state, 'deleted') + model.Session.remove() + + def test_create_private_package_with_no_organization(self): + '''Test that private packages with no organization cannot be created. + + ''' + testsysadmin = model.User.by_name('testsysadmin') + result = tests.call_action_api(self.app, 'package_create', name='test', + private=True, apikey=testsysadmin.apikey, status=409) + assert result == {'__type': 'Validation Error', + 'private': ["Datasets with no organization can't be private."]} + + def test_create_public_package_with_no_organization(self): + '''Test that public packages with no organization can be created.''' + testsysadmin = model.User.by_name('testsysadmin') + tests.call_action_api(self.app, 'package_create', name='test', + private=False, apikey=testsysadmin.apikey) + + def test_make_package_with_no_organization_private(self): + '''Test that private packages with no organization cannot be created + by package_update. + + ''' + testsysadmin = model.User.by_name('testsysadmin') + package = tests.call_action_api(self.app, 'package_create', + name='test_2', private=False, apikey=testsysadmin.apikey) + package['private'] = True + result = tests.call_action_api(self.app, 'package_update', + apikey=testsysadmin.apikey, status=409, **package) + assert result == {'__type': 'Validation Error', + 'private': ["Datasets with no organization can't be private."]} diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_ratings.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_ratings.py new file mode 100644 index 00000000..f6df158d --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_ratings.py @@ -0,0 +1,100 @@ +# encoding: utf-8 + +from nose.tools import assert_equal +from nose.plugins.skip import SkipTest + +from ckan import model +from ckan.lib.create_test_data import CreateTestData + +from ckan.tests.legacy.functional.api.base import BaseModelApiTestCase + + +class RatingsTestCase(BaseModelApiTestCase): + + @classmethod + def setup_class(cls): + CreateTestData.create() + cls.testsysadmin = model.User.by_name(u'testsysadmin') + cls.comment = u'Comment umlaut: \xfc.' + cls.user_name = u'annafan' # created in CreateTestData + cls.init_extra_environ(cls.user_name) + + @classmethod + def teardown_class(cls): + model.repo.rebuild_db() + + def test_register_get(self): + raise SkipTest('"Rating register get" functionality is not implemented') + rating1 = model.Rating(user_ip_address='1.2.3.4', + package=self.anna, + rating=4.0) + rating2 = model.Rating(user=model.User.by_name(u'annafan'), + package=self.anna, + rating=2.0) + model.Session.add_all((rating1, rating2)) + model.repo.commit_and_remove() + + offset = self.rating_offset() + res = self.app.get(offset, status=[200]) + + def test_entity_get(self): + raise SkipTest('"Rating entity get" functionality is not implemented') + rating = model.Rating(user_ip_address='1.2.3.4', + package=self.anna, + rating=4.0) + model.Session.add(rating) + model.repo.commit_and_remove() + + offset = self.rating_offset(self.anna.name) + res = self.app.get(offset, status=[200]) + assert_equal(res, rating_opts['rating']) + + def test_register_post(self): + # Test Rating Register Post 200. + self.clear_all_tst_ratings() + offset = self.rating_offset() + rating_opts = {'package':u'warandpeace', + 'rating':5} + pkg_name = rating_opts['package'] + postparams = '%s=1' % self.dumps(rating_opts) + res = self.app.post(offset, params=postparams, status=[201], + extra_environ=self.extra_environ) + model.Session.remove() + pkg = self.get_package_by_name(pkg_name) + assert pkg + assert len(pkg.ratings) == 1 + assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings + + # Get package to see rating + offset = self.package_offset(pkg_name) + res = self.app.get(offset, status=[200]) + assert pkg_name in res, res + assert '"ratings_average": %s.0' % rating_opts['rating'] in res, res + assert '"ratings_count": 1' in res, res + + model.Session.remove() + + # Rerate package + offset = self.rating_offset() + postparams = '%s=1' % self.dumps(rating_opts) + res = self.app.post(offset, params=postparams, status=[201], + extra_environ=self.extra_environ) + model.Session.remove() + pkg = self.get_package_by_name(pkg_name) + assert pkg + assert len(pkg.ratings) == 1 + assert pkg.ratings[0].rating == rating_opts['rating'], pkg.ratings + + def test_entity_post_invalid(self): + self.clear_all_tst_ratings() + offset = self.rating_offset() + rating_opts = {'package':u'warandpeace', + 'rating':0} + postparams = '%s=1' % self.dumps(rating_opts) + res = self.app.post(offset, params=postparams, status=[409], + extra_environ=self.extra_environ) + self.assert_json_response(res, 'rating') + model.Session.remove() + pkg = self.get_package_by_name(rating_opts['package']) + assert pkg + assert len(pkg.ratings) == 0 diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_revisions.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_revisions.py new file mode 100644 index 00000000..d0837536 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_revisions.py @@ -0,0 +1,63 @@ +# encoding: utf-8 + +from nose.tools import assert_equal + +from ckan import model +from ckan.lib.create_test_data import CreateTestData + +from ckan.tests.legacy.functional.api.base import BaseModelApiTestCase + + +class RevisionsTestCase(BaseModelApiTestCase): + + @classmethod + def setup_class(cls): + CreateTestData.create() + cls.user_name = u'annafan' # created in CreateTestData + cls.init_extra_environ(cls.user_name) + + @classmethod + def teardown_class(cls): + model.repo.rebuild_db() + + def test_register_get_ok(self): + # Comparison list - newest first + revs = model.Session.query(model.Revision).\ + order_by(model.Revision.timestamp.desc()).all() + assert revs + + # Check list of revisions + offset = self.revision_offset() + res = self.app.get(offset, status=200) + revs_result = self.data_from_res(res) + + assert_equal(revs_result, [rev.id for rev in revs]) + + def test_entity_get_ok(self): + rev = model.repo.history().all()[0] # newest revision is the creation of pkgs + assert rev.id + assert rev.timestamp.isoformat() + offset = self.revision_offset(rev.id) + response = self.app.get(offset, status=[200]) + response_data = self.data_from_res(response) + assert_equal(rev.id, response_data['id']) + assert_equal(rev.timestamp.isoformat(), response_data['timestamp']) + assert 'packages' in response_data + packages = response_data['packages'] + assert isinstance(packages, list) + #assert len(packages) != 0, "Revision packages is empty: %s" % packages + assert self.ref_package(self.anna) in packages, packages + assert self.ref_package(self.war) in packages, packages + + def test_entity_get_404(self): + revision_id = "xxxxxxxxxxxxxxxxxxxxxxxxxx" + offset = self.revision_offset(revision_id) + res = self.app.get(offset, status=404) + self.assert_json_response(res, 'Not found') + + def test_entity_get_301(self): + # see what happens when you miss the ID altogether + revision_id = '' + offset = self.revision_offset(revision_id) + res = self.app.get(offset, status=301) + # redirects "/api/revision/" to "/api/revision" diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_tag.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_tag.py new file mode 100644 index 00000000..0e75766d --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_tag.py @@ -0,0 +1,57 @@ +# encoding: utf-8 + +import copy + +from nose.tools import assert_equal + +from ckan import model +from ckan.lib.create_test_data import CreateTestData +import ckan.lib.search as search + +from ckan.tests.legacy.functional.api.base import BaseModelApiTestCase + + +class TagsTestCase(BaseModelApiTestCase): + + @classmethod + def setup_class(cls): + search.clear_all() + CreateTestData.create() + cls.testsysadmin = model.User.by_name(u'testsysadmin') + cls.comment = u'Comment umlaut: \xfc.' + cls.user_name = u'annafan' # created in CreateTestData + cls.init_extra_environ(cls.user_name) + + @classmethod + def teardown_class(cls): + search.clear_all() + model.repo.rebuild_db() + + def test_register_get_ok(self): + offset = self.tag_offset() + res = self.app.get(offset, status=self.STATUS_200_OK) + results = self.loads(res.body) + assert self.russian.name in results, results + assert self.tolstoy.name in results, results + assert self.flexible_tag.name in results, results + + def test_entity_get_ok(self): + offset = self.tag_offset(self.russian.name) + res = self.app.get(offset, status=self.STATUS_200_OK) + self.assert_msg_represents_russian(msg=res.body) + + def test_entity_get_ok_flexible_tag(self): + """ + Asserts that searching for a tag name with spaces and punctuation works. + + The tag name is u'Flexible \u30a1', and both the 'warandpeace' + and 'annakarenina' packages should be returned. + """ + offset = self.tag_offset(self.flexible_tag.name) + res = self.app.get(offset, status=self.STATUS_200_OK) + self.assert_msg_represents_flexible_tag(msg=res.body) + + def test_entity_get_not_found(self): + offset = self.tag_offset('doesntexist') + res = self.app.get(offset, status=404) + self.assert_json_response(res, 'Not found') diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_vocabulary.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_vocabulary.py new file mode 100644 index 00000000..feb03af3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/functional/api/model/test_vocabulary.py @@ -0,0 +1,1126 @@ +# encoding: utf-8 + +import ckan +import pylons.test +import paste.fixture +import ckan.lib.helpers as helpers +import ckan.lib.dictization.model_dictize as model_dictize + + +class TestVocabulary(object): + + @classmethod + def setup_class(self): + self.app = paste.fixture.TestApp(pylons.test.pylonsapp) + + @classmethod + def teardown_class(self): + ckan.model.repo.rebuild_db() + + def setup(self): + self.clean_vocab() + model = ckan.model + context = {'model': model} + + genre = model.Vocabulary("Genre") + time_period = ckan.model.Vocabulary("Time Period") + composers = ckan.model.Vocabulary("Composers") + model.Session.add_all([genre, time_period, composers]) + + self.genre_vocab = model_dictize.vocabulary_dictize(genre, context) + self.timeperiod_vocab = model_dictize.vocabulary_dictize(time_period, + context) + self.composers_vocab = model_dictize.vocabulary_dictize(composers, + context) + ckan.model.Session.commit() + + self.sysadmin_user = ckan.model.User.get('admin') + self.normal_user = ckan.model.User.get('normal') + if not self.sysadmin_user: + normal_user = ckan.model.User(name=u'normal', password=u'annafan') + sysadmin_user = ckan.model.User(name=u'admin', + password=u'testsysadmin') + sysadmin_user.sysadmin = True + ckan.model.Session.add(normal_user) + ckan.model.Session.add(sysadmin_user) + ckan.model.Session.commit() + self.sysadmin_user = ckan.model.User.get('admin') + self.normal_user = ckan.model.User.get('normal') + self.sysadmin_apikey = self.sysadmin_user.apikey + + def clean_vocab(self): + ckan.model.Session.execute('delete from package_tag_revision') + ckan.model.Session.execute('delete from package_tag') + ckan.model.Session.execute('delete from tag') + ckan.model.Session.execute('delete from vocabulary') + ckan.model.Session.commit() + + @classmethod + def _post(self, url, params=None, extra_environ=None): + if params is None: + params = {} + param_string = helpers.json.dumps(params) + response = self.app.post(url, params=param_string, + extra_environ=extra_environ) + assert not response.errors + return response.json + + @classmethod + def _create_vocabulary(self, vocab_name=None, user=None): + # Create a new vocabulary. + params = {'name': vocab_name} + if user: + extra_environ = {'Authorization': str(user.apikey)} + else: + extra_environ = None + response = self._post('/api/action/vocabulary_create', params=params, + extra_environ=extra_environ) + + # Check the values of the response. + assert response['success'] is True + assert response['result'] + created_vocab = response['result'] + assert created_vocab['name'] == vocab_name + assert created_vocab['id'] + + # Get the list of vocabularies. + response = self._post('/api/action/vocabulary_list') + # Check that the vocabulary we created is in the list. + assert response['success'] is True + assert response['result'] + assert response['result'].count(created_vocab) == 1 + + # Get the created vocabulary. + params = {'id': created_vocab['id']} + response = self._post('/api/action/vocabulary_show', params) + # Check that retrieving the vocab by name gives the same result. + by_name_params = {'id': created_vocab['name']} + assert response == self._post('/api/action/vocabulary_show', + by_name_params) + # Check that it matches what we created. + assert response['success'] is True + assert response['result'] == created_vocab + + return created_vocab + + def _update_vocabulary(self, params, user=None): + if user: + extra_environ = {'Authorization': str(user.apikey)} + else: + extra_environ = None + + original_vocab = self._post('/api/action/vocabulary_show', + {'id': params.get('id') or params.get('name')})['result'] + + response = self._post('/api/action/vocabulary_update', params=params, + extra_environ=extra_environ) + + # Check the values of the response. + assert response['success'] is True + assert response['result'] + updated_vocab = response['result'] + # id should never change. + assert updated_vocab['id'] == original_vocab['id'] + if 'id' in params: + assert updated_vocab['id'] == params['id'] + # name should change only if given in params. + if 'name' in params: + assert updated_vocab['name'] == params['name'] + else: + assert updated_vocab['name'] == original_vocab['name'] + # tags should change only if given in params. + if 'tags' in params: + assert sorted([tag['name'] for tag in params['tags']]) \ + == sorted([tag['name'] for tag in updated_vocab['tags']]) + else: + assert updated_vocab['tags'] == original_vocab['tags'] + + # Get the list of vocabularies. + response = self._post('/api/action/vocabulary_list') + # Check that the vocabulary we created is in the list. + assert response['success'] is True + assert response['result'] + assert response['result'].count(updated_vocab) == 1 + + # Get the created vocabulary. + params = {'id': updated_vocab['id']} + response = self._post('/api/action/vocabulary_show', params) + # Check that retrieving the vocab by name gives the same result. + by_name_params = {'id': updated_vocab['name']} + assert response == self._post('/api/action/vocabulary_show', + by_name_params) + # Check that it matches what we created. + assert response['success'] is True + assert response['result'] == updated_vocab + + return updated_vocab + + def _delete_vocabulary(self, vocab_id, user=None): + if user: + extra_environ = {'Authorization': str(user.apikey)} + else: + extra_environ = None + params = {'id': vocab_id} + response = self._post('/api/action/vocabulary_delete', params=params, + extra_environ=extra_environ) + + # Check the values of the response. + assert response['success'] is True + assert response['result'] is None + response['result'] + + # Get the list of vocabularies. + response = self._post('/api/action/vocabulary_list') + assert response['success'] is True + assert response['result'] + # Check that the vocabulary we deleted is not in the list. + assert vocab_id not in [vocab['id'] for vocab in response['result']] + + # Check that the deleted vocabulary can no longer be retrieved. + response = self.app.post('/api/action/vocabulary_show', + params=helpers.json.dumps(params), + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=404) + assert response.json['success'] is False + + def _list_tags(self, vocabulary=None, user=None): + params = {} + if vocabulary: + params['vocabulary_id'] = vocabulary['id'] + if user: + extra_environ = {'Authorization': str(user.apikey)} + else: + extra_environ = None + response = self._post('/api/action/tag_list', params=params, + extra_environ=extra_environ) + assert response['success'] is True + return response['result'] + + def _create_tag(self, user, tag_name, vocabulary=None): + tag_dict = {'name': tag_name} + if vocabulary: + tag_dict['vocabulary_id'] = vocabulary['id'] + if user: + extra_environ = {'Authorization': str(user.apikey)} + else: + extra_environ = None + response = self._post('/api/action/tag_create', params=tag_dict, + extra_environ=extra_environ) + assert response['success'] is True + return response['result'] + + def _delete_tag(self, user, tag_id_or_name, vocab_id_or_name=None): + params = {'id': tag_id_or_name} + if vocab_id_or_name: + params['vocabulary_id'] = vocab_id_or_name + if user: + extra_environ = {'Authorization': str(user.apikey)} + else: + extra_environ = None + response = self._post('/api/action/tag_delete', params=params, + extra_environ=extra_environ) + assert response['success'] is True + return response['result'] + + def test_vocabulary_create(self): + '''Test adding a new vocabulary to a CKAN instance via the action + API. + + ''' + self._create_vocabulary(vocab_name="My cool vocab", + user=self.sysadmin_user) + + def test_vocabulary_create_with_tags(self): + '''Test adding a new vocabulary with some tags. + + ''' + params = {'name': 'foobar'} + tag1 = {'name': 'foo'} + tag2 = {'name': 'bar'} + params['tags'] = [tag1, tag2] + response = self._post('/api/action/vocabulary_create', + params=params, + extra_environ={'Authorization': str(self.sysadmin_apikey)}) + assert response['success'] is True + assert response['result'] + created_vocab = response['result'] + assert created_vocab['name'] == 'foobar' + assert created_vocab['id'] + + # Get the list of vocabularies. + response = self._post('/api/action/vocabulary_list') + # Check that the vocabulary we created is in the list. + assert response['success'] is True + assert response['result'] + assert response['result'].count(created_vocab) == 1 + + # Get the created vocabulary. + params = {'id': created_vocab['id']} + response = self._post('/api/action/vocabulary_show', params) + # Check that retrieving the vocab by name gives the same result. + by_name_params = {'id': created_vocab['name']} + assert response == self._post('/api/action/vocabulary_show', + by_name_params) + # Check that it matches what we created. + assert response['success'] is True + assert response['result'] == created_vocab + + # Get the list of tags for the vocabulary. + tags = self._list_tags(created_vocab) + assert len(tags) == 2 + assert tags.count('foo') == 1 + assert tags.count('bar') == 1 + + def test_vocabulary_create_bad_tags(self): + '''Test creating new vocabularies with invalid tags. + + ''' + for tags in ( + [{'id': 'xxx'}, {'name': 'foo'}], + [{'name': 'foo'}, {'name': None}], + [{'name': 'foo'}, {'name': ''}], + [{'name': 'foo'}, {'name': 'f'}], + [{'name': 'f' * 200}, {'name': 'foo'}], + [{'name': 'Invalid!'}, {'name': 'foo'}], + ): + params = {'name': 'foobar', 'tags': tags} + response = self.app.post('/api/action/vocabulary_create', + params=helpers.json.dumps(params), + extra_environ={'Authorization': str(self.sysadmin_apikey)}, + status=409) + assert response.json['success'] is False + assert 'tags' in response.json['error'] + assert len(response.json['error']) == 2 + + def test_vocabulary_create_none_tags(self): + '''Test creating new vocabularies with None for 'tags'. + + ''' + params = {'name': 'foobar', 'tags': None} + response = self.app.post('/api/action/vocabulary_create', + params=helpers.json.dumps(params), + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=400) + assert "Integrity Error" in response.body + + def test_vocabulary_create_empty_tags(self): + '''Test creating new vocabularies with [] for 'tags'. + + ''' + params = {'name': 'foobar', 'tags': []} + response = self.app.post('/api/action/vocabulary_create', + params=helpers.json.dumps(params), + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=200) + assert response.json['success'] is True + assert response.json['result'] + created_vocab = response.json['result'] + assert created_vocab['name'] == 'foobar' + assert created_vocab['id'] + assert created_vocab['tags'] == [] + params = {'id': created_vocab['id']} + response = self._post('/api/action/vocabulary_show', params) + assert response['success'] is True + assert response['result'] == created_vocab + tags = self._list_tags(created_vocab) + assert tags == [] + + def test_vocabulary_create_id(self): + '''Test error response when user tries to supply their own ID when + creating a vocabulary. + + ''' + params = {'id': 'xxx', 'name': 'foobar'} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_create', + params=param_string, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=409) + assert response.json['success'] is False + assert response.json['error']['id'] == [u'The input field id was ' + 'not expected.'] + + def test_vocabulary_create_no_name(self): + '''Test error response when user tries to create a vocab without a + name. + + ''' + params = {} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_create', + params=param_string, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=409) + assert response.json['success'] is False + assert response.json['error']['name'] == [u'Missing value'] + + def test_vocabulary_create_invalid_name(self): + '''Test error response when user tries to create a vocab with an + invalid name. + + ''' + for name in (None, '', 'a', 'foobar' * 100): + params = {'name': name} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_create', + params=param_string, + extra_environ={'Authorization': + str(self.sysadmin_apikey)}, + status=409) + assert response.json['success'] is False + assert response.json['error']['name'] + + def test_vocabulary_create_exists(self): + '''Test error response when user tries to create a vocab that already + exists. + + ''' + params = {'name': self.genre_vocab['name']} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_create', + params=param_string, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=409) + assert response.json['success'] is False + assert response.json['error']['name'] == [u'That vocabulary name is ' + 'already in use.'] + + def test_vocabulary_create_not_logged_in(self): + '''Test that users who are not logged in cannot create vocabularies.''' + + params = {'name': + "Spam Vocabulary: SpamCo Duck Rental: Rent Your Ducks From Us!"} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_create', + params=param_string, + status=403) + assert response.json['success'] is False + assert response.json['error']['__type'] == 'Authorization Error' + + def test_vocabulary_create_not_authorized(self): + '''Test that users who are not authorized cannot create vocabs.''' + + params = {'name': 'My Unauthorised Vocabulary'} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_create', + params=param_string, + extra_environ={'Authorization': + str(self.normal_user.apikey)}, + status=403) + assert response.json['success'] is False + assert response.json['error']['__type'] == 'Authorization Error' + + def test_vocabulary_update_id_only(self): + self._update_vocabulary({'id': self.genre_vocab['id']}, + self.sysadmin_user) + + def test_vocabulary_update_id_and_same_name(self): + self._update_vocabulary({'id': self.genre_vocab['id'], + 'name': self.genre_vocab['name']}, self.sysadmin_user) + + def test_vocabulary_update_id_and_new_name(self): + self._update_vocabulary({'id': self.genre_vocab['id'], + 'name': 'new name'}, self.sysadmin_user) + + def test_vocabulary_update_id_and_same_tags(self): + self._update_vocabulary({'id': self.genre_vocab['id'], + 'tags': self.genre_vocab['tags']}, self.sysadmin_user) + + def test_vocabulary_update_id_and_new_tags(self): + tags = [ + {'name': 'new test tag one'}, + {'name': 'new test tag two'}, + {'name': 'new test tag three'}, + ] + self._update_vocabulary({'id': self.genre_vocab['id'], 'tags': tags}, + self.sysadmin_user) + + def test_vocabulary_update_id_same_name_and_same_tags(self): + self._update_vocabulary({'id': self.genre_vocab['id'], + 'name': self.genre_vocab['name'], + 'tags': self.genre_vocab['tags']}, self.sysadmin_user) + + def test_vocabulary_update_id_same_name_and_new_tags(self): + tags = [ + {'name': 'new test tag one'}, + {'name': 'new test tag two'}, + {'name': 'new test tag three'}, + ] + self._update_vocabulary({'id': self.genre_vocab['id'], + 'name': self.genre_vocab['name'], + 'tags': tags}, self.sysadmin_user) + + def test_vocabulary_update_id_new_name_and_same_tags(self): + self._update_vocabulary({'id': self.genre_vocab['id'], + 'name': 'new name', + 'tags': self.genre_vocab['tags']}, self.sysadmin_user) + + def test_vocabulary_update_not_exists(self): + '''Test the error response given when a user tries to update a + vocabulary that doesn't exist. + + ''' + params = {'id': 'xxxxxxx', 'name': 'updated_name'} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_update', + params=param_string, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=404) + assert response.json['success'] is False + assert response.json['error']['message'].startswith('Not found: ') + + def test_vocabulary_update_no_id(self): + params = {'name': 'bagel radio'} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_update', + params=param_string, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=409) + assert response.json['success'] is False + assert 'id' in response.json['error'] + assert response.json['error']['id'] == 'id not in data' + + def test_vocabulary_update_not_logged_in(self): + '''Test that users who are not logged in cannot update vocabularies.''' + params = {'id': self.genre_vocab['id']} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_update', + params=param_string, + status=403) + assert response.json['success'] is False + assert response.json['error']['__type'] == 'Authorization Error' + + def test_vocabulary_update_with_tags(self): + tags = [ + {'name': 'drone'}, + {'name': 'noise'}, + {'name': 'fuzz'}, + {'name': 'field recordings'}, + {'name': 'hypnagogia'}, + {'name': 'textures without rhythm'}, + ] + self._update_vocabulary( + { + 'id': self.genre_vocab['id'], + 'name': self.genre_vocab['name'], + 'tags': tags + }, + self.sysadmin_user) + + params = {'id': self.genre_vocab['id']} + response = self._post('/api/action/vocabulary_show', params) + # Check that retrieving the vocab by name gives the same result. + assert len(response['result']['tags']) == len(tags) + + def test_vocabulary_update_not_authorized(self): + '''Test that users who are not authorized cannot update vocabs.''' + params = {'id': self.genre_vocab['id']} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_update', + params=param_string, + extra_environ={'Authorization': + str(self.normal_user.apikey)}, + status=403) + assert response.json['success'] is False + assert response.json['error']['message'] == 'Access denied' + + def test_vocabulary_update_bad_tags(self): + '''Test updating vocabularies with invalid tags. + + ''' + apikey = str(self.sysadmin_user.apikey) + + for tags in ( + [{'id': 'xxx'}, {'name': 'foo'}], + [{'name': 'foo'}, {'name': None}], + [{'name': 'foo'}, {'name': ''}], + [{'name': 'foo'}, {'name': 'f'}], + [{'name': 'f' * 200}, {'name': 'foo'}], + [{'name': 'Invalid!'}, {'name': 'foo'}], + ): + params = {'id': self.genre_vocab['name'], 'tags': tags} + response = self.app.post('/api/action/vocabulary_update', + params=helpers.json.dumps(params), + extra_environ={'Authorization': apikey}, + status=409) + assert response.json['success'] is False + assert response.json['error']['tags'] + + def test_vocabulary_update_none_tags(self): + '''Test updating vocabularies with None for 'tags'. + + ''' + params = {'id': self.genre_vocab['id'], 'tags': None} + response = self.app.post('/api/action/vocabulary_update', + params=helpers.json.dumps(params), + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=400) + assert "Integrity Error" in response.body, response.body + + def test_vocabulary_update_empty_tags(self): + '''Test updating vocabularies with [] for 'tags'. + + ''' + params = {'id': self.genre_vocab['id'], 'tags': []} + response = self.app.post('/api/action/vocabulary_update', + params=helpers.json.dumps(params), + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=200) + assert response.json['success'] is True + assert response.json['result'] + updated_vocab = response.json['result'] + assert updated_vocab['name'] == self.genre_vocab['name'] + assert updated_vocab['id'] == self.genre_vocab['id'] + assert updated_vocab['tags'] == [] + params = {'id': updated_vocab['id']} + response = self._post('/api/action/vocabulary_show', params) + assert response['success'] is True + assert response['result'] == updated_vocab + tags = self._list_tags(updated_vocab) + assert tags == [] + + def test_vocabulary_delete(self): + self._delete_vocabulary(self.genre_vocab['id'], self.sysadmin_user) + + def test_vocabulary_delete_not_exists(self): + '''Test the error response given when a user tries to delete a + vocabulary that doesn't exist. + + ''' + params = {'id': 'xxxxxxx'} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_delete', + params=param_string, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=404) + assert response.json['success'] is False + assert response.json['error']['message'].startswith('Not found: ' + 'Could not find vocabulary') + + def test_vocabulary_delete_no_id(self): + '''Test the error response given when a user tries to delete a + vocabulary without giving the vocabulary id. + + ''' + params = {} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_delete', + params=param_string, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=409) + assert response.json['success'] is False + assert 'id' in response.json['error'] + assert response.json['error']['id'] == 'id not in data' + + def test_vocabulary_delete_not_logged_in(self): + '''Test that users who are not logged in cannot delete vocabularies.''' + params = {'id': self.genre_vocab['id']} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_delete', + params=param_string, + status=403) + assert response.json['success'] is False + assert response.json['error']['__type'] == 'Authorization Error' + + def test_vocabulary_delete_not_authorized(self): + '''Test that users who are not authorized cannot delete vocabs.''' + params = {'id': self.genre_vocab['id']} + param_string = helpers.json.dumps(params) + response = self.app.post('/api/action/vocabulary_delete', + params=param_string, + extra_environ={'Authorization': + str(self.normal_user.apikey)}, + status=403) + assert response.json['success'] is False + assert response.json['error']['__type'] == 'Authorization Error' + + def test_add_tag_to_vocab(self): + '''Test that a tag can be added to and then retrieved from a vocab.''' + vocab = self.genre_vocab + tags_before = self._list_tags(vocab) + tag_created = self._create_tag(self.sysadmin_user, 'noise', vocab) + tags_after = self._list_tags(vocab) + new_tag_names = [tag_name for tag_name in tags_after if tag_name not in + tags_before] + assert len(new_tag_names) == 1 + assert tag_created['name'] in new_tag_names + + def test_add_tag_no_vocab(self): + '''Test the error response when a user tries to create a tag without + specifying a vocab. + + ''' + tag_dict = {'name': 'noise'} + tag_string = helpers.json.dumps(tag_dict) + response = self.app.post('/api/action/tag_create', + params=tag_string, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=409) + assert response.json['success'] is False + assert response.json['error']['vocabulary_id'] == ['Missing value'] + + def test_add_tag_vocab_not_exists(self): + '''Test the error response when a user tries to add a tag to a vocab + that doesn't exist. + + ''' + tag_dict = {'name': 'noise', 'vocabulary_id': 'does not exist'} + tag_string = helpers.json.dumps(tag_dict) + response = self.app.post('/api/action/tag_create', + params=tag_string, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=409) + assert response.json['success'] is False + assert response.json['error']['vocabulary_id'] == [ + 'Tag vocabulary was not found.'] + + def test_add_tag_already_added(self): + '''Test the error response when a user tries to add a tag to a vocab + that already has a tag with the same name. + + ''' + self.test_add_tag_to_vocab() + vocab = self.genre_vocab + tag_dict = {'name': 'noise', 'vocabulary_id': vocab['id']} + tag_string = helpers.json.dumps(tag_dict) + response = self.app.post('/api/action/tag_create', + params=tag_string, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=409) + assert response.json['success'] is False + assert response.json['error']['vocabulary_id'][0].startswith( + 'Tag noise already belongs to vocabulary') + + def test_add_tag_with_id(self): + '''Test the error response when a user tries to specify the tag ID when + adding a tag to a vocab. + + ''' + tag_dict = { + 'id': 'dsagdsgsgsd', + 'name': 'noise', + 'vocabulary_id': self.genre_vocab['id'] + } + tag_string = helpers.json.dumps(tag_dict) + response = self.app.post('/api/action/tag_create', + params=tag_string, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=409) + assert response.json['success'] is False + assert response.json['error']['id'] == [u'The input field id was not ' + 'expected.'] + + def test_add_tag_without_name(self): + '''Test the error response when a user tries to create a tag without a + name. + + ''' + tag_dict = { + 'vocabulary_id': self.genre_vocab['id'] + } + tag_string = helpers.json.dumps(tag_dict) + response = self.app.post('/api/action/tag_create', + params=tag_string, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=409) + assert response.json['success'] is False + assert response.json['error']['name'] == [u'Missing value'] + + def test_add_tag_invalid_name(self): + for name in ('Not a valid tag name!', '', None): + tag_dict = { + 'name': name, + 'vocabulary_id': self.genre_vocab['id'] + } + tag_string = helpers.json.dumps(tag_dict) + response = self.app.post('/api/action/tag_create', + params=tag_string, + extra_environ={'Authorization': + str(self.sysadmin_apikey)}, + status=409) + assert response.json['success'] is False + assert response.json['error']['name'] + + def test_add_tag_invalid_vocab_id(self): + tag_dict = { + 'name': 'noise', + 'vocabulary_id': 'xxcxzczxczxc', + } + tag_string = helpers.json.dumps(tag_dict) + response = self.app.post('/api/action/tag_create', + params=tag_string, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=409) + assert response.json['success'] is False + assert response.json['error']['vocabulary_id'] == [ + u'Tag vocabulary was not found.'] + + def test_add_tag_not_logged_in(self): + tag_dict = { + 'name': 'noise', + 'vocabulary_id': self.genre_vocab['id'] + } + tag_string = helpers.json.dumps(tag_dict) + response = self.app.post('/api/action/tag_create', + params=tag_string, + status=403) + assert response.json['success'] is False + assert response.json['error']['__type'] == 'Authorization Error' + + def test_add_tag_not_authorized(self): + tag_dict = { + 'name': 'noise', + 'vocabulary_id': self.genre_vocab['id'] + } + tag_string = helpers.json.dumps(tag_dict) + response = self.app.post('/api/action/tag_create', + params=tag_string, + extra_environ={'Authorization': + str(self.normal_user.apikey)}, + status=403) + assert response.json['success'] is False + assert response.json['error']['__type'] == 'Authorization Error' + + def test_add_vocab_tag_to_dataset(self): + '''Test that a tag belonging to a vocab can be added to a dataset, + retrieved from the dataset, and then removed from the dataset.''' + + ckan.model.repo.rebuild_db() + self.setup() + ckan.tests.legacy.CreateTestData.create() + # First add a tag to the vocab. + vocab = self.genre_vocab + tag = self._create_tag(self.sysadmin_user, 'noise', vocab) + + # Get a package from the API. + package = (self._post('/api/action/package_show', + {'id': self._post('/api/action/package_list')['result'][0]}) + ['result']) + + # Add the new vocab tag to the package. + package['tags'].append(tag) + + updated_package = self._post('/api/action/package_update', + params={'id': package['id'], 'tags': package['tags']}, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)})['result'] + + # Test that the new vocab tag was added to the package. + tags_in_pkg = [tag_in_pkg for tag_in_pkg in updated_package['tags'] if + tag_in_pkg['name'] == tag['name'] and + tag_in_pkg['vocabulary_id'] == tag['vocabulary_id']] + assert len(tags_in_pkg) == 1 + + # Test that the package appears in tag_show. + noise_tag = self._post('/api/action/tag_show', + params={'id': 'noise', + 'vocabulary_id': vocab['id'], + 'include_datasets': True} + )['result'] + assert len([p for p in noise_tag['packages'] if + p['id'] == updated_package['id']]) == 1 + + # Remove the new vocab tag from the package. + package['tags'].remove(tag) + updated_package = self._post('/api/action/package_update', + params={'id': package['id'], 'tags': package['tags']}, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)})['result'] + + # Test that the tag no longer appears in the list of tags for the + # package. + package = (self._post('/api/action/package_show', + {'id': self._post('/api/action/package_list')['result'][0]}) + ['result']) + tags_in_pkg = [tag_in_pkg for tag_in_pkg in package['tags'] if + tag_in_pkg['name'] == tag['name'] and + tag_in_pkg['vocabulary_id'] == tag['vocabulary_id']] + assert len(tags_in_pkg) == 0 + + def test_delete_tag_from_vocab(self): + '''Test that a tag can be deleted from a vocab.''' + + ckan.model.repo.rebuild_db() + self.setup() + ckan.tests.legacy.CreateTestData.create() + vocab = self.genre_vocab + + # First add some tags to the vocab. + noise_tag = self._create_tag(self.sysadmin_user, 'noise', vocab) + ragga_tag = self._create_tag(self.sysadmin_user, 'ragga', vocab) + grunge_tag = self._create_tag(self.sysadmin_user, 'grunge', vocab) + funk_tag = self._create_tag(self.sysadmin_user, 'funk', vocab) + tags = (noise_tag, ragga_tag, grunge_tag, funk_tag) + + # Get a package from the API. + package = (self._post('/api/action/package_show', + {'id': self._post('/api/action/package_list')['result'][0]}) + ['result']) + + # Add the new vocab tags to the package. + for tag in tags: + package['tags'].append(tag) + + updated_package = self._post('/api/action/package_update', + params={'id': package['id'], 'tags': package['tags']}, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)})['result'] + + # Test that the new vocab tags were added to the package. + for tag in tags: + tags_in_pkg = [tag_in_pkg for tag_in_pkg in + updated_package['tags'] if tag_in_pkg['name'] == + tag['name'] and tag_in_pkg['vocabulary_id'] == + tag['vocabulary_id']] + assert len(tags_in_pkg) == 1 + + # Now delete the tags from the vocab. + tags_before = self._list_tags(vocab) + self._delete_tag(self.sysadmin_user, noise_tag['name'], vocab['name']) + self._delete_tag(self.sysadmin_user, ragga_tag['id'], vocab['name']) + self._delete_tag(self.sysadmin_user, grunge_tag['id'], vocab['id']) + self._delete_tag(self.sysadmin_user, funk_tag['name'], vocab['id']) + + # Test that the tags no longer appear in the list of tags for the + # vocab. + tags_after = self._list_tags(vocab) + assert len(tags_after) == len(tags_before) - 4 + assert tag['name'] not in tags_after + difference = [tag_name for tag_name in tags_before if tag_name not in + tags_after] + assert sorted(difference) == sorted([tag['name'] for tag in tags]) + + # Test that the tags no longer appear in the list of tags for the + # package. + package = (self._post('/api/action/package_show', + {'id': self._post('/api/action/package_list')['result'][0]}) + ['result']) + for tag in tags: + tags_in_pkg = [tag_in_pkg for tag_in_pkg in package['tags'] if + tag_in_pkg['name'] == tag['name'] and + tag_in_pkg['vocabulary_id'] == tag['vocabulary_id']] + assert len(tags_in_pkg) == 0 + + def test_delete_free_tag(self): + '''Test that a free tag can be deleted via the API, and is + automatically removed from datasets. + + ''' + ckan.model.repo.rebuild_db() + self.setup() + ckan.tests.legacy.CreateTestData.create() + # Get a package from the API. + package = (self._post('/api/action/package_show', + {'id': self._post('/api/action/package_list')['result'][0]}) + ['result']) + package_id = package['id'] + + # Add some new free tags to the package. + tags = package['tags'] + tags.append({'name': 'ducks'}) + tags.append({'name': 'birds'}) + self._post('/api/action/package_update', + params={'id': package['id'], 'tags': tags}, + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}) + + # Test that the new tags appear in the list of tags. + tags = self._list_tags() + assert [tag for tag in tags].count('ducks') == 1 + assert [tag for tag in tags].count('birds') == 1 + + # Test that the new tags appear in the package's list of tags. + package = (self._post('/api/action/package_show', + {'id': package_id})['result']) + packages_tags = [tag['name'] for tag in package['tags']] + assert [tag for tag in packages_tags].count('ducks') == 1 + assert [tag for tag in packages_tags].count('birds') == 1 + + # Now delete the tags. + self._delete_tag(self.sysadmin_user, 'ducks') + birds_tag_id = self._post('/api/action/tag_show', + {'id': 'birds'})['result']['id'] + self._delete_tag(self.sysadmin_user, birds_tag_id) + + # Test that the tags no longer appear in the list of tags. + tags = self._list_tags() + assert [tag for tag in tags].count('ducks') == 0 + assert [tag for tag in tags].count('birds') == 0 + + # Test that the tags no longer appear in the package's list of tags. + package = (self._post('/api/action/package_show', + {'id': package_id})['result']) + packages_tags = [tag['name'] for tag in package['tags']] + assert [tag for tag in packages_tags].count('ducks') == 0 + assert [tag for tag in packages_tags].count('birds') == 0 + + def test_delete_tag_no_id(self): + '''Test the error response when a user tries to delete a tag without + giving the tag id. + + ''' + vocab = self.genre_vocab + self._create_tag(self.sysadmin_user, 'noise', vocab) + + for tag_id in ('missing', '', None): + # Now try to delete the tag from the vocab. + params = {'vocabulary_id': vocab['name']} + if tag_id != 'missing': + params['id'] = tag_id + response = self.app.post('/api/action/tag_delete', + params=helpers.json.dumps(params), + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=409) + assert response.json['success'] is False + assert 'id' in response.json['error'] + assert response.json['error']['id'] == 'id not in data' + + def test_delete_tag_no_vocab(self): + '''Test the error response when a user tries to delete a vocab tag + without giving the vocab name. + + ''' + vocab = self.genre_vocab + tag = self._create_tag(self.sysadmin_user, 'noise', vocab) + + # Now try to delete the tag from the vocab. + for vocab_name in ('', None, 'missing'): + params = {'id': tag['name']} + if vocab_name != 'missing': + params['vocabulary_id'] = vocab_name + response = self.app.post('/api/action/tag_delete', + params=helpers.json.dumps(params), + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=404) + assert response.json['success'] is False + msg = response.json['error']['message'] + assert msg == u'Not found: Could not find tag "{0}"'.format( + tag['name']), msg + + def test_delete_tag_not_exists(self): + '''Test the error response when a user tries to delete a from a vocab + but there is no tag with that name in the vocab. + + ''' + vocab = self.genre_vocab + self._create_tag(self.sysadmin_user, 'noise', vocab) + + params = {'id': 'nonexistent', + 'vocabulary_id': self.genre_vocab['name']} + response = self.app.post('/api/action/tag_delete', + params=helpers.json.dumps(params), + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=404) + assert response.json['success'] is False + msg = response.json['error']['message'] + assert msg == u'Not found: Could not find tag "%s"' % 'nonexistent', \ + msg + + def test_delete_tag_vocab_not_exists(self): + '''Test the error response when a user tries to delete a from a vocab + but there is no vocab with that name. + + ''' + vocab = self.genre_vocab + tag = self._create_tag(self.sysadmin_user, 'noise', vocab) + + params = {'id': tag['name'], + 'vocabulary_id': 'nonexistent'} + response = self.app.post('/api/action/tag_delete', + params=helpers.json.dumps(params), + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=404) + assert response.json['success'] is False + msg = response.json['error']['message'] + assert msg == u"Not found: could not find vocabulary 'nonexistent'", \ + msg + + def test_delete_tag_invalid_tag(self): + '''Test the error response when a user tries to delete a tag but gives + an invalid tag name. + + ''' + vocab = self.genre_vocab + self._create_tag(self.sysadmin_user, 'noise', vocab) + + for tag_name in ('Invalid!', ' '): + params = {'id': tag_name, + 'vocabulary_id': self.genre_vocab['name']} + response = self.app.post('/api/action/tag_delete', + params=helpers.json.dumps(params), + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=404) + assert response.json['success'] is False + msg = response.json['error']['message'] + assert msg == u'Not found: Could not find tag "%s"' % tag_name, msg + + def test_delete_tag_invalid_vocab(self): + '''Test the error response when a user tries to delete a tag but gives + an invalid vocab name. + + ''' + vocab = self.genre_vocab + tag = self._create_tag(self.sysadmin_user, 'noise', vocab) + + for vocab_name in ('Invalid!', ' '): + params = {'id': tag['name'], 'vocabulary_id': vocab_name} + response = self.app.post('/api/action/tag_delete', + params=helpers.json.dumps(params), + extra_environ={'Authorization': + str(self.sysadmin_user.apikey)}, + status=404) + assert response.json['success'] is False + msg = response.json['error']['message'] + assert msg == u"Not found: could not find vocabulary '%s'" \ + % vocab_name, msg + + def test_delete_tag_not_logged_in(self): + vocab = self.genre_vocab + tag = self._create_tag(self.sysadmin_user, 'noise', vocab) + + params = {'id': tag['name'], + 'vocabulary_id': self.genre_vocab['name']} + response = self.app.post('/api/action/tag_delete', + params=helpers.json.dumps(params), + status=403) + assert response.json['success'] is False + error = response.json['error']['__type'] + assert error == u"Authorization Error", error + + def test_delete_tag_not_authorized(self): + vocab = self.genre_vocab + tag = self._create_tag(self.sysadmin_user, 'noise', vocab) + + params = {'id': tag['name'], + 'vocabulary_id': self.genre_vocab['name']} + response = self.app.post('/api/action/tag_delete', + params=helpers.json.dumps(params), + extra_environ={'Authorization': + str(self.normal_user.apikey)}, + status=403) + assert response.json['success'] is False + msg = response.json['error']['__type'] + assert msg == u"Authorization Error" diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/html_check.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/html_check.py new file mode 100644 index 00000000..c5b8a729 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/html_check.py @@ -0,0 +1,114 @@ +# encoding: utf-8 + +import re +import sgmllib + +from six import string_types, text_type + + +import paste.fixture + + +class HtmlCheckMethods(object): + '''A collection of methods to check properties of a html page, usually + in the form returned by paster.''' + + def named_div(self, div_name, html): + 'strips html to just the
    section' + the_html = self._get_html_from_res(html) + start_div = the_html.find(u'
    ' % div_name) + if end_div == -1: + end_div = the_html.find(u'' % div_name) + div_html = the_html[start_div:end_div] + assert div_html + return div_html + + def main_div(self, html): + 'strips html to just the
    section' + return self.named_div('main', html) + + def sidebar(self, html): + 'strips html to just the
    section' + return self.named_div('primary', html) + + def strip_tags(self, res): + '''Call strip_tags on a TestResponse object to strip any and all HTML and normalise whitespace.''' + if not isinstance(res, string_types): + res = res.body.decode('utf-8') + return Stripper().strip(res) + + def check_named_element(self, html, tag_name, *html_to_find): + '''Searches in the html and returns True if it can find a particular + tag and all its subtags & data which contains all the of the + html_to_find''' + named_element_re = re.compile('(<(%(tag)s\w*).*?(>.*?)' % {'tag':tag_name}) + html_str = self._get_html_from_res(html) + self._check_html(named_element_re, html_str.replace('\n', ''), html_to_find) + + def check_tag_and_data(self, html, *html_to_find): + '''Searches in the html and returns True if it can find a tag and its + PC Data immediately following it which contains all the of the + html_to_find''' + if not hasattr(self, 'tag_and_data_re'): + self.tag_and_data_re = re.compile('(<(?P\w*)[^>]*>[^<]*?)') + # matches " stuff " + self._check_html(self.tag_and_data_re, html, html_to_find) + + def check_tag(self, html, *html_to_find): + '''Searches in the html and returns True if it can find a tag which + contains all the of the html_to_find''' + if not hasattr(self, 'tag_re'): + self.tag_re = re.compile('(<[^>]*>)') + self._check_html(self.tag_re, html, html_to_find) + + def _get_html_from_res(self, html): + if isinstance(html, paste.fixture.TestResponse): + html_str = html.body.decode('utf8') + elif isinstance(html, text_type): + html_str = html + elif isinstance(html, str): + html_str = html.decode('utf8') + else: + raise TypeError + return html_str # always unicode + + def _check_html(self, regex_compiled, html, html_to_find): + html_to_find = [text_type(html_bit) for html_bit in html_to_find] + partly_matching_tags = [] + html_str = self._get_html_from_res(html) + for tag in regex_compiled.finditer(html_str): + found_all=True + for i, html_bit_to_find in enumerate(html_to_find): + assert isinstance(html_bit_to_find, string_types), html_bit_to_find + html_bit_to_find = text_type(html_bit_to_find) + find_inverse = html_bit_to_find.startswith('!') + if (find_inverse and html_bit_to_find[1:] in tag.group()) or \ + (not find_inverse and html_bit_to_find not in tag.group()): + found_all = False + if i>0: + partly_matching_tags.append(tag.group()) + break + if found_all: + return # found it + # didn't find it + if partly_matching_tags: + assert 0, "Couldn't find %s in html. Closest matches were:\n%s" % (', '.join(["'%s'" % html.encode('utf8') for html in html_to_find]), '\n'.join([tag.encode('utf8') for tag in partly_matching_tags])) + else: + assert 0, "Couldn't find %s in html. Tags matched were:\n%s" % (', '.join(["'%s'" % html.encode('utf8') for html in html_to_find]), '\n'.join([tag.group() for tag in regex_compiled.finditer(html_str)])) + + + +class Stripper(sgmllib.SGMLParser): + '''A simple helper class to cleanly strip HTML from a response.''' + def __init__(self): + sgmllib.SGMLParser.__init__(self) + + def strip(self, html): + self.str = u"" + self.feed(html) + self.close() + return u' '.join(self.str.split()) + + def handle_data(self, data): + self.str += data diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/mock_mail_server.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/mock_mail_server.py new file mode 100644 index 00000000..501cdd10 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/mock_mail_server.py @@ -0,0 +1,85 @@ +# encoding: utf-8 + +import threading +import asyncore +import socket +from smtpd import SMTPServer + +from ckan.common import config + + +class MockSmtpServer(SMTPServer): + '''A mock SMTP server that operates in an asyncore loop''' + def __init__(self, host, port): + self.msgs = [] + SMTPServer.__init__(self, (host, port), None) + + def process_message(self, peer, mailfrom, rcpttos, data): + self.msgs.append((peer, mailfrom, rcpttos, data)) + + def get_smtp_messages(self): + return self.msgs + + def clear_smtp_messages(self): + self.msgs = [] + + +class MockSmtpServerThread(threading.Thread): + '''Runs the mock SMTP server in a thread''' + def __init__(self, host, port): + self.assert_port_free(host, port) + # init thread + self._stop_event = threading.Event() + self.thread_name = self.__class__ + threading.Thread.__init__(self, name=self.thread_name) + # init smtp server + self.server = MockSmtpServer(host, port) + + def assert_port_free(self, host, port): + test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, + test_socket.getsockopt(socket.SOL_SOCKET, + socket.SO_REUSEADDR) | 1) + test_socket.bind((host, port)) + test_socket.close() + + def run(self): + while not self._stop_event.isSet(): + asyncore.loop(timeout=0.01, count=1) + + def stop(self, timeout=None): + self._stop_event.set() + threading.Thread.join(self, timeout) + self.server.close() + + def get_smtp_messages(self): + return self.server.get_smtp_messages() + + def clear_smtp_messages(self): + return self.server.clear_smtp_messages() + + +class SmtpServerHarness(object): + '''Derive from this class to run MockSMTP - a test harness that + records what email messages are requested to be sent by it.''' + + @classmethod + def setup_class(cls): + smtp_server = config.get('smtp.test_server') or config['smtp_server'] + if ':' in smtp_server: + host, port = smtp_server.split(':') + else: + host, port = smtp_server, 25 + cls.port = port + cls.smtp_thread = MockSmtpServerThread(host, int(port)) + cls.smtp_thread.start() + + @classmethod + def teardown_class(cls): + cls.smtp_thread.stop() + + def get_smtp_messages(self): + return self.smtp_thread.get_smtp_messages() + + def clear_smtp_messages(self): + return self.smtp_thread.clear_smtp_messages() diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/mock_plugin.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/mock_plugin.py new file mode 100644 index 00000000..d9405912 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/mock_plugin.py @@ -0,0 +1,51 @@ +# encoding: utf-8 + +from ckan.plugins import Plugin, SingletonPlugin + +class _MockPlugin(object): + """ + MockPlugin tracks method calls via __getattr__ for rapid mocking of + plugins. + + Use MockPlugin.calls or MockPlugin..calls to access + call information + """ + + class MockMethod(object): + registry = {} + def __init__(self, boundto, name): + self.name = name + self.calls = [] + self.boundto = boundto + + def __call__(self, *args, **kwargs): + self.boundto.calls.append((self.name, args, kwargs)) + self.calls.append((args, kwargs)) + + def __init__(self, *arg, **kw): + self.calls = [] + self.__mockmethods__ = {} + + def __getattr__(self, name): + if name not in self.__mockmethods__: + self.__mockmethods__[name] = self.MockMethod(self, name) + return self.__mockmethods__[name] + + def reset_calls(self): + """ + Reset call information for this instance + """ + for mockmethod in self.MockMethod.registry.values(): + mockmethod.calls = [] + self.__mockmethods__ = {} + self.calls = [] + +class MockPlugin(_MockPlugin, Plugin): + """ + Mock a plugin + """ + +class MockSingletonPlugin(_MockPlugin, SingletonPlugin): + """ + Mock a singleton plugin + """ diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/pylons_controller.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/pylons_controller.py new file mode 100644 index 00000000..3f4bf75f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/pylons_controller.py @@ -0,0 +1,71 @@ +# encoding: utf-8 + +'''For unit testing that does not use paste fixture web requests, but needs +pylons set up for access to c, g or the template engine. + +Based on answer at: +http://groups.google.com/group/pylons-discuss/browse_thread/thread/5f8d8f59fd459a77 +''' + +from unittest import TestCase +from paste.registry import Registry +import pylons +from pylons.util import AttribSafeContextObj +import ckan.lib.app_globals as app_globals +from pylons.controllers.util import Request, Response +from routes.util import URLGenerator + +from ckan.config.routing import make_map +from ckan.tests.legacy import * +from ckan.lib.cli import MockTranslator + +class TestPylonsSession(dict): + last_accessed = None + + def save(self): + pass + + +class PylonsTestCase(object): + """A basic test case which allows access to pylons.c and pylons.request. + """ + @classmethod + def setup_class(cls): + cls.registry=Registry() + cls.registry.prepare() + + cls.context_obj=AttribSafeContextObj() + cls.registry.register(pylons.c, cls.context_obj) + + cls.app_globals_obj = app_globals.app_globals + cls.registry.register(pylons.g, cls.app_globals_obj) + + cls.request_obj=Request(dict(HTTP_HOST="nohost", REQUEST_METHOD="GET")) + cls.registry.register(pylons.request, cls.request_obj) + + cls.translator_obj=MockTranslator() + cls.registry.register(pylons.translator, cls.translator_obj) + + cls.registry.register(pylons.response, Response()) + mapper = make_map() + cls.registry.register(pylons.url, URLGenerator(mapper, {})) + cls.registry.register(pylons.session, TestPylonsSession()) + + # Templates often want to find out the request's routes info, so put + # some dummy values into the routes_dict, so the templates that do + # this don't cause an exception. + pylons.request.environ.update({'pylons.routes_dict': { + 'action': 'test-action', + 'controller': 'test-package::', + }}) + pylons.c.environ = pylons.request.environ + + @classmethod + def teardown_class(cls): + """ + Although there is nothing to teardown in this class, `PylonsTestCase` + is used as the superclass for a bunch of test cases. So this empty + declaration exists to that subclasses can safely call `teardown_class` + on their superclasses. + """ + pass diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/test_coding_standards.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/test_coding_standards.py new file mode 100644 index 00000000..8eb14e31 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/test_coding_standards.py @@ -0,0 +1,893 @@ +# encoding: utf-8 + +''' +The aim of these tests is to check and improve the coding standards in ckan. +Common issues are tested for here and tests fail if they are discovered in +files that are either new or were previously good. Bad files are +blacklisted to prevent them throwing errors in many cases because of the +number of affected files e.g. PEP8. However if files start to pass a test +will fail and the file should be removed from the blacklist so that it will +then be kept clean in future. + +The idea is to slowly improve the code quality in ckan without having files +deteriourating when they do reach the required standard. + +Please do not add new files to the list as any new files should meet the +current coding standards. Please add comments by files that fail if there +are legitimate reasons for the failure. +''' + +import cStringIO +import inspect +import itertools +import os +import re +import sys + +import pycodestyle + +file_path = os.path.dirname(__file__) +base_path = os.path.abspath(os.path.join(file_path, '..', '..', '..')) + + +def process_directory(directory, ext='.py'): + base_len = len(base_path) + 1 + for (dirpath, dirnames, filenames) in os.walk(directory): + # ignore hidden files and dir + filenames = [f for f in filenames if not f[0] == '.'] + dirnames[:] = [d for d in dirnames if not d[0] == '.'] + for name in filenames: + if name.endswith(ext): + path = os.path.join(dirpath, name) + filename = path[base_len:] + yield path, filename + + +def output_errors(filename, errors): + out = [''] + out.append('-' * len(filename)) + out.append(filename) + out.append('-' * len(filename)) + for error in errors: + out.append(error) + return '\n'.join(out) + + +def show_fails(msg, errors): + if errors: + msg = ['\n%s' % msg] + for error in errors: + msg.append(errors[error]) + msg.append('\n\nFailing Files:\n==============') + msg += sorted(errors) + raise Exception('\n'.join(msg)) + + +def show_passing(msg, errors): + if errors: + raise Exception('\n%s\n\n' % msg + '\n'.join(sorted(errors))) + + +def cs_filter(f, filter_, ignore_comment_lines=True): + ''' filter the file removing comments if requested. + looks for comments like + # CS: ignore + # CS: ignore x line + and removes the requested number of lines. Lines are removed by + blanking so the line numbers reported will be correct. This allows us + to check files that have known violations of the test rules. ''' + + # this RegEx is of poor quality but works + exp = r'^\s*#\s+CS:.*%s.*ignore\D*((\d+)\s+line)*' + re_ignore = re.compile(exp % filter_) + ignore = 0 + out = [] + count = 1 + for line in f: + # ignore the line if we have been told too + if ignore > 0: + line = '' + ignore -= 1 + matches = re_ignore.search(line) + if matches: + ignore = int(matches.group(2) or 1) + # ignore comments out lines + if ignore_comment_lines and line.lstrip().startswith('#'): + line = '' + out.append(line) + count += 1 + return out + + +class TestBadSpellings(object): + + BAD_SPELLING_BLACKLIST_FILES = [] + + # these are the bad spellings with the correct spelling + # use LOWER case + BAD_SPELLINGS = { + # CS: bad_spelling ignore 2 lines + 'licence': 'license', + 'organisation': 'organization', + } + + fails = {} + passes = [] + done = False + + @classmethod + def setup(cls): + if not cls.done: + cls.process() + cls.done = True + + @classmethod + def process(cls): + blacklist = cls.BAD_SPELLING_BLACKLIST_FILES + re_bad_spelling = re.compile( + r'(%s)' % '|'.join([x for x in cls.BAD_SPELLINGS]), + flags=re.IGNORECASE + ) + files = itertools.chain.from_iterable([ + process_directory(base_path), + process_directory(base_path, ext='.rst')]) + for path, filename in files: + f = open(path, 'r') + count = 1 + errors = [] + for line in cs_filter(f, 'bad_spelling'): + matches = re_bad_spelling.findall(line) + if matches: + bad_words = [] + for m in matches: + if m not in bad_words: + bad_words.append('%s use %s' % + (m, cls.BAD_SPELLINGS[m.lower()])) + bad = ', '.join(bad_words) + errors.append('ln:%s \t%s\n<%s>' % (count, line[:-1], bad)) + count += 1 + if errors and filename not in blacklist: + cls.fails[filename] = output_errors(filename, errors) + elif not errors and filename in blacklist: + cls.passes.append(filename) + + def test_good(self): + msg = 'The following files passed bad spellings rules' + msg += '\nThey need removing from the test blacklist' + show_passing(msg, self.passes) + + def test_bad(self): + msg = 'The following files have bad spellings that need fixing' + show_fails(msg, self.fails) + + +class TestNastyString(object): + # CS: nasty_string ignore + ''' Look for a common coding problem in ckan '..%s..' % str(x) ''' + + # Nasty str() issues + # + # There are places in ckan where code is like `'...%s..' % str(..)` + # these cause problems when unicode is present but can remain dormant + # for a long time before the issue is apparent so try to remove these. + # The value is converted to a string anyway so the str() is unneeded in + # any place. + + NASTY_STR_BLACKLIST_FILES = [] + + fails = {} + passes = [] + done = False + + @classmethod + def setup(cls): + if not cls.done: + cls.process() + cls.done = True + + @classmethod + def process(cls): + blacklist = cls.NASTY_STR_BLACKLIST_FILES + re_nasty_str = re.compile( + r'''("[^"]*\%s[^"]*"|'[^']*\%s[^']*').*%.*str\(''' + ) + for path, filename in process_directory(base_path): + f = open(path, 'r') + count = 1 + errors = [] + for line in cs_filter(f, 'nasty_string'): + if re_nasty_str.search(line): + errors.append('ln:%s \t%s' % (count, line[:-1])) + count += 1 + if errors and filename not in blacklist: + cls.fails[filename] = output_errors(filename, errors) + elif not errors and filename in blacklist: + cls.passes.append(filename) + + def test_good(self): + msg = 'The following files passed nasty str() rules' + msg += '\nThey need removing from the test blacklist' + show_passing(msg, self.passes) + + def test_bad(self): + # CS: nasty_string ignore next 2 lines + msg = ('The following files have nasty str() issues that need' + ' resolving\nCode is like `\'...%s..\' % str(..)`' + 'and should just be `\'...%s..\' % ..`') + show_fails(msg, self.fails) + + +class TestImportStar(object): + ''' Find files using from xxx import * ''' + + # Import * file exceptions + # + # The following files contain one or more `from ... import *` lines + # which should not be used in ckan where possible. If the files get + # fixed they should be removed from this list. + # + # import * is bad for many reasons and should be avoided. + + IMPORT_STAR_BLACKLIST_FILES = [ + 'ckan/migration/versions/001_add_existing_tables.py', + 'ckan/migration/versions/002_add_author_and_maintainer.py', + 'ckan/migration/versions/003_add_user_object.py', + 'ckan/migration/versions/004_add_group_object.py', + 'ckan/migration/versions/005_add_authorization_tables.py', + 'ckan/migration/versions/006_add_ratings.py', + 'ckan/migration/versions/007_add_system_roles.py', + 'ckan/migration/versions/008_update_vdm_ids.py', + 'ckan/migration/versions/009_add_creation_timestamps.py', + 'ckan/migration/versions/010_add_user_about.py', + 'ckan/migration/versions/011_add_package_search_vector.py', + 'ckan/migration/versions/012_add_resources.py', + 'ckan/migration/versions/013_add_hash.py', + 'ckan/migration/versions/014_hash_2.py', + 'ckan/migration/versions/015_remove_state_object.py', + 'ckan/migration/versions/016_uuids_everywhere.py', + 'ckan/migration/versions/017_add_pkg_relationships.py', + 'ckan/migration/versions/018_adjust_licenses.py', + 'ckan/migration/versions/019_pkg_relationships_state.py', + 'ckan/migration/versions/020_add_changeset.py', + 'ckan/migration/versions/022_add_group_extras.py', + 'ckan/migration/versions/023_add_harvesting.py', + 'ckan/migration/versions/024_add_harvested_document.py', + 'ckan/migration/versions/025_add_authorization_groups.py', + 'ckan/migration/versions/026_authorization_group_user_pk.py', + 'ckan/migration/versions/027_adjust_harvester.py', + 'ckan/migration/versions/028_drop_harvest_source_status.py', + 'ckan/migration/versions/029_version_groups.py', + 'ckan/migration/versions/030_additional_user_attributes.py', + 'ckan/migration/versions/031_move_openid_to_new_field.py', + 'ckan/migration/versions/032_add_extra_info_field_to_resources.py', + 'ckan/migration/versions/033_auth_group_user_id_add_conditional.py', + 'ckan/migration/versions/034_resource_group_table.py', + 'ckan/migration/versions/035_harvesting_doc_versioning.py', + 'ckan/migration/versions/036_lockdown_roles.py', + 'ckan/migration/versions/037_role_anon_editor.py', + 'ckan/migration/versions/038_delete_migration_tables.py', + 'ckan/migration/versions/039_add_expired_id_and_dates.py', + 'ckan/migration/versions/040_reset_key_on_user.py', + 'ckan/migration/versions/041_resource_new_fields.py', + 'ckan/migration/versions/042_user_revision_indexes.py', + 'ckan/migration/versions/043_drop_postgres_search.py', + 'ckan/migration/versions/044_add_task_status.py', + 'ckan/migration/versions/045_user_name_unique.py', + 'ckan/migration/versions/046_drop_changesets.py', + 'ckan/migration/versions/047_rename_package_group_member.py', + 'ckan/migration/versions/048_add_activity_streams_tables.py', + 'ckan/migration/versions/049_add_group_approval_status.py', + 'ckan/migration/versions/050_term_translation_table.py', + 'ckan/migration/versions/051_add_tag_vocabulary.py', + 'ckan/migration/versions/052_update_member_capacities.py', + 'ckan/migration/versions/053_add_group_logo.py', + 'ckan/migration/versions/056_add_related_table.py', + 'ckan/migration/versions/057_tracking.py', + 'ckan/migration/versions/058_add_follower_tables.py', + 'ckan/migration/versions/059_add_related_count_and_flag.py', + 'ckan/migration/versions/060_add_system_info_table.py', + 'ckan/migration/versions/061_add_follower__group_table.py', + 'ckan/migration/versions/062_add_dashboard_table.py', + 'ckan/migration/versions/063_org_changes.py', + 'ckan/migration/versions/064_add_email_last_sent_column.py', + 'ckan/migration/versions/065_add_email_notifications_preference.py', + 'ckan/plugins/__init__.py', + 'ckan/tests/legacy/functional/api/base.py', + 'ckan/tests/legacy/functional/api/test_api.py', + 'ckan/tests/legacy/functional/api/test_misc.py', + 'ckan/tests/legacy/functional/api/test_package_search.py', + 'ckan/tests/legacy/functional/api/test_resource_search.py', + 'ckan/tests/legacy/functional/api/test_revision_search.py', + 'ckan/tests/legacy/functional/test_group.py', + 'ckan/tests/legacy/functional/test_home.py', + 'ckan/tests/legacy/functional/test_package.py', + 'ckan/tests/legacy/functional/test_package_relationships.py', + 'ckan/tests/legacy/functional/test_tag.py', + 'ckan/tests/legacy/lib/test_helpers.py', + 'ckan/tests/legacy/lib/test_resource_search.py', + 'ckan/tests/legacy/lib/test_tag_search.py', + 'ckan/tests/legacy/misc/test_sync.py', + 'ckan/tests/legacy/models/test_extras.py', + 'ckan/tests/legacy/models/test_misc.py', + 'ckan/tests/legacy/models/test_package.py', + 'ckan/tests/legacy/models/test_package_relationships.py', + 'ckan/tests/legacy/models/test_purge_revision.py', + 'ckan/tests/legacy/models/test_resource.py', + 'ckan/tests/legacy/models/test_revision.py', + 'ckan/tests/legacy/models/test_user.py', + 'ckan/tests/legacy/pylons_controller.py', + 'fabfile.py', + ] + fails = {} + passes = [] + done = False + + @classmethod + def setup(cls): + if not cls.done: + cls.process() + cls.done = True + + @classmethod + def process(cls): + blacklist = cls.IMPORT_STAR_BLACKLIST_FILES + re_import_star = re.compile(r'^\s*from\s+.*\simport\s+\*') + for path, filename in process_directory(base_path): + f = open(path, 'r') + count = 1 + errors = [] + for line in f: + if re_import_star.search(line): + errors.append('%s ln:%s import *\n\t%s' + % (filename, count, line)) + count += 1 + if errors and filename not in blacklist: + cls.fails[filename] = output_errors(filename, errors) + elif not errors and filename in blacklist: + cls.passes.append(filename) + + def test_import_good(self): + msg = 'The following files passed import * rules' + msg += '\nThey need removing from the test blacklist' + show_passing(msg, self.passes) + + def test_import_bad(self): + msg = ('The following files have import * issues that need resolving\n' + '`from ... import *` lines which should not be used in ckan' + ' where possible.') + show_fails(msg, self.fails) + + +class TestPep8(object): + ''' Check that .py files are pep8 compliant ''' + + # PEP8 File exceptions + # + # The following files have known PEP8 errors. When the files get to a + # point of not having any such errors they should be removed from this + # list to prevent new errors being added to the file. + + PEP8_BLACKLIST_FILES = [ + 'bin/running_stats.py', + 'ckan/__init__.py', + 'ckan/ckan_nose_plugin.py', + 'ckan/config/middleware.py', + 'ckan/config/routing.py', + 'ckan/config/sp_config.py', + 'ckan/controllers/admin.py', + 'ckan/controllers/revision.py', + 'ckan/include/rcssmin.py', + 'ckan/include/rjsmin.py', + 'ckan/lib/activity_streams.py', + 'ckan/lib/activity_streams_session_extension.py', + 'ckan/lib/alphabet_paginate.py', + 'ckan/lib/app_globals.py', + 'ckan/lib/captcha.py', + 'ckan/lib/cli.py', + 'ckan/lib/create_test_data.py', + 'ckan/lib/dictization/__init__.py', + 'ckan/lib/dictization/model_dictize.py', + 'ckan/lib/dictization/model_save.py', + 'ckan/lib/email_notifications.py', + 'ckan/lib/extract.py', + 'ckan/lib/fanstatic_extensions.py', + 'ckan/lib/fanstatic_resources.py', + 'ckan/lib/formatters.py', + 'ckan/lib/hash.py', + 'ckan/lib/help/flash_messages.py', + 'ckan/lib/jinja_extensions.py', + 'ckan/lib/jsonp.py', + 'ckan/lib/maintain.py', + 'ckan/lib/navl/validators.py', + 'ckan/lib/package_saver.py', + 'ckan/lib/plugins.py', + 'ckan/lib/render.py', + 'ckan/lib/search/__init__.py', + 'ckan/lib/search/index.py', + 'ckan/lib/search/query.py', + 'ckan/lib/search/sql.py', + 'ckan/logic/action/__init__.py', + 'ckan/logic/action/delete.py', + 'ckan/logic/action/get.py', + 'ckan/logic/action/update.py', + 'ckan/logic/auth/create.py', + 'ckan/logic/auth/delete.py', + 'ckan/logic/auth/get.py', + 'ckan/logic/auth/update.py', + 'ckan/logic/converters.py', + 'ckan/logic/validators.py', + 'ckan/migration/versions/001_add_existing_tables.py', + 'ckan/migration/versions/002_add_author_and_maintainer.py', + 'ckan/migration/versions/003_add_user_object.py', + 'ckan/migration/versions/004_add_group_object.py', + 'ckan/migration/versions/005_add_authorization_tables.py', + 'ckan/migration/versions/006_add_ratings.py', + 'ckan/migration/versions/007_add_system_roles.py', + 'ckan/migration/versions/008_update_vdm_ids.py', + 'ckan/migration/versions/009_add_creation_timestamps.py', + 'ckan/migration/versions/010_add_user_about.py', + 'ckan/migration/versions/011_add_package_search_vector.py', + 'ckan/migration/versions/012_add_resources.py', + 'ckan/migration/versions/013_add_hash.py', + 'ckan/migration/versions/014_hash_2.py', + 'ckan/migration/versions/015_remove_state_object.py', + 'ckan/migration/versions/016_uuids_everywhere.py', + 'ckan/migration/versions/017_add_pkg_relationships.py', + 'ckan/migration/versions/018_adjust_licenses.py', + 'ckan/migration/versions/019_pkg_relationships_state.py', + 'ckan/migration/versions/020_add_changeset.py', + 'ckan/migration/versions/022_add_group_extras.py', + 'ckan/migration/versions/023_add_harvesting.py', + 'ckan/migration/versions/024_add_harvested_document.py', + 'ckan/migration/versions/025_add_authorization_groups.py', + 'ckan/migration/versions/026_authorization_group_user_pk.py', + 'ckan/migration/versions/027_adjust_harvester.py', + 'ckan/migration/versions/028_drop_harvest_source_status.py', + 'ckan/migration/versions/029_version_groups.py', + 'ckan/migration/versions/030_additional_user_attributes.py', + 'ckan/migration/versions/031_move_openid_to_new_field.py', + 'ckan/migration/versions/032_add_extra_info_field_to_resources.py', + 'ckan/migration/versions/033_auth_group_user_id_add_conditional.py', + 'ckan/migration/versions/034_resource_group_table.py', + 'ckan/migration/versions/035_harvesting_doc_versioning.py', + 'ckan/migration/versions/036_lockdown_roles.py', + 'ckan/migration/versions/037_role_anon_editor.py', + 'ckan/migration/versions/038_delete_migration_tables.py', + 'ckan/migration/versions/039_add_expired_id_and_dates.py', + 'ckan/migration/versions/040_reset_key_on_user.py', + 'ckan/migration/versions/041_resource_new_fields.py', + 'ckan/migration/versions/042_user_revision_indexes.py', + 'ckan/migration/versions/043_drop_postgres_search.py', + 'ckan/migration/versions/044_add_task_status.py', + 'ckan/migration/versions/045_user_name_unique.py', + 'ckan/migration/versions/046_drop_changesets.py', + 'ckan/migration/versions/047_rename_package_group_member.py', + 'ckan/migration/versions/048_add_activity_streams_tables.py', + 'ckan/migration/versions/049_add_group_approval_status.py', + 'ckan/migration/versions/050_term_translation_table.py', + 'ckan/migration/versions/051_add_tag_vocabulary.py', + 'ckan/migration/versions/052_update_member_capacities.py', + 'ckan/migration/versions/053_add_group_logo.py', + 'ckan/migration/versions/054_add_resource_created_date.py', + 'ckan/migration/versions/055_update_user_and_activity_detail.py', + 'ckan/migration/versions/056_add_related_table.py', + 'ckan/migration/versions/057_tracking.py', + 'ckan/migration/versions/058_add_follower_tables.py', + 'ckan/migration/versions/059_add_related_count_and_flag.py', + 'ckan/migration/versions/060_add_system_info_table.py', + 'ckan/migration/versions/061_add_follower__group_table.py', + 'ckan/migration/versions/062_add_dashboard_table.py', + 'ckan/migration/versions/063_org_changes.py', + 'ckan/migration/versions/064_add_email_last_sent_column.py', + 'ckan/migration/versions/065_add_email_notifications_preference.py', + 'ckan/migration/versions/067_turn_extras_to_strings.py', + 'ckan/misc.py', + 'ckan/model/__init__.py', + 'ckan/model/activity.py', + 'ckan/model/authz.py', + 'ckan/model/dashboard.py', + 'ckan/model/domain_object.py', + 'ckan/model/follower.py', + 'ckan/model/group.py', + 'ckan/model/group_extra.py', + 'ckan/model/license.py', + 'ckan/model/meta.py', + 'ckan/model/misc.py', + 'ckan/model/modification.py', + 'ckan/model/package.py', + 'ckan/model/package_extra.py', + 'ckan/model/package_relationship.py', + 'ckan/model/rating.py', + 'ckan/model/resource.py', + 'ckan/model/system_info.py', + 'ckan/model/tag.py', + 'ckan/model/task_status.py', + 'ckan/model/term_translation.py', + 'ckan/model/test_user.py', + 'ckan/model/tracking.py', + 'ckan/model/types.py', + 'ckan/model/user.py', + 'ckan/model/vocabulary.py', + 'ckan/authz.py', + 'ckan/pastertemplates/__init__.py', + 'ckan/plugins/interfaces.py', + 'ckan/poo.py', + 'ckan/rating.py', + 'ckan/templates_legacy/home/__init__.py', + 'ckan/tests/legacy/__init__.py', + 'ckan/tests/legacy/ckantestplugin/ckantestplugin/__init__.py', + 'ckan/tests/legacy/ckantestplugin/setup.py', + 'ckan/tests/legacy/ckantestplugins.py', + 'ckan/tests/legacy/functional/api/base.py', + 'ckan/tests/legacy/functional/api/model/test_group.py', + 'ckan/tests/legacy/functional/api/model/test_package.py', + 'ckan/tests/legacy/functional/api/model/test_ratings.py', + 'ckan/tests/legacy/functional/api/model/test_relationships.py', + 'ckan/tests/legacy/functional/api/model/test_revisions.py', + 'ckan/tests/legacy/functional/api/model/test_tag.py', + 'ckan/tests/legacy/functional/api/model/test_vocabulary.py', + 'ckan/tests/legacy/functional/api/test_activity.py', + 'ckan/tests/legacy/functional/api/test_api.py', + 'ckan/tests/legacy/functional/api/test_dashboard.py', + 'ckan/tests/legacy/functional/api/test_email_notifications.py', + 'ckan/tests/legacy/functional/api/test_follow.py', + 'ckan/tests/legacy/functional/api/test_misc.py', + 'ckan/tests/legacy/functional/api/test_package_search.py', + 'ckan/tests/legacy/functional/api/test_resource.py', + 'ckan/tests/legacy/functional/api/test_resource_search.py', + 'ckan/tests/legacy/functional/api/test_revision_search.py', + 'ckan/tests/legacy/functional/api/test_user.py', + 'ckan/tests/legacy/functional/api/test_util.py', + 'ckan/tests/legacy/functional/base.py', + 'ckan/tests/legacy/functional/test_activity.py', + 'ckan/tests/legacy/functional/test_admin.py', + 'ckan/tests/legacy/functional/test_cors.py', + 'ckan/tests/legacy/functional/test_error.py', + 'ckan/tests/legacy/functional/test_home.py', + 'ckan/tests/legacy/functional/test_package.py', + 'ckan/tests/legacy/functional/test_package_relationships.py', + 'ckan/tests/legacy/functional/test_pagination.py', + 'ckan/tests/legacy/functional/test_preview_interface.py', + 'ckan/tests/legacy/functional/test_revision.py', + 'ckan/tests/legacy/functional/test_search.py', + 'ckan/tests/legacy/functional/test_tag.py', + 'ckan/tests/legacy/functional/test_tag_vocab.py', + 'ckan/tests/legacy/functional/test_tracking.py', + 'ckan/tests/legacy/functional/test_upload.py', + 'ckan/tests/legacy/functional/test_user.py', + 'ckan/tests/legacy/html_check.py', + 'ckan/tests/legacy/lib/__init__.py', + 'ckan/tests/legacy/lib/test_accept.py', + 'ckan/tests/legacy/lib/test_cli.py', + 'ckan/tests/legacy/lib/test_dictization.py', + 'ckan/tests/legacy/lib/test_email_notifications.py', + 'ckan/tests/legacy/lib/test_hash.py', + 'ckan/tests/legacy/lib/test_helpers.py', + 'ckan/tests/legacy/lib/test_mailer.py', + 'ckan/tests/legacy/lib/test_munge.py', + 'ckan/tests/legacy/lib/test_navl.py', + 'ckan/tests/legacy/lib/test_resource_search.py', + 'ckan/tests/legacy/lib/test_simple_search.py', + 'ckan/tests/legacy/lib/test_solr_package_search.py', + 'ckan/tests/legacy/lib/test_solr_package_search_synchronous_update.py', + 'ckan/tests/legacy/lib/test_solr_schema_version.py', + 'ckan/tests/legacy/lib/test_solr_search_index.py', + 'ckan/tests/legacy/lib/test_tag_search.py', + 'ckan/tests/legacy/logic/test_action.py', + 'ckan/tests/legacy/logic/test_auth.py', + 'ckan/tests/legacy/logic/test_tag.py', + 'ckan/tests/legacy/logic/test_validators.py', + 'ckan/tests/legacy/misc/test_format_text.py', + 'ckan/tests/legacy/misc/test_mock_mail_server.py', + 'ckan/tests/legacy/misc/test_sync.py', + 'ckan/tests/legacy/mock_plugin.py', + 'ckan/tests/legacy/models/test_extras.py', + 'ckan/tests/legacy/models/test_group.py', + 'ckan/tests/legacy/models/test_license.py', + 'ckan/tests/legacy/models/test_misc.py', + 'ckan/tests/legacy/models/test_package.py', + 'ckan/tests/legacy/models/test_package_relationships.py', + 'ckan/tests/legacy/models/test_purge_revision.py', + 'ckan/tests/legacy/models/test_resource.py', + 'ckan/tests/legacy/models/test_revision.py', + 'ckan/tests/legacy/models/test_user.py', + 'ckan/tests/legacy/monkey.py', + 'ckan/tests/legacy/pylons_controller.py', + 'ckan/tests/legacy/schema/test_schema.py', + 'ckan/tests/legacy/test_plugins.py', + 'ckan/tests/legacy/test_versions.py', + 'ckan/websetup.py', + 'ckanext/datastore/bin/datastore_setup.py', + 'ckanext/datastore/logic/action.py', + 'ckanext/datastore/tests/test_create.py', + 'ckanext/datastore/tests/test_search.py', + 'ckanext/datastore/tests/test_upsert.py', + 'ckanext/example_idatasetform/plugin.py', + 'ckanext/example_itemplatehelpers/plugin.py', + 'ckanext/multilingual/plugin.py', + 'ckanext/resourceproxy/plugin.py', + 'ckanext/stats/controller.py', + 'ckanext/stats/plugin.py', + 'ckanext/stats/stats.py', + 'ckanext/stats/tests/test_stats_lib.py', + 'ckanext/stats/tests/test_stats_plugin.py', + 'ckanext/test_tag_vocab_plugin.py', + 'ckanext/tests/plugin.py', + 'doc/conf.py', + 'fabfile.py', + 'profile_tests.py', + 'setup.py', + ] + fails = {} + passes = [] + done = False + + @classmethod + def setup(cls): + if not cls.done: + cls.process() + cls.done = True + + @classmethod + def process(cls): + blacklist = cls.PEP8_BLACKLIST_FILES + for path, filename in process_directory(base_path): + errors = cls.find_pep8_errors(filename=path) + if errors and filename not in blacklist: + cls.fails[filename] = output_errors(filename, errors) + elif not errors and filename in blacklist: + cls.passes.append(filename) + + def test_pep8_fails(self): + msg = 'The following files have pep8 issues that need resolving' + msg += '\nThey need removing from the test blacklist' + show_fails(msg, self.fails) + + def test_pep8_pass(self): + msg = 'The following files passed pep8 but are blacklisted' + show_passing(msg, self.passes) + + @classmethod + def find_pep8_errors(cls, filename=None, lines=None): + try: + sys.stdout = cStringIO.StringIO() + config = {} + + # Ignore long lines on test files, as the test names can get long + # when following our test naming standards. + if cls._is_test(filename): + config['ignore'] = ['E501'] + + checker = pycodestyle.Checker(filename=filename, lines=lines, + **config) + checker.check_all() + output = sys.stdout.getvalue() + finally: + sys.stdout = sys.__stdout__ + + errors = [] + for line in output.split('\n'): + parts = line.split(' ', 2) + if len(parts) == 3: + location, error, desc = parts + line_no = location.split(':')[1] + errors.append('%s ln:%s %s' % (error, line_no, desc)) + return errors + + @classmethod + def _is_test(cls, filename): + return bool(re.search('(^|\W)test_.*\.py$', filename, re.IGNORECASE)) + + +class TestActionAuth(object): + ''' These tests check the logic auth/action functions are compliant. The + main tests are that each action has a corresponding auth function and + that each auth function has an action. We check the function only + accepts (context, data_dict) as parameters. ''' + + ACTION_FN_SIGNATURES_BLACKLIST = [ + 'create: activity_create', + ] + + ACTION_NO_AUTH_BLACKLIST = [ + 'create: follow_dataset', + 'create: follow_group', + 'create: follow_user', + 'delete: unfollow_dataset', + 'delete: unfollow_group', + 'delete: unfollow_user', + 'get: activity_detail_list', + 'get: am_following_dataset', + 'get: am_following_group', + 'get: am_following_user', + 'get: dashboard_activity_list_html', + 'get: dataset_followee_count', + 'get: dataset_follower_count', + 'get: followee_count', + 'get: group_activity_list', + 'get: group_activity_list_html', + 'get: group_followee_count', + 'get: group_follower_count', + 'get: group_package_show', + 'get: member_list', + 'get: organization_activity_list', + 'get: organization_activity_list_html', + 'get: organization_follower_count', + 'get: package_activity_list', + 'get: package_activity_list_html', + 'get: recently_changed_packages_activity_list', + 'get: recently_changed_packages_activity_list_html', + 'get: resource_search', + 'get: roles_show', + 'get: status_show', + 'get: tag_search', + 'get: term_translation_show', + 'get: user_activity_list', + 'get: user_activity_list_html', + 'get: user_followee_count', + 'get: user_follower_count', + 'update: task_status_update_many', + 'update: term_translation_update_many', + ] + + AUTH_NO_ACTION_BLACKLIST = [ + 'create: file_upload', + 'delete: revision_delete', + 'delete: revision_undelete', + 'get: group_list_available', + 'get: sysadmin', + 'get: request_reset', + 'get: user_reset', + 'update: group_change_state', + 'update: group_edit_permissions', + 'update: package_change_state', + 'update: revision_change_state', + ] + + ACTION_NO_DOC_STR_BLACKLIST = [ + 'get: get_site_user', + ] + + done = False + + @classmethod + def setup(cls): + if not cls.done: + cls.process() + cls.done = True + + @classmethod + def process(cls): + def get_functions(module_root): + fns = {} + for auth_module_name in ['get', 'create', 'update', 'delete', 'patch']: + module_path = '%s.%s' % (module_root, auth_module_name,) + try: + module = __import__(module_path) + except ImportError: + print ('No auth module for action "%s"' % auth_module_name) + + for part in module_path.split('.')[1:]: + module = getattr(module, part) + + for key, v in module.__dict__.items(): + if not hasattr(v, '__call__'): + continue + if v.__module__ != module_path: + continue + if not key.startswith('_'): + name = '%s: %s' % (auth_module_name, key) + fns[name] = v + return fns + cls.actions = get_functions('logic.action') + cls.auths = get_functions('logic.auth') + + def test_actions_have_auth_fn(self): + actions_no_auth = set(self.actions.keys()) - set(self.auths.keys()) + actions_no_auth -= set(self.ACTION_NO_AUTH_BLACKLIST) + assert not actions_no_auth, 'These actions have no auth function\n%s' \ + % '\n'.join(sorted(list(actions_no_auth))) + + def test_actions_have_auth_fn_blacklist(self): + actions_no_auth = set(self.actions.keys()) & set(self.auths.keys()) + actions_no_auth &= set(self.ACTION_NO_AUTH_BLACKLIST) + assert not actions_no_auth, 'These actions blacklisted but ' + \ + 'shouldn\'t be \n%s' % '\n'.join(sorted(list(actions_no_auth))) + + def test_auths_have_action_fn(self): + auths_no_action = set(self.auths.keys()) - set(self.actions.keys()) + auths_no_action -= set(self.AUTH_NO_ACTION_BLACKLIST) + assert not auths_no_action, 'These auth functions have no action\n%s' \ + % '\n'.join(sorted(list(auths_no_action))) + + def test_auths_have_action_fn_blacklist(self): + auths_no_action = set(self.auths.keys()) & set(self.actions.keys()) + auths_no_action &= set(self.AUTH_NO_ACTION_BLACKLIST) + assert not auths_no_action, 'These auths functions blacklisted but' + \ + ' shouldn\'t be \n%s' % '\n'.join(sorted(list(auths_no_action))) + + def test_fn_signatures(self): + errors = [] + for name, fn in self.actions.iteritems(): + args_info = inspect.getargspec(fn) + if args_info.args != ['context', 'data_dict'] \ + or args_info.varargs is not None \ + or args_info.keywords is not None: + if name not in self.ACTION_FN_SIGNATURES_BLACKLIST: + errors.append(name) + assert not errors, 'These action functions have the wrong function' + \ + ' signature, should be (context, data_dict)\n%s' \ + % '\n'.join(sorted(errors)) + + def test_fn_docstrings(self): + errors = [] + for name, fn in self.actions.iteritems(): + if not getattr(fn, '__doc__', None): + if name not in self.ACTION_NO_DOC_STR_BLACKLIST: + errors.append(name) + assert not errors, 'These action functions need docstrings\n%s' \ + % '\n'.join(sorted(errors)) + + +class TestBadExceptions(object): + ''' Look for a common coding problem in ckan Exception(_'...') ''' + + # Exceptions should not on the whole be translated as they are for + # programmers to read in trace backs or log files. However some like + # Invalid used in validation functions do get passed back up to the user + # and so should be translated. + + NASTY_EXCEPTION_BLACKLIST_FILES = [ + 'ckan/controllers/api.py', + 'ckan/controllers/user.py', + 'ckan/lib/mailer.py', + 'ckan/logic/action/create.py', + 'ckan/logic/action/delete.py', + 'ckan/logic/action/get.py', + 'ckan/logic/action/update.py', + 'ckan/logic/auth/create.py', + 'ckan/logic/auth/delete.py', + 'ckan/logic/auth/get.py', + 'ckan/authz.py', + 'ckanext/datastore/logic/action.py', + ] + fails = {} + passes = [] + done = False + + @classmethod + def setup(cls): + if not cls.done: + cls.process() + cls.done = True + + @classmethod + def process(cls): + blacklist = cls.NASTY_EXCEPTION_BLACKLIST_FILES + re_nasty_exception = re.compile( + r'''raise\W+(?![^I]*Invalid\().*_\(''' + ) + for path, filename in process_directory(base_path): + f = open(path, 'r') + count = 1 + errors = [] + for line in f: + if re_nasty_exception.search(line): + errors.append('ln:%s \t%s' % (count, line[:-1])) + count += 1 + if errors and filename not in blacklist: + cls.fails[filename] = output_errors(filename, errors) + elif not errors and filename in blacklist: + cls.passes.append(filename) + + def test_good(self): + msg = 'The following files passed nasty exceptions rules' + msg += '\nThey need removing from the test blacklist' + show_passing(msg, self.passes) + + def test_bad(self): + msg = ('The following files have nasty exception issues that need' + ' resolving\nWe should not be translating exceptions in most' + ' situations. We need to when the exception message is passed' + ' to the front end for example validation') + show_fails(msg, self.fails) diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/test_plugins.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/test_plugins.py new file mode 100644 index 00000000..3af1524a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/test_plugins.py @@ -0,0 +1,197 @@ +# encoding: utf-8 + +""" +Tests for plugin loading via PCA +""" +from nose.tools import raises, assert_equal +from unittest import TestCase +from pyutilib.component.core import PluginGlobals +from ckan.common import config + +import ckan.logic as logic +import ckan.authz as authz +import ckan.plugins as plugins +from ckan.plugins.core import find_system_plugins +from ckan.lib.create_test_data import CreateTestData +from ckan.tests import factories + +def _make_calls(*args): + out = [] + for arg in args: + out.append(((arg,), {})) + return out + +def get_calls(mock_observer_func): + '''Given a mock IPluginObserver method, returns the plugins that caused its + methods to be called, so basically a list of plugins that + loaded/unloaded''' + return [call_tuple[0][0].name for call_tuple in mock_observer_func.calls] + +class IFoo(plugins.Interface): + pass + +class IBar(plugins.Interface): + pass + +class FooImpl(object): + plugins.implements(IFoo) + +class BarImpl(object): + plugins.implements(IBar) + +class FooBarImpl(object): + plugins.implements(IFoo) + plugins.implements(IBar) + +class TestInterface(TestCase): + + def test_implemented_by(self): + assert IFoo.implemented_by(FooImpl) + assert IFoo.implemented_by(FooBarImpl) + assert not IFoo.implemented_by(BarImpl) + + @raises(TypeError) + def test_implemented_by_raises_exception_on_instances(self): + assert not IFoo.implemented_by(FooImpl()) + + def test_provided_by(self): + assert IFoo.provided_by(FooImpl()) + assert IFoo.provided_by(FooBarImpl()) + assert not IFoo.provided_by(BarImpl()) + + +class TestIPluginObserverPlugin(object): + + @classmethod + def setup(cls): + cls.observer = plugins.load('test_observer_plugin') + + @classmethod + def teardown(cls): + plugins.unload('test_observer_plugin') + + def test_notified_on_load(self): + + observer = self.observer + observer.reset_calls() + with plugins.use_plugin('action_plugin'): + assert_equal(get_calls(observer.before_load), ['action_plugin']) + assert_equal(get_calls(observer.after_load), ['action_plugin']) + assert_equal(get_calls(observer.before_unload), []) + assert_equal(get_calls(observer.after_unload), []) + + def test_notified_on_unload(self): + + with plugins.use_plugin('action_plugin') as action: + observer = self.observer + observer.reset_calls() + assert observer.before_load.calls == [] + assert observer.after_load.calls == [] + assert observer.before_unload.calls == _make_calls(action) + assert observer.after_unload.calls == _make_calls(action) + +class TestPlugins(object): + + + def test_plugins_load(self): + + config_plugins = config['ckan.plugins'] + config['ckan.plugins'] = 'mapper_plugin routes_plugin' + plugins.load_all() + + # synchronous_search automatically gets loaded + current_plugins = set([plugins.get_plugin(p) for p in ['mapper_plugin', 'routes_plugin', 'synchronous_search'] + find_system_plugins()]) + assert PluginGlobals.env().services == current_plugins + # cleanup + config['ckan.plugins'] = config_plugins + plugins.load_all() + + def test_only_configured_plugins_loaded(self): + with plugins.use_plugin('mapper_plugin') as p: + # MapperPlugin should be loaded as it is listed in + assert p in plugins.PluginImplementations(plugins.IMapper) + # MapperPlugin2 and RoutesPlugin should NOT be loaded + assert len(plugins.PluginImplementations(plugins.IMapper)) == 1 + + def test_plugin_loading_order(self): + """ + Check that plugins are loaded in the order specified in the config + """ + config_plugins = config['ckan.plugins'] + config['ckan.plugins'] = 'test_observer_plugin mapper_plugin mapper_plugin2' + plugins.load_all() + + observerplugin = plugins.get_plugin('test_observer_plugin') + + expected_order = _make_calls(plugins.get_plugin('mapper_plugin'), + plugins.get_plugin('mapper_plugin2')) + + assert observerplugin.before_load.calls[:2] == expected_order + expected_order = _make_calls(plugins.get_plugin('test_observer_plugin'), + plugins.get_plugin('mapper_plugin'), + plugins.get_plugin('mapper_plugin2')) + assert observerplugin.after_load.calls[:3] == expected_order + + config['ckan.plugins'] = 'test_observer_plugin mapper_plugin2 mapper_plugin' + plugins.load_all() + + expected_order = _make_calls(plugins.get_plugin('mapper_plugin2'), + plugins.get_plugin('mapper_plugin')) + assert observerplugin.before_load.calls[:2] == expected_order + expected_order = _make_calls(plugins.get_plugin('test_observer_plugin'), + plugins.get_plugin('mapper_plugin2'), + plugins.get_plugin('mapper_plugin')) + assert observerplugin.after_load.calls[:3] == expected_order + # cleanup + config['ckan.plugins'] = config_plugins + plugins.load_all() + + def test_mapper_plugin_fired_on_insert(self): + with plugins.use_plugin('mapper_plugin') as mapper_plugin: + CreateTestData.create_arbitrary([{'name': u'testpkg'}]) + assert mapper_plugin.calls == [ + ('before_insert', 'testpkg'), + ('after_insert', 'testpkg'), + ] + + def test_mapper_plugin_fired_on_delete(self): + with plugins.use_plugin('mapper_plugin') as mapper_plugin: + CreateTestData.create_arbitrary([{'name': u'testpkg'}]) + mapper_plugin.calls = [] + # remove this data + user = factories.User() + context = {'user': user['name']} + logic.get_action('package_delete')(context, {'id': 'testpkg'}) + # state=deleted doesn't trigger before_delete() + assert_equal(mapper_plugin.calls, []) + from ckan import model + # purging the package does trigger before_delete() + model.Package.get('testpkg').purge() + model.Session.commit() + model.Session.remove() + assert_equal(mapper_plugin.calls, + [('before_delete', 'testpkg'), + ('after_delete', 'testpkg')]) + + def test_routes_plugin_fired(self): + with plugins.use_plugin('routes_plugin'): + routes_plugin = PluginGlobals.env_registry['pca'].plugin_registry['RoutesPlugin'].__instance__ + assert routes_plugin.calls_made == ['before_map', 'after_map'], \ + routes_plugin.calls_made + + + def test_action_plugin_override(self): + status_show_original = logic.get_action('status_show')(None, {}) + with plugins.use_plugin('action_plugin'): + assert logic.get_action('status_show')(None, {}) != status_show_original + assert logic.get_action('status_show')(None, {}) == status_show_original + + def test_auth_plugin_override(self): + package_list_original = authz.is_authorized('package_list', {}) + with plugins.use_plugin('auth_plugin'): + assert authz.is_authorized('package_list', {}) != package_list_original + assert authz.is_authorized('package_list', {}) == package_list_original + + @raises(plugins.PluginNotFoundException) + def test_inexistent_plugin_loading(self): + plugins.load('inexistent-plugin') diff --git a/venv/lib/python2.7/site-packages/ckan/tests/legacy/test_versions.py b/venv/lib/python2.7/site-packages/ckan/tests/legacy/test_versions.py new file mode 100644 index 00000000..c9873e34 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/legacy/test_versions.py @@ -0,0 +1,14 @@ +# encoding: utf-8 + +import subprocess + +class TestVersions(object): + + no_db = True + + def test_pylons(self): + p = subprocess.Popen( + 'pip freeze | grep Pylons', shell=True, + stdout=subprocess.PIPE) + pylons_version = p.communicate()[0].strip() + assert pylons_version == "Pylons==0.9.7" diff --git a/venv/lib/python2.7/site-packages/ckan/tests/lib/__init__.py b/venv/lib/python2.7/site-packages/ckan/tests/lib/__init__.py new file mode 100644 index 00000000..bb537786 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/lib/__init__.py @@ -0,0 +1,19 @@ +# encoding: utf-8 + +'''**All lib functions should have tests**. + +.. todo:: + + Write the tests for one ``ckan.lib`` module, figuring out the best way + to write lib tests. Then fill in this guidelines section, using the first + + + We probably want to make these unit tests rather than high-level tests and + mock out ``ckan.model``, so the tests are really fast and simple. + + Note that some things in lib are particularly important, e.g. the functions + in :py:mod:`ckan.lib.helpers` are exported for templates (including + extensions) to use, so all of these functions should really have tests and + docstrings. It's probably worth focusing on these modules first. + +''' diff --git a/venv/lib/python2.7/site-packages/ckan/tests/lib/test_app_globals.py b/venv/lib/python2.7/site-packages/ckan/tests/lib/test_app_globals.py new file mode 100644 index 00000000..714acc4f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/lib/test_app_globals.py @@ -0,0 +1,19 @@ +# encoding: utf-8 + +from ckan.lib.app_globals import app_globals as g + + +class TestGlobals(object): + def test_config_not_set(self): + # ckan.site_about has not been configured. + # Behaviour has always been to return an empty string. + assert g.site_about == '' + + def test_config_set_to_blank(self): + # ckan.site_description is configured but with no value. + # Behaviour has always been to return an empty string. + assert g.site_description == '' + + def test_set_from_ini(self): + # ckan.template_head_end is configured in test-core.ini + assert g.template_head_end == '' diff --git a/venv/lib/python2.7/site-packages/ckan/tests/lib/test_auth_tkt.py b/venv/lib/python2.7/site-packages/ckan/tests/lib/test_auth_tkt.py new file mode 100644 index 00000000..dbddcfbb --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/lib/test_auth_tkt.py @@ -0,0 +1,140 @@ +# encoding: utf-8 + +from nose import tools as nose_tools + +from ckan.tests import helpers +from ckan.lib.auth_tkt import make_plugin + + +class TestCkanAuthTktCookiePlugin(helpers.FunctionalTestBase): + + ''' + Test the added methods used by this subclass of + repoze.who.plugins.auth_tkt.AuthTktCookiePlugin + + Subclassing FunctionalTestBase ensures the original config is restored + after each test. + ''' + + @helpers.change_config('who.httponly', True) + def test_httponly_expected_cookies_with_config_httponly_true(self): + ''' + The returned cookies are in the format we expect, with HttpOnly flag. + ''' + plugin = make_plugin(secret='sosecret') + cookies = plugin._get_cookies(environ={'SERVER_NAME': '0.0.0.0'}, + value='HELLO') + expected_cookies = [ + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; HttpOnly'), + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; Domain=0.0.0.0; HttpOnly'), + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; Domain=.0.0.0.0; HttpOnly') + ] + assert cookies == expected_cookies + + @helpers.change_config('who.httponly', False) + def test_httponly_expected_cookies_with_config_httponly_false(self): + ''' + The returned cookies are in the format we expect, without HttpOnly + flag. + ''' + plugin = make_plugin(secret='sosecret') + cookies = plugin._get_cookies(environ={'SERVER_NAME': '0.0.0.0'}, + value='HELLO') + expected_cookies = [ + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/'), + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; Domain=0.0.0.0'), + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; Domain=.0.0.0.0') + ] + assert cookies == expected_cookies + + def test_httponly_expected_cookies_without_config_httponly(self): + ''' + The returned cookies are in the format we expect, with HttpOnly flag. + ''' + plugin = make_plugin(secret='sosecret') + cookies = plugin._get_cookies(environ={'SERVER_NAME': '0.0.0.0'}, + value='HELLO') + expected_cookies = [ + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; HttpOnly'), + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; Domain=0.0.0.0; HttpOnly'), + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; Domain=.0.0.0.0; HttpOnly') + ] + assert cookies == expected_cookies + + @helpers.change_config('who.secure', True) + def test_secure_expected_cookies_with_config_secure_true(self): + ''' + The returned cookies are in the format we expect, with secure flag. + ''' + plugin = make_plugin(secret='sosecret') + cookies = plugin._get_cookies(environ={'SERVER_NAME': '0.0.0.0'}, + value='HELLO') + expected_cookies = [ + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; secure; HttpOnly'), + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; Domain=0.0.0.0; secure; HttpOnly'), + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; Domain=.0.0.0.0; secure; HttpOnly') + ] + assert cookies == expected_cookies + + @helpers.change_config('who.secure', False) + def test_secure_expected_cookies_with_config_secure_false(self): + ''' + The returned cookies are in the format we expect, without secure + flag. + ''' + plugin = make_plugin(secret='sosecret') + cookies = plugin._get_cookies(environ={'SERVER_NAME': '0.0.0.0'}, + value='HELLO') + expected_cookies = [ + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; HttpOnly'), + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; Domain=0.0.0.0; HttpOnly'), + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; Domain=.0.0.0.0; HttpOnly') + ] + assert cookies == expected_cookies + + def test_secure_expected_cookies_without_config_secure(self): + ''' + The returned cookies are in the format we expect, without secure flag. + ''' + plugin = make_plugin(secret='sosecret') + cookies = plugin._get_cookies(environ={'SERVER_NAME': '0.0.0.0'}, + value='HELLO') + expected_cookies = [ + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; HttpOnly'), + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; Domain=0.0.0.0; HttpOnly'), + ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; Domain=.0.0.0.0; HttpOnly') + ] + assert cookies == expected_cookies + + def test_timeout_not_set_in_config(self): + ''' + Creating a CkanAuthTktCookiePlugin instance without setting timeout in + config sets correct values in CkanAuthTktCookiePlugin instance. + ''' + plugin = make_plugin(secret='sosecret') + + nose_tools.assert_equal(plugin.timeout, None) + nose_tools.assert_equal(plugin.reissue_time, None) + + @helpers.change_config('who.timeout', 9000) + def test_timeout_set_in_config(self): + ''' + Setting who.timeout in config sets correct values in + CkanAuthTktCookiePlugin instance. + ''' + plugin = make_plugin(secret='sosecret') + + nose_tools.assert_equal(plugin.timeout, 9000) + nose_tools.assert_equal(plugin.reissue_time, 900) + + @helpers.change_config('who.timeout', 9000) + @helpers.change_config('who.reissue_time', 200) + def test_reissue_set_in_config(self): + ''' + Setting who.reissue in config sets correct values in + CkanAuthTktCookiePlugin instance. + ''' + plugin = make_plugin(secret='sosecret') + + nose_tools.assert_equal(plugin.timeout, 9000) + nose_tools.assert_equal(plugin.reissue_time, 200) diff --git a/venv/lib/python2.7/site-packages/ckan/tests/lib/test_base.py b/venv/lib/python2.7/site-packages/ckan/tests/lib/test_base.py new file mode 100644 index 00000000..1be68119 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckan/tests/lib/test_base.py @@ -0,0 +1,347 @@ +# encoding: utf-8 + +from nose import tools as nose_tools + +import ckan.tests.helpers as helpers + +import ckan.plugins as p +import ckan.tests.factories as factories + + +class TestRenderSnippet(helpers.FunctionalTestBase): + """ + Test ``ckan.lib.base.render_snippet``. + """ + @helpers.change_config('debug', True) + def test_comment_present_if_debug_true(self): + response = self._get_test_app().get('/') + assert '0;) /* i++ because from-argument is sadly inclusive */ + if (i in this && this[i]===find) + return i; + return -1; + }; +} +if (!('forEach' in Array.prototype)) { + Array.prototype.forEach= function(action, that /*opt*/) { + for (var i= 0, n= this.length; iDataset +my.Dataset = Backbone.Model.extend({ + constructor: function Dataset() { + Backbone.Model.prototype.constructor.apply(this, arguments); + }, + + // ### initialize + initialize: function() { + var self = this; + _.bindAll(this, 'query'); + this.backend = null; + if (this.get('backend')) { + this.backend = this._backendFromString(this.get('backend')); + } else { // try to guess backend ... + if (this.get('records')) { + this.backend = recline.Backend.Memory; + } + } + this.fields = new my.FieldList(); + this.records = new my.RecordList(); + this._changes = { + deletes: [], + updates: [], + creates: [] + }; + this.facets = new my.FacetList(); + this.recordCount = null; + this.queryState = new my.Query(); + this.queryState.bind('change facet:add', function () { + self.query(); // We want to call query() without any arguments. + }); + // store is what we query and save against + // store will either be the backend or be a memory store if Backend fetch + // tells us to use memory store + this._store = this.backend; + + // if backend has a handleQueryResultFunction, use that + this._handleResult = (this.backend != null && _.has(this.backend, 'handleQueryResult')) ? + this.backend.handleQueryResult : this._handleQueryResult; + if (this.backend == recline.Backend.Memory) { + this.fetch(); + } + }, + + sync: function(method, model, options) { + return this.backend.sync(method, model, options); + }, + + // ### fetch + // + // Retrieve dataset and (some) records from the backend. + fetch: function() { + var self = this; + var dfd = new Deferred(); + + if (this.backend !== recline.Backend.Memory) { + this.backend.fetch(this.toJSON()) + .done(handleResults) + .fail(function(args) { + dfd.reject(args); + }); + } else { + // special case where we have been given data directly + handleResults({ + records: this.get('records'), + fields: this.get('fields'), + useMemoryStore: true + }); + } + + function handleResults(results) { + // if explicitly given the fields + // (e.g. var dataset = new Dataset({fields: fields, ...}) + // use that field info over anything we get back by parsing the data + // (results.fields) + var fields = self.get('fields') || results.fields; + + var out = self._normalizeRecordsAndFields(results.records, fields); + if (results.useMemoryStore) { + self._store = new recline.Backend.Memory.Store(out.records, out.fields); + } + + self.set(results.metadata); + self.fields.reset(out.fields); + self.query() + .done(function() { + dfd.resolve(self); + }) + .fail(function(args) { + dfd.reject(args); + }); + } + + return dfd.promise(); + }, + + // ### _normalizeRecordsAndFields + // + // Get a proper set of fields and records from incoming set of fields and records either of which may be null or arrays or objects + // + // e.g. fields = ['a', 'b', 'c'] and records = [ [1,2,3] ] => + // fields = [ {id: a}, {id: b}, {id: c}], records = [ {a: 1}, {b: 2}, {c: 3}] + _normalizeRecordsAndFields: function(records, fields) { + // if no fields get them from records + if (!fields && records && records.length > 0) { + // records is array then fields is first row of records ... + if (records[0] instanceof Array) { + fields = records[0]; + records = records.slice(1); + } else { + fields = _.map(_.keys(records[0]), function(key) { + return {id: key}; + }); + } + } + + // fields is an array of strings (i.e. list of field headings/ids) + if (fields && fields.length > 0 && (fields[0] === null || typeof(fields[0]) != 'object')) { + // Rename duplicate fieldIds as each field name needs to be + // unique. + var seen = {}; + fields = _.map(fields, function(field, index) { + if (field === null) { + field = ''; + } else { + field = field.toString(); + } + // cannot use trim as not supported by IE7 + var fieldId = field.replace(/^\s+|\s+$/g, ''); + if (fieldId === '') { + fieldId = '_noname_'; + field = fieldId; + } + while (fieldId in seen) { + seen[field] += 1; + fieldId = field + seen[field]; + } + if (!(field in seen)) { + seen[field] = 0; + } + // TODO: decide whether to keep original name as label ... + // return { id: fieldId, label: field || fieldId } + return { id: fieldId }; + }); + } + // records is provided as arrays so need to zip together with fields + // NB: this requires you to have fields to match arrays + if (records && records.length > 0 && records[0] instanceof Array) { + records = _.map(records, function(doc) { + var tmp = {}; + _.each(fields, function(field, idx) { + tmp[field.id] = doc[idx]; + }); + return tmp; + }); + } + return { + fields: fields, + records: records + }; + }, + + save: function() { + var self = this; + // TODO: need to reset the changes ... + return this._store.save(this._changes, this.toJSON()); + }, + + // ### query + // + // AJAX method with promise API to get records from the backend. + // + // It will query based on current query state (given by this.queryState) + // updated by queryObj (if provided). + // + // Resulting RecordList are used to reset this.records and are + // also returned. + query: function(queryObj) { + var self = this; + var dfd = new Deferred(); + this.trigger('query:start'); + + if (queryObj) { + var attributes = queryObj; + if (queryObj instanceof my.Query) { + attributes = queryObj.toJSON(); + } + this.queryState.set(attributes, {silent: true}); + } + var actualQuery = this.queryState.toJSON(); + + this._store.query(actualQuery, this.toJSON()) + .done(function(queryResult) { + self._handleResult(queryResult); + self.trigger('query:done'); + dfd.resolve(self.records); + }) + .fail(function(args) { + self.trigger('query:fail', args); + dfd.reject(args); + }); + return dfd.promise(); + }, + + _handleQueryResult: function(queryResult) { + var self = this; + self.recordCount = queryResult.total; + var docs = _.map(queryResult.hits, function(hit) { + var _doc = new my.Record(hit); + _doc.fields = self.fields; + _doc.bind('change', function(doc) { + self._changes.updates.push(doc.toJSON()); + }); + _doc.bind('destroy', function(doc) { + self._changes.deletes.push(doc.toJSON()); + }); + return _doc; + }); + self.records.reset(docs); + if (queryResult.facets) { + var facets = _.map(queryResult.facets, function(facetResult, facetId) { + facetResult.id = facetId; + return new my.Facet(facetResult); + }); + self.facets.reset(facets); + } + }, + + toTemplateJSON: function() { + var data = this.toJSON(); + data.recordCount = this.recordCount; + data.fields = this.fields.toJSON(); + return data; + }, + + // ### getFieldsSummary + // + // Get a summary for each field in the form of a `Facet`. + // + // @return null as this is async function. Provides deferred/promise interface. + getFieldsSummary: function() { + var self = this; + var query = new my.Query(); + query.set({size: 0}); + this.fields.each(function(field) { + query.addFacet(field.id); + }); + var dfd = new Deferred(); + this._store.query(query.toJSON(), this.toJSON()).done(function(queryResult) { + if (queryResult.facets) { + _.each(queryResult.facets, function(facetResult, facetId) { + facetResult.id = facetId; + var facet = new my.Facet(facetResult); + // TODO: probably want replace rather than reset (i.e. just replace the facet with this id) + self.fields.get(facetId).facets.reset(facet); + }); + } + dfd.resolve(queryResult); + }); + return dfd.promise(); + }, + + // Deprecated (as of v0.5) - use record.summary() + recordSummary: function(record) { + return record.summary(); + }, + + // ### _backendFromString(backendString) + // + // Look up a backend module from a backend string (look in recline.Backend) + _backendFromString: function(backendString) { + var backend = null; + if (recline && recline.Backend) { + _.each(_.keys(recline.Backend), function(name) { + if (name.toLowerCase() === backendString.toLowerCase()) { + backend = recline.Backend[name]; + } + }); + } + return backend; + } +}); + + +// ## A Record +// +// A single record (or row) in the dataset +my.Record = Backbone.Model.extend({ + constructor: function Record() { + Backbone.Model.prototype.constructor.apply(this, arguments); + }, + + // ### initialize + // + // Create a Record + // + // You usually will not do this directly but will have records created by + // Dataset e.g. in query method + // + // Certain methods require presence of a fields attribute (identical to that on Dataset) + initialize: function() { + _.bindAll(this, 'getFieldValue'); + }, + + // ### getFieldValue + // + // For the provided Field get the corresponding rendered computed data value + // for this record. + // + // NB: if field is undefined a default '' value will be returned + getFieldValue: function(field) { + var val = this.getFieldValueUnrendered(field); + if (field && !_.isUndefined(field.renderer)) { + val = field.renderer(val, field, this.toJSON()); + } + return val; + }, + + // ### getFieldValueUnrendered + // + // For the provided Field get the corresponding computed data value + // for this record. + // + // NB: if field is undefined a default '' value will be returned + getFieldValueUnrendered: function(field) { + if (!field) { + return ''; + } + var val = this.get(field.id); + if (field.deriver) { + val = field.deriver(val, field, this); + } + return val; + }, + + // ### summary + // + // Get a simple html summary of this record in form of key/value list + summary: function(record) { + var self = this; + var html = '
    '; + this.fields.each(function(field) { + if (field.id != 'id') { + html += '
    ' + field.get('label') + ': ' + self.getFieldValue(field) + '
    '; + } + }); + html += '
    '; + return html; + }, + + // Override Backbone save, fetch and destroy so they do nothing + // Instead, Dataset object that created this Record should take care of + // handling these changes (discovery will occur via event notifications) + // WARNING: these will not persist *unless* you call save on Dataset + fetch: function() {}, + save: function() {}, + destroy: function() { this.trigger('destroy', this); } +}); + + +// ## A Backbone collection of Records +my.RecordList = Backbone.Collection.extend({ + constructor: function RecordList() { + Backbone.Collection.prototype.constructor.apply(this, arguments); + }, + model: my.Record +}); + + +// ## A Field (aka Column) on a Dataset +my.Field = Backbone.Model.extend({ + constructor: function Field() { + Backbone.Model.prototype.constructor.apply(this, arguments); + }, + // ### defaults - define default values + defaults: { + label: null, + type: 'string', + format: null, + is_derived: false + }, + // ### initialize + // + // @param {Object} data: standard Backbone model attributes + // + // @param {Object} options: renderer and/or deriver functions. + initialize: function(data, options) { + // if a hash not passed in the first argument throw error + if ('0' in data) { + throw new Error('Looks like you did not pass a proper hash with id to Field constructor'); + } + if (this.attributes.label === null) { + this.set({label: this.id}); + } + if (this.attributes.type.toLowerCase() in this._typeMap) { + this.attributes.type = this._typeMap[this.attributes.type.toLowerCase()]; + } + if (options) { + this.renderer = options.renderer; + this.deriver = options.deriver; + } + if (!this.renderer) { + this.renderer = this.defaultRenderers[this.get('type')]; + } + this.facets = new my.FacetList(); + }, + _typeMap: { + 'text': 'string', + 'double': 'number', + 'float': 'number', + 'numeric': 'number', + 'int': 'integer', + 'datetime': 'date-time', + 'bool': 'boolean', + 'timestamp': 'date-time', + 'json': 'object' + }, + defaultRenderers: { + object: function(val, field, doc) { + return JSON.stringify(val); + }, + geo_point: function(val, field, doc) { + return JSON.stringify(val); + }, + 'number': function(val, field, doc) { + var format = field.get('format'); + if (format === 'percentage') { + return val + '%'; + } + return val; + }, + 'string': function(val, field, doc) { + var format = field.get('format'); + if (format === 'markdown') { + if (typeof Showdown !== 'undefined') { + var showdown = new Showdown.converter(); + out = showdown.makeHtml(val); + return out; + } else { + return val; + } + } else if (format == 'plain') { + return val; + } else { + // as this is the default and default type is string may get things + // here that are not actually strings + if (val && typeof val === 'string') { + val = val.replace(/(https?:\/\/[^ ]+)/g, '$1'); + } + return val; + } + } + } +}); + +my.FieldList = Backbone.Collection.extend({ + constructor: function FieldList() { + Backbone.Collection.prototype.constructor.apply(this, arguments); + }, + model: my.Field +}); + +// ## Query +my.Query = Backbone.Model.extend({ + constructor: function Query() { + Backbone.Model.prototype.constructor.apply(this, arguments); + }, + defaults: function() { + return { + size: 100, + from: 0, + q: '', + facets: {}, + filters: [] + }; + }, + _filterTemplates: { + term: { + type: 'term', + // TODO do we need this attribute here? + field: '', + term: '' + }, + range: { + type: 'range', + from: '', + to: '' + }, + geo_distance: { + type: 'geo_distance', + distance: 10, + unit: 'km', + point: { + lon: 0, + lat: 0 + } + } + }, + // ### addFilter(filter) + // + // Add a new filter specified by the filter hash and append to the list of filters + // + // @param filter an object specifying the filter - see _filterTemplates for examples. If only type is provided will generate a filter by cloning _filterTemplates + addFilter: function(filter) { + // crude deep copy + var ourfilter = JSON.parse(JSON.stringify(filter)); + // not fully specified so use template and over-write + if (_.keys(filter).length <= 3) { + ourfilter = _.defaults(ourfilter, this._filterTemplates[filter.type]); + } + var filters = this.get('filters'); + filters.push(ourfilter); + this.trigger('change:filters:new-blank'); + }, + replaceFilter: function(filter) { + // delete filter on the same field, then add + var filters = this.get('filters'); + var idx = -1; + _.each(this.get('filters'), function(f, key, list) { + if (filter.field == f.field) { + idx = key; + } + }); + // trigger just one event (change:filters:new-blank) instead of one for remove and + // one for add + if (idx >= 0) { + filters.splice(idx, 1); + this.set({filters: filters}); + } + this.addFilter(filter); + }, + updateFilter: function(index, value) { + }, + // ### removeFilter + // + // Remove a filter from filters at index filterIndex + removeFilter: function(filterIndex) { + var filters = this.get('filters'); + filters.splice(filterIndex, 1); + this.set({filters: filters}); + this.trigger('change'); + }, + // ### addFacet + // + // Add a Facet to this query + // + // See + addFacet: function(fieldId, size, silent) { + var facets = this.get('facets'); + // Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field) + if (_.contains(_.keys(facets), fieldId)) { + return; + } + facets[fieldId] = { + terms: { field: fieldId } + }; + if (!_.isUndefined(size)) { + facets[fieldId].terms.size = size; + } + this.set({facets: facets}, {silent: true}); + if (!silent) { + this.trigger('facet:add', this); + } + }, + addHistogramFacet: function(fieldId) { + var facets = this.get('facets'); + facets[fieldId] = { + date_histogram: { + field: fieldId, + interval: 'day' + } + }; + this.set({facets: facets}, {silent: true}); + this.trigger('facet:add', this); + }, + removeFacet: function(fieldId) { + var facets = this.get('facets'); + // Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field) + if (!_.contains(_.keys(facets), fieldId)) { + return; + } + delete facets[fieldId]; + this.set({facets: facets}, {silent: true}); + this.trigger('facet:remove', this); + }, + clearFacets: function() { + var facets = this.get('facets'); + _.each(_.keys(facets), function(fieldId) { + delete facets[fieldId]; + }); + this.trigger('facet:remove', this); + }, + // trigger a facet add; use this to trigger a single event after adding + // multiple facets + refreshFacets: function() { + this.trigger('facet:add', this); + } + +}); + + +// ## A Facet (Result) +my.Facet = Backbone.Model.extend({ + constructor: function Facet() { + Backbone.Model.prototype.constructor.apply(this, arguments); + }, + defaults: function() { + return { + _type: 'terms', + total: 0, + other: 0, + missing: 0, + terms: [] + }; + } +}); + +// ## A Collection/List of Facets +my.FacetList = Backbone.Collection.extend({ + constructor: function FacetList() { + Backbone.Collection.prototype.constructor.apply(this, arguments); + }, + model: my.Facet +}); + +// ## Object State +// +// Convenience Backbone model for storing (configuration) state of objects like Views. +my.ObjectState = Backbone.Model.extend({ +}); + + +// ## Backbone.sync +// +// Override Backbone.sync to hand off to sync function in relevant backend +// Backbone.sync = function(method, model, options) { +// return model.backend.sync(method, model, options); +// }; + +}(this.recline.Model)); + +/*jshint multistr:true */ + +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; + +(function($, my) { + "use strict"; +// ## Graph view for a Dataset using Flot graphing library. +// +// Initialization arguments (in a hash in first parameter): +// +// * model: recline.Model.Dataset +// * state: (optional) configuration hash of form: +// +// { +// group: {column name for x-axis}, +// series: [{column name for series A}, {column name series B}, ... ], +// // options are: lines, points, lines-and-points, bars, columns +// graphType: 'lines', +// graphOptions: {custom [flot options]} +// } +// +// NB: should *not* provide an el argument to the view but must let the view +// generate the element itself (you can then append view.el to the DOM. +my.Flot = Backbone.View.extend({ + template: ' \ +
    \ +
    \ +
    \ +

    Hey there!

    \ +

    There\'s no graph here yet because we don\'t know what fields you\'d like to see plotted.

    \ +

    Please tell us by using the menu on the right and a graph will automatically appear.

    \ +
    \ +
    \ +
    \ +', + + initialize: function(options) { + var self = this; + this.graphColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"]; + + _.bindAll(this, 'render', 'redraw', '_toolTip', '_xaxisLabel'); + this.needToRedraw = false; + this.listenTo(this.model, 'change', this.render); + this.listenTo(this.model.fields, 'reset add', this.render); + this.listenTo(this.model.records, 'reset add', this.redraw); + var stateData = _.extend({ + group: null, + // so that at least one series chooser box shows up + series: [], + graphType: 'lines-and-points' + }, + options.state + ); + this.state = new recline.Model.ObjectState(stateData); + this.previousTooltipPoint = {x: null, y: null}; + this.editor = new my.FlotControls({ + model: this.model, + state: this.state.toJSON() + }); + this.listenTo(this.editor.state, 'change', function() { + self.state.set(self.editor.state.toJSON()); + self.redraw(); + }); + this.elSidebar = this.editor.$el; + }, + + render: function() { + var self = this; + var tmplData = this.model.toTemplateJSON(); + var htmls = Mustache.render(this.template, tmplData); + this.$el.html(htmls); + this.$graph = this.$el.find('.panel.graph'); + this.$graph.on("plothover", this._toolTip); + return this; + }, + + remove: function () { + this.editor.remove(); + Backbone.View.prototype.remove.apply(this, arguments); + }, + + redraw: function() { + // There are issues generating a Flot graph if either: + // * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flot will complain with + // Uncaught Invalid dimensions for plot, width = 0, height = 0 + // * There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value' + var areWeVisible = !jQuery.expr.filters.hidden(this.el); + if ((!areWeVisible || this.model.records.length === 0)) { + this.needToRedraw = true; + return; + } + + // check we have something to plot + if (this.state.get('group') && this.state.get('series')) { + var series = this.createSeries(); + var options = this.getGraphOptions(this.state.attributes.graphType, series[0].data.length); + this.plot = $.plot(this.$graph, series, options); + } + }, + + show: function() { + // because we cannot redraw when hidden we may need to when becoming visible + if (this.needToRedraw) { + this.redraw(); + } + }, + + // infoboxes on mouse hover on points/bars etc + _toolTip: function (event, pos, item) { + if (item) { + if (this.previousTooltipPoint.x !== item.dataIndex || + this.previousTooltipPoint.y !== item.seriesIndex) { + this.previousTooltipPoint.x = item.dataIndex; + this.previousTooltipPoint.y = item.seriesIndex; + $("#recline-flot-tooltip").remove(); + + var x = item.datapoint[0].toFixed(2), + y = item.datapoint[1].toFixed(2); + + if (this.state.attributes.graphType === 'bars') { + x = item.datapoint[1].toFixed(2), + y = item.datapoint[0].toFixed(2); + } + + var content = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>', { + group: this.state.attributes.group, + x: this._xaxisLabel(x), + series: item.series.label, + y: y + }); + + // use a different tooltip location offset for bar charts + var xLocation, yLocation; + if (this.state.attributes.graphType === 'bars') { + xLocation = item.pageX + 15; + yLocation = item.pageY - 10; + } else if (this.state.attributes.graphType === 'columns') { + xLocation = item.pageX + 15; + yLocation = item.pageY; + } else { + xLocation = item.pageX + 10; + yLocation = item.pageY - 20; + } + + $('
    ' + content + '
    ').css({ + top: yLocation, + left: xLocation + }).appendTo("body").fadeIn(200); + } + } else { + $("#recline-flot-tooltip").remove(); + this.previousTooltipPoint.x = null; + this.previousTooltipPoint.y = null; + } + }, + + _xaxisLabel: function (x) { + if (this._groupFieldIsDateTime()) { + // oddly x comes through as milliseconds *string* (rather than int + // or float) so we have to reparse + x = new Date(parseFloat(x)).toLocaleDateString(); + } else if (this.xvaluesAreIndex) { + x = parseInt(x, 10); + // HACK: deal with bar graph style cases where x-axis items were strings + // In this case x at this point is the index of the item in the list of + // records not its actual x-axis value + x = this.model.records.models[x].get(this.state.attributes.group); + } + + return x; + }, + + // ### getGraphOptions + // + // Get options for Flot Graph + // + // needs to be function as can depend on state + // + // @param typeId graphType id (lines, lines-and-points etc) + // @param numPoints the number of points that will be plotted + getGraphOptions: function(typeId, numPoints) { + var self = this; + var groupFieldIsDateTime = self._groupFieldIsDateTime(); + var xaxis = {}; + + if (!groupFieldIsDateTime) { + xaxis.tickFormatter = function (x) { + // convert x to a string and make sure that it is not too long or the + // tick labels will overlap + // TODO: find a more accurate way of calculating the size of tick labels + var label = self._xaxisLabel(x) || ""; + + if (typeof label !== 'string') { + label = label.toString(); + } + if (self.state.attributes.graphType !== 'bars' && label.length > 10) { + label = label.slice(0, 10) + "..."; + } + + return label; + }; + } + + // for labels case we only want ticks at the label intervals + // HACK: however we also get this case with Date fields. In that case we + // could have a lot of values and so we limit to max 15 (we assume) + if (this.xvaluesAreIndex) { + var numTicks = Math.min(this.model.records.length, 15); + var increment = this.model.records.length / numTicks; + var ticks = []; + for (var i=0; i \ +
    \ +
    \ +
    \ + \ +
    \ + \ +
    \ +
    \ +
    \ + \ +
    \ + \ +
    \ +
    \ +
    \ +
    \ +
    \ +
    \ + \ +
    \ + \ +
    \ +
    \ +', + templateSeriesEditor: ' \ +
    \ +
    \ + \ +
    \ + \ +
    \ +
    \ +
    \ + ', + events: { + 'change form select': 'onEditorSubmit', + 'click .editor-add': '_onAddSeries', + 'click .action-remove-series': 'removeSeries' + }, + + initialize: function(options) { + var self = this; + _.bindAll(this, 'render'); + this.listenTo(this.model.fields, 'reset add', this.render); + this.state = new recline.Model.ObjectState(options.state); + this.render(); + }, + + render: function() { + var self = this; + var tmplData = this.model.toTemplateJSON(); + var htmls = Mustache.render(this.template, tmplData); + this.$el.html(htmls); + + // set up editor from state + if (this.state.get('graphType')) { + this._selectOption('.editor-type', this.state.get('graphType')); + } + if (this.state.get('group')) { + this._selectOption('.editor-group', this.state.get('group')); + } + // ensure at least one series box shows up + var tmpSeries = [""]; + if (this.state.get('series').length > 0) { + tmpSeries = this.state.get('series'); + } + _.each(tmpSeries, function(series, idx) { + self.addSeries(idx); + self._selectOption('.editor-series.js-series-' + idx, series); + }); + return this; + }, + + // Private: Helper function to select an option from a select list + // + _selectOption: function(id,value){ + var options = this.$el.find(id + ' select > option'); + if (options) { + options.each(function(opt){ + if (this.value == value) { + $(this).attr('selected','selected'); + return false; + } + }); + } + }, + + onEditorSubmit: function(e) { + var select = this.$el.find('.editor-group select'); + var $editor = this; + var $series = this.$el.find('.editor-series select'); + var series = $series.map(function () { + return $(this).val(); + }); + var updatedState = { + series: $.makeArray(series), + group: this.$el.find('.editor-group select').val(), + graphType: this.$el.find('.editor-type select').val() + }; + this.state.set(updatedState); + }, + + // Public: Adds a new empty series select box to the editor. + // + // @param [int] idx index of this series in the list of series + // + // Returns itself. + addSeries: function (idx) { + var data = _.extend({ + seriesIndex: idx, + seriesName: String.fromCharCode(idx + 64 + 1) + }, this.model.toTemplateJSON()); + + var htmls = Mustache.render(this.templateSeriesEditor, data); + this.$el.find('.editor-series-group').append(htmls); + return this; + }, + + _onAddSeries: function(e) { + e.preventDefault(); + this.addSeries(this.state.get('series').length); + }, + + // Public: Removes a series list item from the editor. + // + // Also updates the labels of the remaining series elements. + removeSeries: function (e) { + e.preventDefault(); + var $el = $(e.target); + $el.parent().parent().remove(); + this.onEditorSubmit(); + } +}); + +})(jQuery, recline.View); +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; +this.recline.View.Graph = this.recline.View.Flot; +this.recline.View.GraphControls = this.recline.View.FlotControls; +/*jshint multistr:true */ + +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; + +(function($, my) { + "use strict"; +// ## (Data) Grid Dataset View +// +// Provides a tabular view on a Dataset. +// +// Initialize it with a `recline.Model.Dataset`. +my.Grid = Backbone.View.extend({ + tagName: "div", + className: "recline-grid-container", + + initialize: function(modelEtc) { + var self = this; + _.bindAll(this, 'render', 'onHorizontalScroll'); + this.listenTo(this.model.records, 'add reset remove', this.render); + this.tempState = {}; + var state = _.extend({ + hiddenFields: [] + }, modelEtc.state + ); + this.state = new recline.Model.ObjectState(state); + }, + + events: { + // does not work here so done at end of render function + // 'scroll .recline-grid tbody': 'onHorizontalScroll' + }, + + // ====================================================== + // Column and row menus + + setColumnSort: function(order) { + var sort = [{}]; + sort[0][this.tempState.currentColumn] = {order: order}; + this.model.query({sort: sort}); + }, + + hideColumn: function() { + var hiddenFields = this.state.get('hiddenFields'); + hiddenFields.push(this.tempState.currentColumn); + this.state.set({hiddenFields: hiddenFields}); + // change event not being triggered (because it is an array?) so trigger manually + this.state.trigger('change'); + this.render(); + }, + + showColumn: function(e) { + var hiddenFields = _.without(this.state.get('hiddenFields'), $(e.target).data('column')); + this.state.set({hiddenFields: hiddenFields}); + this.render(); + }, + + onHorizontalScroll: function(e) { + var currentScroll = $(e.target).scrollLeft(); + this.$el.find('.recline-grid thead tr').scrollLeft(currentScroll); + }, + + // ====================================================== + // #### Templating + template: ' \ +
    \ + \ + \ + \ + {{#fields}} \ + \ + {{/fields}} \ + \ + \ + \ + \ +
    \ + {{label}} \ +
    \ +
    \ + ', + + toTemplateJSON: function() { + var self = this; + var modelData = this.model.toJSON(); + modelData.notEmpty = ( this.fields.length > 0 ); + // TODO: move this sort of thing into a toTemplateJSON method on Dataset? + modelData.fields = this.fields.map(function(field) { + return field.toJSON(); + }); + // last header width = scroll bar - border (2px) */ + modelData.lastHeaderWidth = this.scrollbarDimensions.width - 2; + return modelData; + }, + render: function() { + var self = this; + this.fields = new recline.Model.FieldList(this.model.fields.filter(function(field) { + return _.indexOf(self.state.get('hiddenFields'), field.id) == -1; + })); + + this.scrollbarDimensions = this.scrollbarDimensions || this._scrollbarSize(); // skip measurement if already have dimensions + var numFields = this.fields.length; + // compute field widths (-20 for first menu col + 10px for padding on each col and finally 16px for the scrollbar) + var fullWidth = self.$el.width() - 20 - 10 * numFields - this.scrollbarDimensions.width; + var width = parseInt(Math.max(50, fullWidth / numFields), 10); + // if columns extend outside viewport then remainder is 0 + var remainder = Math.max(fullWidth - numFields * width,0); + this.fields.each(function(field, idx) { + // add the remainder to the first field width so we make up full col + if (idx === 0) { + field.set({width: width+remainder}); + } else { + field.set({width: width}); + } + }); + var htmls = Mustache.render(this.template, this.toTemplateJSON()); + this.$el.html(htmls); + this.model.records.forEach(function(doc) { + var tr = $(''); + self.$el.find('tbody').append(tr); + var newView = new my.GridRow({ + model: doc, + el: tr, + fields: self.fields + }); + newView.render(); + }); + // hide extra header col if no scrollbar to avoid unsightly overhang + var $tbody = this.$el.find('tbody')[0]; + if ($tbody.scrollHeight <= $tbody.offsetHeight) { + this.$el.find('th.last-header').hide(); + } + this.$el.find('.recline-grid').toggleClass('no-hidden', (self.state.get('hiddenFields').length === 0)); + this.$el.find('.recline-grid tbody').scroll(this.onHorizontalScroll); + return this; + }, + + // ### _scrollbarSize + // + // Measure width of a vertical scrollbar and height of a horizontal scrollbar. + // + // @return: { width: pixelWidth, height: pixelHeight } + _scrollbarSize: function() { + var $c = $("
    ").appendTo("body"); + var dim = { width: $c.width() - $c[0].clientWidth + 1, height: $c.height() - $c[0].clientHeight }; + $c.remove(); + return dim; + } +}); + +// ## GridRow View for rendering an individual record. +// +// Since we want this to update in place it is up to creator to provider the element to attach to. +// +// In addition you *must* pass in a FieldList in the constructor options. This should be list of fields for the Grid. +// +// Example: +// +//
    +// var row = new GridRow({
    +//   model: dataset-record,
    +//     el: dom-element,
    +//     fields: mydatasets.fields // a FieldList object
    +//   });
    +// 
    +my.GridRow = Backbone.View.extend({ + initialize: function(initData) { + _.bindAll(this, 'render'); + this._fields = initData.fields; + this.listenTo(this.model, 'change', this.render); + }, + + template: ' \ + {{#cells}} \ + \ +
    \ +   \ +
    {{{value}}}
    \ +
    \ + \ + {{/cells}} \ + ', + events: { + 'click .data-table-cell-edit': 'onEditClick', + 'click .data-table-cell-editor .okButton': 'onEditorOK', + 'click .data-table-cell-editor .cancelButton': 'onEditorCancel' + }, + + toTemplateJSON: function() { + var self = this; + var doc = this.model; + var cellData = this._fields.map(function(field) { + return { + field: field.id, + width: field.get('width'), + value: doc.getFieldValue(field) + }; + }); + return { id: this.id, cells: cellData }; + }, + + render: function() { + this.$el.attr('data-id', this.model.id); + var html = Mustache.render(this.template, this.toTemplateJSON()); + this.$el.html(html); + return this; + }, + + // =================== + // Cell Editor methods + + cellEditorTemplate: ' \ + \ + ', + + onEditClick: function(e) { + var editing = this.$el.find('.data-table-cell-editor-editor'); + if (editing.length > 0) { + editing.parents('.data-table-cell-value').html(editing.text()).siblings('.data-table-cell-edit').removeClass("hidden"); + } + $(e.target).addClass("hidden"); + var cell = $(e.target).siblings('.data-table-cell-value'); + cell.data("previousContents", cell.text()); + var templated = Mustache.render(this.cellEditorTemplate, {value: cell.text()}); + cell.html(templated); + }, + + onEditorOK: function(e) { + var self = this; + var cell = $(e.target); + var rowId = cell.parents('tr').attr('data-id'); + var field = cell.parents('td').attr('data-field'); + var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val(); + var newData = {}; + newData[field] = newValue; + this.model.set(newData); + this.trigger('recline:flash', {message: "Updating row...", loader: true}); + this.model.save().then(function(response) { + this.trigger('recline:flash', {message: "Row updated successfully", category: 'success'}); + }) + .fail(function() { + this.trigger('recline:flash', { + message: 'Error saving row', + category: 'error', + persist: true + }); + }); + }, + + onEditorCancel: function(e) { + var cell = $(e.target).parents('.data-table-cell-value'); + cell.html(cell.data('previousContents')).siblings('.data-table-cell-edit').removeClass("hidden"); + } +}); + +})(jQuery, recline.View); +/*jshint multistr:true */ + +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; + +(function($, my) { + "use strict"; +// ## Map view for a Dataset using Leaflet mapping library. +// +// This view allows to plot gereferenced records on a map. The location +// information can be provided in 2 ways: +// +// 1. Via a single field. This field must be either a geo_point or +// [GeoJSON](http://geojson.org) object +// 2. Via two fields with latitude and longitude coordinates. +// +// Which fields in the data these correspond to can be configured via the state +// (and are guessed if no info is provided). +// +// Initialization arguments are as standard for Dataset Views. State object may +// have the following (optional) configuration options: +// +//
    +//   {
    +//     // geomField if specified will be used in preference to lat/lon
    +//     geomField: {id of field containing geometry in the dataset}
    +//     lonField: {id of field containing longitude in the dataset}
    +//     latField: {id of field containing latitude in the dataset}
    +//     autoZoom: true,
    +//     // use cluster support
    +//     // cluster: true = always on
    +//     // cluster: false = always off
    +//     cluster: false
    +//   }
    +// 
    +// +// Useful attributes to know about (if e.g. customizing) +// +// * map: the Leaflet map (L.Map) +// * features: Leaflet GeoJSON layer containing all the features (L.GeoJSON) +my.Map = Backbone.View.extend({ + template: ' \ +
    \ +
    \ +
    \ +', + + // These are the default (case-insensitive) names of field that are used if found. + // If not found, the user will need to define the fields via the editor. + latitudeFieldNames: ['lat','latitude'], + longitudeFieldNames: ['lon','longitude'], + geometryFieldNames: ['geojson', 'geom','the_geom','geometry','spatial','location', 'geo', 'lonlat'], + + initialize: function(options) { + var self = this; + this.visible = this.$el.is(':visible'); + this.mapReady = false; + // this will be the Leaflet L.Map object (setup below) + this.map = null; + + var stateData = _.extend({ + geomField: null, + lonField: null, + latField: null, + autoZoom: true, + cluster: false + }, + options.state + ); + this.state = new recline.Model.ObjectState(stateData); + + this._clusterOptions = { + zoomToBoundsOnClick: true, + //disableClusteringAtZoom: 10, + maxClusterRadius: 80, + singleMarkerMode: false, + skipDuplicateAddTesting: true, + animateAddingMarkers: false + }; + + // Listen to changes in the fields + this.listenTo(this.model.fields, 'change', function() { + self._setupGeometryField(); + self.render(); + }); + + // Listen to changes in the records + this.listenTo(this.model.records, 'add', function(doc){self.redraw('add',doc);}); + this.listenTo(this.model.records, 'change', function(doc){ + self.redraw('remove',doc); + self.redraw('add',doc); + }); + this.listenTo(this.model.records, 'remove', function(doc){self.redraw('remove',doc);}); + this.listenTo(this.model.records, 'reset', function(){self.redraw('reset');}); + + this.menu = new my.MapMenu({ + model: this.model, + state: this.state.toJSON() + }); + this.listenTo(this.menu.state, 'change', function() { + self.state.set(self.menu.state.toJSON()); + self.redraw(); + }); + this.listenTo(this.state, 'change', function() { + self.redraw(); + }); + this.elSidebar = this.menu.$el; + }, + + // ## Customization Functions + // + // The following methods are designed for overriding in order to customize + // behaviour + + // ### infobox + // + // Function to create infoboxes used in popups. The default behaviour is very simple and just lists all attributes. + // + // Users should override this function to customize behaviour i.e. + // + // view = new View({...}); + // view.infobox = function(record) { + // ... + // } + infobox: function(record) { + var html = ''; + for (var key in record.attributes){ + if (!(this.state.get('geomField') && key == this.state.get('geomField'))){ + html += '
    ' + key + ': '+ record.attributes[key] + '
    '; + } + } + return html; + }, + + // Options to use for the [Leaflet GeoJSON layer](http://leaflet.cloudmade.com/reference.html#geojson) + // See also + // + // e.g. + // + // pointToLayer: function(feature, latLng) + // onEachFeature: function(feature, layer) + // + // See defaults for examples + geoJsonLayerOptions: { + // pointToLayer function to use when creating points + // + // Default behaviour shown here is to create a marker using the + // popupContent set on the feature properties (created via infobox function + // during feature generation) + // + // NB: inside pointToLayer `this` will be set to point to this map view + // instance (which allows e.g. this.markers to work in this default case) + pointToLayer: function (feature, latlng) { + var marker = new L.Marker(latlng); + marker.bindPopup(feature.properties.popupContent); + // this is for cluster case + this.markers.addLayer(marker); + return marker; + }, + // onEachFeature default which adds popup in + onEachFeature: function(feature, layer) { + if (feature.properties && feature.properties.popupContent) { + layer.bindPopup(feature.properties.popupContent); + } + } + }, + + // END: Customization section + // ---- + + // ### Public: Adds the necessary elements to the page. + // + // Also sets up the editor fields and the map if necessary. + render: function() { + var self = this; + var htmls = Mustache.render(this.template, this.model.toTemplateJSON()); + this.$el.html(htmls); + this.$map = this.$el.find('.panel.map'); + this.redraw(); + return this; + }, + + // ### Public: Redraws the features on the map according to the action provided + // + // Actions can be: + // + // * reset: Clear all features + // * add: Add one or n features (records) + // * remove: Remove one or n features (records) + // * refresh: Clear existing features and add all current records + redraw: function(action, doc){ + var self = this; + action = action || 'refresh'; + // try to set things up if not already + if (!self._geomReady()){ + self._setupGeometryField(); + } + if (!self.mapReady){ + self._setupMap(); + } + + if (this._geomReady() && this.mapReady){ + // removing ad re-adding the layer enables faster bulk loading + this.map.removeLayer(this.features); + this.map.removeLayer(this.markers); + + var countBefore = 0; + this.features.eachLayer(function(){countBefore++;}); + + if (action == 'refresh' || action == 'reset') { + this.features.clearLayers(); + // recreate cluster group because of issues with clearLayer + this.map.removeLayer(this.markers); + this.markers = new L.MarkerClusterGroup(this._clusterOptions); + this._add(this.model.records.models); + } else if (action == 'add' && doc){ + this._add(doc); + } else if (action == 'remove' && doc){ + this._remove(doc); + } + + // this must come before zooming! + // if not: errors when using e.g. circle markers like + // "Cannot call method 'project' of undefined" + if (this.state.get('cluster')) { + this.map.addLayer(this.markers); + } else { + this.map.addLayer(this.features); + } + + if (this.state.get('autoZoom')){ + if (this.visible){ + this._zoomToFeatures(); + } else { + this._zoomPending = true; + } + } + } + }, + + show: function() { + // If the div was hidden, Leaflet needs to recalculate some sizes + // to display properly + if (this.map){ + this.map.invalidateSize(); + if (this._zoomPending && this.state.get('autoZoom')) { + this._zoomToFeatures(); + this._zoomPending = false; + } + } + this.visible = true; + }, + + hide: function() { + this.visible = false; + }, + + _geomReady: function() { + return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); + }, + + // Private: Add one or n features to the map + // + // For each record passed, a GeoJSON geometry will be extracted and added + // to the features layer. If an exception is thrown, the process will be + // stopped and an error notification shown. + // + // Each feature will have a popup associated with all the record fields. + // + _add: function(docs){ + var self = this; + + if (!(docs instanceof Array)) docs = [docs]; + + var count = 0; + var wrongSoFar = 0; + _.every(docs, function(doc){ + count += 1; + var feature = self._getGeometryFromRecord(doc); + if (typeof feature === 'undefined' || feature === null){ + // Empty field + return true; + } else if (feature instanceof Object){ + feature.properties = { + popupContent: self.infobox(doc), + // Add a reference to the model id, which will allow us to + // link this Leaflet layer to a Recline doc + cid: doc.cid + }; + + try { + self.features.addData(feature); + } catch (except) { + wrongSoFar += 1; + var msg = 'Wrong geometry value'; + if (except.message) msg += ' (' + except.message + ')'; + if (wrongSoFar <= 10) { + self.trigger('recline:flash', {message: msg, category:'error'}); + } + } + } else { + wrongSoFar += 1; + if (wrongSoFar <= 10) { + self.trigger('recline:flash', {message: 'Wrong geometry value', category:'error'}); + } + } + return true; + }); + }, + + // Private: Remove one or n features from the map + // + _remove: function(docs){ + + var self = this; + + if (!(docs instanceof Array)) docs = [docs]; + + _.each(docs,function(doc){ + for (var key in self.features._layers){ + if (self.features._layers[key].feature.geometry.properties.cid == doc.cid){ + self.features.removeLayer(self.features._layers[key]); + } + } + }); + + }, + + // Private: convert DMS coordinates to decimal + // + // north and east are positive, south and west are negative + // + _parseCoordinateString: function(coord){ + if (typeof(coord) != 'string') { + return(parseFloat(coord)); + } + var dms = coord.split(/[^-?\.\d\w]+/); + var deg = 0; var m = 0; + var toDeg = [1, 60, 3600]; // conversion factors for Deg, min, sec + var i; + for (i = 0; i < dms.length; ++i) { + if (isNaN(parseFloat(dms[i]))) { + continue; + } + deg += parseFloat(dms[i]) / toDeg[m]; + m += 1; + } + if (coord.match(/[SW]/)) { + deg = -1*deg; + } + return(deg); + }, + + // Private: Return a GeoJSON geomtry extracted from the record fields + // + _getGeometryFromRecord: function(doc){ + if (this.state.get('geomField')){ + var value = doc.get(this.state.get('geomField')); + if (typeof(value) === 'string'){ + // We *may* have a GeoJSON string representation + try { + value = $.parseJSON(value); + } catch(e) {} + } + if (typeof(value) === 'string') { + value = value.replace('(', '').replace(')', ''); + var parts = value.split(','); + var lat = this._parseCoordinateString(parts[0]); + var lon = this._parseCoordinateString(parts[1]); + + if (!isNaN(lon) && !isNaN(parseFloat(lat))) { + return { + "type": "Point", + "coordinates": [lon, lat] + }; + } else { + return null; + } + } else if (value && _.isArray(value)) { + // [ lon, lat ] + return { + "type": "Point", + "coordinates": [value[0], value[1]] + }; + } else if (value && value.lat) { + // of form { lat: ..., lon: ...} + return { + "type": "Point", + "coordinates": [value.lon || value.lng, value.lat] + }; + } + // We o/w assume that contents of the field are a valid GeoJSON object + return value; + } else if (this.state.get('lonField') && this.state.get('latField')){ + // We'll create a GeoJSON like point object from the two lat/lon fields + var lon = doc.get(this.state.get('lonField')); + var lat = doc.get(this.state.get('latField')); + lon = this._parseCoordinateString(lon); + lat = this._parseCoordinateString(lat); + + if (!isNaN(parseFloat(lon)) && !isNaN(parseFloat(lat))) { + return { + type: 'Point', + coordinates: [lon,lat] + }; + } + } + return null; + }, + + // Private: Check if there is a field with GeoJSON geometries or alternatively, + // two fields with lat/lon values. + // + // If not found, the user can define them via the UI form. + _setupGeometryField: function(){ + // should not overwrite if we have already set this (e.g. explicitly via state) + if (!this._geomReady()) { + this.state.set({ + geomField: this._checkField(this.geometryFieldNames), + latField: this._checkField(this.latitudeFieldNames), + lonField: this._checkField(this.longitudeFieldNames) + }); + this.menu.state.set(this.state.toJSON()); + } + }, + + // Private: Check if a field in the current model exists in the provided + // list of names. + // + // + _checkField: function(fieldNames){ + var field; + var modelFieldNames = this.model.fields.pluck('id'); + for (var i = 0; i < fieldNames.length; i++){ + for (var j = 0; j < modelFieldNames.length; j++){ + if (modelFieldNames[j].toLowerCase() == fieldNames[i].toLowerCase()) + return modelFieldNames[j]; + } + } + return null; + }, + + // Private: Zoom to map to current features extent if any, or to the full + // extent if none. + // + _zoomToFeatures: function(){ + var bounds = this.features.getBounds(); + if (bounds && bounds.getNorthEast() && bounds.getSouthWest()){ + this.map.fitBounds(bounds); + } else { + this.map.setView([0, 0], 2); + } + }, + + // Private: Sets up the Leaflet map control and the features layer. + // + // The map uses a base layer from [Stamen](http://maps.stamen.com) based + // on [OpenStreetMap data](http://openstreetmap.org) by default, but it can + // be configured passing the `mapTilesURL` and `mapTilesAttribution` options + // (`mapTilesSubdomains` is also supported), eg: + // + // view = new recline.View.Map({ + // model: dataset, + // mapTilesURL: '//{s}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v7/{z}/{x}/{y}.png?access_token=pk.XXXX', + // mapTilesAttribution: '© MapBox etc..', + // mapTilesSubdomains: 'ab' + // }) + // + // + _setupMap: function(){ + var self = this; + this.map = new L.Map(this.$map.get(0)); + var mapUrl = this.options.mapTilesURL || 'https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png'; + var attribution = this.options.mapTilesAttribution ||'Map tiles by Stamen Design (CC BY 3.0). Data by OpenStreetMap (CC BY SA)'; + var subdomains = this.options.mapTilesSubdomains || 'abc'; + + var bg = new L.TileLayer(mapUrl, {maxZoom: 19, attribution: attribution, subdomains: subdomains}); + this.map.addLayer(bg); + + this.markers = new L.MarkerClusterGroup(this._clusterOptions); + + // rebind this (as needed in e.g. default case above) + this.geoJsonLayerOptions.pointToLayer = _.bind( + this.geoJsonLayerOptions.pointToLayer, + this); + this.features = new L.GeoJSON(null, this.geoJsonLayerOptions); + + this.map.setView([0, 0], 2); + + this.mapReady = true; + }, + + // Private: Helper function to select an option from a select list + // + _selectOption: function(id,value){ + var options = $('.' + id + ' > select > option'); + if (options){ + options.each(function(opt){ + if (this.value == value) { + $(this).attr('selected','selected'); + return false; + } + }); + } + } +}); + +my.MapMenu = Backbone.View.extend({ + className: 'editor', + + template: ' \ +
    \ +
    \ +
    \ + \ + \ +
    \ +
    \ + \ +
    \ + \ +
    \ + \ +
    \ + \ +
    \ +
    \ + \ +
    \ +
    \ + \ +
    \ +
    \ + \ + \ +
    \ + \ +
    \ + ', + + // Define here events for UI elements + events: { + 'click .editor-update-map': 'onEditorSubmit', + 'change .editor-field-type': 'onFieldTypeChange', + 'click #editor-auto-zoom': 'onAutoZoomChange', + 'click #editor-cluster': 'onClusteringChange' + }, + + initialize: function(options) { + var self = this; + _.bindAll(this, 'render'); + this.listenTo(this.model.fields, 'change', this.render); + this.state = new recline.Model.ObjectState(options.state); + this.listenTo(this.state, 'change', this.render); + this.render(); + }, + + // ### Public: Adds the necessary elements to the page. + // + // Also sets up the editor fields and the map if necessary. + render: function() { + var self = this; + var htmls = Mustache.render(this.template, this.model.toTemplateJSON()); + this.$el.html(htmls); + + if (this._geomReady() && this.model.fields.length){ + if (this.state.get('geomField')){ + this._selectOption('editor-geom-field',this.state.get('geomField')); + this.$el.find('#editor-field-type-geom').attr('checked','checked').change(); + } else{ + this._selectOption('editor-lon-field',this.state.get('lonField')); + this._selectOption('editor-lat-field',this.state.get('latField')); + this.$el.find('#editor-field-type-latlon').attr('checked','checked').change(); + } + } + if (this.state.get('autoZoom')) { + this.$el.find('#editor-auto-zoom').attr('checked', 'checked'); + } else { + this.$el.find('#editor-auto-zoom').removeAttr('checked'); + } + if (this.state.get('cluster')) { + this.$el.find('#editor-cluster').attr('checked', 'checked'); + } else { + this.$el.find('#editor-cluster').removeAttr('checked'); + } + return this; + }, + + _geomReady: function() { + return Boolean(this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); + }, + + // ## UI Event handlers + // + + // Public: Update map with user options + // + // Right now the only configurable option is what field(s) contains the + // location information. + // + onEditorSubmit: function(e){ + e.preventDefault(); + if (this.$el.find('#editor-field-type-geom').attr('checked')){ + this.state.set({ + geomField: this.$el.find('.editor-geom-field > select > option:selected').val(), + lonField: null, + latField: null + }); + } else { + this.state.set({ + geomField: null, + lonField: this.$el.find('.editor-lon-field > select > option:selected').val(), + latField: this.$el.find('.editor-lat-field > select > option:selected').val() + }); + } + return false; + }, + + // Public: Shows the relevant select lists depending on the location field + // type selected. + // + onFieldTypeChange: function(e){ + if (e.target.value == 'geom'){ + this.$el.find('.editor-field-type-geom').show(); + this.$el.find('.editor-field-type-latlon').hide(); + } else { + this.$el.find('.editor-field-type-geom').hide(); + this.$el.find('.editor-field-type-latlon').show(); + } + }, + + onAutoZoomChange: function(e){ + this.state.set({autoZoom: !this.state.get('autoZoom')}); + }, + + onClusteringChange: function(e){ + this.state.set({cluster: !this.state.get('cluster')}); + }, + + // Private: Helper function to select an option from a select list + // + _selectOption: function(id,value){ + var options = this.$el.find('.' + id + ' > select > option'); + if (options){ + options.each(function(opt){ + if (this.value == value) { + $(this).attr('selected','selected'); + return false; + } + }); + } + } +}); + +})(jQuery, recline.View); +/*jshint multistr:true */ + +// Standard JS module setup +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; + +(function($, my) { + "use strict"; +// ## MultiView +// +// Manage multiple views together along with query editor etc. Usage: +// +//
    +// var myExplorer = new recline.View.MultiView({
    +//   model: {{recline.Model.Dataset instance}}
    +//   el: {{an existing dom element}}
    +//   views: {{dataset views}}
    +//   state: {{state configuration -- see below}}
    +// });
    +// 
    +// +// ### Parameters +// +// **model**: (required) recline.model.Dataset instance. +// +// **el**: (required) DOM element to bind to. NB: the element already +// being in the DOM is important for rendering of some subviews (e.g. +// Graph). +// +// **views**: (optional) the dataset views (Grid, Graph etc) for +// MultiView to show. This is an array of view hashes. If not provided +// initialize with (recline.View.)Grid, Graph, and Map views (with obvious id +// and labels!). +// +//
    +// var views = [
    +//   {
    +//     id: 'grid', // used for routing
    +//     label: 'Grid', // used for view switcher
    +//     view: new recline.View.Grid({
    +//       model: dataset
    +//     })
    +//   },
    +//   {
    +//     id: 'graph',
    +//     label: 'Graph',
    +//     view: new recline.View.Graph({
    +//       model: dataset
    +//     })
    +//   }
    +// ];
    +// 
    +// +// **sidebarViews**: (optional) the sidebar views (Filters, Fields) for +// MultiView to show. This is an array of view hashes. If not provided +// initialize with (recline.View.)FilterEditor and Fields views (with obvious +// id and labels!). +// +//
    +// var sidebarViews = [
    +//   {
    +//     id: 'filterEditor', // used for routing
    +//     label: 'Filters', // used for view switcher
    +//     view: new recline.View.FilterEditor({
    +//       model: dataset
    +//     })
    +//   },
    +//   {
    +//     id: 'fieldsView',
    +//     label: 'Fields',
    +//     view: new recline.View.Fields({
    +//       model: dataset
    +//     })
    +//   }
    +// ];
    +// 
    +// +// **state**: standard state config for this view. This state is slightly +// special as it includes config of many of the subviews. +// +//
    +// var state = {
    +//     query: {dataset query state - see dataset.queryState object}
    +//     'view-{id1}': {view-state for this view}
    +//     'view-{id2}': {view-state for }
    +//     ...
    +//     // Explorer
    +//     currentView: id of current view (defaults to first view if not specified)
    +//     readOnly: (default: false) run in read-only mode
    +// }
    +// 
    +// +// Note that at present we do *not* serialize information about the actual set +// of views in use -- e.g. those specified by the views argument -- but instead +// expect either that the default views are fine or that the client to have +// initialized the MultiView with the relevant views themselves. +my.MultiView = Backbone.View.extend({ + template: ' \ +
    \ +
    \ + \ +
    \ + \ +
    \ + {{recordCount}} records\ +
    \ + \ +
    \ +
    \ +
    \ +
    \ +
    \ + ', + events: { + 'click .menu-right button': '_onMenuClick', + 'click .navigation button': '_onSwitchView' + }, + + initialize: function(options) { + var self = this; + this._setupState(options.state); + + // Hash of 'page' views (i.e. those for whole page) keyed by page name + if (options.views) { + this.pageViews = options.views; + } else { + this.pageViews = [{ + id: 'grid', + label: 'Grid', + view: new my.SlickGrid({ + model: this.model, + state: this.state.get('view-grid') + }) + }, { + id: 'graph', + label: 'Graph', + view: new my.Graph({ + model: this.model, + state: this.state.get('view-graph') + }) + }, { + id: 'map', + label: 'Map', + view: new my.Map({ + model: this.model, + state: this.state.get('view-map') + }) + }, { + id: 'timeline', + label: 'Timeline', + view: new my.Timeline({ + model: this.model, + state: this.state.get('view-timeline') + }) + }]; + } + // Hashes of sidebar elements + if(options.sidebarViews) { + this.sidebarViews = options.sidebarViews; + } else { + this.sidebarViews = [{ + id: 'filterEditor', + label: 'Filters', + view: new my.FilterEditor({ + model: this.model + }) + }, { + id: 'fieldsView', + label: 'Fields', + view: new my.Fields({ + model: this.model + }) + }]; + } + // these must be called after pageViews are created + this.render(); + this._bindStateChanges(); + this._bindFlashNotifications(); + // now do updates based on state (need to come after render) + if (this.state.get('readOnly')) { + this.setReadOnly(); + } + if (this.state.get('currentView')) { + this.updateNav(this.state.get('currentView')); + } else { + this.updateNav(this.pageViews[0].id); + } + this._showHideSidebar(); + + this.listenTo(this.model, 'query:start', function() { + self.notify({loader: true, persist: true}); + }); + this.listenTo(this.model, 'query:done', function() { + self.clearNotifications(); + self.$el.find('.doc-count').text(self.model.recordCount || 'Unknown'); + }); + this.listenTo(this.model, 'query:fail', function(error) { + self.clearNotifications(); + var msg = ''; + if (typeof(error) == 'string') { + msg = error; + } else if (typeof(error) == 'object') { + if (error.title) { + msg = error.title + ': '; + } + if (error.message) { + msg += error.message; + } + } else { + msg = 'There was an error querying the backend'; + } + self.notify({message: msg, category: 'error', persist: true}); + }); + + // retrieve basic data like fields etc + // note this.model and dataset returned are the same + // TODO: set query state ...? + this.model.queryState.set(self.state.get('query'), {silent: true}); + }, + + setReadOnly: function() { + this.$el.addClass('recline-read-only'); + }, + + render: function() { + var tmplData = this.model.toTemplateJSON(); + tmplData.views = this.pageViews; + tmplData.sidebarViews = this.sidebarViews; + var template = Mustache.render(this.template, tmplData); + this.$el.html(template); + + // now create and append other views + var $dataViewContainer = this.$el.find('.data-view-container'); + var $dataSidebar = this.$el.find('.data-view-sidebar'); + + // the main views + _.each(this.pageViews, function(view, pageName) { + view.view.render(); + if (view.view.redraw) { + view.view.redraw(); + } + $dataViewContainer.append(view.view.el); + if (view.view.elSidebar) { + $dataSidebar.append(view.view.elSidebar); + } + }); + + _.each(this.sidebarViews, function(view) { + this['$'+view.id] = view.view.$el; + $dataSidebar.append(view.view.el); + }, this); + + this.pager = new recline.View.Pager({ + model: this.model + }); + this.$el.find('.recline-results-info').after(this.pager.el); + + this.queryEditor = new recline.View.QueryEditor({ + model: this.model.queryState + }); + this.$el.find('.query-editor-here').append(this.queryEditor.el); + + }, + + remove: function () { + _.each(this.pageViews, function (view) { + view.view.remove(); + }); + _.each(this.sidebarViews, function (view) { + view.view.remove(); + }); + this.pager.remove(); + this.queryEditor.remove(); + Backbone.View.prototype.remove.apply(this, arguments); + }, + + // hide the sidebar if empty + _showHideSidebar: function() { + var $dataSidebar = this.$el.find('.data-view-sidebar'); + var visibleChildren = $dataSidebar.children().filter(function() { + return $(this).css("display") != "none"; + }).length; + + if (visibleChildren > 0) { + $dataSidebar.show(); + } else { + $dataSidebar.hide(); + } + }, + + updateNav: function(pageName) { + this.$el.find('.navigation button').removeClass('active'); + var $el = this.$el.find('.navigation button[data-view="' + pageName + '"]'); + $el.addClass('active'); + + // add/remove sidebars and hide inactive views + _.each(this.pageViews, function(view, idx) { + if (view.id === pageName) { + view.view.$el.show(); + if (view.view.elSidebar) { + view.view.elSidebar.show(); + } + } else { + view.view.$el.hide(); + if (view.view.elSidebar) { + view.view.elSidebar.hide(); + } + if (view.view.hide) { + view.view.hide(); + } + } + }); + + this._showHideSidebar(); + + // call view.view.show after sidebar visibility has been determined so + // that views can correctly calculate their maximum width + _.each(this.pageViews, function(view, idx) { + if (view.id === pageName) { + if (view.view.show) { + view.view.show(); + } + } + }); + }, + + _onMenuClick: function(e) { + e.preventDefault(); + var action = $(e.target).attr('data-action'); + this['$'+action].toggle(); + this._showHideSidebar(); + }, + + _onSwitchView: function(e) { + e.preventDefault(); + var viewName = $(e.target).attr('data-view'); + this.updateNav(viewName); + this.state.set({currentView: viewName}); + }, + + // create a state object for this view and do the job of + // + // a) initializing it from both data passed in and other sources (e.g. hash url) + // + // b) ensure the state object is updated in responese to changes in subviews, query etc. + _setupState: function(initialState) { + var self = this; + // get data from the query string / hash url plus some defaults + var qs = my.parseHashQueryString(); + var query = qs.reclineQuery; + query = query ? JSON.parse(query) : self.model.queryState.toJSON(); + // backwards compatability (now named view-graph but was named graph) + var graphState = qs['view-graph'] || qs.graph; + graphState = graphState ? JSON.parse(graphState) : {}; + + // now get default data + hash url plus initial state and initial our state object with it + var stateData = _.extend({ + query: query, + 'view-graph': graphState, + backend: this.model.backend.__type__, + url: this.model.get('url'), + dataset: this.model.toJSON(), + currentView: null, + readOnly: false + }, + initialState); + this.state = new recline.Model.ObjectState(stateData); + }, + + _bindStateChanges: function() { + var self = this; + // finally ensure we update our state object when state of sub-object changes so that state is always up to date + this.listenTo(this.model.queryState, 'change', function() { + self.state.set({query: self.model.queryState.toJSON()}); + }); + _.each(this.pageViews, function(pageView) { + if (pageView.view.state && pageView.view.state.bind) { + var update = {}; + update['view-' + pageView.id] = pageView.view.state.toJSON(); + self.state.set(update); + self.listenTo(pageView.view.state, 'change', function() { + var update = {}; + update['view-' + pageView.id] = pageView.view.state.toJSON(); + // had problems where change not being triggered for e.g. grid view so let's do it explicitly + self.state.set(update, {silent: true}); + self.state.trigger('change'); + }); + } + }); + }, + + _bindFlashNotifications: function() { + var self = this; + _.each(this.pageViews, function(pageView) { + self.listenTo(pageView.view, 'recline:flash', function(flash) { + self.notify(flash); + }); + }); + }, + + // ### notify + // + // Create a notification (a div.alert in div.alert-messsages) using provided + // flash object. Flash attributes (all are optional): + // + // * message: message to show. + // * category: warning (default), success, error + // * persist: if true alert is persistent, o/w hidden after 3s (default = false) + // * loader: if true show loading spinner + notify: function(flash) { + var tmplData = _.extend({ + message: 'Loading', + category: 'warning', + loader: false + }, + flash + ); + var _template; + if (tmplData.loader) { + _template = ' \ +
    \ + {{message}} \ +   \ +
    '; + } else { + _template = ' \ +
    × \ + {{message}} \ +
    '; + } + var _templated = $(Mustache.render(_template, tmplData)); + _templated = $(_templated).appendTo($('.recline-data-explorer .alert-messages')); + if (!flash.persist) { + setTimeout(function() { + $(_templated).fadeOut(1000, function() { + $(this).remove(); + }); + }, 1000); + } + }, + + // ### clearNotifications + // + // Clear all existing notifications + clearNotifications: function() { + var $notifications = $('.recline-data-explorer .alert-messages .alert'); + $notifications.fadeOut(1500, function() { + $(this).remove(); + }); + } +}); + +// ### MultiView.restore +// +// Restore a MultiView instance from a serialized state including the associated dataset +// +// This inverts the state serialization process in Multiview +my.MultiView.restore = function(state) { + // hack-y - restoring a memory dataset does not mean much ... (but useful for testing!) + var datasetInfo; + if (state.backend === 'memory') { + datasetInfo = { + backend: 'memory', + records: [{stub: 'this is a stub dataset because we do not restore memory datasets'}] + }; + } else { + datasetInfo = _.extend({ + url: state.url, + backend: state.backend + }, + state.dataset + ); + } + var dataset = new recline.Model.Dataset(datasetInfo); + var explorer = new my.MultiView({ + model: dataset, + state: state + }); + return explorer; +}; + +// ## Miscellaneous Utilities +var urlPathRegex = /^([^?]+)(\?.*)?/; + +// Parse the Hash section of a URL into path and query string +my.parseHashUrl = function(hashUrl) { + var parsed = urlPathRegex.exec(hashUrl); + if (parsed === null) { + return {}; + } else { + return { + path: parsed[1], + query: parsed[2] || '' + }; + } +}; + +// Parse a URL query string (?xyz=abc...) into a dictionary. +my.parseQueryString = function(q) { + if (!q) { + return {}; + } + var urlParams = {}, + e, d = function (s) { + return unescape(s.replace(/\+/g, " ")); + }, + r = /([^&=]+)=?([^&]*)/g; + + if (q && q.length && q[0] === '?') { + q = q.slice(1); + } + while (e = r.exec(q)) { + // TODO: have values be array as query string allow repetition of keys + urlParams[d(e[1])] = d(e[2]); + } + return urlParams; +}; + +// Parse the query string out of the URL hash +my.parseHashQueryString = function() { + var q = my.parseHashUrl(window.location.hash).query; + return my.parseQueryString(q); +}; + +// Compse a Query String +my.composeQueryString = function(queryParams) { + var queryString = '?'; + var items = []; + $.each(queryParams, function(key, value) { + if (typeof(value) === 'object') { + value = JSON.stringify(value); + } + items.push(key + '=' + encodeURIComponent(value)); + }); + queryString += items.join('&'); + return queryString; +}; + +my.getNewHashForQueryString = function(queryParams) { + var queryPart = my.composeQueryString(queryParams); + if (window.location.hash) { + // slice(1) to remove # at start + return window.location.hash.split('?')[0].slice(1) + queryPart; + } else { + return queryPart; + } +}; + +my.setHashQueryString = function(queryParams) { + window.location.hash = my.getNewHashForQueryString(queryParams); +}; + +})(jQuery, recline.View); + +/*jshint multistr:true */ + +this.recline = this.recline || {}; +this.recline.View = this.recline.View || {}; + +(function($, my) { + "use strict"; + +// ## SlickGrid Dataset View +// +// Provides a tabular view on a Dataset, based on SlickGrid. +// +// https://github.com/mleibman/SlickGrid +// +// Initialize it with a `recline.Model.Dataset`. +// +// Additional options to drive SlickGrid grid can be given through state. +// The following keys allow for customization: +// * gridOptions: to add options at grid level +// * columnsEditor: to add editor for editable columns +// +// For example: +// var grid = new recline.View.SlickGrid({ +// model: dataset, +// el: $el, +// state: { +// gridOptions: { +// editable: true, +// enableAddRow: true +// // Enable support for row delete +// enabledDelRow: true, +// // Enable support for row Reorder +// enableReOrderRow:true, +// ... +// }, +// columnsEditor: [ +// {column: 'date', editor: Slick.Editors.Date }, +// {column: 'title', editor: Slick.Editors.Text} +// ] +// } +// }); +//// NB: you need an explicit height on the element for slickgrid to work +my.SlickGrid = Backbone.View.extend({ + initialize: function(modelEtc) { + var self = this; + this.$el.addClass('recline-slickgrid'); + + // Template for row delete menu , change it if you don't love + this.templates = { + "deleterow" : '' + }; + + _.bindAll(this, 'render', 'onRecordChanged'); + this.listenTo(this.model.records, 'add remove reset', this.render); + this.listenTo(this.model.records, 'change', this.onRecordChanged); + var state = _.extend({ + hiddenColumns: [], + columnsOrder: [], + columnsSort: {}, + columnsWidth: [], + columnsEditor: [], + options: {}, + fitColumns: false + }, modelEtc.state + + ); + this.state = new recline.Model.ObjectState(state); + this._slickHandler = new Slick.EventHandler(); + + //add menu for new row , check if enableAddRow is set to true or not set + if(this.state.get("gridOptions") + && this.state.get("gridOptions").enabledAddRow != undefined + && this.state.get("gridOptions").enabledAddRow == true ){ + this.editor = new my.GridControl() + this.elSidebar = this.editor.$el + this.listenTo(this.editor.state, 'change', function(){ + this.model.records.add(new recline.Model.Record()) + }); + } + }, + + onRecordChanged: function(record) { + // Ignore if the grid is not yet drawn + if (!this.grid) { + return; + } + // Let's find the row corresponding to the index + var row_index = this.grid.getData().getModelRow( record ); + this.grid.invalidateRow(row_index); + this.grid.getData().updateItem(record, row_index); + this.grid.render(); + }, + + render: function() { + var self = this; + var options = _.extend({ + enableCellNavigation: true, + enableColumnReorder: true, + explicitInitialization: true, + syncColumnCellResize: true, + forceFitColumns: this.state.get('fitColumns') + }, self.state.get('gridOptions')); + + // We need all columns, even the hidden ones, to show on the column picker + var columns = []; + + // custom formatter as default one escapes html + // plus this way we distinguish between rendering/formatting and computed value (so e.g. sort still works ...) + // row = row index, cell = cell index, value = value, columnDef = column definition, dataContext = full row values + var formatter = function(row, cell, value, columnDef, dataContext) { + if(columnDef.id == "del"){ + return self.templates.deleterow + } + var field = self.model.fields.get(columnDef.id); + if (field.renderer) { + return field.renderer(value, field, dataContext); + } else { + return value + } + }; + + // we need to be sure that user is entering a valid input , for exemple if + // field is date type and field.format ='YY-MM-DD', we should be sure that + // user enter a correct value + var validator = function(field) { + return function(value){ + if (field.type == "date" && isNaN(Date.parse(value))){ + return { + valid: false, + msg: "A date is required, check field field-date-format" + }; + } else { + return {valid: true, msg :null } + } + } + }; + + // Add column for row reorder support + if (this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow == true) { + columns.push({ + id: "#", + name: "", + width: 22, + behavior: "selectAndMove", + selectable: false, + resizable: false, + cssClass: "recline-cell-reorder" + }) + } + // Add column for row delete support + if (this.state.get("gridOptions") && this.state.get("gridOptions").enabledDelRow == true) { + columns.push({ + id: 'del', + name: '', + field: 'del', + sortable: true, + width: 38, + formatter: formatter, + validator:validator + }) + } + + function sanitizeFieldName(name) { + return $('
    ').text(name).html(); + } + + _.each(this.model.fields.toJSON(),function(field){ + var column = { + id: field.id, + name: sanitizeFieldName(field.label), + field: field.id, + sortable: true, + minWidth: 80, + formatter: formatter, + validator:validator(field) + }; + var widthInfo = _.find(self.state.get('columnsWidth'),function(c){return c.column === field.id;}); + if (widthInfo){ + column.width = widthInfo.width; + } + var editInfo = _.find(self.state.get('columnsEditor'),function(c){return c.column === field.id;}); + if (editInfo){ + column.editor = editInfo.editor; + } else { + // guess editor type + var typeToEditorMap = { + 'string': Slick.Editors.LongText, + 'integer': Slick.Editors.IntegerEditor, + 'number': Slick.Editors.Text, + // TODO: need a way to ensure we format date in the right way + // Plus what if dates are in distant past or future ... (?) + // 'date': Slick.Editors.DateEditor, + 'date': Slick.Editors.Text, + 'boolean': Slick.Editors.YesNoSelectEditor + // TODO: (?) percent ... + }; + if (field.type in typeToEditorMap) { + column.editor = typeToEditorMap[field.type] + } else { + column.editor = Slick.Editors.LongText; + } + } + columns.push(column); + }); + // Restrict the visible columns + var visibleColumns = _.filter(columns, function(column) { + return _.indexOf(self.state.get('hiddenColumns'), column.id) === -1; + }); + // Order them if there is ordering info on the state + if (this.state.get('columnsOrder') && this.state.get('columnsOrder').length > 0) { + visibleColumns = visibleColumns.sort(function(a,b){ + return _.indexOf(self.state.get('columnsOrder'),a.id) > _.indexOf(self.state.get('columnsOrder'),b.id) ? 1 : -1; + }); + columns = columns.sort(function(a,b){ + return _.indexOf(self.state.get('columnsOrder'),a.id) > _.indexOf(self.state.get('columnsOrder'),b.id) ? 1 : -1; + }); + } + + // Move hidden columns to the end, so they appear at the bottom of the + // column picker + var tempHiddenColumns = []; + for (var i = columns.length -1; i >= 0; i--){ + if (_.indexOf(_.pluck(visibleColumns,'id'),columns[i].id) === -1){ + tempHiddenColumns.push(columns.splice(i,1)[0]); + } + } + columns = columns.concat(tempHiddenColumns); + + // Transform a model object into a row + function toRow(m) { + var row = {}; + self.model.fields.each(function(field) { + var render = ""; + //when adding row from slickgrid the field value is undefined + if(!_.isUndefined(m.getFieldValueUnrendered(field))){ + render =m.getFieldValueUnrendered(field) + } + row[field.id] = render + }); + return row; + } + + function RowSet() { + var models = []; + var rows = []; + + this.push = function(model, row) { + models.push(model); + rows.push(row); + }; + + this.getLength = function() {return rows.length; }; + this.getItem = function(index) {return rows[index];}; + this.getItemMetadata = function(index) {return {};}; + this.getModel = function(index) {return models[index];}; + this.getModelRow = function(m) {return _.indexOf(models, m);}; + this.updateItem = function(m,i) { + rows[i] = toRow(m); + models[i] = m; + }; + } + + var data = new RowSet(); + + this.model.records.each(function(doc){ + data.push(doc, toRow(doc)); + }); + + this.grid = new Slick.Grid(this.el, data, visibleColumns, options); + // Column sorting + var sortInfo = this.model.queryState.get('sort'); + if (sortInfo){ + var column = sortInfo[0].field; + var sortAsc = sortInfo[0].order !== 'desc'; + this.grid.setSortColumn(column, sortAsc); + } + + if (this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow) { + this._setupRowReordering(); + } + + this._slickHandler.subscribe(this.grid.onSort, function(e, args){ + var order = (args.sortAsc) ? 'asc':'desc'; + var sort = [{ + field: args.sortCol.field, + order: order + }]; + self.model.query({sort: sort}); + }); + + this._slickHandler.subscribe(this.grid.onColumnsReordered, function(e, args){ + self.state.set({columnsOrder: _.pluck(self.grid.getColumns(),'id')}); + }); + + this.grid.onColumnsResized.subscribe(function(e, args){ + var columns = args.grid.getColumns(); + var defaultColumnWidth = args.grid.getOptions().defaultColumnWidth; + var columnsWidth = []; + _.each(columns,function(column){ + if (column.width != defaultColumnWidth){ + columnsWidth.push({column:column.id,width:column.width}); + } + }); + self.state.set({columnsWidth:columnsWidth}); + }); + + this._slickHandler.subscribe(this.grid.onCellChange, function (e, args) { + // We need to change the model associated value + var grid = args.grid; + var model = data.getModel(args.row); + var field = grid.getColumns()[args.cell].id; + var v = {}; + v[field] = args.item[field]; + model.set(v); + }); + this._slickHandler.subscribe(this.grid.onClick,function(e, args){ + //try catch , because this fail in qunit , but no + //error on browser. + try{e.preventDefault()}catch(e){} + + // The cell of grid that handle row delete is The first cell (0) if + // The grid ReOrder is not present ie enableReOrderRow == false + // else it is The the second cell (1) , because The 0 is now cell + // that handle row Reoder. + var cell =0 + if(self.state.get("gridOptions") + && self.state.get("gridOptions").enableReOrderRow != undefined + && self.state.get("gridOptions").enableReOrderRow == true ){ + cell =1 + } + if (args.cell == cell && self.state.get("gridOptions").enabledDelRow == true){ + // We need to delete the associated model + var model = data.getModel(args.row); + model.destroy() + } + }) ; + var columnpicker = new Slick.Controls.ColumnPicker(columns, this.grid, + _.extend(options,{state:this.state})); + if (self.visible){ + self.grid.init(); + self.rendered = true; + } else { + // Defer rendering until the view is visible + self.rendered = false; + } + return this; + }, + + // Row reordering support based on + // https://github.com/mleibman/SlickGrid/blob/gh-pages/examples/example9-row-reordering.html + _setupRowReordering: function() { + var self = this; + self.grid.setSelectionModel(new Slick.RowSelectionModel()); + + var moveRowsPlugin = new Slick.RowMoveManager({ + cancelEditOnDrag: true + }); + + moveRowsPlugin.onBeforeMoveRows.subscribe(function (e, data) { + for (var i = 0; i < data.rows.length; i++) { + // no point in moving before or after itself + if (data.rows[i] == data.insertBefore || data.rows[i] == data.insertBefore - 1) { + e.stopPropagation(); + return false; + } + } + return true; + }); + + moveRowsPlugin.onMoveRows.subscribe(function (e, args) { + var extractedRows = [], left, right; + var rows = args.rows; + var insertBefore = args.insertBefore; + + var data = self.model.records.toJSON() + left = data.slice(0, insertBefore); + right= data.slice(insertBefore, data.length); + + rows.sort(function(a,b) { return a-b; }); + + for (var i = 0; i < rows.length; i++) { + extractedRows.push(data[rows[i]]); + } + + rows.reverse(); + + for (var i = 0; i < rows.length; i++) { + var row = rows[i]; + if (row < insertBefore) { + left.splice(row, 1); + } else { + right.splice(row - insertBefore, 1); + } + } + + data = left.concat(extractedRows.concat(right)); + var selectedRows = []; + for (var i = 0; i < rows.length; i++) + selectedRows.push(left.length + i); + + self.model.records.reset(data) + + }); + //register The plugin to handle row Reorder + if(this.state.get("gridOptions") && this.state.get("gridOptions").enableReOrderRow) { + self.grid.registerPlugin(moveRowsPlugin); + } + }, + + remove: function () { + this._slickHandler.unsubscribeAll(); + Backbone.View.prototype.remove.apply(this, arguments); + }, + + show: function() { + // If the div is hidden, SlickGrid will calculate wrongly some + // sizes so we must render it explicitly when the view is visible + if (!this.rendered){ + if (!this.grid){ + this.render(); + } + this.grid.init(); + this.rendered = true; + } + this.visible = true; + }, + + hide: function() { + this.visible = false; + } +}); + +// Add new grid Control to display a new row add menu bouton +// It display a simple side-bar menu ,for user to add new +// row to grid +my.GridControl= Backbone.View.extend({ + className: "recline-row-add", + // Template for row edit menu , change it if you don't love + template: '

    ', + + initialize: function(options){ + var self = this; + _.bindAll(this, 'render'); + this.state = new recline.Model.ObjectState(); + this.render(); + }, + + render: function() { + var self = this; + this.$el.html(this.template) + }, + + events : { + "click .recline-row-add" : "addNewRow" + }, + + addNewRow : function(e){ + e.preventDefault() + this.state.trigger("change") + } +}); + +})(jQuery, recline.View); + +/* +* Context menu for the column picker, adapted from +* http://mleibman.github.com/SlickGrid/examples/example-grouping +* +*/ +(function ($) { + function SlickColumnPicker(columns, grid, options) { + var $menu; + var columnCheckboxes; + + var defaults = { + fadeSpeed:250 + }; + + function init() { + grid.onHeaderContextMenu.subscribe(handleHeaderContextMenu); + options = $.extend({}, defaults, options); + + $menu = $('
    ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}), +d=document.activeElement;c.wrap(b);if(c[0]===d||f.contains(c[0],d))f(d).focus();b=c.parent();if(c.css("position")=="static"){b.css({position:"relative"});c.css({position:"relative"})}else{f.extend(a,{position:c.css("position"),zIndex:c.css("z-index")});f.each(["top","left","bottom","right"],function(e,g){a[g]=c.css(g);if(isNaN(parseInt(a[g],10)))a[g]="auto"});c.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})}return b.css(a).show()},removeWrapper:function(c){var a,b=document.activeElement; +if(c.parent().is(".ui-effects-wrapper")){a=c.parent().replaceWith(c);if(c[0]===b||f.contains(c[0],b))f(b).focus();return a}return c},setTransition:function(c,a,b,d){d=d||{};f.each(a,function(e,g){unit=c.cssUnit(g);if(unit[0]>0)d[g]=unit[0]*b+unit[1]});return d}});f.fn.extend({effect:function(c){var a=k.apply(this,arguments),b={options:a[1],duration:a[2],callback:a[3]};a=b.options.mode;var d=f.effects[c];if(f.fx.off||!d)return a?this[a](b.duration,b.callback):this.each(function(){b.callback&&b.callback.call(this)}); +return d.call(this,b)},_show:f.fn.show,show:function(c){if(l(c))return this._show.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="show";return this.effect.apply(this,a)}},_hide:f.fn.hide,hide:function(c){if(l(c))return this._hide.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="hide";return this.effect.apply(this,a)}},__toggle:f.fn.toggle,toggle:function(c){if(l(c)||typeof c==="boolean"||f.isFunction(c))return this.__toggle.apply(this,arguments);else{var a=k.apply(this, +arguments);a[1].mode="toggle";return this.effect.apply(this,a)}},cssUnit:function(c){var a=this.css(c),b=[];f.each(["em","px","%","pt"],function(d,e){if(a.indexOf(e)>0)b=[parseFloat(a),e]});return b}});f.easing.jswing=f.easing.swing;f.extend(f.easing,{def:"easeOutQuad",swing:function(c,a,b,d,e){return f.easing[f.easing.def](c,a,b,d,e)},easeInQuad:function(c,a,b,d,e){return d*(a/=e)*a+b},easeOutQuad:function(c,a,b,d,e){return-d*(a/=e)*(a-2)+b},easeInOutQuad:function(c,a,b,d,e){if((a/=e/2)<1)return d/ +2*a*a+b;return-d/2*(--a*(a-2)-1)+b},easeInCubic:function(c,a,b,d,e){return d*(a/=e)*a*a+b},easeOutCubic:function(c,a,b,d,e){return d*((a=a/e-1)*a*a+1)+b},easeInOutCubic:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a+b;return d/2*((a-=2)*a*a+2)+b},easeInQuart:function(c,a,b,d,e){return d*(a/=e)*a*a*a+b},easeOutQuart:function(c,a,b,d,e){return-d*((a=a/e-1)*a*a*a-1)+b},easeInOutQuart:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a+b;return-d/2*((a-=2)*a*a*a-2)+b},easeInQuint:function(c,a,b, +d,e){return d*(a/=e)*a*a*a*a+b},easeOutQuint:function(c,a,b,d,e){return d*((a=a/e-1)*a*a*a*a+1)+b},easeInOutQuint:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a*a+b;return d/2*((a-=2)*a*a*a*a+2)+b},easeInSine:function(c,a,b,d,e){return-d*Math.cos(a/e*(Math.PI/2))+d+b},easeOutSine:function(c,a,b,d,e){return d*Math.sin(a/e*(Math.PI/2))+b},easeInOutSine:function(c,a,b,d,e){return-d/2*(Math.cos(Math.PI*a/e)-1)+b},easeInExpo:function(c,a,b,d,e){return a==0?b:d*Math.pow(2,10*(a/e-1))+b},easeOutExpo:function(c, +a,b,d,e){return a==e?b+d:d*(-Math.pow(2,-10*a/e)+1)+b},easeInOutExpo:function(c,a,b,d,e){if(a==0)return b;if(a==e)return b+d;if((a/=e/2)<1)return d/2*Math.pow(2,10*(a-1))+b;return d/2*(-Math.pow(2,-10*--a)+2)+b},easeInCirc:function(c,a,b,d,e){return-d*(Math.sqrt(1-(a/=e)*a)-1)+b},easeOutCirc:function(c,a,b,d,e){return d*Math.sqrt(1-(a=a/e-1)*a)+b},easeInOutCirc:function(c,a,b,d,e){if((a/=e/2)<1)return-d/2*(Math.sqrt(1-a*a)-1)+b;return d/2*(Math.sqrt(1-(a-=2)*a)+1)+b},easeInElastic:function(c,a,b, +d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h
    ").css({position:"absolute",visibility:"visible",left:-f*(h/d),top:-e*(i/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:h/d,height:i/c,left:g.left+f*(h/d)+(a.options.mode=="show"?(f-Math.floor(d/2))*(h/d):0),top:g.top+e*(i/c)+(a.options.mode=="show"?(e-Math.floor(c/2))*(i/c):0),opacity:a.options.mode=="show"?0:1}).animate({left:g.left+f*(h/d)+(a.options.mode=="show"?0:(f-Math.floor(d/2))*(h/d)),top:g.top+ +e*(i/c)+(a.options.mode=="show"?0:(e-Math.floor(c/2))*(i/c)),opacity:a.options.mode=="show"?1:0},a.duration||500);setTimeout(function(){a.options.mode=="show"?b.css({visibility:"visible"}):b.css({visibility:"visible"}).hide();a.callback&&a.callback.apply(b[0]);b.dequeue();j("div.ui-effects-explode").remove()},a.duration||500)})}})(jQuery); +;/* + * jQuery UI Effects Fade 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Fade + * + * Depends: + * jquery.effects.core.js + */ +(function(b){b.effects.fade=function(a){return this.queue(function(){var c=b(this),d=b.effects.setMode(c,a.options.mode||"hide");c.animate({opacity:d},{queue:false,duration:a.duration,easing:a.options.easing,complete:function(){a.callback&&a.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery); +;/* + * jQuery UI Effects Fold 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Fold + * + * Depends: + * jquery.effects.core.js + */ +(function(c){c.effects.fold=function(a){return this.queue(function(){var b=c(this),j=["position","top","bottom","left","right"],d=c.effects.setMode(b,a.options.mode||"hide"),g=a.options.size||15,h=!!a.options.horizFirst,k=a.duration?a.duration/2:c.fx.speeds._default/2;c.effects.save(b,j);b.show();var e=c.effects.createWrapper(b).css({overflow:"hidden"}),f=d=="show"!=h,l=f?["width","height"]:["height","width"];f=f?[e.width(),e.height()]:[e.height(),e.width()];var i=/([0-9]+)%/.exec(g);if(i)g=parseInt(i[1], +10)/100*f[d=="hide"?0:1];if(d=="show")e.css(h?{height:0,width:g}:{height:g,width:0});h={};i={};h[l[0]]=d=="show"?f[0]:g;i[l[1]]=d=="show"?f[1]:0;e.animate(h,k,a.options.easing).animate(i,k,a.options.easing,function(){d=="hide"&&b.hide();c.effects.restore(b,j);c.effects.removeWrapper(b);a.callback&&a.callback.apply(b[0],arguments);b.dequeue()})})}})(jQuery); +;/* + * jQuery UI Effects Highlight 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Highlight + * + * Depends: + * jquery.effects.core.js + */ +(function(b){b.effects.highlight=function(c){return this.queue(function(){var a=b(this),e=["backgroundImage","backgroundColor","opacity"],d=b.effects.setMode(a,c.options.mode||"show"),f={backgroundColor:a.css("backgroundColor")};if(d=="hide")f.opacity=0;b.effects.save(a,e);a.show().css({backgroundImage:"none",backgroundColor:c.options.color||"#ffff99"}).animate(f,{queue:false,duration:c.duration,easing:c.options.easing,complete:function(){d=="hide"&&a.hide();b.effects.restore(a,e);d=="show"&&!b.support.opacity&& +this.style.removeAttribute("filter");c.callback&&c.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery); +;/* + * jQuery UI Effects Pulsate 1.8.16 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Pulsate + * + * Depends: + * jquery.effects.core.js + */ +(function(d){d.effects.pulsate=function(a){return this.queue(function(){var b=d(this),c=d.effects.setMode(b,a.options.mode||"show");times=(a.options.times||5)*2-1;duration=a.duration?a.duration/2:d.fx.speeds._default/2;isVisible=b.is(":visible");animateTo=0;if(!isVisible){b.css("opacity",0).show();animateTo=1}if(c=="hide"&&isVisible||c=="show"&&!isVisible)times--;for(c=0;c
    ').appendTo(document.body).addClass(a.options.className).css({top:d.top,left:d.left,height:b.innerHeight(),width:b.innerWidth(),position:"absolute"}).animate(c,a.duration,a.options.easing,function(){f.remove();a.callback&&a.callback.apply(b[0],arguments); +b.dequeue()})})}})(jQuery); +; \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery.event.drag-2.2.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery.event.drag-2.2.js new file mode 100644 index 00000000..f2c1d57e --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery.event.drag-2.2.js @@ -0,0 +1,402 @@ +/*! + * jquery.event.drag - v 2.2 + * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com + * Open Source MIT License - http://threedubmedia.com/code/license + */ +// Created: 2008-06-04 +// Updated: 2012-05-21 +// REQUIRES: jquery 1.7.x + +;(function( $ ){ + +// add the jquery instance method +$.fn.drag = function( str, arg, opts ){ + // figure out the event type + var type = typeof str == "string" ? str : "", + // figure out the event handler... + fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null; + // fix the event type + if ( type.indexOf("drag") !== 0 ) + type = "drag"+ type; + // were options passed + opts = ( str == fn ? arg : opts ) || {}; + // trigger or bind event handler + return fn ? this.bind( type, opts, fn ) : this.trigger( type ); +}; + +// local refs (increase compression) +var $event = $.event, +$special = $event.special, +// configure the drag special event +drag = $special.drag = { + + // these are the default settings + defaults: { + which: 1, // mouse button pressed to start drag sequence + distance: 0, // distance dragged before dragstart + not: ':input', // selector to suppress dragging on target elements + handle: null, // selector to match handle target elements + relative: false, // true to use "position", false to use "offset" + drop: true, // false to suppress drop events, true or selector to allow + click: false // false to suppress click events after dragend (no proxy) + }, + + // the key name for stored drag data + datakey: "dragdata", + + // prevent bubbling for better performance + noBubble: true, + + // count bound related events + add: function( obj ){ + // read the interaction data + var data = $.data( this, drag.datakey ), + // read any passed options + opts = obj.data || {}; + // count another realted event + data.related += 1; + // extend data options bound with this event + // don't iterate "opts" in case it is a node + $.each( drag.defaults, function( key, def ){ + if ( opts[ key ] !== undefined ) + data[ key ] = opts[ key ]; + }); + }, + + // forget unbound related events + remove: function(){ + $.data( this, drag.datakey ).related -= 1; + }, + + // configure interaction, capture settings + setup: function(){ + // check for related events + if ( $.data( this, drag.datakey ) ) + return; + // initialize the drag data with copied defaults + var data = $.extend({ related:0 }, drag.defaults ); + // store the interaction data + $.data( this, drag.datakey, data ); + // bind the mousedown event, which starts drag interactions + $event.add( this, "touchstart mousedown", drag.init, data ); + // prevent image dragging in IE... + if ( this.attachEvent ) + this.attachEvent("ondragstart", drag.dontstart ); + }, + + // destroy configured interaction + teardown: function(){ + var data = $.data( this, drag.datakey ) || {}; + // check for related events + if ( data.related ) + return; + // remove the stored data + $.removeData( this, drag.datakey ); + // remove the mousedown event + $event.remove( this, "touchstart mousedown", drag.init ); + // enable text selection + drag.textselect( true ); + // un-prevent image dragging in IE... + if ( this.detachEvent ) + this.detachEvent("ondragstart", drag.dontstart ); + }, + + // initialize the interaction + init: function( event ){ + // sorry, only one touch at a time + if ( drag.touched ) + return; + // the drag/drop interaction data + var dd = event.data, results; + // check the which directive + if ( event.which != 0 && dd.which > 0 && event.which != dd.which ) + return; + // check for suppressed selector + if ( $( event.target ).is( dd.not ) ) + return; + // check for handle selector + if ( dd.handle && !$( event.target ).closest( dd.handle, event.currentTarget ).length ) + return; + + drag.touched = event.type == 'touchstart' ? this : null; + dd.propagates = 1; + dd.mousedown = this; + dd.interactions = [ drag.interaction( this, dd ) ]; + dd.target = event.target; + dd.pageX = event.pageX; + dd.pageY = event.pageY; + dd.dragging = null; + // handle draginit event... + results = drag.hijack( event, "draginit", dd ); + // early cancel + if ( !dd.propagates ) + return; + // flatten the result set + results = drag.flatten( results ); + // insert new interaction elements + if ( results && results.length ){ + dd.interactions = []; + $.each( results, function(){ + dd.interactions.push( drag.interaction( this, dd ) ); + }); + } + // remember how many interactions are propagating + dd.propagates = dd.interactions.length; + // locate and init the drop targets + if ( dd.drop !== false && $special.drop ) + $special.drop.handler( event, dd ); + // disable text selection + drag.textselect( false ); + // bind additional events... + if ( drag.touched ) + $event.add( drag.touched, "touchmove touchend", drag.handler, dd ); + else + $event.add( document, "mousemove mouseup", drag.handler, dd ); + // helps prevent text selection or scrolling + if ( !drag.touched || dd.live ) + return false; + }, + + // returns an interaction object + interaction: function( elem, dd ){ + var offset = $( elem )[ dd.relative ? "position" : "offset" ]() || { top:0, left:0 }; + return { + drag: elem, + callback: new drag.callback(), + droppable: [], + offset: offset + }; + }, + + // handle drag-releatd DOM events + handler: function( event ){ + // read the data before hijacking anything + var dd = event.data; + // handle various events + switch ( event.type ){ + // mousemove, check distance, start dragging + case !dd.dragging && 'touchmove': + event.preventDefault(); + case !dd.dragging && 'mousemove': + // drag tolerance, x≤ + y≤ = distance≤ + if ( Math.pow( event.pageX-dd.pageX, 2 ) + Math.pow( event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) ) + break; // distance tolerance not reached + event.target = dd.target; // force target from "mousedown" event (fix distance issue) + drag.hijack( event, "dragstart", dd ); // trigger "dragstart" + if ( dd.propagates ) // "dragstart" not rejected + dd.dragging = true; // activate interaction + // mousemove, dragging + case 'touchmove': + event.preventDefault(); + case 'mousemove': + if ( dd.dragging ){ + // trigger "drag" + drag.hijack( event, "drag", dd ); + if ( dd.propagates ){ + // manage drop events + if ( dd.drop !== false && $special.drop ) + $special.drop.handler( event, dd ); // "dropstart", "dropend" + break; // "drag" not rejected, stop + } + event.type = "mouseup"; // helps "drop" handler behave + } + // mouseup, stop dragging + case 'touchend': + case 'mouseup': + default: + if ( drag.touched ) + $event.remove( drag.touched, "touchmove touchend", drag.handler ); // remove touch events + else + $event.remove( document, "mousemove mouseup", drag.handler ); // remove page events + if ( dd.dragging ){ + if ( dd.drop !== false && $special.drop ) + $special.drop.handler( event, dd ); // "drop" + drag.hijack( event, "dragend", dd ); // trigger "dragend" + } + drag.textselect( true ); // enable text selection + // if suppressing click events... + if ( dd.click === false && dd.dragging ) + $.data( dd.mousedown, "suppress.click", new Date().getTime() + 5 ); + dd.dragging = drag.touched = false; // deactivate element + break; + } + }, + + // re-use event object for custom events + hijack: function( event, type, dd, x, elem ){ + // not configured + if ( !dd ) + return; + // remember the original event and type + var orig = { event:event.originalEvent, type:event.type }, + // is the event drag related or drog related? + mode = type.indexOf("drop") ? "drag" : "drop", + // iteration vars + result, i = x || 0, ia, $elems, callback, + len = !isNaN( x ) ? x : dd.interactions.length; + // modify the event type + event.type = type; + // remove the original event + event.originalEvent = null; + // initialize the results + dd.results = []; + // handle each interacted element + do if ( ia = dd.interactions[ i ] ){ + // validate the interaction + if ( type !== "dragend" && ia.cancelled ) + continue; + // set the dragdrop properties on the event object + callback = drag.properties( event, dd, ia ); + // prepare for more results + ia.results = []; + // handle each element + $( elem || ia[ mode ] || dd.droppable ).each(function( p, subject ){ + // identify drag or drop targets individually + callback.target = subject; + // force propagtion of the custom event + event.isPropagationStopped = function(){ return false; }; + // handle the event + result = subject ? $event.dispatch.call( subject, event, callback ) : null; + // stop the drag interaction for this element + if ( result === false ){ + if ( mode == "drag" ){ + ia.cancelled = true; + dd.propagates -= 1; + } + if ( type == "drop" ){ + ia[ mode ][p] = null; + } + } + // assign any dropinit elements + else if ( type == "dropinit" ) + ia.droppable.push( drag.element( result ) || subject ); + // accept a returned proxy element + if ( type == "dragstart" ) + ia.proxy = $( drag.element( result ) || ia.drag )[0]; + // remember this result + ia.results.push( result ); + // forget the event result, for recycling + delete event.result; + // break on cancelled handler + if ( type !== "dropinit" ) + return result; + }); + // flatten the results + dd.results[ i ] = drag.flatten( ia.results ); + // accept a set of valid drop targets + if ( type == "dropinit" ) + ia.droppable = drag.flatten( ia.droppable ); + // locate drop targets + if ( type == "dragstart" && !ia.cancelled ) + callback.update(); + } + while ( ++i < len ) + // restore the original event & type + event.type = orig.type; + event.originalEvent = orig.event; + // return all handler results + return drag.flatten( dd.results ); + }, + + // extend the callback object with drag/drop properties... + properties: function( event, dd, ia ){ + var obj = ia.callback; + // elements + obj.drag = ia.drag; + obj.proxy = ia.proxy || ia.drag; + // starting mouse position + obj.startX = dd.pageX; + obj.startY = dd.pageY; + // current distance dragged + obj.deltaX = event.pageX - dd.pageX; + obj.deltaY = event.pageY - dd.pageY; + // original element position + obj.originalX = ia.offset.left; + obj.originalY = ia.offset.top; + // adjusted element position + obj.offsetX = obj.originalX + obj.deltaX; + obj.offsetY = obj.originalY + obj.deltaY; + // assign the drop targets information + obj.drop = drag.flatten( ( ia.drop || [] ).slice() ); + obj.available = drag.flatten( ( ia.droppable || [] ).slice() ); + return obj; + }, + + // determine is the argument is an element or jquery instance + element: function( arg ){ + if ( arg && ( arg.jquery || arg.nodeType == 1 ) ) + return arg; + }, + + // flatten nested jquery objects and arrays into a single dimension array + flatten: function( arr ){ + return $.map( arr, function( member ){ + return member && member.jquery ? $.makeArray( member ) : + member && member.length ? drag.flatten( member ) : member; + }); + }, + + // toggles text selection attributes ON (true) or OFF (false) + textselect: function( bool ){ + $( document )[ bool ? "unbind" : "bind" ]("selectstart", drag.dontstart ) + .css("MozUserSelect", bool ? "" : "none" ); + // .attr("unselectable", bool ? "off" : "on" ) + document.unselectable = bool ? "off" : "on"; + }, + + // suppress "selectstart" and "ondragstart" events + dontstart: function(){ + return false; + }, + + // a callback instance contructor + callback: function(){} + +}; + +// callback methods +drag.callback.prototype = { + update: function(){ + if ( $special.drop && this.available.length ) + $.each( this.available, function( i ){ + $special.drop.locate( this, i ); + }); + } +}; + +// patch $.event.$dispatch to allow suppressing clicks +var $dispatch = $event.dispatch; +$event.dispatch = function( event ){ + if ( $.data( this, "suppress."+ event.type ) - new Date().getTime() > 0 ){ + $.removeData( this, "suppress."+ event.type ); + return; + } + return $dispatch.apply( this, arguments ); +}; + +// event fix hooks for touch events... +var touchHooks = +$event.fixHooks.touchstart = +$event.fixHooks.touchmove = +$event.fixHooks.touchend = +$event.fixHooks.touchcancel = { + props: "clientX clientY pageX pageY screenX screenY".split( " " ), + filter: function( event, orig ) { + if ( orig ){ + var touched = ( orig.touches && orig.touches[0] ) + || ( orig.changedTouches && orig.changedTouches[0] ) + || null; + // iOS webkit: touchstart, touchmove, touchend + if ( touched ) + $.each( touchHooks.props, function( i, prop ){ + event[ prop ] = touched[ prop ]; + }); + } + return event; + } +}; + +// share the same special event configuration with related events... +$special.draginit = $special.dragstart = $special.dragend = drag; + +})( jQuery ); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery.event.drop-2.2.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery.event.drop-2.2.js new file mode 100644 index 00000000..7599ef91 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/jquery.event.drop-2.2.js @@ -0,0 +1,302 @@ +/*! + * jquery.event.drop - v 2.2 + * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com + * Open Source MIT License - http://threedubmedia.com/code/license + */ +// Created: 2008-06-04 +// Updated: 2012-05-21 +// REQUIRES: jquery 1.7.x, event.drag 2.2 + +;(function($){ // secure $ jQuery alias + +// Events: drop, dropstart, dropend + +// add the jquery instance method +$.fn.drop = function( str, arg, opts ){ + // figure out the event type + var type = typeof str == "string" ? str : "", + // figure out the event handler... + fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null; + // fix the event type + if ( type.indexOf("drop") !== 0 ) + type = "drop"+ type; + // were options passed + opts = ( str == fn ? arg : opts ) || {}; + // trigger or bind event handler + return fn ? this.bind( type, opts, fn ) : this.trigger( type ); +}; + +// DROP MANAGEMENT UTILITY +// returns filtered drop target elements, caches their positions +$.drop = function( opts ){ + opts = opts || {}; + // safely set new options... + drop.multi = opts.multi === true ? Infinity : + opts.multi === false ? 1 : !isNaN( opts.multi ) ? opts.multi : drop.multi; + drop.delay = opts.delay || drop.delay; + drop.tolerance = $.isFunction( opts.tolerance ) ? opts.tolerance : + opts.tolerance === null ? null : drop.tolerance; + drop.mode = opts.mode || drop.mode || 'intersect'; +}; + +// local refs (increase compression) +var $event = $.event, +$special = $event.special, +// configure the drop special event +drop = $.event.special.drop = { + + // these are the default settings + multi: 1, // allow multiple drop winners per dragged element + delay: 20, // async timeout delay + mode: 'overlap', // drop tolerance mode + + // internal cache + targets: [], + + // the key name for stored drop data + datakey: "dropdata", + + // prevent bubbling for better performance + noBubble: true, + + // count bound related events + add: function( obj ){ + // read the interaction data + var data = $.data( this, drop.datakey ); + // count another realted event + data.related += 1; + }, + + // forget unbound related events + remove: function(){ + $.data( this, drop.datakey ).related -= 1; + }, + + // configure the interactions + setup: function(){ + // check for related events + if ( $.data( this, drop.datakey ) ) + return; + // initialize the drop element data + var data = { + related: 0, + active: [], + anyactive: 0, + winner: 0, + location: {} + }; + // store the drop data on the element + $.data( this, drop.datakey, data ); + // store the drop target in internal cache + drop.targets.push( this ); + }, + + // destroy the configure interaction + teardown: function(){ + var data = $.data( this, drop.datakey ) || {}; + // check for related events + if ( data.related ) + return; + // remove the stored data + $.removeData( this, drop.datakey ); + // reference the targeted element + var element = this; + // remove from the internal cache + drop.targets = $.grep( drop.targets, function( target ){ + return ( target !== element ); + }); + }, + + // shared event handler + handler: function( event, dd ){ + // local vars + var results, $targets; + // make sure the right data is available + if ( !dd ) + return; + // handle various events + switch ( event.type ){ + // draginit, from $.event.special.drag + case 'mousedown': // DROPINIT >> + case 'touchstart': // DROPINIT >> + // collect and assign the drop targets + $targets = $( drop.targets ); + if ( typeof dd.drop == "string" ) + $targets = $targets.filter( dd.drop ); + // reset drop data winner properties + $targets.each(function(){ + var data = $.data( this, drop.datakey ); + data.active = []; + data.anyactive = 0; + data.winner = 0; + }); + // set available target elements + dd.droppable = $targets; + // activate drop targets for the initial element being dragged + $special.drag.hijack( event, "dropinit", dd ); + break; + // drag, from $.event.special.drag + case 'mousemove': // TOLERATE >> + case 'touchmove': // TOLERATE >> + drop.event = event; // store the mousemove event + if ( !drop.timer ) + // monitor drop targets + drop.tolerate( dd ); + break; + // dragend, from $.event.special.drag + case 'mouseup': // DROP >> DROPEND >> + case 'touchend': // DROP >> DROPEND >> + drop.timer = clearTimeout( drop.timer ); // delete timer + if ( dd.propagates ){ + $special.drag.hijack( event, "drop", dd ); + $special.drag.hijack( event, "dropend", dd ); + } + break; + + } + }, + + // returns the location positions of an element + locate: function( elem, index ){ + var data = $.data( elem, drop.datakey ), + $elem = $( elem ), + posi = $elem.offset() || {}, + height = $elem.outerHeight(), + width = $elem.outerWidth(), + location = { + elem: elem, + width: width, + height: height, + top: posi.top, + left: posi.left, + right: posi.left + width, + bottom: posi.top + height + }; + // drag elements might not have dropdata + if ( data ){ + data.location = location; + data.index = index; + data.elem = elem; + } + return location; + }, + + // test the location positions of an element against another OR an X,Y coord + contains: function( target, test ){ // target { location } contains test [x,y] or { location } + return ( ( test[0] || test.left ) >= target.left && ( test[0] || test.right ) <= target.right + && ( test[1] || test.top ) >= target.top && ( test[1] || test.bottom ) <= target.bottom ); + }, + + // stored tolerance modes + modes: { // fn scope: "$.event.special.drop" object + // target with mouse wins, else target with most overlap wins + 'intersect': function( event, proxy, target ){ + return this.contains( target, [ event.pageX, event.pageY ] ) ? // check cursor + 1e9 : this.modes.overlap.apply( this, arguments ); // check overlap + }, + // target with most overlap wins + 'overlap': function( event, proxy, target ){ + // calculate the area of overlap... + return Math.max( 0, Math.min( target.bottom, proxy.bottom ) - Math.max( target.top, proxy.top ) ) + * Math.max( 0, Math.min( target.right, proxy.right ) - Math.max( target.left, proxy.left ) ); + }, + // proxy is completely contained within target bounds + 'fit': function( event, proxy, target ){ + return this.contains( target, proxy ) ? 1 : 0; + }, + // center of the proxy is contained within target bounds + 'middle': function( event, proxy, target ){ + return this.contains( target, [ proxy.left + proxy.width * .5, proxy.top + proxy.height * .5 ] ) ? 1 : 0; + } + }, + + // sort drop target cache by by winner (dsc), then index (asc) + sort: function( a, b ){ + return ( b.winner - a.winner ) || ( a.index - b.index ); + }, + + // async, recursive tolerance execution + tolerate: function( dd ){ + // declare local refs + var i, drp, drg, data, arr, len, elem, + // interaction iteration variables + x = 0, ia, end = dd.interactions.length, + // determine the mouse coords + xy = [ drop.event.pageX, drop.event.pageY ], + // custom or stored tolerance fn + tolerance = drop.tolerance || drop.modes[ drop.mode ]; + // go through each passed interaction... + do if ( ia = dd.interactions[x] ){ + // check valid interaction + if ( !ia ) + return; + // initialize or clear the drop data + ia.drop = []; + // holds the drop elements + arr = []; + len = ia.droppable.length; + // determine the proxy location, if needed + if ( tolerance ) + drg = drop.locate( ia.proxy ); + // reset the loop + i = 0; + // loop each stored drop target + do if ( elem = ia.droppable[i] ){ + data = $.data( elem, drop.datakey ); + drp = data.location; + if ( !drp ) continue; + // find a winner: tolerance function is defined, call it + data.winner = tolerance ? tolerance.call( drop, drop.event, drg, drp ) + // mouse position is always the fallback + : drop.contains( drp, xy ) ? 1 : 0; + arr.push( data ); + } while ( ++i < len ); // loop + // sort the drop targets + arr.sort( drop.sort ); + // reset the loop + i = 0; + // loop through all of the targets again + do if ( data = arr[ i ] ){ + // winners... + if ( data.winner && ia.drop.length < drop.multi ){ + // new winner... dropstart + if ( !data.active[x] && !data.anyactive ){ + // check to make sure that this is not prevented + if ( $special.drag.hijack( drop.event, "dropstart", dd, x, data.elem )[0] !== false ){ + data.active[x] = 1; + data.anyactive += 1; + } + // if false, it is not a winner + else + data.winner = 0; + } + // if it is still a winner + if ( data.winner ) + ia.drop.push( data.elem ); + } + // losers... + else if ( data.active[x] && data.anyactive == 1 ){ + // former winner... dropend + $special.drag.hijack( drop.event, "dropend", dd, x, data.elem ); + data.active[x] = 0; + data.anyactive -= 1; + } + } while ( ++i < len ); // loop + } while ( ++x < end ) // loop + // check if the mouse is still moving or is idle + if ( drop.last && xy[0] == drop.last.pageX && xy[1] == drop.last.pageY ) + delete drop.timer; // idle, don't recurse + else // recurse + drop.timer = setTimeout(function(){ + drop.tolerate( dd ); + }, drop.delay ); + // remember event, to compare idleness + drop.last = drop.event; + } + +}; + +// share the same special event configuration with related events... +$special.dropinit = $special.dropstart = $special.dropend = drop; + +})(jQuery); // confine scope \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.autotooltips.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.autotooltips.js new file mode 100644 index 00000000..955684f2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.autotooltips.js @@ -0,0 +1,83 @@ +(function ($) { + // Register namespace + $.extend(true, window, { + "Slick": { + "AutoTooltips": AutoTooltips + } + }); + + /** + * AutoTooltips plugin to show/hide tooltips when columns are too narrow to fit content. + * @constructor + * @param {boolean} [options.enableForCells=true] - Enable tooltip for grid cells + * @param {boolean} [options.enableForHeaderCells=false] - Enable tooltip for header cells + * @param {number} [options.maxToolTipLength=null] - The maximum length for a tooltip + */ + function AutoTooltips(options) { + var _grid; + var _self = this; + var _defaults = { + enableForCells: true, + enableForHeaderCells: false, + maxToolTipLength: null + }; + + /** + * Initialize plugin. + */ + function init(grid) { + options = $.extend(true, {}, _defaults, options); + _grid = grid; + if (options.enableForCells) _grid.onMouseEnter.subscribe(handleMouseEnter); + if (options.enableForHeaderCells) _grid.onHeaderMouseEnter.subscribe(handleHeaderMouseEnter); + } + + /** + * Destroy plugin. + */ + function destroy() { + if (options.enableForCells) _grid.onMouseEnter.unsubscribe(handleMouseEnter); + if (options.enableForHeaderCells) _grid.onHeaderMouseEnter.unsubscribe(handleHeaderMouseEnter); + } + + /** + * Handle mouse entering grid cell to add/remove tooltip. + * @param {jQuery.Event} e - The event + */ + function handleMouseEnter(e) { + var cell = _grid.getCellFromEvent(e); + if (cell) { + var $node = $(_grid.getCellNode(cell.row, cell.cell)); + var text; + if ($node.innerWidth() < $node[0].scrollWidth) { + text = $.trim($node.text()); + if (options.maxToolTipLength && text.length > options.maxToolTipLength) { + text = text.substr(0, options.maxToolTipLength - 3) + "..."; + } + } else { + text = ""; + } + $node.attr("title", text); + } + } + + /** + * Handle mouse entering header cell to add/remove tooltip. + * @param {jQuery.Event} e - The event + * @param {object} args.column - The column definition + */ + function handleHeaderMouseEnter(e, args) { + var column = args.column, + $node = $(e.target).closest(".slick-header-column"); + if (!column.toolTip) { + $node.attr("title", ($node.innerWidth() < $node[0].scrollWidth) ? column.name : ""); + } + } + + // Public API + $.extend(this, { + "init": init, + "destroy": destroy + }); + } +})(jQuery); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellcopymanager.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellcopymanager.js new file mode 100644 index 00000000..c74018de --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellcopymanager.js @@ -0,0 +1,86 @@ +(function ($) { + // register namespace + $.extend(true, window, { + "Slick": { + "CellCopyManager": CellCopyManager + } + }); + + + function CellCopyManager() { + var _grid; + var _self = this; + var _copiedRanges; + + function init(grid) { + _grid = grid; + _grid.onKeyDown.subscribe(handleKeyDown); + } + + function destroy() { + _grid.onKeyDown.unsubscribe(handleKeyDown); + } + + function handleKeyDown(e, args) { + var ranges; + if (!_grid.getEditorLock().isActive()) { + if (e.which == $.ui.keyCode.ESCAPE) { + if (_copiedRanges) { + e.preventDefault(); + clearCopySelection(); + _self.onCopyCancelled.notify({ranges: _copiedRanges}); + _copiedRanges = null; + } + } + + if (e.which == 67 && (e.ctrlKey || e.metaKey)) { + ranges = _grid.getSelectionModel().getSelectedRanges(); + if (ranges.length != 0) { + e.preventDefault(); + _copiedRanges = ranges; + markCopySelection(ranges); + _self.onCopyCells.notify({ranges: ranges}); + } + } + + if (e.which == 86 && (e.ctrlKey || e.metaKey)) { + if (_copiedRanges) { + e.preventDefault(); + clearCopySelection(); + ranges = _grid.getSelectionModel().getSelectedRanges(); + _self.onPasteCells.notify({from: _copiedRanges, to: ranges}); + _copiedRanges = null; + } + } + } + } + + function markCopySelection(ranges) { + var columns = _grid.getColumns(); + var hash = {}; + for (var i = 0; i < ranges.length; i++) { + for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) { + hash[j] = {}; + for (var k = ranges[i].fromCell; k <= ranges[i].toCell; k++) { + hash[j][columns[k].id] = "copied"; + } + } + } + _grid.setCellCssStyles("copy-manager", hash); + } + + function clearCopySelection() { + _grid.removeCellCssStyles("copy-manager"); + } + + $.extend(this, { + "init": init, + "destroy": destroy, + "clearCopySelection": clearCopySelection, + + "onCopyCells": new Slick.Event(), + "onCopyCancelled": new Slick.Event(), + "onPasteCells": new Slick.Event() + }); + } +})(jQuery); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellrangedecorator.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellrangedecorator.js new file mode 100644 index 00000000..0cbe71d4 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellrangedecorator.js @@ -0,0 +1,66 @@ +(function ($) { + // register namespace + $.extend(true, window, { + "Slick": { + "CellRangeDecorator": CellRangeDecorator + } + }); + + /*** + * Displays an overlay on top of a given cell range. + * + * TODO: + * Currently, it blocks mouse events to DOM nodes behind it. + * Use FF and WebKit-specific "pointer-events" CSS style, or some kind of event forwarding. + * Could also construct the borders separately using 4 individual DIVs. + * + * @param {Grid} grid + * @param {Object} options + */ + function CellRangeDecorator(grid, options) { + var _elem; + var _defaults = { + selectionCssClass: 'slick-range-decorator', + selectionCss: { + "zIndex": "9999", + "border": "2px dashed red" + } + }; + + options = $.extend(true, {}, _defaults, options); + + + function show(range) { + if (!_elem) { + _elem = $("
    ", {css: options.selectionCss}) + .addClass(options.selectionCssClass) + .css("position", "absolute") + .appendTo(grid.getCanvasNode()); + } + + var from = grid.getCellNodeBox(range.fromRow, range.fromCell); + var to = grid.getCellNodeBox(range.toRow, range.toCell); + + _elem.css({ + top: from.top - 1, + left: from.left - 1, + height: to.bottom - from.top - 2, + width: to.right - from.left - 2 + }); + + return _elem; + } + + function hide() { + if (_elem) { + _elem.remove(); + _elem = null; + } + } + + $.extend(this, { + "show": show, + "hide": hide + }); + } +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellrangeselector.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellrangeselector.js new file mode 100644 index 00000000..520b17f3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellrangeselector.js @@ -0,0 +1,113 @@ +(function ($) { + // register namespace + $.extend(true, window, { + "Slick": { + "CellRangeSelector": CellRangeSelector + } + }); + + + function CellRangeSelector(options) { + var _grid; + var _canvas; + var _dragging; + var _decorator; + var _self = this; + var _handler = new Slick.EventHandler(); + var _defaults = { + selectionCss: { + "border": "2px dashed blue" + } + }; + + + function init(grid) { + options = $.extend(true, {}, _defaults, options); + _decorator = new Slick.CellRangeDecorator(grid, options); + _grid = grid; + _canvas = _grid.getCanvasNode(); + _handler + .subscribe(_grid.onDragInit, handleDragInit) + .subscribe(_grid.onDragStart, handleDragStart) + .subscribe(_grid.onDrag, handleDrag) + .subscribe(_grid.onDragEnd, handleDragEnd); + } + + function destroy() { + _handler.unsubscribeAll(); + } + + function handleDragInit(e, dd) { + // prevent the grid from cancelling drag'n'drop by default + e.stopImmediatePropagation(); + } + + function handleDragStart(e, dd) { + var cell = _grid.getCellFromEvent(e); + if (_self.onBeforeCellRangeSelected.notify(cell) !== false) { + if (_grid.canCellBeSelected(cell.row, cell.cell)) { + _dragging = true; + e.stopImmediatePropagation(); + } + } + if (!_dragging) { + return; + } + + _grid.focus(); + + var start = _grid.getCellFromPoint( + dd.startX - $(_canvas).offset().left, + dd.startY - $(_canvas).offset().top); + + dd.range = {start: start, end: {}}; + + return _decorator.show(new Slick.Range(start.row, start.cell)); + } + + function handleDrag(e, dd) { + if (!_dragging) { + return; + } + e.stopImmediatePropagation(); + + var end = _grid.getCellFromPoint( + e.pageX - $(_canvas).offset().left, + e.pageY - $(_canvas).offset().top); + + if (!_grid.canCellBeSelected(end.row, end.cell)) { + return; + } + + dd.range.end = end; + _decorator.show(new Slick.Range(dd.range.start.row, dd.range.start.cell, end.row, end.cell)); + } + + function handleDragEnd(e, dd) { + if (!_dragging) { + return; + } + + _dragging = false; + e.stopImmediatePropagation(); + + _decorator.hide(); + _self.onCellRangeSelected.notify({ + range: new Slick.Range( + dd.range.start.row, + dd.range.start.cell, + dd.range.end.row, + dd.range.end.cell + ) + }); + } + + $.extend(this, { + "init": init, + "destroy": destroy, + + "onBeforeCellRangeSelected": new Slick.Event(), + "onCellRangeSelected": new Slick.Event() + }); + } +})(jQuery); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellselectionmodel.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellselectionmodel.js new file mode 100644 index 00000000..74bc3eb7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.cellselectionmodel.js @@ -0,0 +1,154 @@ +(function ($) { + // register namespace + $.extend(true, window, { + "Slick": { + "CellSelectionModel": CellSelectionModel + } + }); + + + function CellSelectionModel(options) { + var _grid; + var _canvas; + var _ranges = []; + var _self = this; + var _selector = new Slick.CellRangeSelector({ + "selectionCss": { + "border": "2px solid black" + } + }); + var _options; + var _defaults = { + selectActiveCell: true + }; + + + function init(grid) { + _options = $.extend(true, {}, _defaults, options); + _grid = grid; + _canvas = _grid.getCanvasNode(); + _grid.onActiveCellChanged.subscribe(handleActiveCellChange); + _grid.onKeyDown.subscribe(handleKeyDown); + grid.registerPlugin(_selector); + _selector.onCellRangeSelected.subscribe(handleCellRangeSelected); + _selector.onBeforeCellRangeSelected.subscribe(handleBeforeCellRangeSelected); + } + + function destroy() { + _grid.onActiveCellChanged.unsubscribe(handleActiveCellChange); + _grid.onKeyDown.unsubscribe(handleKeyDown); + _selector.onCellRangeSelected.unsubscribe(handleCellRangeSelected); + _selector.onBeforeCellRangeSelected.unsubscribe(handleBeforeCellRangeSelected); + _grid.unregisterPlugin(_selector); + } + + function removeInvalidRanges(ranges) { + var result = []; + + for (var i = 0; i < ranges.length; i++) { + var r = ranges[i]; + if (_grid.canCellBeSelected(r.fromRow, r.fromCell) && _grid.canCellBeSelected(r.toRow, r.toCell)) { + result.push(r); + } + } + + return result; + } + + function setSelectedRanges(ranges) { + _ranges = removeInvalidRanges(ranges); + _self.onSelectedRangesChanged.notify(_ranges); + } + + function getSelectedRanges() { + return _ranges; + } + + function handleBeforeCellRangeSelected(e, args) { + if (_grid.getEditorLock().isActive()) { + e.stopPropagation(); + return false; + } + } + + function handleCellRangeSelected(e, args) { + setSelectedRanges([args.range]); + } + + function handleActiveCellChange(e, args) { + if (_options.selectActiveCell && args.row != null && args.cell != null) { + setSelectedRanges([new Slick.Range(args.row, args.cell)]); + } + } + + function handleKeyDown(e) { + /*** + * Кey codes + * 37 left + * 38 up + * 39 right + * 40 down + */ + var ranges, last; + var active = _grid.getActiveCell(); + + if ( active && e.shiftKey && !e.ctrlKey && !e.altKey && + (e.which == 37 || e.which == 39 || e.which == 38 || e.which == 40) ) { + + ranges = getSelectedRanges(); + if (!ranges.length) + ranges.push(new Slick.Range(active.row, active.cell)); + + // keyboard can work with last range only + last = ranges.pop(); + + // can't handle selection out of active cell + if (!last.contains(active.row, active.cell)) + last = new Slick.Range(active.row, active.cell); + + var dRow = last.toRow - last.fromRow, + dCell = last.toCell - last.fromCell, + // walking direction + dirRow = active.row == last.fromRow ? 1 : -1, + dirCell = active.cell == last.fromCell ? 1 : -1; + + if (e.which == 37) { + dCell -= dirCell; + } else if (e.which == 39) { + dCell += dirCell ; + } else if (e.which == 38) { + dRow -= dirRow; + } else if (e.which == 40) { + dRow += dirRow; + } + + // define new selection range + var new_last = new Slick.Range(active.row, active.cell, active.row + dirRow*dRow, active.cell + dirCell*dCell); + if (removeInvalidRanges([new_last]).length) { + ranges.push(new_last); + var viewRow = dirRow > 0 ? new_last.toRow : new_last.fromRow; + var viewCell = dirCell > 0 ? new_last.toCell : new_last.fromCell; + _grid.scrollRowIntoView(viewRow); + _grid.scrollCellIntoView(viewRow, viewCell); + } + else + ranges.push(last); + + setSelectedRanges(ranges); + + e.preventDefault(); + e.stopPropagation(); + } + } + + $.extend(this, { + "getSelectedRanges": getSelectedRanges, + "setSelectedRanges": setSelectedRanges, + + "init": init, + "destroy": destroy, + + "onSelectedRangesChanged": new Slick.Event() + }); + } +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.checkboxselectcolumn.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.checkboxselectcolumn.js new file mode 100644 index 00000000..83d8d500 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.checkboxselectcolumn.js @@ -0,0 +1,153 @@ +(function ($) { + // register namespace + $.extend(true, window, { + "Slick": { + "CheckboxSelectColumn": CheckboxSelectColumn + } + }); + + + function CheckboxSelectColumn(options) { + var _grid; + var _self = this; + var _handler = new Slick.EventHandler(); + var _selectedRowsLookup = {}; + var _defaults = { + columnId: "_checkbox_selector", + cssClass: null, + toolTip: "Select/Deselect All", + width: 30 + }; + + var _options = $.extend(true, {}, _defaults, options); + + function init(grid) { + _grid = grid; + _handler + .subscribe(_grid.onSelectedRowsChanged, handleSelectedRowsChanged) + .subscribe(_grid.onClick, handleClick) + .subscribe(_grid.onHeaderClick, handleHeaderClick) + .subscribe(_grid.onKeyDown, handleKeyDown); + } + + function destroy() { + _handler.unsubscribeAll(); + } + + function handleSelectedRowsChanged(e, args) { + var selectedRows = _grid.getSelectedRows(); + var lookup = {}, row, i; + for (i = 0; i < selectedRows.length; i++) { + row = selectedRows[i]; + lookup[row] = true; + if (lookup[row] !== _selectedRowsLookup[row]) { + _grid.invalidateRow(row); + delete _selectedRowsLookup[row]; + } + } + for (i in _selectedRowsLookup) { + _grid.invalidateRow(i); + } + _selectedRowsLookup = lookup; + _grid.render(); + + if (selectedRows.length && selectedRows.length == _grid.getDataLength()) { + _grid.updateColumnHeader(_options.columnId, "", _options.toolTip); + } else { + _grid.updateColumnHeader(_options.columnId, "", _options.toolTip); + } + } + + function handleKeyDown(e, args) { + if (e.which == 32) { + if (_grid.getColumns()[args.cell].id === _options.columnId) { + // if editing, try to commit + if (!_grid.getEditorLock().isActive() || _grid.getEditorLock().commitCurrentEdit()) { + toggleRowSelection(args.row); + } + e.preventDefault(); + e.stopImmediatePropagation(); + } + } + } + + function handleClick(e, args) { + // clicking on a row select checkbox + if (_grid.getColumns()[args.cell].id === _options.columnId && $(e.target).is(":checkbox")) { + // if editing, try to commit + if (_grid.getEditorLock().isActive() && !_grid.getEditorLock().commitCurrentEdit()) { + e.preventDefault(); + e.stopImmediatePropagation(); + return; + } + + toggleRowSelection(args.row); + e.stopPropagation(); + e.stopImmediatePropagation(); + } + } + + function toggleRowSelection(row) { + if (_selectedRowsLookup[row]) { + _grid.setSelectedRows($.grep(_grid.getSelectedRows(), function (n) { + return n != row + })); + } else { + _grid.setSelectedRows(_grid.getSelectedRows().concat(row)); + } + } + + function handleHeaderClick(e, args) { + if (args.column.id == _options.columnId && $(e.target).is(":checkbox")) { + // if editing, try to commit + if (_grid.getEditorLock().isActive() && !_grid.getEditorLock().commitCurrentEdit()) { + e.preventDefault(); + e.stopImmediatePropagation(); + return; + } + + if ($(e.target).is(":checked")) { + var rows = []; + for (var i = 0; i < _grid.getDataLength(); i++) { + rows.push(i); + } + _grid.setSelectedRows(rows); + } else { + _grid.setSelectedRows([]); + } + e.stopPropagation(); + e.stopImmediatePropagation(); + } + } + + function getColumnDefinition() { + return { + id: _options.columnId, + name: "", + toolTip: _options.toolTip, + field: "sel", + width: _options.width, + resizable: false, + sortable: false, + cssClass: _options.cssClass, + formatter: checkboxSelectionFormatter + }; + } + + function checkboxSelectionFormatter(row, cell, value, columnDef, dataContext) { + if (dataContext) { + return _selectedRowsLookup[row] + ? "" + : ""; + } + return null; + } + + $.extend(this, { + "init": init, + "destroy": destroy, + + "getColumnDefinition": getColumnDefinition + }); + } +})(jQuery); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headerbuttons.css b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headerbuttons.css new file mode 100644 index 00000000..0ba79ea0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headerbuttons.css @@ -0,0 +1,39 @@ +.slick-column-name, +.slick-sort-indicator { + /** + * This makes all "float:right" elements after it that spill over to the next line + * display way below the lower boundary of the column thus hiding them. + */ + display: inline-block; + float: left; + margin-bottom: 100px; +} + +.slick-header-button { + display: inline-block; + float: right; + vertical-align: top; + margin: 1px; + /** + * This makes all "float:right" elements after it that spill over to the next line + * display way below the lower boundary of the column thus hiding them. + */ + margin-bottom: 100px; + height: 15px; + width: 15px; + background-repeat: no-repeat; + background-position: center center; + cursor: pointer; +} + +.slick-header-button-hidden { + width: 0; + + -webkit-transition: 0.2s width; + -ms-transition: 0.2s width; + transition: 0.2s width; +} + +.slick-header-column:hover > .slick-header-button { + width: 15px; +} \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headerbuttons.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headerbuttons.js new file mode 100644 index 00000000..8e612735 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headerbuttons.js @@ -0,0 +1,177 @@ +(function ($) { + // register namespace + $.extend(true, window, { + "Slick": { + "Plugins": { + "HeaderButtons": HeaderButtons + } + } + }); + + + /*** + * A plugin to add custom buttons to column headers. + * + * USAGE: + * + * Add the plugin .js & .css files and register it with the grid. + * + * To specify a custom button in a column header, extend the column definition like so: + * + * var columns = [ + * { + * id: 'myColumn', + * name: 'My column', + * + * // This is the relevant part + * header: { + * buttons: [ + * { + * // button options + * }, + * { + * // button options + * } + * ] + * } + * } + * ]; + * + * Available button options: + * cssClass: CSS class to add to the button. + * image: Relative button image path. + * tooltip: Button tooltip. + * showOnHover: Only show the button on hover. + * handler: Button click handler. + * command: A command identifier to be passed to the onCommand event handlers. + * + * The plugin exposes the following events: + * onCommand: Fired on button click for buttons with 'command' specified. + * Event args: + * grid: Reference to the grid. + * column: Column definition. + * command: Button command identified. + * button: Button options. Note that you can change the button options in your + * event handler, and the column header will be automatically updated to + * reflect them. This is useful if you want to implement something like a + * toggle button. + * + * + * @param options {Object} Options: + * buttonCssClass: a CSS class to use for buttons (default 'slick-header-button') + * @class Slick.Plugins.HeaderButtons + * @constructor + */ + function HeaderButtons(options) { + var _grid; + var _self = this; + var _handler = new Slick.EventHandler(); + var _defaults = { + buttonCssClass: "slick-header-button" + }; + + + function init(grid) { + options = $.extend(true, {}, _defaults, options); + _grid = grid; + _handler + .subscribe(_grid.onHeaderCellRendered, handleHeaderCellRendered) + .subscribe(_grid.onBeforeHeaderCellDestroy, handleBeforeHeaderCellDestroy); + + // Force the grid to re-render the header now that the events are hooked up. + _grid.setColumns(_grid.getColumns()); + } + + + function destroy() { + _handler.unsubscribeAll(); + } + + + function handleHeaderCellRendered(e, args) { + var column = args.column; + + if (column.header && column.header.buttons) { + // Append buttons in reverse order since they are floated to the right. + var i = column.header.buttons.length; + while (i--) { + var button = column.header.buttons[i]; + var btn = $("
    ") + .addClass(options.buttonCssClass) + .data("column", column) + .data("button", button); + + if (button.showOnHover) { + btn.addClass("slick-header-button-hidden"); + } + + if (button.image) { + btn.css("backgroundImage", "url(" + button.image + ")"); + } + + if (button.cssClass) { + btn.addClass(button.cssClass); + } + + if (button.tooltip) { + btn.attr("title", button.tooltip); + } + + if (button.command) { + btn.data("command", button.command); + } + + if (button.handler) { + btn.bind("click", button.handler); + } + + btn + .bind("click", handleButtonClick) + .appendTo(args.node); + } + } + } + + + function handleBeforeHeaderCellDestroy(e, args) { + var column = args.column; + + if (column.header && column.header.buttons) { + // Removing buttons via jQuery will also clean up any event handlers and data. + // NOTE: If you attach event handlers directly or using a different framework, + // you must also clean them up here to avoid memory leaks. + $(args.node).find("." + options.buttonCssClass).remove(); + } + } + + + function handleButtonClick(e) { + var command = $(this).data("command"); + var columnDef = $(this).data("column"); + var button = $(this).data("button"); + + if (command != null) { + _self.onCommand.notify({ + "grid": _grid, + "column": columnDef, + "command": command, + "button": button + }, e, _self); + + // Update the header in case the user updated the button definition in the handler. + _grid.updateColumnHeader(columnDef.id); + } + + // Stop propagation so that it doesn't register as a header click event. + e.preventDefault(); + e.stopPropagation(); + } + + $.extend(this, { + "init": init, + "destroy": destroy, + + "onCommand": new Slick.Event() + }); + } +})(jQuery); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headermenu.css b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headermenu.css new file mode 100644 index 00000000..8b0b6a9f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headermenu.css @@ -0,0 +1,59 @@ +/* Menu button */ +.slick-header-menubutton { + position: absolute; + right: 0; + top: 0; + bottom: 0; + width: 14px; + background-repeat: no-repeat; + background-position: left center; + background-image: url(../images/down.gif); + cursor: pointer; + + display: none; + border-left: thin ridge silver; +} + +.slick-header-column:hover > .slick-header-menubutton, +.slick-header-column-active .slick-header-menubutton { + display: inline-block; +} + +/* Menu */ +.slick-header-menu { + position: absolute; + display: inline-block; + margin: 0; + padding: 2px; + cursor: default; +} + + +/* Menu items */ +.slick-header-menuitem { + list-style: none; + margin: 0; + padding: 0; + cursor: pointer; +} + +.slick-header-menuicon { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; + margin-right: 4px; + background-repeat: no-repeat; + background-position: center center; +} + +.slick-header-menucontent { + display: inline-block; + vertical-align: middle; +} + + +/* Disabled */ +.slick-header-menuitem-disabled { + color: silver; +} diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headermenu.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headermenu.js new file mode 100644 index 00000000..ec8244da --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.headermenu.js @@ -0,0 +1,275 @@ +(function ($) { + // register namespace + $.extend(true, window, { + "Slick": { + "Plugins": { + "HeaderMenu": HeaderMenu + } + } + }); + + + /*** + * A plugin to add drop-down menus to column headers. + * + * USAGE: + * + * Add the plugin .js & .css files and register it with the grid. + * + * To specify a menu in a column header, extend the column definition like so: + * + * var columns = [ + * { + * id: 'myColumn', + * name: 'My column', + * + * // This is the relevant part + * header: { + * menu: { + * items: [ + * { + * // menu item options + * }, + * { + * // menu item options + * } + * ] + * } + * } + * } + * ]; + * + * + * Available menu options: + * tooltip: Menu button tooltip. + * + * + * Available menu item options: + * title: Menu item text. + * disabled: Whether the item is disabled. + * tooltip: Item tooltip. + * command: A command identifier to be passed to the onCommand event handlers. + * iconCssClass: A CSS class to be added to the menu item icon. + * iconImage: A url to the icon image. + * + * + * The plugin exposes the following events: + * onBeforeMenuShow: Fired before the menu is shown. You can customize the menu or dismiss it by returning false. + * Event args: + * grid: Reference to the grid. + * column: Column definition. + * menu: Menu options. Note that you can change the menu items here. + * + * onCommand: Fired on menu item click for buttons with 'command' specified. + * Event args: + * grid: Reference to the grid. + * column: Column definition. + * command: Button command identified. + * button: Button options. Note that you can change the button options in your + * event handler, and the column header will be automatically updated to + * reflect them. This is useful if you want to implement something like a + * toggle button. + * + * + * @param options {Object} Options: + * buttonCssClass: an extra CSS class to add to the menu button + * buttonImage: a url to the menu button image (default '../images/down.gif') + * @class Slick.Plugins.HeaderButtons + * @constructor + */ + function HeaderMenu(options) { + var _grid; + var _self = this; + var _handler = new Slick.EventHandler(); + var _defaults = { + buttonCssClass: null, + buttonImage: null + }; + var $menu; + var $activeHeaderColumn; + + + function init(grid) { + options = $.extend(true, {}, _defaults, options); + _grid = grid; + _handler + .subscribe(_grid.onHeaderCellRendered, handleHeaderCellRendered) + .subscribe(_grid.onBeforeHeaderCellDestroy, handleBeforeHeaderCellDestroy); + + // Force the grid to re-render the header now that the events are hooked up. + _grid.setColumns(_grid.getColumns()); + + // Hide the menu on outside click. + $(document.body).bind("mousedown", handleBodyMouseDown); + } + + + function destroy() { + _handler.unsubscribeAll(); + $(document.body).unbind("mousedown", handleBodyMouseDown); + } + + + function handleBodyMouseDown(e) { + if ($menu && $menu[0] != e.target && !$.contains($menu[0], e.target)) { + hideMenu(); + } + } + + + function hideMenu() { + if ($menu) { + $menu.remove(); + $menu = null; + $activeHeaderColumn + .removeClass("slick-header-column-active"); + } + } + + function handleHeaderCellRendered(e, args) { + var column = args.column; + var menu = column.header && column.header.menu; + + if (menu) { + var $el = $("
    ") + .addClass("slick-header-menubutton") + .data("column", column) + .data("menu", menu); + + if (options.buttonCssClass) { + $el.addClass(options.buttonCssClass); + } + + if (options.buttonImage) { + $el.css("background-image", "url(" + options.buttonImage + ")"); + } + + if (menu.tooltip) { + $el.attr("title", menu.tooltip); + } + + $el + .bind("click", showMenu) + .appendTo(args.node); + } + } + + + function handleBeforeHeaderCellDestroy(e, args) { + var column = args.column; + + if (column.header && column.header.menu) { + $(args.node).find(".slick-header-menubutton").remove(); + } + } + + + function showMenu(e) { + var $menuButton = $(this); + var menu = $menuButton.data("menu"); + var columnDef = $menuButton.data("column"); + + // Let the user modify the menu or cancel altogether, + // or provide alternative menu implementation. + if (_self.onBeforeMenuShow.notify({ + "grid": _grid, + "column": columnDef, + "menu": menu + }, e, _self) == false) { + return; + } + + + if (!$menu) { + $menu = $("
    ") + .appendTo(_grid.getContainerNode()); + } + $menu.empty(); + + + // Construct the menu items. + for (var i = 0; i < menu.items.length; i++) { + var item = menu.items[i]; + + var $li = $("
    ") + .data("command", item.command || '') + .data("column", columnDef) + .data("item", item) + .bind("click", handleMenuItemClick) + .appendTo($menu); + + if (item.disabled) { + $li.addClass("slick-header-menuitem-disabled"); + } + + if (item.tooltip) { + $li.attr("title", item.tooltip); + } + + var $icon = $("
    ") + .appendTo($li); + + if (item.iconCssClass) { + $icon.addClass(item.iconCssClass); + } + + if (item.iconImage) { + $icon.css("background-image", "url(" + item.iconImage + ")"); + } + + $("") + .text(item.title) + .appendTo($li); + } + + + // Position the menu. + $menu + .offset({ top: $(this).offset().top + $(this).height(), left: $(this).offset().left }); + + + // Mark the header as active to keep the highlighting. + $activeHeaderColumn = $menuButton.closest(".slick-header-column"); + $activeHeaderColumn + .addClass("slick-header-column-active"); + + // Stop propagation so that it doesn't register as a header click event. + e.preventDefault(); + e.stopPropagation(); + } + + + function handleMenuItemClick(e) { + var command = $(this).data("command"); + var columnDef = $(this).data("column"); + var item = $(this).data("item"); + + if (item.disabled) { + return; + } + + hideMenu(); + + if (command != null && command != '') { + _self.onCommand.notify({ + "grid": _grid, + "column": columnDef, + "command": command, + "item": item + }, e, _self); + } + + // Stop propagation so that it doesn't register as a header click event. + e.preventDefault(); + e.stopPropagation(); + } + + $.extend(this, { + "init": init, + "destroy": destroy, + + "onBeforeMenuShow": new Slick.Event(), + "onCommand": new Slick.Event() + }); + } +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.rowmovemanager.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.rowmovemanager.js new file mode 100644 index 00000000..5f87a1ed --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.rowmovemanager.js @@ -0,0 +1,138 @@ +(function ($) { + // register namespace + $.extend(true, window, { + "Slick": { + "RowMoveManager": RowMoveManager + } + }); + + function RowMoveManager(options) { + var _grid; + var _canvas; + var _dragging; + var _self = this; + var _handler = new Slick.EventHandler(); + var _defaults = { + cancelEditOnDrag: false + }; + + function init(grid) { + options = $.extend(true, {}, _defaults, options); + _grid = grid; + _canvas = _grid.getCanvasNode(); + _handler + .subscribe(_grid.onDragInit, handleDragInit) + .subscribe(_grid.onDragStart, handleDragStart) + .subscribe(_grid.onDrag, handleDrag) + .subscribe(_grid.onDragEnd, handleDragEnd); + } + + function destroy() { + _handler.unsubscribeAll(); + } + + function handleDragInit(e, dd) { + // prevent the grid from cancelling drag'n'drop by default + e.stopImmediatePropagation(); + } + + function handleDragStart(e, dd) { + var cell = _grid.getCellFromEvent(e); + + if (options.cancelEditOnDrag && _grid.getEditorLock().isActive()) { + _grid.getEditorLock().cancelCurrentEdit(); + } + + if (_grid.getEditorLock().isActive() || !/move|selectAndMove/.test(_grid.getColumns()[cell.cell].behavior)) { + return false; + } + + _dragging = true; + e.stopImmediatePropagation(); + + var selectedRows = _grid.getSelectedRows(); + + if (selectedRows.length == 0 || $.inArray(cell.row, selectedRows) == -1) { + selectedRows = [cell.row]; + _grid.setSelectedRows(selectedRows); + } + + var rowHeight = _grid.getOptions().rowHeight; + + dd.selectedRows = selectedRows; + + dd.selectionProxy = $("
    ") + .css("position", "absolute") + .css("zIndex", "99999") + .css("width", $(_canvas).innerWidth()) + .css("height", rowHeight * selectedRows.length) + .appendTo(_canvas); + + dd.guide = $("
    ") + .css("position", "absolute") + .css("zIndex", "99998") + .css("width", $(_canvas).innerWidth()) + .css("top", -1000) + .appendTo(_canvas); + + dd.insertBefore = -1; + } + + function handleDrag(e, dd) { + if (!_dragging) { + return; + } + + e.stopImmediatePropagation(); + + var top = e.pageY - $(_canvas).offset().top; + dd.selectionProxy.css("top", top - 5); + + var insertBefore = Math.max(0, Math.min(Math.round(top / _grid.getOptions().rowHeight), _grid.getDataLength())); + if (insertBefore !== dd.insertBefore) { + var eventData = { + "rows": dd.selectedRows, + "insertBefore": insertBefore + }; + + if (_self.onBeforeMoveRows.notify(eventData) === false) { + dd.guide.css("top", -1000); + dd.canMove = false; + } else { + dd.guide.css("top", insertBefore * _grid.getOptions().rowHeight); + dd.canMove = true; + } + + dd.insertBefore = insertBefore; + } + } + + function handleDragEnd(e, dd) { + if (!_dragging) { + return; + } + _dragging = false; + e.stopImmediatePropagation(); + + dd.guide.remove(); + dd.selectionProxy.remove(); + + if (dd.canMove) { + var eventData = { + "rows": dd.selectedRows, + "insertBefore": dd.insertBefore + }; + // TODO: _grid.remapCellCssClasses ? + _self.onMoveRows.notify(eventData); + } + } + + $.extend(this, { + "onBeforeMoveRows": new Slick.Event(), + "onMoveRows": new Slick.Event(), + + "init": init, + "destroy": destroy + }); + } +})(jQuery); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.rowselectionmodel.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.rowselectionmodel.js new file mode 100644 index 00000000..0de8dd3a --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/plugins/slick.rowselectionmodel.js @@ -0,0 +1,187 @@ +(function ($) { + // register namespace + $.extend(true, window, { + "Slick": { + "RowSelectionModel": RowSelectionModel + } + }); + + function RowSelectionModel(options) { + var _grid; + var _ranges = []; + var _self = this; + var _handler = new Slick.EventHandler(); + var _inHandler; + var _options; + var _defaults = { + selectActiveRow: true + }; + + function init(grid) { + _options = $.extend(true, {}, _defaults, options); + _grid = grid; + _handler.subscribe(_grid.onActiveCellChanged, + wrapHandler(handleActiveCellChange)); + _handler.subscribe(_grid.onKeyDown, + wrapHandler(handleKeyDown)); + _handler.subscribe(_grid.onClick, + wrapHandler(handleClick)); + } + + function destroy() { + _handler.unsubscribeAll(); + } + + function wrapHandler(handler) { + return function () { + if (!_inHandler) { + _inHandler = true; + handler.apply(this, arguments); + _inHandler = false; + } + }; + } + + function rangesToRows(ranges) { + var rows = []; + for (var i = 0; i < ranges.length; i++) { + for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) { + rows.push(j); + } + } + return rows; + } + + function rowsToRanges(rows) { + var ranges = []; + var lastCell = _grid.getColumns().length - 1; + for (var i = 0; i < rows.length; i++) { + ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell)); + } + return ranges; + } + + function getRowsRange(from, to) { + var i, rows = []; + for (i = from; i <= to; i++) { + rows.push(i); + } + for (i = to; i < from; i++) { + rows.push(i); + } + return rows; + } + + function getSelectedRows() { + return rangesToRows(_ranges); + } + + function setSelectedRows(rows) { + setSelectedRanges(rowsToRanges(rows)); + } + + function setSelectedRanges(ranges) { + _ranges = ranges; + _self.onSelectedRangesChanged.notify(_ranges); + } + + function getSelectedRanges() { + return _ranges; + } + + function handleActiveCellChange(e, data) { + if (_options.selectActiveRow && data.row != null) { + setSelectedRanges([new Slick.Range(data.row, 0, data.row, _grid.getColumns().length - 1)]); + } + } + + function handleKeyDown(e) { + var activeRow = _grid.getActiveCell(); + if (activeRow && e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey && (e.which == 38 || e.which == 40)) { + var selectedRows = getSelectedRows(); + selectedRows.sort(function (x, y) { + return x - y + }); + + if (!selectedRows.length) { + selectedRows = [activeRow.row]; + } + + var top = selectedRows[0]; + var bottom = selectedRows[selectedRows.length - 1]; + var active; + + if (e.which == 40) { + active = activeRow.row < bottom || top == bottom ? ++bottom : ++top; + } else { + active = activeRow.row < bottom ? --bottom : --top; + } + + if (active >= 0 && active < _grid.getDataLength()) { + _grid.scrollRowIntoView(active); + _ranges = rowsToRanges(getRowsRange(top, bottom)); + setSelectedRanges(_ranges); + } + + e.preventDefault(); + e.stopPropagation(); + } + } + + function handleClick(e) { + var cell = _grid.getCellFromEvent(e); + if (!cell || !_grid.canCellBeActive(cell.row, cell.cell)) { + return false; + } + + if (!_grid.getOptions().multiSelect || ( + !e.ctrlKey && !e.shiftKey && !e.metaKey)) { + return false; + } + + var selection = rangesToRows(_ranges); + var idx = $.inArray(cell.row, selection); + + if (idx === -1 && (e.ctrlKey || e.metaKey)) { + selection.push(cell.row); + _grid.setActiveCell(cell.row, cell.cell); + } else if (idx !== -1 && (e.ctrlKey || e.metaKey)) { + selection = $.grep(selection, function (o, i) { + return (o !== cell.row); + }); + _grid.setActiveCell(cell.row, cell.cell); + } else if (selection.length && e.shiftKey) { + var last = selection.pop(); + var from = Math.min(cell.row, last); + var to = Math.max(cell.row, last); + selection = []; + for (var i = from; i <= to; i++) { + if (i !== last) { + selection.push(i); + } + } + selection.push(last); + _grid.setActiveCell(cell.row, cell.cell); + } + + _ranges = rowsToRanges(selection); + setSelectedRanges(_ranges); + e.stopImmediatePropagation(); + + return true; + } + + $.extend(this, { + "getSelectedRows": getSelectedRows, + "setSelectedRows": setSelectedRows, + + "getSelectedRanges": getSelectedRanges, + "setSelectedRanges": setSelectedRanges, + + "init": init, + "destroy": destroy, + + "onSelectedRangesChanged": new Slick.Event() + }); + } +})(jQuery); \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick-default-theme.css b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick-default-theme.css new file mode 100644 index 00000000..efc74154 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick-default-theme.css @@ -0,0 +1,118 @@ +/* +IMPORTANT: +In order to preserve the uniform grid appearance, all cell styles need to have padding, margin and border sizes. +No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) or user-specified CSS +classes should alter those! +*/ + +.slick-header-columns { + background: url('images/header-columns-bg.gif') repeat-x center bottom; + border-bottom: 1px solid silver; +} + +.slick-header-column { + background: url('images/header-columns-bg.gif') repeat-x center bottom; + border-right: 1px solid silver; +} + +.slick-header-column:hover, .slick-header-column-active { + background: white url('images/header-columns-over-bg.gif') repeat-x center bottom; +} + +.slick-headerrow { + background: #fafafa; +} + +.slick-headerrow-column { + background: #fafafa; + border-bottom: 0; + height: 100%; +} + +.slick-row.ui-state-active { + background: #F5F7D7; +} + +.slick-row { + position: absolute; + background: white; + border: 0px; + line-height: 20px; +} + +.slick-row.selected { + z-index: 10; + background: #DFE8F6; +} + +.slick-cell { + padding-left: 4px; + padding-right: 4px; +} + +.slick-group { + border-bottom: 2px solid silver; +} + +.slick-group-toggle { + width: 9px; + height: 9px; + margin-right: 5px; +} + +.slick-group-toggle.expanded { + background: url(images/collapse.gif) no-repeat center center; +} + +.slick-group-toggle.collapsed { + background: url(images/expand.gif) no-repeat center center; +} + +.slick-group-totals { + color: gray; + background: white; +} + +.slick-cell.selected { + background-color: beige; +} + +.slick-cell.active { + border-color: gray; + border-style: solid; +} + +.slick-sortable-placeholder { + background: silver !important; +} + +.slick-row.odd { + background: #fafafa; +} + +.slick-row.ui-state-active { + background: #F5F7D7; +} + +.slick-row.loading { + opacity: 0.5; + filter: alpha(opacity = 50); +} + +.slick-cell.invalid { + border-color: red; + -moz-animation-duration: 0.2s; + -webkit-animation-duration: 0.2s; + -moz-animation-name: slickgrid-invalid-hilite; + -webkit-animation-name: slickgrid-invalid-hilite; +} + +@-moz-keyframes slickgrid-invalid-hilite { + from { box-shadow: 0 0 6px red; } + to { box-shadow: none; } +} + +@-webkit-keyframes slickgrid-invalid-hilite { + from { box-shadow: 0 0 6px red; } + to { box-shadow: none; } +} \ No newline at end of file diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.core.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.core.js new file mode 100644 index 00000000..2f097b1d --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.core.js @@ -0,0 +1,467 @@ +/*** + * Contains core SlickGrid classes. + * @module Core + * @namespace Slick + */ + +(function ($) { + // register namespace + $.extend(true, window, { + "Slick": { + "Event": Event, + "EventData": EventData, + "EventHandler": EventHandler, + "Range": Range, + "NonDataRow": NonDataItem, + "Group": Group, + "GroupTotals": GroupTotals, + "EditorLock": EditorLock, + + /*** + * A global singleton editor lock. + * @class GlobalEditorLock + * @static + * @constructor + */ + "GlobalEditorLock": new EditorLock() + } + }); + + /*** + * An event object for passing data to event handlers and letting them control propagation. + *

    This is pretty much identical to how W3C and jQuery implement events.

    + * @class EventData + * @constructor + */ + function EventData() { + var isPropagationStopped = false; + var isImmediatePropagationStopped = false; + + /*** + * Stops event from propagating up the DOM tree. + * @method stopPropagation + */ + this.stopPropagation = function () { + isPropagationStopped = true; + }; + + /*** + * Returns whether stopPropagation was called on this event object. + * @method isPropagationStopped + * @return {Boolean} + */ + this.isPropagationStopped = function () { + return isPropagationStopped; + }; + + /*** + * Prevents the rest of the handlers from being executed. + * @method stopImmediatePropagation + */ + this.stopImmediatePropagation = function () { + isImmediatePropagationStopped = true; + }; + + /*** + * Returns whether stopImmediatePropagation was called on this event object.\ + * @method isImmediatePropagationStopped + * @return {Boolean} + */ + this.isImmediatePropagationStopped = function () { + return isImmediatePropagationStopped; + } + } + + /*** + * A simple publisher-subscriber implementation. + * @class Event + * @constructor + */ + function Event() { + var handlers = []; + + /*** + * Adds an event handler to be called when the event is fired. + *

    Event handler will receive two arguments - an EventData and the data + * object the event was fired with.

    + * @method subscribe + * @param fn {Function} Event handler. + */ + this.subscribe = function (fn) { + handlers.push(fn); + }; + + /*** + * Removes an event handler added with subscribe(fn). + * @method unsubscribe + * @param fn {Function} Event handler to be removed. + */ + this.unsubscribe = function (fn) { + for (var i = handlers.length - 1; i >= 0; i--) { + if (handlers[i] === fn) { + handlers.splice(i, 1); + } + } + }; + + /*** + * Fires an event notifying all subscribers. + * @method notify + * @param args {Object} Additional data object to be passed to all handlers. + * @param e {EventData} + * Optional. + * An EventData object to be passed to all handlers. + * For DOM events, an existing W3C/jQuery event object can be passed in. + * @param scope {Object} + * Optional. + * The scope ("this") within which the handler will be executed. + * If not specified, the scope will be set to the Event instance. + */ + this.notify = function (args, e, scope) { + e = e || new EventData(); + scope = scope || this; + + var returnValue; + for (var i = 0; i < handlers.length && !(e.isPropagationStopped() || e.isImmediatePropagationStopped()); i++) { + returnValue = handlers[i].call(scope, e, args); + } + + return returnValue; + }; + } + + function EventHandler() { + var handlers = []; + + this.subscribe = function (event, handler) { + handlers.push({ + event: event, + handler: handler + }); + event.subscribe(handler); + + return this; // allow chaining + }; + + this.unsubscribe = function (event, handler) { + var i = handlers.length; + while (i--) { + if (handlers[i].event === event && + handlers[i].handler === handler) { + handlers.splice(i, 1); + event.unsubscribe(handler); + return; + } + } + + return this; // allow chaining + }; + + this.unsubscribeAll = function () { + var i = handlers.length; + while (i--) { + handlers[i].event.unsubscribe(handlers[i].handler); + } + handlers = []; + + return this; // allow chaining + } + } + + /*** + * A structure containing a range of cells. + * @class Range + * @constructor + * @param fromRow {Integer} Starting row. + * @param fromCell {Integer} Starting cell. + * @param toRow {Integer} Optional. Ending row. Defaults to fromRow. + * @param toCell {Integer} Optional. Ending cell. Defaults to fromCell. + */ + function Range(fromRow, fromCell, toRow, toCell) { + if (toRow === undefined && toCell === undefined) { + toRow = fromRow; + toCell = fromCell; + } + + /*** + * @property fromRow + * @type {Integer} + */ + this.fromRow = Math.min(fromRow, toRow); + + /*** + * @property fromCell + * @type {Integer} + */ + this.fromCell = Math.min(fromCell, toCell); + + /*** + * @property toRow + * @type {Integer} + */ + this.toRow = Math.max(fromRow, toRow); + + /*** + * @property toCell + * @type {Integer} + */ + this.toCell = Math.max(fromCell, toCell); + + /*** + * Returns whether a range represents a single row. + * @method isSingleRow + * @return {Boolean} + */ + this.isSingleRow = function () { + return this.fromRow == this.toRow; + }; + + /*** + * Returns whether a range represents a single cell. + * @method isSingleCell + * @return {Boolean} + */ + this.isSingleCell = function () { + return this.fromRow == this.toRow && this.fromCell == this.toCell; + }; + + /*** + * Returns whether a range contains a given cell. + * @method contains + * @param row {Integer} + * @param cell {Integer} + * @return {Boolean} + */ + this.contains = function (row, cell) { + return row >= this.fromRow && row <= this.toRow && + cell >= this.fromCell && cell <= this.toCell; + }; + + /*** + * Returns a readable representation of a range. + * @method toString + * @return {String} + */ + this.toString = function () { + if (this.isSingleCell()) { + return "(" + this.fromRow + ":" + this.fromCell + ")"; + } + else { + return "(" + this.fromRow + ":" + this.fromCell + " - " + this.toRow + ":" + this.toCell + ")"; + } + } + } + + + /*** + * A base class that all special / non-data rows (like Group and GroupTotals) derive from. + * @class NonDataItem + * @constructor + */ + function NonDataItem() { + this.__nonDataRow = true; + } + + + /*** + * Information about a group of rows. + * @class Group + * @extends Slick.NonDataItem + * @constructor + */ + function Group() { + this.__group = true; + + /** + * Grouping level, starting with 0. + * @property level + * @type {Number} + */ + this.level = 0; + + /*** + * Number of rows in the group. + * @property count + * @type {Integer} + */ + this.count = 0; + + /*** + * Grouping value. + * @property value + * @type {Object} + */ + this.value = null; + + /*** + * Formatted display value of the group. + * @property title + * @type {String} + */ + this.title = null; + + /*** + * Whether a group is collapsed. + * @property collapsed + * @type {Boolean} + */ + this.collapsed = false; + + /*** + * GroupTotals, if any. + * @property totals + * @type {GroupTotals} + */ + this.totals = null; + + /** + * Rows that are part of the group. + * @property rows + * @type {Array} + */ + this.rows = []; + + /** + * Sub-groups that are part of the group. + * @property groups + * @type {Array} + */ + this.groups = null; + + /** + * A unique key used to identify the group. This key can be used in calls to DataView + * collapseGroup() or expandGroup(). + * @property groupingKey + * @type {Object} + */ + this.groupingKey = null; + } + + Group.prototype = new NonDataItem(); + + /*** + * Compares two Group instances. + * @method equals + * @return {Boolean} + * @param group {Group} Group instance to compare to. + */ + Group.prototype.equals = function (group) { + return this.value === group.value && + this.count === group.count && + this.collapsed === group.collapsed && + this.title === group.title; + }; + + /*** + * Information about group totals. + * An instance of GroupTotals will be created for each totals row and passed to the aggregators + * so that they can store arbitrary data in it. That data can later be accessed by group totals + * formatters during the display. + * @class GroupTotals + * @extends Slick.NonDataItem + * @constructor + */ + function GroupTotals() { + this.__groupTotals = true; + + /*** + * Parent Group. + * @param group + * @type {Group} + */ + this.group = null; + + /*** + * Whether the totals have been fully initialized / calculated. + * Will be set to false for lazy-calculated group totals. + * @param initialized + * @type {Boolean} + */ + this.initialized = false; + } + + GroupTotals.prototype = new NonDataItem(); + + /*** + * A locking helper to track the active edit controller and ensure that only a single controller + * can be active at a time. This prevents a whole class of state and validation synchronization + * issues. An edit controller (such as SlickGrid) can query if an active edit is in progress + * and attempt a commit or cancel before proceeding. + * @class EditorLock + * @constructor + */ + function EditorLock() { + var activeEditController = null; + + /*** + * Returns true if a specified edit controller is active (has the edit lock). + * If the parameter is not specified, returns true if any edit controller is active. + * @method isActive + * @param editController {EditController} + * @return {Boolean} + */ + this.isActive = function (editController) { + return (editController ? activeEditController === editController : activeEditController !== null); + }; + + /*** + * Sets the specified edit controller as the active edit controller (acquire edit lock). + * If another edit controller is already active, and exception will be thrown. + * @method activate + * @param editController {EditController} edit controller acquiring the lock + */ + this.activate = function (editController) { + if (editController === activeEditController) { // already activated? + return; + } + if (activeEditController !== null) { + throw "SlickGrid.EditorLock.activate: an editController is still active, can't activate another editController"; + } + if (!editController.commitCurrentEdit) { + throw "SlickGrid.EditorLock.activate: editController must implement .commitCurrentEdit()"; + } + if (!editController.cancelCurrentEdit) { + throw "SlickGrid.EditorLock.activate: editController must implement .cancelCurrentEdit()"; + } + activeEditController = editController; + }; + + /*** + * Unsets the specified edit controller as the active edit controller (release edit lock). + * If the specified edit controller is not the active one, an exception will be thrown. + * @method deactivate + * @param editController {EditController} edit controller releasing the lock + */ + this.deactivate = function (editController) { + if (activeEditController !== editController) { + throw "SlickGrid.EditorLock.deactivate: specified editController is not the currently active one"; + } + activeEditController = null; + }; + + /*** + * Attempts to commit the current edit by calling "commitCurrentEdit" method on the active edit + * controller and returns whether the commit attempt was successful (commit may fail due to validation + * errors, etc.). Edit controller's "commitCurrentEdit" must return true if the commit has succeeded + * and false otherwise. If no edit controller is active, returns true. + * @method commitCurrentEdit + * @return {Boolean} + */ + this.commitCurrentEdit = function () { + return (activeEditController ? activeEditController.commitCurrentEdit() : true); + }; + + /*** + * Attempts to cancel the current edit by calling "cancelCurrentEdit" method on the active edit + * controller and returns whether the edit was successfully cancelled. If no edit controller is + * active, returns true. + * @method cancelCurrentEdit + * @return {Boolean} + */ + this.cancelCurrentEdit = function cancelCurrentEdit() { + return (activeEditController ? activeEditController.cancelCurrentEdit() : true); + }; + } +})(jQuery); + + diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.dataview.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.dataview.js new file mode 100644 index 00000000..f1c1b5e3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.dataview.js @@ -0,0 +1,1126 @@ +(function ($) { + $.extend(true, window, { + Slick: { + Data: { + DataView: DataView, + Aggregators: { + Avg: AvgAggregator, + Min: MinAggregator, + Max: MaxAggregator, + Sum: SumAggregator + } + } + } + }); + + + /*** + * A sample Model implementation. + * Provides a filtered view of the underlying data. + * + * Relies on the data item having an "id" property uniquely identifying it. + */ + function DataView(options) { + var self = this; + + var defaults = { + groupItemMetadataProvider: null, + inlineFilters: false + }; + + + // private + var idProperty = "id"; // property holding a unique row id + var items = []; // data by index + var rows = []; // data by row + var idxById = {}; // indexes by id + var rowsById = null; // rows by id; lazy-calculated + var filter = null; // filter function + var updated = null; // updated item ids + var suspend = false; // suspends the recalculation + var sortAsc = true; + var fastSortField; + var sortComparer; + var refreshHints = {}; + var prevRefreshHints = {}; + var filterArgs; + var filteredItems = []; + var compiledFilter; + var compiledFilterWithCaching; + var filterCache = []; + + // grouping + var groupingInfoDefaults = { + getter: null, + formatter: null, + comparer: function(a, b) { return a.value - b.value; }, + predefinedValues: [], + aggregators: [], + aggregateEmpty: false, + aggregateCollapsed: false, + aggregateChildGroups: false, + collapsed: false, + displayTotalsRow: true, + lazyTotalsCalculation: false + }; + var groupingInfos = []; + var groups = []; + var toggledGroupsByLevel = []; + var groupingDelimiter = ':|:'; + + var pagesize = 0; + var pagenum = 0; + var totalRows = 0; + + // events + var onRowCountChanged = new Slick.Event(); + var onRowsChanged = new Slick.Event(); + var onPagingInfoChanged = new Slick.Event(); + + options = $.extend(true, {}, defaults, options); + + + function beginUpdate() { + suspend = true; + } + + function endUpdate() { + suspend = false; + refresh(); + } + + function setRefreshHints(hints) { + refreshHints = hints; + } + + function setFilterArgs(args) { + filterArgs = args; + } + + function updateIdxById(startingIndex) { + startingIndex = startingIndex || 0; + var id; + for (var i = startingIndex, l = items.length; i < l; i++) { + id = items[i][idProperty]; + if (id === undefined) { + throw "Each data element must implement a unique 'id' property"; + } + idxById[id] = i; + } + } + + function ensureIdUniqueness() { + var id; + for (var i = 0, l = items.length; i < l; i++) { + id = items[i][idProperty]; + if (id === undefined || idxById[id] !== i) { + throw "Each data element must implement a unique 'id' property"; + } + } + } + + function getItems() { + return items; + } + + function setItems(data, objectIdProperty) { + if (objectIdProperty !== undefined) { + idProperty = objectIdProperty; + } + items = filteredItems = data; + idxById = {}; + updateIdxById(); + ensureIdUniqueness(); + refresh(); + } + + function setPagingOptions(args) { + if (args.pageSize != undefined) { + pagesize = args.pageSize; + pagenum = pagesize ? Math.min(pagenum, Math.max(0, Math.ceil(totalRows / pagesize) - 1)) : 0; + } + + if (args.pageNum != undefined) { + pagenum = Math.min(args.pageNum, Math.max(0, Math.ceil(totalRows / pagesize) - 1)); + } + + onPagingInfoChanged.notify(getPagingInfo(), null, self); + + refresh(); + } + + function getPagingInfo() { + var totalPages = pagesize ? Math.max(1, Math.ceil(totalRows / pagesize)) : 1; + return {pageSize: pagesize, pageNum: pagenum, totalRows: totalRows, totalPages: totalPages}; + } + + function sort(comparer, ascending) { + sortAsc = ascending; + sortComparer = comparer; + fastSortField = null; + if (ascending === false) { + items.reverse(); + } + items.sort(comparer); + if (ascending === false) { + items.reverse(); + } + idxById = {}; + updateIdxById(); + refresh(); + } + + /*** + * Provides a workaround for the extremely slow sorting in IE. + * Does a [lexicographic] sort on a give column by temporarily overriding Object.prototype.toString + * to return the value of that field and then doing a native Array.sort(). + */ + function fastSort(field, ascending) { + sortAsc = ascending; + fastSortField = field; + sortComparer = null; + var oldToString = Object.prototype.toString; + Object.prototype.toString = (typeof field == "function") ? field : function () { + return this[field] + }; + // an extra reversal for descending sort keeps the sort stable + // (assuming a stable native sort implementation, which isn't true in some cases) + if (ascending === false) { + items.reverse(); + } + items.sort(); + Object.prototype.toString = oldToString; + if (ascending === false) { + items.reverse(); + } + idxById = {}; + updateIdxById(); + refresh(); + } + + function reSort() { + if (sortComparer) { + sort(sortComparer, sortAsc); + } else if (fastSortField) { + fastSort(fastSortField, sortAsc); + } + } + + function setFilter(filterFn) { + filter = filterFn; + if (options.inlineFilters) { + compiledFilter = compileFilter(); + compiledFilterWithCaching = compileFilterWithCaching(); + } + refresh(); + } + + function getGrouping() { + return groupingInfos; + } + + function setGrouping(groupingInfo) { + if (!options.groupItemMetadataProvider) { + options.groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider(); + } + + groups = []; + toggledGroupsByLevel = []; + groupingInfo = groupingInfo || []; + groupingInfos = (groupingInfo instanceof Array) ? groupingInfo : [groupingInfo]; + + for (var i = 0; i < groupingInfos.length; i++) { + var gi = groupingInfos[i] = $.extend(true, {}, groupingInfoDefaults, groupingInfos[i]); + gi.getterIsAFn = typeof gi.getter === "function"; + + // pre-compile accumulator loops + gi.compiledAccumulators = []; + var idx = gi.aggregators.length; + while (idx--) { + gi.compiledAccumulators[idx] = compileAccumulatorLoop(gi.aggregators[idx]); + } + + toggledGroupsByLevel[i] = {}; + } + + refresh(); + } + + /** + * @deprecated Please use {@link setGrouping}. + */ + function groupBy(valueGetter, valueFormatter, sortComparer) { + if (valueGetter == null) { + setGrouping([]); + return; + } + + setGrouping({ + getter: valueGetter, + formatter: valueFormatter, + comparer: sortComparer + }); + } + + /** + * @deprecated Please use {@link setGrouping}. + */ + function setAggregators(groupAggregators, includeCollapsed) { + if (!groupingInfos.length) { + throw new Error("At least one grouping must be specified before calling setAggregators()."); + } + + groupingInfos[0].aggregators = groupAggregators; + groupingInfos[0].aggregateCollapsed = includeCollapsed; + + setGrouping(groupingInfos); + } + + function getItemByIdx(i) { + return items[i]; + } + + function getIdxById(id) { + return idxById[id]; + } + + function ensureRowsByIdCache() { + if (!rowsById) { + rowsById = {}; + for (var i = 0, l = rows.length; i < l; i++) { + rowsById[rows[i][idProperty]] = i; + } + } + } + + function getRowById(id) { + ensureRowsByIdCache(); + return rowsById[id]; + } + + function getItemById(id) { + return items[idxById[id]]; + } + + function mapIdsToRows(idArray) { + var rows = []; + ensureRowsByIdCache(); + for (var i = 0, l = idArray.length; i < l; i++) { + var row = rowsById[idArray[i]]; + if (row != null) { + rows[rows.length] = row; + } + } + return rows; + } + + function mapRowsToIds(rowArray) { + var ids = []; + for (var i = 0, l = rowArray.length; i < l; i++) { + if (rowArray[i] < rows.length) { + ids[ids.length] = rows[rowArray[i]][idProperty]; + } + } + return ids; + } + + function updateItem(id, item) { + if (idxById[id] === undefined || id !== item[idProperty]) { + throw "Invalid or non-matching id"; + } + items[idxById[id]] = item; + if (!updated) { + updated = {}; + } + updated[id] = true; + refresh(); + } + + function insertItem(insertBefore, item) { + items.splice(insertBefore, 0, item); + updateIdxById(insertBefore); + refresh(); + } + + function addItem(item) { + items.push(item); + updateIdxById(items.length - 1); + refresh(); + } + + function deleteItem(id) { + var idx = idxById[id]; + if (idx === undefined) { + throw "Invalid id"; + } + delete idxById[id]; + items.splice(idx, 1); + updateIdxById(idx); + refresh(); + } + + function getLength() { + return rows.length; + } + + function getItem(i) { + var item = rows[i]; + + // if this is a group row, make sure totals are calculated and update the title + if (item && item.__group && item.totals && !item.totals.initialized) { + var gi = groupingInfos[item.level]; + if (!gi.displayTotalsRow) { + calculateTotals(item.totals); + item.title = gi.formatter ? gi.formatter(item) : item.value; + } + } + // if this is a totals row, make sure it's calculated + else if (item && item.__groupTotals && !item.initialized) { + calculateTotals(item); + } + + return item; + } + + function getItemMetadata(i) { + var item = rows[i]; + if (item === undefined) { + return null; + } + + // overrides for grouping rows + if (item.__group) { + return options.groupItemMetadataProvider.getGroupRowMetadata(item); + } + + // overrides for totals rows + if (item.__groupTotals) { + return options.groupItemMetadataProvider.getTotalsRowMetadata(item); + } + + return null; + } + + function expandCollapseAllGroups(level, collapse) { + if (level == null) { + for (var i = 0; i < groupingInfos.length; i++) { + toggledGroupsByLevel[i] = {}; + groupingInfos[i].collapsed = collapse; + } + } else { + toggledGroupsByLevel[level] = {}; + groupingInfos[level].collapsed = collapse; + } + refresh(); + } + + /** + * @param level {Number} Optional level to collapse. If not specified, applies to all levels. + */ + function collapseAllGroups(level) { + expandCollapseAllGroups(level, true); + } + + /** + * @param level {Number} Optional level to expand. If not specified, applies to all levels. + */ + function expandAllGroups(level) { + expandCollapseAllGroups(level, false); + } + + function expandCollapseGroup(level, groupingKey, collapse) { + toggledGroupsByLevel[level][groupingKey] = groupingInfos[level].collapsed ^ collapse; + refresh(); + } + + /** + * @param varArgs Either a Slick.Group's "groupingKey" property, or a + * variable argument list of grouping values denoting a unique path to the row. For + * example, calling collapseGroup('high', '10%') will collapse the '10%' subgroup of + * the 'high' group. + */ + function collapseGroup(varArgs) { + var args = Array.prototype.slice.call(arguments); + var arg0 = args[0]; + if (args.length == 1 && arg0.indexOf(groupingDelimiter) != -1) { + expandCollapseGroup(arg0.split(groupingDelimiter).length - 1, arg0, true); + } else { + expandCollapseGroup(args.length - 1, args.join(groupingDelimiter), true); + } + } + + /** + * @param varArgs Either a Slick.Group's "groupingKey" property, or a + * variable argument list of grouping values denoting a unique path to the row. For + * example, calling expandGroup('high', '10%') will expand the '10%' subgroup of + * the 'high' group. + */ + function expandGroup(varArgs) { + var args = Array.prototype.slice.call(arguments); + var arg0 = args[0]; + if (args.length == 1 && arg0.indexOf(groupingDelimiter) != -1) { + expandCollapseGroup(arg0.split(groupingDelimiter).length - 1, arg0, false); + } else { + expandCollapseGroup(args.length - 1, args.join(groupingDelimiter), false); + } + } + + function getGroups() { + return groups; + } + + function extractGroups(rows, parentGroup) { + var group; + var val; + var groups = []; + var groupsByVal = {}; + var r; + var level = parentGroup ? parentGroup.level + 1 : 0; + var gi = groupingInfos[level]; + + for (var i = 0, l = gi.predefinedValues.length; i < l; i++) { + val = gi.predefinedValues[i]; + group = groupsByVal[val]; + if (!group) { + group = new Slick.Group(); + group.value = val; + group.level = level; + group.groupingKey = (parentGroup ? parentGroup.groupingKey + groupingDelimiter : '') + val; + groups[groups.length] = group; + groupsByVal[val] = group; + } + } + + for (var i = 0, l = rows.length; i < l; i++) { + r = rows[i]; + val = gi.getterIsAFn ? gi.getter(r) : r[gi.getter]; + group = groupsByVal[val]; + if (!group) { + group = new Slick.Group(); + group.value = val; + group.level = level; + group.groupingKey = (parentGroup ? parentGroup.groupingKey + groupingDelimiter : '') + val; + groups[groups.length] = group; + groupsByVal[val] = group; + } + + group.rows[group.count++] = r; + } + + if (level < groupingInfos.length - 1) { + for (var i = 0; i < groups.length; i++) { + group = groups[i]; + group.groups = extractGroups(group.rows, group); + } + } + + groups.sort(groupingInfos[level].comparer); + + return groups; + } + + function calculateTotals(totals) { + var group = totals.group; + var gi = groupingInfos[group.level]; + var isLeafLevel = (group.level == groupingInfos.length); + var agg, idx = gi.aggregators.length; + + if (!isLeafLevel && gi.aggregateChildGroups) { + // make sure all the subgroups are calculated + var i = group.groups.length; + while (i--) { + if (!group.groups[i].initialized) { + calculateTotals(group.groups[i]); + } + } + } + + while (idx--) { + agg = gi.aggregators[idx]; + agg.init(); + if (!isLeafLevel && gi.aggregateChildGroups) { + gi.compiledAccumulators[idx].call(agg, group.groups); + } else { + gi.compiledAccumulators[idx].call(agg, group.rows); + } + agg.storeResult(totals); + } + totals.initialized = true; + } + + function addGroupTotals(group) { + var gi = groupingInfos[group.level]; + var totals = new Slick.GroupTotals(); + totals.group = group; + group.totals = totals; + if (!gi.lazyTotalsCalculation) { + calculateTotals(totals); + } + } + + function addTotals(groups, level) { + level = level || 0; + var gi = groupingInfos[level]; + var groupCollapsed = gi.collapsed; + var toggledGroups = toggledGroupsByLevel[level]; + var idx = groups.length, g; + while (idx--) { + g = groups[idx]; + + if (g.collapsed && !gi.aggregateCollapsed) { + continue; + } + + // Do a depth-first aggregation so that parent group aggregators can access subgroup totals. + if (g.groups) { + addTotals(g.groups, level + 1); + } + + if (gi.aggregators.length && ( + gi.aggregateEmpty || g.rows.length || (g.groups && g.groups.length))) { + addGroupTotals(g); + } + + g.collapsed = groupCollapsed ^ toggledGroups[g.groupingKey]; + g.title = gi.formatter ? gi.formatter(g) : g.value; + } + } + + function flattenGroupedRows(groups, level) { + level = level || 0; + var gi = groupingInfos[level]; + var groupedRows = [], rows, gl = 0, g; + for (var i = 0, l = groups.length; i < l; i++) { + g = groups[i]; + groupedRows[gl++] = g; + + if (!g.collapsed) { + rows = g.groups ? flattenGroupedRows(g.groups, level + 1) : g.rows; + for (var j = 0, jj = rows.length; j < jj; j++) { + groupedRows[gl++] = rows[j]; + } + } + + if (g.totals && gi.displayTotalsRow && (!g.collapsed || gi.aggregateCollapsed)) { + groupedRows[gl++] = g.totals; + } + } + return groupedRows; + } + + function getFunctionInfo(fn) { + var fnRegex = /^function[^(]*\(([^)]*)\)\s*{([\s\S]*)}$/; + var matches = fn.toString().match(fnRegex); + return { + params: matches[1].split(","), + body: matches[2] + }; + } + + function compileAccumulatorLoop(aggregator) { + var accumulatorInfo = getFunctionInfo(aggregator.accumulate); + var fn = new Function( + "_items", + "for (var " + accumulatorInfo.params[0] + ", _i=0, _il=_items.length; _i<_il; _i++) {" + + accumulatorInfo.params[0] + " = _items[_i]; " + + accumulatorInfo.body + + "}" + ); + fn.displayName = fn.name = "compiledAccumulatorLoop"; + return fn; + } + + function compileFilter() { + var filterInfo = getFunctionInfo(filter); + + var filterBody = filterInfo.body + .replace(/return false\s*([;}]|$)/gi, "{ continue _coreloop; }$1") + .replace(/return true\s*([;}]|$)/gi, "{ _retval[_idx++] = $item$; continue _coreloop; }$1") + .replace(/return ([^;}]+?)\s*([;}]|$)/gi, + "{ if ($1) { _retval[_idx++] = $item$; }; continue _coreloop; }$2"); + + // This preserves the function template code after JS compression, + // so that replace() commands still work as expected. + var tpl = [ + //"function(_items, _args) { ", + "var _retval = [], _idx = 0; ", + "var $item$, $args$ = _args; ", + "_coreloop: ", + "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ", + "$item$ = _items[_i]; ", + "$filter$; ", + "} ", + "return _retval; " + //"}" + ].join(""); + tpl = tpl.replace(/\$filter\$/gi, filterBody); + tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]); + tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]); + + var fn = new Function("_items,_args", tpl); + fn.displayName = fn.name = "compiledFilter"; + return fn; + } + + function compileFilterWithCaching() { + var filterInfo = getFunctionInfo(filter); + + var filterBody = filterInfo.body + .replace(/return false\s*([;}]|$)/gi, "{ continue _coreloop; }$1") + .replace(/return true\s*([;}]|$)/gi, "{ _cache[_i] = true;_retval[_idx++] = $item$; continue _coreloop; }$1") + .replace(/return ([^;}]+?)\s*([;}]|$)/gi, + "{ if ((_cache[_i] = $1)) { _retval[_idx++] = $item$; }; continue _coreloop; }$2"); + + // This preserves the function template code after JS compression, + // so that replace() commands still work as expected. + var tpl = [ + //"function(_items, _args, _cache) { ", + "var _retval = [], _idx = 0; ", + "var $item$, $args$ = _args; ", + "_coreloop: ", + "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ", + "$item$ = _items[_i]; ", + "if (_cache[_i]) { ", + "_retval[_idx++] = $item$; ", + "continue _coreloop; ", + "} ", + "$filter$; ", + "} ", + "return _retval; " + //"}" + ].join(""); + tpl = tpl.replace(/\$filter\$/gi, filterBody); + tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]); + tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]); + + var fn = new Function("_items,_args,_cache", tpl); + fn.displayName = fn.name = "compiledFilterWithCaching"; + return fn; + } + + function uncompiledFilter(items, args) { + var retval = [], idx = 0; + + for (var i = 0, ii = items.length; i < ii; i++) { + if (filter(items[i], args)) { + retval[idx++] = items[i]; + } + } + + return retval; + } + + function uncompiledFilterWithCaching(items, args, cache) { + var retval = [], idx = 0, item; + + for (var i = 0, ii = items.length; i < ii; i++) { + item = items[i]; + if (cache[i]) { + retval[idx++] = item; + } else if (filter(item, args)) { + retval[idx++] = item; + cache[i] = true; + } + } + + return retval; + } + + function getFilteredAndPagedItems(items) { + if (filter) { + var batchFilter = options.inlineFilters ? compiledFilter : uncompiledFilter; + var batchFilterWithCaching = options.inlineFilters ? compiledFilterWithCaching : uncompiledFilterWithCaching; + + if (refreshHints.isFilterNarrowing) { + filteredItems = batchFilter(filteredItems, filterArgs); + } else if (refreshHints.isFilterExpanding) { + filteredItems = batchFilterWithCaching(items, filterArgs, filterCache); + } else if (!refreshHints.isFilterUnchanged) { + filteredItems = batchFilter(items, filterArgs); + } + } else { + // special case: if not filtering and not paging, the resulting + // rows collection needs to be a copy so that changes due to sort + // can be caught + filteredItems = pagesize ? items : items.concat(); + } + + // get the current page + var paged; + if (pagesize) { + if (filteredItems.length < pagenum * pagesize) { + pagenum = Math.floor(filteredItems.length / pagesize); + } + paged = filteredItems.slice(pagesize * pagenum, pagesize * pagenum + pagesize); + } else { + paged = filteredItems; + } + + return {totalRows: filteredItems.length, rows: paged}; + } + + function getRowDiffs(rows, newRows) { + var item, r, eitherIsNonData, diff = []; + var from = 0, to = newRows.length; + + if (refreshHints && refreshHints.ignoreDiffsBefore) { + from = Math.max(0, + Math.min(newRows.length, refreshHints.ignoreDiffsBefore)); + } + + if (refreshHints && refreshHints.ignoreDiffsAfter) { + to = Math.min(newRows.length, + Math.max(0, refreshHints.ignoreDiffsAfter)); + } + + for (var i = from, rl = rows.length; i < to; i++) { + if (i >= rl) { + diff[diff.length] = i; + } else { + item = newRows[i]; + r = rows[i]; + + if ((groupingInfos.length && (eitherIsNonData = (item.__nonDataRow) || (r.__nonDataRow)) && + item.__group !== r.__group || + item.__group && !item.equals(r)) + || (eitherIsNonData && + // no good way to compare totals since they are arbitrary DTOs + // deep object comparison is pretty expensive + // always considering them 'dirty' seems easier for the time being + (item.__groupTotals || r.__groupTotals)) + || item[idProperty] != r[idProperty] + || (updated && updated[item[idProperty]]) + ) { + diff[diff.length] = i; + } + } + } + return diff; + } + + function recalc(_items) { + rowsById = null; + + if (refreshHints.isFilterNarrowing != prevRefreshHints.isFilterNarrowing || + refreshHints.isFilterExpanding != prevRefreshHints.isFilterExpanding) { + filterCache = []; + } + + var filteredItems = getFilteredAndPagedItems(_items); + totalRows = filteredItems.totalRows; + var newRows = filteredItems.rows; + + groups = []; + if (groupingInfos.length) { + groups = extractGroups(newRows); + if (groups.length) { + addTotals(groups); + newRows = flattenGroupedRows(groups); + } + } + + var diff = getRowDiffs(rows, newRows); + + rows = newRows; + + return diff; + } + + function refresh() { + if (suspend) { + return; + } + + var countBefore = rows.length; + var totalRowsBefore = totalRows; + + var diff = recalc(items, filter); // pass as direct refs to avoid closure perf hit + + // if the current page is no longer valid, go to last page and recalc + // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized + if (pagesize && totalRows < pagenum * pagesize) { + pagenum = Math.max(0, Math.ceil(totalRows / pagesize) - 1); + diff = recalc(items, filter); + } + + updated = null; + prevRefreshHints = refreshHints; + refreshHints = {}; + + if (totalRowsBefore != totalRows) { + onPagingInfoChanged.notify(getPagingInfo(), null, self); + } + if (countBefore != rows.length) { + onRowCountChanged.notify({previous: countBefore, current: rows.length}, null, self); + } + if (diff.length > 0) { + onRowsChanged.notify({rows: diff}, null, self); + } + } + + /*** + * Wires the grid and the DataView together to keep row selection tied to item ids. + * This is useful since, without it, the grid only knows about rows, so if the items + * move around, the same rows stay selected instead of the selection moving along + * with the items. + * + * NOTE: This doesn't work with cell selection model. + * + * @param grid {Slick.Grid} The grid to sync selection with. + * @param preserveHidden {Boolean} Whether to keep selected items that go out of the + * view due to them getting filtered out. + * @param preserveHiddenOnSelectionChange {Boolean} Whether to keep selected items + * that are currently out of the view (see preserveHidden) as selected when selection + * changes. + * @return {Slick.Event} An event that notifies when an internal list of selected row ids + * changes. This is useful since, in combination with the above two options, it allows + * access to the full list selected row ids, and not just the ones visible to the grid. + * @method syncGridSelection + */ + function syncGridSelection(grid, preserveHidden, preserveHiddenOnSelectionChange) { + var self = this; + var inHandler; + var selectedRowIds = self.mapRowsToIds(grid.getSelectedRows()); + var onSelectedRowIdsChanged = new Slick.Event(); + + function setSelectedRowIds(rowIds) { + if (selectedRowIds.join(",") == rowIds.join(",")) { + return; + } + + selectedRowIds = rowIds; + + onSelectedRowIdsChanged.notify({ + "grid": grid, + "ids": selectedRowIds + }, new Slick.EventData(), self); + } + + function update() { + if (selectedRowIds.length > 0) { + inHandler = true; + var selectedRows = self.mapIdsToRows(selectedRowIds); + if (!preserveHidden) { + setSelectedRowIds(self.mapRowsToIds(selectedRows)); + } + grid.setSelectedRows(selectedRows); + inHandler = false; + } + } + + grid.onSelectedRowsChanged.subscribe(function(e, args) { + if (inHandler) { return; } + var newSelectedRowIds = self.mapRowsToIds(grid.getSelectedRows()); + if (!preserveHiddenOnSelectionChange || !grid.getOptions().multiSelect) { + setSelectedRowIds(newSelectedRowIds); + } else { + // keep the ones that are hidden + var existing = $.grep(selectedRowIds, function(id) { return self.getRowById(id) === undefined; }); + // add the newly selected ones + setSelectedRowIds(existing.concat(newSelectedRowIds)); + } + }); + + this.onRowsChanged.subscribe(update); + + this.onRowCountChanged.subscribe(update); + + return onSelectedRowIdsChanged; + } + + function syncGridCellCssStyles(grid, key) { + var hashById; + var inHandler; + + // since this method can be called after the cell styles have been set, + // get the existing ones right away + storeCellCssStyles(grid.getCellCssStyles(key)); + + function storeCellCssStyles(hash) { + hashById = {}; + for (var row in hash) { + var id = rows[row][idProperty]; + hashById[id] = hash[row]; + } + } + + function update() { + if (hashById) { + inHandler = true; + ensureRowsByIdCache(); + var newHash = {}; + for (var id in hashById) { + var row = rowsById[id]; + if (row != undefined) { + newHash[row] = hashById[id]; + } + } + grid.setCellCssStyles(key, newHash); + inHandler = false; + } + } + + grid.onCellCssStylesChanged.subscribe(function(e, args) { + if (inHandler) { return; } + if (key != args.key) { return; } + if (args.hash) { + storeCellCssStyles(args.hash); + } + }); + + this.onRowsChanged.subscribe(update); + + this.onRowCountChanged.subscribe(update); + } + + $.extend(this, { + // methods + "beginUpdate": beginUpdate, + "endUpdate": endUpdate, + "setPagingOptions": setPagingOptions, + "getPagingInfo": getPagingInfo, + "getItems": getItems, + "setItems": setItems, + "setFilter": setFilter, + "sort": sort, + "fastSort": fastSort, + "reSort": reSort, + "setGrouping": setGrouping, + "getGrouping": getGrouping, + "groupBy": groupBy, + "setAggregators": setAggregators, + "collapseAllGroups": collapseAllGroups, + "expandAllGroups": expandAllGroups, + "collapseGroup": collapseGroup, + "expandGroup": expandGroup, + "getGroups": getGroups, + "getIdxById": getIdxById, + "getRowById": getRowById, + "getItemById": getItemById, + "getItemByIdx": getItemByIdx, + "mapRowsToIds": mapRowsToIds, + "mapIdsToRows": mapIdsToRows, + "setRefreshHints": setRefreshHints, + "setFilterArgs": setFilterArgs, + "refresh": refresh, + "updateItem": updateItem, + "insertItem": insertItem, + "addItem": addItem, + "deleteItem": deleteItem, + "syncGridSelection": syncGridSelection, + "syncGridCellCssStyles": syncGridCellCssStyles, + + // data provider methods + "getLength": getLength, + "getItem": getItem, + "getItemMetadata": getItemMetadata, + + // events + "onRowCountChanged": onRowCountChanged, + "onRowsChanged": onRowsChanged, + "onPagingInfoChanged": onPagingInfoChanged + }); + } + + function AvgAggregator(field) { + this.field_ = field; + + this.init = function () { + this.count_ = 0; + this.nonNullCount_ = 0; + this.sum_ = 0; + }; + + this.accumulate = function (item) { + var val = item[this.field_]; + this.count_++; + if (val != null && val !== "" && val !== NaN) { + this.nonNullCount_++; + this.sum_ += parseFloat(val); + } + }; + + this.storeResult = function (groupTotals) { + if (!groupTotals.avg) { + groupTotals.avg = {}; + } + if (this.nonNullCount_ != 0) { + groupTotals.avg[this.field_] = this.sum_ / this.nonNullCount_; + } + }; + } + + function MinAggregator(field) { + this.field_ = field; + + this.init = function () { + this.min_ = null; + }; + + this.accumulate = function (item) { + var val = item[this.field_]; + if (val != null && val !== "" && val !== NaN) { + if (this.min_ == null || val < this.min_) { + this.min_ = val; + } + } + }; + + this.storeResult = function (groupTotals) { + if (!groupTotals.min) { + groupTotals.min = {}; + } + groupTotals.min[this.field_] = this.min_; + } + } + + function MaxAggregator(field) { + this.field_ = field; + + this.init = function () { + this.max_ = null; + }; + + this.accumulate = function (item) { + var val = item[this.field_]; + if (val != null && val !== "" && val !== NaN) { + if (this.max_ == null || val > this.max_) { + this.max_ = val; + } + } + }; + + this.storeResult = function (groupTotals) { + if (!groupTotals.max) { + groupTotals.max = {}; + } + groupTotals.max[this.field_] = this.max_; + } + } + + function SumAggregator(field) { + this.field_ = field; + + this.init = function () { + this.sum_ = null; + }; + + this.accumulate = function (item) { + var val = item[this.field_]; + if (val != null && val !== "" && val !== NaN) { + this.sum_ += parseFloat(val); + } + }; + + this.storeResult = function (groupTotals) { + if (!groupTotals.sum) { + groupTotals.sum = {}; + } + groupTotals.sum[this.field_] = this.sum_; + } + } + + // TODO: add more built-in aggregators + // TODO: merge common aggregators in one to prevent needles iterating + +})(jQuery); diff --git a/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.editors.js b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.editors.js new file mode 100644 index 00000000..04b20d2f --- /dev/null +++ b/venv/lib/python2.7/site-packages/ckanext/reclineview/theme/public/vendor/slickgrid/2.2/slick.editors.js @@ -0,0 +1,512 @@ +/*** + * Contains basic SlickGrid editors. + * @module Editors + * @namespace Slick + */ + +(function ($) { + // register namespace + $.extend(true, window, { + "Slick": { + "Editors": { + "Text": TextEditor, + "Integer": IntegerEditor, + "Date": DateEditor, + "YesNoSelect": YesNoSelectEditor, + "Checkbox": CheckboxEditor, + "PercentComplete": PercentCompleteEditor, + "LongText": LongTextEditor + } + } + }); + + function TextEditor(args) { + var $input; + var defaultValue; + var scope = this; + + this.init = function () { + $input = $("") + .appendTo(args.container) + .bind("keydown.nav", function (e) { + if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { + e.stopImmediatePropagation(); + } + }) + .focus() + .select(); + }; + + this.destroy = function () { + $input.remove(); + }; + + this.focus = function () { + $input.focus(); + }; + + this.getValue = function () { + return $input.val(); + }; + + this.setValue = function (val) { + $input.val(val); + }; + + this.loadValue = function (item) { + defaultValue = item[args.column.field] || ""; + $input.val(defaultValue); + $input[0].defaultValue = defaultValue; + $input.select(); + }; + + this.serializeValue = function () { + return $input.val(); + }; + + this.applyValue = function (item, state) { + item[args.column.field] = state; + }; + + this.isValueChanged = function () { + return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); + }; + + this.validate = function () { + if (args.column.validator) { + var validationResults = args.column.validator($input.val()); + if (!validationResults.valid) { + return validationResults; + } + } + + return { + valid: true, + msg: null + }; + }; + + this.init(); + } + + function IntegerEditor(args) { + var $input; + var defaultValue; + var scope = this; + + this.init = function () { + $input = $(""); + + $input.bind("keydown.nav", function (e) { + if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) { + e.stopImmediatePropagation(); + } + }); + + $input.appendTo(args.container); + $input.focus().select(); + }; + + this.destroy = function () { + $input.remove(); + }; + + this.focus = function () { + $input.focus(); + }; + + this.loadValue = function (item) { + defaultValue = item[args.column.field]; + $input.val(defaultValue); + $input[0].defaultValue = defaultValue; + $input.select(); + }; + + this.serializeValue = function () { + return parseInt($input.val(), 10) || 0; + }; + + this.applyValue = function (item, state) { + item[args.column.field] = state; + }; + + this.isValueChanged = function () { + return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); + }; + + this.validate = function () { + if (isNaN($input.val())) { + return { + valid: false, + msg: "Please enter a valid integer" + }; + } + + return { + valid: true, + msg: null + }; + }; + + this.init(); + } + + function DateEditor(args) { + var $input; + var defaultValue; + var scope = this; + var calendarOpen = false; + + this.init = function () { + $input = $(""); + $input.appendTo(args.container); + $input.focus().select(); + $input.datepicker({ + showOn: "button", + buttonImageOnly: true, + buttonImage: "../images/calendar.gif", + beforeShow: function () { + calendarOpen = true + }, + onClose: function () { + calendarOpen = false + } + }); + $input.width($input.width() - 18); + }; + + this.destroy = function () { + $.datepicker.dpDiv.stop(true, true); + $input.datepicker("hide"); + $input.datepicker("destroy"); + $input.remove(); + }; + + this.show = function () { + if (calendarOpen) { + $.datepicker.dpDiv.stop(true, true).show(); + } + }; + + this.hide = function () { + if (calendarOpen) { + $.datepicker.dpDiv.stop(true, true).hide(); + } + }; + + this.position = function (position) { + if (!calendarOpen) { + return; + } + $.datepicker.dpDiv + .css("top", position.top + 30) + .css("left", position.left); + }; + + this.focus = function () { + $input.focus(); + }; + + this.loadValue = function (item) { + defaultValue = item[args.column.field]; + $input.val(defaultValue); + $input[0].defaultValue = defaultValue; + $input.select(); + }; + + this.serializeValue = function () { + return $input.val(); + }; + + this.applyValue = function (item, state) { + item[args.column.field] = state; + }; + + this.isValueChanged = function () { + return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue); + }; + + this.validate = function () { + return { + valid: true, + msg: null + }; + }; + + this.init(); + } + + function YesNoSelectEditor(args) { + var $select; + var defaultValue; + var scope = this; + + this.init = function () { + $select = $(""); + $select.appendTo(args.container); + $select.focus(); + }; + + this.destroy = function () { + $select.remove(); + }; + + this.focus = function () { + $select.focus(); + }; + + this.loadValue = function (item) { + $select.val((defaultValue = item[args.column.field]) ? "yes" : "no"); + $select.select(); + }; + + this.serializeValue = function () { + return ($select.val() == "yes"); + }; + + this.applyValue = function (item, state) { + item[args.column.field] = state; + }; + + this.isValueChanged = function () { + return ($select.val() != defaultValue); + }; + + this.validate = function () { + return { + valid: true, + msg: null + }; + }; + + this.init(); + } + + function CheckboxEditor(args) { + var $select; + var defaultValue; + var scope = this; + + this.init = function () { + $select = $(""); + $select.appendTo(args.container); + $select.focus(); + }; + + this.destroy = function () { + $select.remove(); + }; + + this.focus = function () { + $select.focus(); + }; + + this.loadValue = function (item) { + defaultValue = !!item[args.column.field]; + if (defaultValue) { + $select.prop('checked', true); + } else { + $select.prop('checked', false); + } + }; + + this.serializeValue = function () { + return $select.prop('checked'); + }; + + this.applyValue = function (item, state) { + item[args.column.field] = state; + }; + + this.isValueChanged = function () { + return (this.serializeValue() !== defaultValue); + }; + + this.validate = function () { + return { + valid: true, + msg: null + }; + }; + + this.init(); + } + + function PercentCompleteEditor(args) { + var $input, $picker; + var defaultValue; + var scope = this; + + this.init = function () { + $input = $(""); + $input.width($(args.container).innerWidth() - 25); + $input.appendTo(args.container); + + $picker = $("

    ").appendTo(args.container); + $picker.append("
    "); + + $picker.find(".editor-percentcomplete-buttons").append("

    "); + + $input.focus().select(); + + $picker.find(".editor-percentcomplete-slider").slider({ + orientation: "vertical", + range: "min", + value: defaultValue, + slide: function (event, ui) { + $input.val(ui.value) + } + }); + + $picker.find(".editor-percentcomplete-buttons button").bind("click", function (e) { + $input.val($(this).attr("val")); + $picker.find(".editor-percentcomplete-slider").slider("value", $(this).attr("val")); + }) + }; + + this.destroy = function () { + $input.remove(); + $picker.remove(); + }; + + this.focus = function () { + $input.focus(); + }; + + this.loadValue = function (item) { + $input.val(defaultValue = item[args.column.field]); + $input.select(); + }; + + this.serializeValue = function () { + return parseInt($input.val(), 10) || 0; + }; + + this.applyValue = function (item, state) { + item[args.column.field] = state; + }; + + this.isValueChanged = function () { + return (!($input.val() == "" && defaultValue == null)) && ((parseInt($input.val(), 10) || 0) != defaultValue); + }; + + this.validate = function () { + if (isNaN(parseInt($input.val(), 10))) { + return { + valid: false, + msg: "Please enter a valid positive number" + }; + } + + return { + valid: true, + msg: null + }; + }; + + this.init(); + } + + /* + * An example of a "detached" editor. + * The UI is added onto document BODY and .position(), .show() and .hide() are implemented. + * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter. + */ + function LongTextEditor(args) { + var $input, $wrapper; + var defaultValue; + var scope = this; + + this.init = function () { + var $container = $("body"); + + $wrapper = $("
    ") + .appendTo($container); + + $input = $("",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
    ",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0gTe~DWM4fkj^!0 literal 0 HcmV?d00001 diff --git a/venv/lib/python2.7/site-packages/werkzeug/debug/shared/more.png b/venv/lib/python2.7/site-packages/werkzeug/debug/shared/more.png new file mode 100644 index 0000000000000000000000000000000000000000..804fa226fe3ed9e6cc2bd044a848f33a2d7b4e4f GIT binary patch literal 200 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhjKx9jP7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=fm(z?n2}-D90{Nxdx@v7EBg&5DRB)3hmHb?jr_0{K>_1cC{J-%1r lr(<|}#G9!1a#KtW>0AF44oJ8ZkqR`E!PC{xWt~$(698mrJ|X}B literal 0 HcmV?d00001 diff --git a/venv/lib/python2.7/site-packages/werkzeug/debug/shared/source.png b/venv/lib/python2.7/site-packages/werkzeug/debug/shared/source.png new file mode 100644 index 0000000000000000000000000000000000000000..f7ea90419d950f9e69d977a1f5847456d96a5f0b GIT binary patch literal 818 zcmV-21I_%2P)@LCln44|RX7Ti z0HI3&7jPq){odH{?_{%nYVq_;n_c4WbUpvU(&Cvnj!vq|kVC-vpF6vp^;;e0mm6HW z+WPzA`AZ|;pPp$&dNjzrc??4rt`k%Q1l*u-BPD0MQ}Fbm8jnsyezNt7+u{23>t7Em zJtETY?ja9KrVs^!LJ$xEMF3-bAZO;-IQJavE60KA7fO$VY_%N)R6s>g5mW>fL4&aR z*EVgKKTBXm!=L?S0?xM zYqL@C$|EDF2q*3zWW7;PDZ}SK*IE8;i!3U62=qn80C&*I1Le7WwNP5EcX;_oh2dJn zf#HgBe4@r$GcjHjmj2vAfT%(YN?}kK=(*+1*DkNNc1H5R++vfBMhACi<5uFUU+N4+ z<&U*CPmWi}REa7C6-t>2im1CWv5Jkefxa6>)dEj-CAW wWa{_}BJ!}~75?MkfaCnj>Dn=~vkLS70Pk`;z)@TQj{pDw07*qoM6N<$f@imYHUIzs literal 0 HcmV?d00001 diff --git a/venv/lib/python2.7/site-packages/werkzeug/debug/shared/style.css b/venv/lib/python2.7/site-packages/werkzeug/debug/shared/style.css new file mode 100644 index 00000000..107863e1 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/debug/shared/style.css @@ -0,0 +1,154 @@ +@font-face { + font-family: 'Ubuntu'; + font-style: normal; + font-weight: normal; + src: local('Ubuntu'), local('Ubuntu-Regular'), + url('?__debugger__=yes&cmd=resource&f=ubuntu.ttf') format('truetype'); +} + +body, input { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', + 'Verdana', sans-serif; color: #000; text-align: center; + margin: 1em; padding: 0; font-size: 15px; } +h1, h2, h3 { font-family: 'Ubuntu', 'Lucida Grande', 'Lucida Sans Unicode', + 'Geneva', 'Verdana', sans-serif; font-weight: normal; } + +input { background-color: #fff; margin: 0; text-align: left; + outline: none !important; } +input[type="submit"] { padding: 3px 6px; } +a { color: #11557C; } +a:hover { color: #177199; } +pre, code, +textarea { font-family: 'Consolas', 'Monaco', 'Bitstream Vera Sans Mono', + monospace; font-size: 14px; } + +div.debugger { text-align: left; padding: 12px; margin: auto; + background-color: white; } +h1 { font-size: 36px; margin: 0 0 0.3em 0; } +div.detail { cursor: pointer; } +div.detail p { margin: 0 0 8px 13px; font-size: 14px; white-space: pre-wrap; + font-family: monospace; } +div.explanation { margin: 20px 13px; font-size: 15px; color: #555; } +div.footer { font-size: 13px; text-align: right; margin: 30px 0; + color: #86989B; } + +h2 { font-size: 16px; margin: 1.3em 0 0.0 0; padding: 9px; + background-color: #11557C; color: white; } +h2 em, h3 em { font-style: normal; color: #A5D6D9; font-weight: normal; } + +div.traceback, div.plain { border: 1px solid #ddd; margin: 0 0 1em 0; padding: 10px; } +div.plain p { margin: 0; } +div.plain textarea, +div.plain pre { margin: 10px 0 0 0; padding: 4px; + background-color: #E8EFF0; border: 1px solid #D3E7E9; } +div.plain textarea { width: 99%; height: 300px; } +div.traceback h3 { font-size: 1em; margin: 0 0 0.8em 0; } +div.traceback ul { list-style: none; margin: 0; padding: 0 0 0 1em; } +div.traceback h4 { font-size: 13px; font-weight: normal; margin: 0.7em 0 0.1em 0; } +div.traceback pre { margin: 0; padding: 5px 0 3px 15px; + background-color: #E8EFF0; border: 1px solid #D3E7E9; } +div.traceback .library .current { background: white; color: #555; } +div.traceback .expanded .current { background: #E8EFF0; color: black; } +div.traceback pre:hover { background-color: #DDECEE; color: black; cursor: pointer; } +div.traceback div.source.expanded pre + pre { border-top: none; } + +div.traceback span.ws { display: none; } +div.traceback pre.before, div.traceback pre.after { display: none; background: white; } +div.traceback div.source.expanded pre.before, +div.traceback div.source.expanded pre.after { + display: block; +} + +div.traceback div.source.expanded span.ws { + display: inline; +} + +div.traceback blockquote { margin: 1em 0 0 0; padding: 0; white-space: pre-line; } +div.traceback img { float: right; padding: 2px; margin: -3px 2px 0 0; display: none; } +div.traceback img:hover { background-color: #ddd; cursor: pointer; + border-color: #BFDDE0; } +div.traceback pre:hover img { display: block; } +div.traceback cite.filename { font-style: normal; color: #3B666B; } + +pre.console { border: 1px solid #ccc; background: white!important; + color: black; padding: 5px!important; + margin: 3px 0 0 0!important; cursor: default!important; + max-height: 400px; overflow: auto; } +pre.console form { color: #555; } +pre.console input { background-color: transparent; color: #555; + width: 90%; font-family: 'Consolas', 'Deja Vu Sans Mono', + 'Bitstream Vera Sans Mono', monospace; font-size: 14px; + border: none!important; } + +span.string { color: #30799B; } +span.number { color: #9C1A1C; } +span.help { color: #3A7734; } +span.object { color: #485F6E; } +span.extended { opacity: 0.5; } +span.extended:hover { opacity: 1; } +a.toggle { text-decoration: none; background-repeat: no-repeat; + background-position: center center; + background-image: url(?__debugger__=yes&cmd=resource&f=more.png); } +a.toggle:hover { background-color: #444; } +a.open { background-image: url(?__debugger__=yes&cmd=resource&f=less.png); } + +pre.console div.traceback, +pre.console div.box { margin: 5px 10px; white-space: normal; + border: 1px solid #11557C; padding: 10px; + font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', + 'Verdana', sans-serif; } +pre.console div.box h3, +pre.console div.traceback h3 { margin: -10px -10px 10px -10px; padding: 5px; + background: #11557C; color: white; } + +pre.console div.traceback pre:hover { cursor: default; background: #E8EFF0; } +pre.console div.traceback pre.syntaxerror { background: inherit; border: none; + margin: 20px -10px -10px -10px; + padding: 10px; border-top: 1px solid #BFDDE0; + background: #E8EFF0; } +pre.console div.noframe-traceback pre.syntaxerror { margin-top: -10px; border: none; } + +pre.console div.box pre.repr { padding: 0; margin: 0; background-color: white; border: none; } +pre.console div.box table { margin-top: 6px; } +pre.console div.box pre { border: none; } +pre.console div.box pre.help { background-color: white; } +pre.console div.box pre.help:hover { cursor: default; } +pre.console table tr { vertical-align: top; } +div.console { border: 1px solid #ccc; padding: 4px; background-color: #fafafa; } + +div.traceback pre, div.console pre { + white-space: pre-wrap; /* css-3 should we be so lucky... */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 ?? */ + white-space: -o-pre-wrap; /* Opera 7 ?? */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ + _white-space: pre; /* IE only hack to re-specify in + addition to word-wrap */ +} + + +div.pin-prompt { + position: absolute; + display: none; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: rgba(255, 255, 255, 0.8); +} + +div.pin-prompt .inner { + background: #eee; + padding: 10px 50px; + width: 350px; + margin: 10% auto 0 auto; + border: 1px solid #ccc; + border-radius: 2px; +} + +div.exc-divider { + margin: 0.7em 0 0 -1em; + padding: 0.5em; + background: #11557C; + color: #ddd; + border: 1px solid #ddd; +} diff --git a/venv/lib/python2.7/site-packages/werkzeug/debug/shared/ubuntu.ttf b/venv/lib/python2.7/site-packages/werkzeug/debug/shared/ubuntu.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8079f938c9fa5aede9a151439f136e267958ccd1 GIT binary patch literal 70220 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

    %lg zINZYFmOi1!X^9<~q;Nv$-hQ9->=Am__pF?MPUuZuyAZ9-;NiA<1@u}yf9pq znb)v5ED9%fe51X5vEpB~{jANM-l^UG_*^BV37YxTsdZQ%+%-EJ^!3Y$YdWOZ+^-f*h^XZ31d z7}6R;dUd1XEn2og>-6jXcUfXDAJFR~!hS6oRot&Qr#P?e4e0lU;Zm08g;`a9dVA*C zz*hZkZzC(t9T;Ksf!*$w&ro@{o*;KqTWk%9i~ z=urPaE}P#xkiGEI(V_fkcKz^Be*Wc+1KCr1w5fxc!~9x7rFa8s>ZlsiCz;`bflZ@Z z`V<_za`ts=Wi`>ujT(_;%g;(^k5U5Wm?v*fA zeOvPK^}N2mP3{SAetmcDygK`ty4~NZu$`YCHPzjEp4Ck@XOBAMunKopg|IoF-*)Dz zRol03@99UtR`K_29Ny~J{@)usxCg$e?)vYQ&zJ6;l)Ftqo#5cVJ|?;I9m8Ef2C6{# z;MZUK%kLh1gV(pHvjml&zP7#pwZ$#ca>W~NG3aTHIsNzm;_V(Yx{jZgfPjAa@Jtc;|&wjh|WGCr9=I>DD`8$>6I$2M8J4Iud9(5caK~K~D{8sDU zZl~)&afZfMzL(G%jZ@yQXK9|T=6+D0&vmNNR|S6fkm}6ORYmv))$5)w)gM)j@&&T^ z<2o!|q{`G!C~xBW%TH-G>1V>l%476pI>N0Q<7oDExHAO)^_ci88^^N&rh{I3A-@_~6)o@Grxkl!9t4jV-b@y+nL)@d?I<4_->9tZ?BHW+T)9~-&g4Aq5BoP7wF&J|Ga&VfA7kaPT}{5 H?s(!~BBTR` literal 0 HcmV?d00001 diff --git a/venv/lib/python2.7/site-packages/werkzeug/debug/tbtools.py b/venv/lib/python2.7/site-packages/werkzeug/debug/tbtools.py new file mode 100644 index 00000000..c8358882 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/debug/tbtools.py @@ -0,0 +1,629 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.debug.tbtools + ~~~~~~~~~~~~~~~~~~~~~~ + + This module provides various traceback related utility functions. + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import codecs +import inspect +import json +import os +import re +import sys +import sysconfig +import traceback +from tokenize import TokenError + +from .._compat import PY2 +from .._compat import range_type +from .._compat import reraise +from .._compat import string_types +from .._compat import text_type +from .._compat import to_native +from .._compat import to_unicode +from ..filesystem import get_filesystem_encoding +from ..utils import cached_property +from ..utils import escape +from .console import Console + + +_coding_re = re.compile(br"coding[:=]\s*([-\w.]+)") +_line_re = re.compile(br"^(.*?)$", re.MULTILINE) +_funcdef_re = re.compile(r"^(\s*def\s)|(.*(? + + + %(title)s // Werkzeug Debugger + + + + + + + + +

    +""" +FOOTER = u"""\ + +
    + +
    +
    +

    Console Locked

    +

    + The console is locked and needs to be unlocked by entering the PIN. + You can find the PIN printed out on the standard output of your + shell that runs the server. +

    +

    PIN: + + +

    +
    +
    + + +""" + +PAGE_HTML = ( + HEADER + + u"""\ +

    %(exception_type)s

    +
    +

    %(exception)s

    +
    +

    Traceback (most recent call last)

    +%(summary)s +
    +
    +

    + + This is the Copy/Paste friendly version of the traceback. You can also paste this traceback into + a gist: + +

    + +
    +
    +
    + The debugger caught an exception in your WSGI application. You can now + look at the traceback which led to the error. + If you enable JavaScript you can also use additional features such as code + execution (if the evalex feature is enabled), automatic pasting of the + exceptions and much more. +
    +""" + + FOOTER + + """ + +""" +) + +CONSOLE_HTML = ( + HEADER + + u"""\ +

    Interactive Console

    +
    +In this console you can execute Python expressions in the context of the +application. The initial namespace was created by the debugger automatically. +
    +
    The Console requires JavaScript.
    +""" + + FOOTER +) + +SUMMARY_HTML = u"""\ +
    + %(title)s +
      %(frames)s
    + %(description)s +
    +""" + +FRAME_HTML = u"""\ +
    +

    File "%(filename)s", + line %(lineno)s, + in %(function_name)s

    +
    %(lines)s
    +
    +""" + +SOURCE_LINE_HTML = u"""\ + + %(lineno)s + %(code)s + +""" + + +def render_console_html(secret, evalex_trusted=True): + return CONSOLE_HTML % { + "evalex": "true", + "evalex_trusted": "true" if evalex_trusted else "false", + "console": "true", + "title": "Console", + "secret": secret, + "traceback_id": -1, + } + + +def get_current_traceback( + ignore_system_exceptions=False, show_hidden_frames=False, skip=0 +): + """Get the current exception info as `Traceback` object. Per default + calling this method will reraise system exceptions such as generator exit, + system exit or others. This behavior can be disabled by passing `False` + to the function as first parameter. + """ + exc_type, exc_value, tb = sys.exc_info() + if ignore_system_exceptions and exc_type in system_exceptions: + reraise(exc_type, exc_value, tb) + for _ in range_type(skip): + if tb.tb_next is None: + break + tb = tb.tb_next + tb = Traceback(exc_type, exc_value, tb) + if not show_hidden_frames: + tb.filter_hidden_frames() + return tb + + +class Line(object): + """Helper for the source renderer.""" + + __slots__ = ("lineno", "code", "in_frame", "current") + + def __init__(self, lineno, code): + self.lineno = lineno + self.code = code + self.in_frame = False + self.current = False + + @property + def classes(self): + rv = ["line"] + if self.in_frame: + rv.append("in-frame") + if self.current: + rv.append("current") + return rv + + def render(self): + return SOURCE_LINE_HTML % { + "classes": u" ".join(self.classes), + "lineno": self.lineno, + "code": escape(self.code), + } + + +class Traceback(object): + """Wraps a traceback.""" + + def __init__(self, exc_type, exc_value, tb): + self.exc_type = exc_type + self.exc_value = exc_value + self.tb = tb + + exception_type = exc_type.__name__ + if exc_type.__module__ not in {"builtins", "__builtin__", "exceptions"}: + exception_type = exc_type.__module__ + "." + exception_type + self.exception_type = exception_type + + self.groups = [] + memo = set() + while True: + self.groups.append(Group(exc_type, exc_value, tb)) + memo.add(id(exc_value)) + if PY2: + break + exc_value = exc_value.__cause__ or exc_value.__context__ + if exc_value is None or id(exc_value) in memo: + break + exc_type = type(exc_value) + tb = exc_value.__traceback__ + self.groups.reverse() + self.frames = [frame for group in self.groups for frame in group.frames] + + def filter_hidden_frames(self): + """Remove the frames according to the paste spec.""" + for group in self.groups: + group.filter_hidden_frames() + + self.frames[:] = [frame for group in self.groups for frame in group.frames] + + @property + def is_syntax_error(self): + """Is it a syntax error?""" + return isinstance(self.exc_value, SyntaxError) + + @property + def exception(self): + """String representation of the final exception.""" + return self.groups[-1].exception + + def log(self, logfile=None): + """Log the ASCII traceback into a file object.""" + if logfile is None: + logfile = sys.stderr + tb = self.plaintext.rstrip() + u"\n" + logfile.write(to_native(tb, "utf-8", "replace")) + + def paste(self): + """Create a paste and return the paste id.""" + data = json.dumps( + { + "description": "Werkzeug Internal Server Error", + "public": False, + "files": {"traceback.txt": {"content": self.plaintext}}, + } + ).encode("utf-8") + try: + from urllib2 import urlopen + except ImportError: + from urllib.request import urlopen + rv = urlopen("https://api.github.com/gists", data=data) + resp = json.loads(rv.read().decode("utf-8")) + rv.close() + return {"url": resp["html_url"], "id": resp["id"]} + + def render_summary(self, include_title=True): + """Render the traceback for the interactive console.""" + title = "" + classes = ["traceback"] + if not self.frames: + classes.append("noframe-traceback") + frames = [] + else: + library_frames = sum(frame.is_library for frame in self.frames) + mark_lib = 0 < library_frames < len(self.frames) + frames = [group.render(mark_lib=mark_lib) for group in self.groups] + + if include_title: + if self.is_syntax_error: + title = u"Syntax Error" + else: + title = u"Traceback (most recent call last):" + + if self.is_syntax_error: + description_wrapper = u"
    %s
    " + else: + description_wrapper = u"
    %s
    " + + return SUMMARY_HTML % { + "classes": u" ".join(classes), + "title": u"

    %s

    " % title if title else u"", + "frames": u"\n".join(frames), + "description": description_wrapper % escape(self.exception), + } + + def render_full(self, evalex=False, secret=None, evalex_trusted=True): + """Render the Full HTML page with the traceback info.""" + exc = escape(self.exception) + return PAGE_HTML % { + "evalex": "true" if evalex else "false", + "evalex_trusted": "true" if evalex_trusted else "false", + "console": "false", + "title": exc, + "exception": exc, + "exception_type": escape(self.exception_type), + "summary": self.render_summary(include_title=False), + "plaintext": escape(self.plaintext), + "plaintext_cs": re.sub("-{2,}", "-", self.plaintext), + "traceback_id": self.id, + "secret": secret, + } + + @cached_property + def plaintext(self): + return u"\n".join([group.render_text() for group in self.groups]) + + @property + def id(self): + return id(self) + + +class Group(object): + """A group of frames for an exception in a traceback. On Python 3, + if the exception has a ``__cause__`` or ``__context__``, there are + multiple exception groups. + """ + + def __init__(self, exc_type, exc_value, tb): + self.exc_type = exc_type + self.exc_value = exc_value + self.info = None + if not PY2: + if exc_value.__cause__ is not None: + self.info = ( + u"The above exception was the direct cause of the" + u" following exception" + ) + elif exc_value.__context__ is not None: + self.info = ( + u"During handling of the above exception, another" + u" exception occurred" + ) + + self.frames = [] + while tb is not None: + self.frames.append(Frame(exc_type, exc_value, tb)) + tb = tb.tb_next + + def filter_hidden_frames(self): + new_frames = [] + hidden = False + + for frame in self.frames: + hide = frame.hide + if hide in ("before", "before_and_this"): + new_frames = [] + hidden = False + if hide == "before_and_this": + continue + elif hide in ("reset", "reset_and_this"): + hidden = False + if hide == "reset_and_this": + continue + elif hide in ("after", "after_and_this"): + hidden = True + if hide == "after_and_this": + continue + elif hide or hidden: + continue + new_frames.append(frame) + + # if we only have one frame and that frame is from the codeop + # module, remove it. + if len(new_frames) == 1 and self.frames[0].module == "codeop": + del self.frames[:] + + # if the last frame is missing something went terrible wrong :( + elif self.frames[-1] in new_frames: + self.frames[:] = new_frames + + @property + def exception(self): + """String representation of the exception.""" + buf = traceback.format_exception_only(self.exc_type, self.exc_value) + rv = "".join(buf).strip() + return to_unicode(rv, "utf-8", "replace") + + def render(self, mark_lib=True): + out = [] + if self.info is not None: + out.append(u'
  • %s:
    ' % self.info) + for frame in self.frames: + out.append( + u"%s" + % ( + u' title="%s"' % escape(frame.info) if frame.info else u"", + frame.render(mark_lib=mark_lib), + ) + ) + return u"\n".join(out) + + def render_text(self): + out = [] + if self.info is not None: + out.append(u"\n%s:\n" % self.info) + out.append(u"Traceback (most recent call last):") + for frame in self.frames: + out.append(frame.render_text()) + out.append(self.exception) + return u"\n".join(out) + + +class Frame(object): + """A single frame in a traceback.""" + + def __init__(self, exc_type, exc_value, tb): + self.lineno = tb.tb_lineno + self.function_name = tb.tb_frame.f_code.co_name + self.locals = tb.tb_frame.f_locals + self.globals = tb.tb_frame.f_globals + + fn = inspect.getsourcefile(tb) or inspect.getfile(tb) + if fn[-4:] in (".pyo", ".pyc"): + fn = fn[:-1] + # if it's a file on the file system resolve the real filename. + if os.path.isfile(fn): + fn = os.path.realpath(fn) + self.filename = to_unicode(fn, get_filesystem_encoding()) + self.module = self.globals.get("__name__") + self.loader = self.globals.get("__loader__") + self.code = tb.tb_frame.f_code + + # support for paste's traceback extensions + self.hide = self.locals.get("__traceback_hide__", False) + info = self.locals.get("__traceback_info__") + if info is not None: + info = to_unicode(info, "utf-8", "replace") + self.info = info + + def render(self, mark_lib=True): + """Render a single frame in a traceback.""" + return FRAME_HTML % { + "id": self.id, + "filename": escape(self.filename), + "lineno": self.lineno, + "function_name": escape(self.function_name), + "lines": self.render_line_context(), + "library": "library" if mark_lib and self.is_library else "", + } + + @cached_property + def is_library(self): + return any( + self.filename.startswith(path) for path in sysconfig.get_paths().values() + ) + + def render_text(self): + return u' File "%s", line %s, in %s\n %s' % ( + self.filename, + self.lineno, + self.function_name, + self.current_line.strip(), + ) + + def render_line_context(self): + before, current, after = self.get_context_lines() + rv = [] + + def render_line(line, cls): + line = line.expandtabs().rstrip() + stripped_line = line.strip() + prefix = len(line) - len(stripped_line) + rv.append( + '
    %s%s
    ' + % (cls, " " * prefix, escape(stripped_line) or " ") + ) + + for line in before: + render_line(line, "before") + render_line(current, "current") + for line in after: + render_line(line, "after") + + return "\n".join(rv) + + def get_annotated_lines(self): + """Helper function that returns lines with extra information.""" + lines = [Line(idx + 1, x) for idx, x in enumerate(self.sourcelines)] + + # find function definition and mark lines + if hasattr(self.code, "co_firstlineno"): + lineno = self.code.co_firstlineno - 1 + while lineno > 0: + if _funcdef_re.match(lines[lineno].code): + break + lineno -= 1 + try: + offset = len(inspect.getblock([x.code + "\n" for x in lines[lineno:]])) + except TokenError: + offset = 0 + for line in lines[lineno : lineno + offset]: + line.in_frame = True + + # mark current line + try: + lines[self.lineno - 1].current = True + except IndexError: + pass + + return lines + + def eval(self, code, mode="single"): + """Evaluate code in the context of the frame.""" + if isinstance(code, string_types): + if PY2 and isinstance(code, text_type): # noqa + code = UTF8_COOKIE + code.encode("utf-8") + code = compile(code, "", mode) + return eval(code, self.globals, self.locals) + + @cached_property + def sourcelines(self): + """The sourcecode of the file as list of unicode strings.""" + # get sourcecode from loader or file + source = None + if self.loader is not None: + try: + if hasattr(self.loader, "get_source"): + source = self.loader.get_source(self.module) + elif hasattr(self.loader, "get_source_by_code"): + source = self.loader.get_source_by_code(self.code) + except Exception: + # we munch the exception so that we don't cause troubles + # if the loader is broken. + pass + + if source is None: + try: + f = open(to_native(self.filename, get_filesystem_encoding()), mode="rb") + except IOError: + return [] + try: + source = f.read() + finally: + f.close() + + # already unicode? return right away + if isinstance(source, text_type): + return source.splitlines() + + # yes. it should be ascii, but we don't want to reject too many + # characters in the debugger if something breaks + charset = "utf-8" + if source.startswith(UTF8_COOKIE): + source = source[3:] + else: + for idx, match in enumerate(_line_re.finditer(source)): + match = _coding_re.search(match.group()) + if match is not None: + charset = match.group(1) + break + if idx > 1: + break + + # on broken cookies we fall back to utf-8 too + charset = to_native(charset) + try: + codecs.lookup(charset) + except LookupError: + charset = "utf-8" + + return source.decode(charset, "replace").splitlines() + + def get_context_lines(self, context=5): + before = self.sourcelines[self.lineno - context - 1 : self.lineno - 1] + past = self.sourcelines[self.lineno : self.lineno + context] + return (before, self.current_line, past) + + @property + def current_line(self): + try: + return self.sourcelines[self.lineno - 1] + except IndexError: + return u"" + + @cached_property + def console(self): + return Console(self.globals, self.locals) + + @property + def id(self): + return id(self) diff --git a/venv/lib/python2.7/site-packages/werkzeug/exceptions.py b/venv/lib/python2.7/site-packages/werkzeug/exceptions.py new file mode 100644 index 00000000..a7295ca7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/exceptions.py @@ -0,0 +1,779 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.exceptions + ~~~~~~~~~~~~~~~~~~~ + + This module implements a number of Python exceptions you can raise from + within your views to trigger a standard non-200 response. + + + Usage Example + ------------- + + :: + + from werkzeug.wrappers import BaseRequest + from werkzeug.wsgi import responder + from werkzeug.exceptions import HTTPException, NotFound + + def view(request): + raise NotFound() + + @responder + def application(environ, start_response): + request = BaseRequest(environ) + try: + return view(request) + except HTTPException as e: + return e + + + As you can see from this example those exceptions are callable WSGI + applications. Because of Python 2.4 compatibility those do not extend + from the response objects but only from the python exception class. + + As a matter of fact they are not Werkzeug response objects. However you + can get a response object by calling ``get_response()`` on a HTTP + exception. + + Keep in mind that you have to pass an environment to ``get_response()`` + because some errors fetch additional information from the WSGI + environment. + + If you want to hook in a different exception page to say, a 404 status + code, you can add a second except for a specific subclass of an error:: + + @responder + def application(environ, start_response): + request = BaseRequest(environ) + try: + return view(request) + except NotFound, e: + return not_found(request) + except HTTPException, e: + return e + + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import sys + +from ._compat import implements_to_string +from ._compat import integer_types +from ._compat import iteritems +from ._compat import text_type +from ._internal import _get_environ +from .utils import escape + + +@implements_to_string +class HTTPException(Exception): + """Baseclass for all HTTP exceptions. This exception can be called as WSGI + application to render a default error page or you can catch the subclasses + of it independently and render nicer error messages. + """ + + code = None + description = None + + def __init__(self, description=None, response=None): + super(HTTPException, self).__init__() + if description is not None: + self.description = description + self.response = response + + @classmethod + def wrap(cls, exception, name=None): + """Create an exception that is a subclass of the calling HTTP + exception and the ``exception`` argument. + + The first argument to the class will be passed to the + wrapped ``exception``, the rest to the HTTP exception. If + ``e.args`` is not empty and ``e.show_exception`` is ``True``, + the wrapped exception message is added to the HTTP error + description. + + .. versionchanged:: 0.15.5 + The ``show_exception`` attribute controls whether the + description includes the wrapped exception message. + + .. versionchanged:: 0.15.0 + The description includes the wrapped exception message. + """ + + class newcls(cls, exception): + _description = cls.description + show_exception = False + + def __init__(self, arg=None, *args, **kwargs): + super(cls, self).__init__(*args, **kwargs) + + if arg is None: + exception.__init__(self) + else: + exception.__init__(self, arg) + + @property + def description(self): + if self.show_exception: + return "{}\n{}: {}".format( + self._description, exception.__name__, exception.__str__(self) + ) + + return self._description + + @description.setter + def description(self, value): + self._description = value + + newcls.__module__ = sys._getframe(1).f_globals.get("__name__") + name = name or cls.__name__ + exception.__name__ + newcls.__name__ = newcls.__qualname__ = name + return newcls + + @property + def name(self): + """The status name.""" + from .http import HTTP_STATUS_CODES + + return HTTP_STATUS_CODES.get(self.code, "Unknown Error") + + def get_description(self, environ=None): + """Get the description.""" + return u"

    %s

    " % escape(self.description).replace("\n", "
    ") + + def get_body(self, environ=None): + """Get the HTML body.""" + return text_type( + ( + u'\n' + u"%(code)s %(name)s\n" + u"

    %(name)s

    \n" + u"%(description)s\n" + ) + % { + "code": self.code, + "name": escape(self.name), + "description": self.get_description(environ), + } + ) + + def get_headers(self, environ=None): + """Get a list of headers.""" + return [("Content-Type", "text/html")] + + def get_response(self, environ=None): + """Get a response object. If one was passed to the exception + it's returned directly. + + :param environ: the optional environ for the request. This + can be used to modify the response depending + on how the request looked like. + :return: a :class:`Response` object or a subclass thereof. + """ + from .wrappers.response import Response + + if self.response is not None: + return self.response + if environ is not None: + environ = _get_environ(environ) + headers = self.get_headers(environ) + return Response(self.get_body(environ), self.code, headers) + + def __call__(self, environ, start_response): + """Call the exception as WSGI application. + + :param environ: the WSGI environment. + :param start_response: the response callable provided by the WSGI + server. + """ + response = self.get_response(environ) + return response(environ, start_response) + + def __str__(self): + code = self.code if self.code is not None else "???" + return "%s %s: %s" % (code, self.name, self.description) + + def __repr__(self): + code = self.code if self.code is not None else "???" + return "<%s '%s: %s'>" % (self.__class__.__name__, code, self.name) + + +class BadRequest(HTTPException): + """*400* `Bad Request` + + Raise if the browser sends something to the application the application + or server cannot handle. + """ + + code = 400 + description = ( + "The browser (or proxy) sent a request that this server could " + "not understand." + ) + + +class ClientDisconnected(BadRequest): + """Internal exception that is raised if Werkzeug detects a disconnected + client. Since the client is already gone at that point attempting to + send the error message to the client might not work and might ultimately + result in another exception in the server. Mainly this is here so that + it is silenced by default as far as Werkzeug is concerned. + + Since disconnections cannot be reliably detected and are unspecified + by WSGI to a large extent this might or might not be raised if a client + is gone. + + .. versionadded:: 0.8 + """ + + +class SecurityError(BadRequest): + """Raised if something triggers a security error. This is otherwise + exactly like a bad request error. + + .. versionadded:: 0.9 + """ + + +class BadHost(BadRequest): + """Raised if the submitted host is badly formatted. + + .. versionadded:: 0.11.2 + """ + + +class Unauthorized(HTTPException): + """*401* ``Unauthorized`` + + Raise if the user is not authorized to access a resource. + + The ``www_authenticate`` argument should be used to set the + ``WWW-Authenticate`` header. This is used for HTTP basic auth and + other schemes. Use :class:`~werkzeug.datastructures.WWWAuthenticate` + to create correctly formatted values. Strictly speaking a 401 + response is invalid if it doesn't provide at least one value for + this header, although real clients typically don't care. + + :param description: Override the default message used for the body + of the response. + :param www-authenticate: A single value, or list of values, for the + WWW-Authenticate header. + + .. versionchanged:: 0.15.3 + If the ``www_authenticate`` argument is not set, the + ``WWW-Authenticate`` header is not set. + + .. versionchanged:: 0.15.3 + The ``response`` argument was restored. + + .. versionchanged:: 0.15.1 + ``description`` was moved back as the first argument, restoring + its previous position. + + .. versionchanged:: 0.15.0 + ``www_authenticate`` was added as the first argument, ahead of + ``description``. + """ + + code = 401 + description = ( + "The server could not verify that you are authorized to access" + " the URL requested. You either supplied the wrong credentials" + " (e.g. a bad password), or your browser doesn't understand" + " how to supply the credentials required." + ) + + def __init__(self, description=None, response=None, www_authenticate=None): + HTTPException.__init__(self, description, response) + + if www_authenticate is not None: + if not isinstance(www_authenticate, (tuple, list)): + www_authenticate = (www_authenticate,) + + self.www_authenticate = www_authenticate + + def get_headers(self, environ=None): + headers = HTTPException.get_headers(self, environ) + if self.www_authenticate: + headers.append( + ("WWW-Authenticate", ", ".join([str(x) for x in self.www_authenticate])) + ) + return headers + + +class Forbidden(HTTPException): + """*403* `Forbidden` + + Raise if the user doesn't have the permission for the requested resource + but was authenticated. + """ + + code = 403 + description = ( + "You don't have the permission to access the requested" + " resource. It is either read-protected or not readable by the" + " server." + ) + + +class NotFound(HTTPException): + """*404* `Not Found` + + Raise if a resource does not exist and never existed. + """ + + code = 404 + description = ( + "The requested URL was not found on the server. If you entered" + " the URL manually please check your spelling and try again." + ) + + +class MethodNotAllowed(HTTPException): + """*405* `Method Not Allowed` + + Raise if the server used a method the resource does not handle. For + example `POST` if the resource is view only. Especially useful for REST. + + The first argument for this exception should be a list of allowed methods. + Strictly speaking the response would be invalid if you don't provide valid + methods in the header which you can do with that list. + """ + + code = 405 + description = "The method is not allowed for the requested URL." + + def __init__(self, valid_methods=None, description=None): + """Takes an optional list of valid http methods + starting with werkzeug 0.3 the list will be mandatory.""" + HTTPException.__init__(self, description) + self.valid_methods = valid_methods + + def get_headers(self, environ=None): + headers = HTTPException.get_headers(self, environ) + if self.valid_methods: + headers.append(("Allow", ", ".join(self.valid_methods))) + return headers + + +class NotAcceptable(HTTPException): + """*406* `Not Acceptable` + + Raise if the server can't return any content conforming to the + `Accept` headers of the client. + """ + + code = 406 + + description = ( + "The resource identified by the request is only capable of" + " generating response entities which have content" + " characteristics not acceptable according to the accept" + " headers sent in the request." + ) + + +class RequestTimeout(HTTPException): + """*408* `Request Timeout` + + Raise to signalize a timeout. + """ + + code = 408 + description = ( + "The server closed the network connection because the browser" + " didn't finish the request within the specified time." + ) + + +class Conflict(HTTPException): + """*409* `Conflict` + + Raise to signal that a request cannot be completed because it conflicts + with the current state on the server. + + .. versionadded:: 0.7 + """ + + code = 409 + description = ( + "A conflict happened while processing the request. The" + " resource might have been modified while the request was being" + " processed." + ) + + +class Gone(HTTPException): + """*410* `Gone` + + Raise if a resource existed previously and went away without new location. + """ + + code = 410 + description = ( + "The requested URL is no longer available on this server and" + " there is no forwarding address. If you followed a link from a" + " foreign page, please contact the author of this page." + ) + + +class LengthRequired(HTTPException): + """*411* `Length Required` + + Raise if the browser submitted data but no ``Content-Length`` header which + is required for the kind of processing the server does. + """ + + code = 411 + description = ( + "A request with this method requires a valid Content-" + "Length header." + ) + + +class PreconditionFailed(HTTPException): + """*412* `Precondition Failed` + + Status code used in combination with ``If-Match``, ``If-None-Match``, or + ``If-Unmodified-Since``. + """ + + code = 412 + description = ( + "The precondition on the request for the URL failed positive evaluation." + ) + + +class RequestEntityTooLarge(HTTPException): + """*413* `Request Entity Too Large` + + The status code one should return if the data submitted exceeded a given + limit. + """ + + code = 413 + description = "The data value transmitted exceeds the capacity limit." + + +class RequestURITooLarge(HTTPException): + """*414* `Request URI Too Large` + + Like *413* but for too long URLs. + """ + + code = 414 + description = ( + "The length of the requested URL exceeds the capacity limit for" + " this server. The request cannot be processed." + ) + + +class UnsupportedMediaType(HTTPException): + """*415* `Unsupported Media Type` + + The status code returned if the server is unable to handle the media type + the client transmitted. + """ + + code = 415 + description = ( + "The server does not support the media type transmitted in the request." + ) + + +class RequestedRangeNotSatisfiable(HTTPException): + """*416* `Requested Range Not Satisfiable` + + The client asked for an invalid part of the file. + + .. versionadded:: 0.7 + """ + + code = 416 + description = "The server cannot provide the requested range." + + def __init__(self, length=None, units="bytes", description=None): + """Takes an optional `Content-Range` header value based on ``length`` + parameter. + """ + HTTPException.__init__(self, description) + self.length = length + self.units = units + + def get_headers(self, environ=None): + headers = HTTPException.get_headers(self, environ) + if self.length is not None: + headers.append(("Content-Range", "%s */%d" % (self.units, self.length))) + return headers + + +class ExpectationFailed(HTTPException): + """*417* `Expectation Failed` + + The server cannot meet the requirements of the Expect request-header. + + .. versionadded:: 0.7 + """ + + code = 417 + description = "The server could not meet the requirements of the Expect header" + + +class ImATeapot(HTTPException): + """*418* `I'm a teapot` + + The server should return this if it is a teapot and someone attempted + to brew coffee with it. + + .. versionadded:: 0.7 + """ + + code = 418 + description = "This server is a teapot, not a coffee machine" + + +class UnprocessableEntity(HTTPException): + """*422* `Unprocessable Entity` + + Used if the request is well formed, but the instructions are otherwise + incorrect. + """ + + code = 422 + description = ( + "The request was well-formed but was unable to be followed due" + " to semantic errors." + ) + + +class Locked(HTTPException): + """*423* `Locked` + + Used if the resource that is being accessed is locked. + """ + + code = 423 + description = "The resource that is being accessed is locked." + + +class FailedDependency(HTTPException): + """*424* `Failed Dependency` + + Used if the method could not be performed on the resource + because the requested action depended on another action and that action failed. + """ + + code = 424 + description = ( + "The method could not be performed on the resource because the" + " requested action depended on another action and that action" + " failed." + ) + + +class PreconditionRequired(HTTPException): + """*428* `Precondition Required` + + The server requires this request to be conditional, typically to prevent + the lost update problem, which is a race condition between two or more + clients attempting to update a resource through PUT or DELETE. By requiring + each client to include a conditional header ("If-Match" or "If-Unmodified- + Since") with the proper value retained from a recent GET request, the + server ensures that each client has at least seen the previous revision of + the resource. + """ + + code = 428 + description = ( + "This request is required to be conditional; try using" + ' "If-Match" or "If-Unmodified-Since".' + ) + + +class TooManyRequests(HTTPException): + """*429* `Too Many Requests` + + The server is limiting the rate at which this user receives responses, and + this request exceeds that rate. (The server may use any convenient method + to identify users and their request rates). The server may include a + "Retry-After" header to indicate how long the user should wait before + retrying. + """ + + code = 429 + description = "This user has exceeded an allotted request count. Try again later." + + +class RequestHeaderFieldsTooLarge(HTTPException): + """*431* `Request Header Fields Too Large` + + The server refuses to process the request because the header fields are too + large. One or more individual fields may be too large, or the set of all + headers is too large. + """ + + code = 431 + description = "One or more header fields exceeds the maximum size." + + +class UnavailableForLegalReasons(HTTPException): + """*451* `Unavailable For Legal Reasons` + + This status code indicates that the server is denying access to the + resource as a consequence of a legal demand. + """ + + code = 451 + description = "Unavailable for legal reasons." + + +class InternalServerError(HTTPException): + """*500* `Internal Server Error` + + Raise if an internal server error occurred. This is a good fallback if an + unknown error occurred in the dispatcher. + """ + + code = 500 + description = ( + "The server encountered an internal error and was unable to" + " complete your request. Either the server is overloaded or" + " there is an error in the application." + ) + + +class NotImplemented(HTTPException): + """*501* `Not Implemented` + + Raise if the application does not support the action requested by the + browser. + """ + + code = 501 + description = "The server does not support the action requested by the browser." + + +class BadGateway(HTTPException): + """*502* `Bad Gateway` + + If you do proxying in your application you should return this status code + if you received an invalid response from the upstream server it accessed + in attempting to fulfill the request. + """ + + code = 502 + description = ( + "The proxy server received an invalid response from an upstream server." + ) + + +class ServiceUnavailable(HTTPException): + """*503* `Service Unavailable` + + Status code you should return if a service is temporarily unavailable. + """ + + code = 503 + description = ( + "The server is temporarily unable to service your request due" + " to maintenance downtime or capacity problems. Please try" + " again later." + ) + + +class GatewayTimeout(HTTPException): + """*504* `Gateway Timeout` + + Status code you should return if a connection to an upstream server + times out. + """ + + code = 504 + description = "The connection to an upstream server timed out." + + +class HTTPVersionNotSupported(HTTPException): + """*505* `HTTP Version Not Supported` + + The server does not support the HTTP protocol version used in the request. + """ + + code = 505 + description = ( + "The server does not support the HTTP protocol version used in the request." + ) + + +default_exceptions = {} +__all__ = ["HTTPException"] + + +def _find_exceptions(): + for _name, obj in iteritems(globals()): + try: + is_http_exception = issubclass(obj, HTTPException) + except TypeError: + is_http_exception = False + if not is_http_exception or obj.code is None: + continue + __all__.append(obj.__name__) + old_obj = default_exceptions.get(obj.code, None) + if old_obj is not None and issubclass(obj, old_obj): + continue + default_exceptions[obj.code] = obj + + +_find_exceptions() +del _find_exceptions + + +class Aborter(object): + """When passed a dict of code -> exception items it can be used as + callable that raises exceptions. If the first argument to the + callable is an integer it will be looked up in the mapping, if it's + a WSGI application it will be raised in a proxy exception. + + The rest of the arguments are forwarded to the exception constructor. + """ + + def __init__(self, mapping=None, extra=None): + if mapping is None: + mapping = default_exceptions + self.mapping = dict(mapping) + if extra is not None: + self.mapping.update(extra) + + def __call__(self, code, *args, **kwargs): + if not args and not kwargs and not isinstance(code, integer_types): + raise HTTPException(response=code) + if code not in self.mapping: + raise LookupError("no exception for %r" % code) + raise self.mapping[code](*args, **kwargs) + + +def abort(status, *args, **kwargs): + """Raises an :py:exc:`HTTPException` for the given status code or WSGI + application:: + + abort(404) # 404 Not Found + abort(Response('Hello World')) + + Can be passed a WSGI application or a status code. If a status code is + given it's looked up in the list of exceptions and will raise that + exception, if passed a WSGI application it will wrap it in a proxy WSGI + exception and raise that:: + + abort(404) + abort(Response('Hello World')) + + """ + return _aborter(status, *args, **kwargs) + + +_aborter = Aborter() + +#: An exception that is used to signal both a :exc:`KeyError` and a +#: :exc:`BadRequest`. Used by many of the datastructures. +BadRequestKeyError = BadRequest.wrap(KeyError) diff --git a/venv/lib/python2.7/site-packages/werkzeug/filesystem.py b/venv/lib/python2.7/site-packages/werkzeug/filesystem.py new file mode 100644 index 00000000..d016caea --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/filesystem.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.filesystem + ~~~~~~~~~~~~~~~~~~~ + + Various utilities for the local filesystem. + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import codecs +import sys +import warnings + +# We do not trust traditional unixes. +has_likely_buggy_unicode_filesystem = ( + sys.platform.startswith("linux") or "bsd" in sys.platform +) + + +def _is_ascii_encoding(encoding): + """Given an encoding this figures out if the encoding is actually ASCII (which + is something we don't actually want in most cases). This is necessary + because ASCII comes under many names such as ANSI_X3.4-1968. + """ + if encoding is None: + return False + try: + return codecs.lookup(encoding).name == "ascii" + except LookupError: + return False + + +class BrokenFilesystemWarning(RuntimeWarning, UnicodeWarning): + """The warning used by Werkzeug to signal a broken filesystem. Will only be + used once per runtime.""" + + +_warned_about_filesystem_encoding = False + + +def get_filesystem_encoding(): + """Returns the filesystem encoding that should be used. Note that this is + different from the Python understanding of the filesystem encoding which + might be deeply flawed. Do not use this value against Python's unicode APIs + because it might be different. See :ref:`filesystem-encoding` for the exact + behavior. + + The concept of a filesystem encoding in generally is not something you + should rely on. As such if you ever need to use this function except for + writing wrapper code reconsider. + """ + global _warned_about_filesystem_encoding + rv = sys.getfilesystemencoding() + if has_likely_buggy_unicode_filesystem and not rv or _is_ascii_encoding(rv): + if not _warned_about_filesystem_encoding: + warnings.warn( + "Detected a misconfigured UNIX filesystem: Will use" + " UTF-8 as filesystem encoding instead of {0!r}".format(rv), + BrokenFilesystemWarning, + ) + _warned_about_filesystem_encoding = True + return "utf-8" + return rv diff --git a/venv/lib/python2.7/site-packages/werkzeug/formparser.py b/venv/lib/python2.7/site-packages/werkzeug/formparser.py new file mode 100644 index 00000000..ffdb9b0f --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/formparser.py @@ -0,0 +1,584 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.formparser + ~~~~~~~~~~~~~~~~~~~ + + This module implements the form parsing. It supports url-encoded forms + as well as non-nested multipart uploads. + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import codecs +import re +from functools import update_wrapper +from itertools import chain +from itertools import repeat +from itertools import tee + +from . import exceptions +from ._compat import BytesIO +from ._compat import text_type +from ._compat import to_native +from .datastructures import FileStorage +from .datastructures import Headers +from .datastructures import MultiDict +from .http import parse_options_header +from .urls import url_decode_stream +from .wsgi import get_content_length +from .wsgi import get_input_stream +from .wsgi import make_line_iter + +# there are some platforms where SpooledTemporaryFile is not available. +# In that case we need to provide a fallback. +try: + from tempfile import SpooledTemporaryFile +except ImportError: + from tempfile import TemporaryFile + + SpooledTemporaryFile = None + + +#: an iterator that yields empty strings +_empty_string_iter = repeat("") + +#: a regular expression for multipart boundaries +_multipart_boundary_re = re.compile("^[ -~]{0,200}[!-~]$") + +#: supported http encodings that are also available in python we support +#: for multipart messages. +_supported_multipart_encodings = frozenset(["base64", "quoted-printable"]) + + +def default_stream_factory( + total_content_length, filename, content_type, content_length=None +): + """The stream factory that is used per default.""" + max_size = 1024 * 500 + if SpooledTemporaryFile is not None: + return SpooledTemporaryFile(max_size=max_size, mode="wb+") + if total_content_length is None or total_content_length > max_size: + return TemporaryFile("wb+") + return BytesIO() + + +def parse_form_data( + environ, + stream_factory=None, + charset="utf-8", + errors="replace", + max_form_memory_size=None, + max_content_length=None, + cls=None, + silent=True, +): + """Parse the form data in the environ and return it as tuple in the form + ``(stream, form, files)``. You should only call this method if the + transport method is `POST`, `PUT`, or `PATCH`. + + If the mimetype of the data transmitted is `multipart/form-data` the + files multidict will be filled with `FileStorage` objects. If the + mimetype is unknown the input stream is wrapped and returned as first + argument, else the stream is empty. + + This is a shortcut for the common usage of :class:`FormDataParser`. + + Have a look at :ref:`dealing-with-request-data` for more details. + + .. versionadded:: 0.5 + The `max_form_memory_size`, `max_content_length` and + `cls` parameters were added. + + .. versionadded:: 0.5.1 + The optional `silent` flag was added. + + :param environ: the WSGI environment to be used for parsing. + :param stream_factory: An optional callable that returns a new read and + writeable file descriptor. This callable works + the same as :meth:`~BaseResponse._get_file_stream`. + :param charset: The character set for URL and url encoded form data. + :param errors: The encoding error behavior. + :param max_form_memory_size: the maximum number of bytes to be accepted for + in-memory stored form data. If the data + exceeds the value specified an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param max_content_length: If this is provided and the transmitted data + is longer than this value an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`MultiDict` is used. + :param silent: If set to False parsing errors will not be caught. + :return: A tuple in the form ``(stream, form, files)``. + """ + return FormDataParser( + stream_factory, + charset, + errors, + max_form_memory_size, + max_content_length, + cls, + silent, + ).parse_from_environ(environ) + + +def exhaust_stream(f): + """Helper decorator for methods that exhausts the stream on return.""" + + def wrapper(self, stream, *args, **kwargs): + try: + return f(self, stream, *args, **kwargs) + finally: + exhaust = getattr(stream, "exhaust", None) + if exhaust is not None: + exhaust() + else: + while 1: + chunk = stream.read(1024 * 64) + if not chunk: + break + + return update_wrapper(wrapper, f) + + +class FormDataParser(object): + """This class implements parsing of form data for Werkzeug. By itself + it can parse multipart and url encoded form data. It can be subclassed + and extended but for most mimetypes it is a better idea to use the + untouched stream and expose it as separate attributes on a request + object. + + .. versionadded:: 0.8 + + :param stream_factory: An optional callable that returns a new read and + writeable file descriptor. This callable works + the same as :meth:`~BaseResponse._get_file_stream`. + :param charset: The character set for URL and url encoded form data. + :param errors: The encoding error behavior. + :param max_form_memory_size: the maximum number of bytes to be accepted for + in-memory stored form data. If the data + exceeds the value specified an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param max_content_length: If this is provided and the transmitted data + is longer than this value an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`MultiDict` is used. + :param silent: If set to False parsing errors will not be caught. + """ + + def __init__( + self, + stream_factory=None, + charset="utf-8", + errors="replace", + max_form_memory_size=None, + max_content_length=None, + cls=None, + silent=True, + ): + if stream_factory is None: + stream_factory = default_stream_factory + self.stream_factory = stream_factory + self.charset = charset + self.errors = errors + self.max_form_memory_size = max_form_memory_size + self.max_content_length = max_content_length + if cls is None: + cls = MultiDict + self.cls = cls + self.silent = silent + + def get_parse_func(self, mimetype, options): + return self.parse_functions.get(mimetype) + + def parse_from_environ(self, environ): + """Parses the information from the environment as form data. + + :param environ: the WSGI environment to be used for parsing. + :return: A tuple in the form ``(stream, form, files)``. + """ + content_type = environ.get("CONTENT_TYPE", "") + content_length = get_content_length(environ) + mimetype, options = parse_options_header(content_type) + return self.parse(get_input_stream(environ), mimetype, content_length, options) + + def parse(self, stream, mimetype, content_length, options=None): + """Parses the information from the given stream, mimetype, + content length and mimetype parameters. + + :param stream: an input stream + :param mimetype: the mimetype of the data + :param content_length: the content length of the incoming data + :param options: optional mimetype parameters (used for + the multipart boundary for instance) + :return: A tuple in the form ``(stream, form, files)``. + """ + if ( + self.max_content_length is not None + and content_length is not None + and content_length > self.max_content_length + ): + raise exceptions.RequestEntityTooLarge() + if options is None: + options = {} + + parse_func = self.get_parse_func(mimetype, options) + if parse_func is not None: + try: + return parse_func(self, stream, mimetype, content_length, options) + except ValueError: + if not self.silent: + raise + + return stream, self.cls(), self.cls() + + @exhaust_stream + def _parse_multipart(self, stream, mimetype, content_length, options): + parser = MultiPartParser( + self.stream_factory, + self.charset, + self.errors, + max_form_memory_size=self.max_form_memory_size, + cls=self.cls, + ) + boundary = options.get("boundary") + if boundary is None: + raise ValueError("Missing boundary") + if isinstance(boundary, text_type): + boundary = boundary.encode("ascii") + form, files = parser.parse(stream, boundary, content_length) + return stream, form, files + + @exhaust_stream + def _parse_urlencoded(self, stream, mimetype, content_length, options): + if ( + self.max_form_memory_size is not None + and content_length is not None + and content_length > self.max_form_memory_size + ): + raise exceptions.RequestEntityTooLarge() + form = url_decode_stream(stream, self.charset, errors=self.errors, cls=self.cls) + return stream, form, self.cls() + + #: mapping of mimetypes to parsing functions + parse_functions = { + "multipart/form-data": _parse_multipart, + "application/x-www-form-urlencoded": _parse_urlencoded, + "application/x-url-encoded": _parse_urlencoded, + } + + +def is_valid_multipart_boundary(boundary): + """Checks if the string given is a valid multipart boundary.""" + return _multipart_boundary_re.match(boundary) is not None + + +def _line_parse(line): + """Removes line ending characters and returns a tuple (`stripped_line`, + `is_terminated`). + """ + if line[-2:] in ["\r\n", b"\r\n"]: + return line[:-2], True + elif line[-1:] in ["\r", "\n", b"\r", b"\n"]: + return line[:-1], True + return line, False + + +def parse_multipart_headers(iterable): + """Parses multipart headers from an iterable that yields lines (including + the trailing newline symbol). The iterable has to be newline terminated. + + The iterable will stop at the line where the headers ended so it can be + further consumed. + + :param iterable: iterable of strings that are newline terminated + """ + result = [] + for line in iterable: + line = to_native(line) + line, line_terminated = _line_parse(line) + if not line_terminated: + raise ValueError("unexpected end of line in multipart header") + if not line: + break + elif line[0] in " \t" and result: + key, value = result[-1] + result[-1] = (key, value + "\n " + line[1:]) + else: + parts = line.split(":", 1) + if len(parts) == 2: + result.append((parts[0].strip(), parts[1].strip())) + + # we link the list to the headers, no need to create a copy, the + # list was not shared anyways. + return Headers(result) + + +_begin_form = "begin_form" +_begin_file = "begin_file" +_cont = "cont" +_end = "end" + + +class MultiPartParser(object): + def __init__( + self, + stream_factory=None, + charset="utf-8", + errors="replace", + max_form_memory_size=None, + cls=None, + buffer_size=64 * 1024, + ): + self.charset = charset + self.errors = errors + self.max_form_memory_size = max_form_memory_size + self.stream_factory = ( + default_stream_factory if stream_factory is None else stream_factory + ) + self.cls = MultiDict if cls is None else cls + + # make sure the buffer size is divisible by four so that we can base64 + # decode chunk by chunk + assert buffer_size % 4 == 0, "buffer size has to be divisible by 4" + # also the buffer size has to be at least 1024 bytes long or long headers + # will freak out the system + assert buffer_size >= 1024, "buffer size has to be at least 1KB" + + self.buffer_size = buffer_size + + def _fix_ie_filename(self, filename): + """Internet Explorer 6 transmits the full file name if a file is + uploaded. This function strips the full path if it thinks the + filename is Windows-like absolute. + """ + if filename[1:3] == ":\\" or filename[:2] == "\\\\": + return filename.split("\\")[-1] + return filename + + def _find_terminator(self, iterator): + """The terminator might have some additional newlines before it. + There is at least one application that sends additional newlines + before headers (the python setuptools package). + """ + for line in iterator: + if not line: + break + line = line.strip() + if line: + return line + return b"" + + def fail(self, message): + raise ValueError(message) + + def get_part_encoding(self, headers): + transfer_encoding = headers.get("content-transfer-encoding") + if ( + transfer_encoding is not None + and transfer_encoding in _supported_multipart_encodings + ): + return transfer_encoding + + def get_part_charset(self, headers): + # Figure out input charset for current part + content_type = headers.get("content-type") + if content_type: + mimetype, ct_params = parse_options_header(content_type) + return ct_params.get("charset", self.charset) + return self.charset + + def start_file_streaming(self, filename, headers, total_content_length): + if isinstance(filename, bytes): + filename = filename.decode(self.charset, self.errors) + filename = self._fix_ie_filename(filename) + content_type = headers.get("content-type") + try: + content_length = int(headers["content-length"]) + except (KeyError, ValueError): + content_length = 0 + container = self.stream_factory( + total_content_length=total_content_length, + filename=filename, + content_type=content_type, + content_length=content_length, + ) + return filename, container + + def in_memory_threshold_reached(self, bytes): + raise exceptions.RequestEntityTooLarge() + + def validate_boundary(self, boundary): + if not boundary: + self.fail("Missing boundary") + if not is_valid_multipart_boundary(boundary): + self.fail("Invalid boundary: %s" % boundary) + if len(boundary) > self.buffer_size: # pragma: no cover + # this should never happen because we check for a minimum size + # of 1024 and boundaries may not be longer than 200. The only + # situation when this happens is for non debug builds where + # the assert is skipped. + self.fail("Boundary longer than buffer size") + + def parse_lines(self, file, boundary, content_length, cap_at_buffer=True): + """Generate parts of + ``('begin_form', (headers, name))`` + ``('begin_file', (headers, name, filename))`` + ``('cont', bytestring)`` + ``('end', None)`` + + Always obeys the grammar + parts = ( begin_form cont* end | + begin_file cont* end )* + """ + next_part = b"--" + boundary + last_part = next_part + b"--" + + iterator = chain( + make_line_iter( + file, + limit=content_length, + buffer_size=self.buffer_size, + cap_at_buffer=cap_at_buffer, + ), + _empty_string_iter, + ) + + terminator = self._find_terminator(iterator) + + if terminator == last_part: + return + elif terminator != next_part: + self.fail("Expected boundary at start of multipart data") + + while terminator != last_part: + headers = parse_multipart_headers(iterator) + + disposition = headers.get("content-disposition") + if disposition is None: + self.fail("Missing Content-Disposition header") + disposition, extra = parse_options_header(disposition) + transfer_encoding = self.get_part_encoding(headers) + name = extra.get("name") + filename = extra.get("filename") + + # if no content type is given we stream into memory. A list is + # used as a temporary container. + if filename is None: + yield _begin_form, (headers, name) + + # otherwise we parse the rest of the headers and ask the stream + # factory for something we can write in. + else: + yield _begin_file, (headers, name, filename) + + buf = b"" + for line in iterator: + if not line: + self.fail("unexpected end of stream") + + if line[:2] == b"--": + terminator = line.rstrip() + if terminator in (next_part, last_part): + break + + if transfer_encoding is not None: + if transfer_encoding == "base64": + transfer_encoding = "base64_codec" + try: + line = codecs.decode(line, transfer_encoding) + except Exception: + self.fail("could not decode transfer encoded chunk") + + # we have something in the buffer from the last iteration. + # this is usually a newline delimiter. + if buf: + yield _cont, buf + buf = b"" + + # If the line ends with windows CRLF we write everything except + # the last two bytes. In all other cases however we write + # everything except the last byte. If it was a newline, that's + # fine, otherwise it does not matter because we will write it + # the next iteration. this ensures we do not write the + # final newline into the stream. That way we do not have to + # truncate the stream. However we do have to make sure that + # if something else than a newline is in there we write it + # out. + if line[-2:] == b"\r\n": + buf = b"\r\n" + cutoff = -2 + else: + buf = line[-1:] + cutoff = -1 + yield _cont, line[:cutoff] + + else: # pragma: no cover + raise ValueError("unexpected end of part") + + # if we have a leftover in the buffer that is not a newline + # character we have to flush it, otherwise we will chop of + # certain values. + if buf not in (b"", b"\r", b"\n", b"\r\n"): + yield _cont, buf + + yield _end, None + + def parse_parts(self, file, boundary, content_length): + """Generate ``('file', (name, val))`` and + ``('form', (name, val))`` parts. + """ + in_memory = 0 + + for ellt, ell in self.parse_lines(file, boundary, content_length): + if ellt == _begin_file: + headers, name, filename = ell + is_file = True + guard_memory = False + filename, container = self.start_file_streaming( + filename, headers, content_length + ) + _write = container.write + + elif ellt == _begin_form: + headers, name = ell + is_file = False + container = [] + _write = container.append + guard_memory = self.max_form_memory_size is not None + + elif ellt == _cont: + _write(ell) + # if we write into memory and there is a memory size limit we + # count the number of bytes in memory and raise an exception if + # there is too much data in memory. + if guard_memory: + in_memory += len(ell) + if in_memory > self.max_form_memory_size: + self.in_memory_threshold_reached(in_memory) + + elif ellt == _end: + if is_file: + container.seek(0) + yield ( + "file", + (name, FileStorage(container, filename, name, headers=headers)), + ) + else: + part_charset = self.get_part_charset(headers) + yield ( + "form", + (name, b"".join(container).decode(part_charset, self.errors)), + ) + + def parse(self, file, boundary, content_length): + formstream, filestream = tee( + self.parse_parts(file, boundary, content_length), 2 + ) + form = (p[1] for p in formstream if p[0] == "form") + files = (p[1] for p in filestream if p[0] == "file") + return self.cls(form), self.cls(files) diff --git a/venv/lib/python2.7/site-packages/werkzeug/http.py b/venv/lib/python2.7/site-packages/werkzeug/http.py new file mode 100644 index 00000000..686824c1 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/http.py @@ -0,0 +1,1259 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.http + ~~~~~~~~~~~~~ + + Werkzeug comes with a bunch of utilities that help Werkzeug to deal with + HTTP data. Most of the classes and functions provided by this module are + used by the wrappers, but they are useful on their own, too, especially if + the response and request objects are not used. + + This covers some of the more HTTP centric features of WSGI, some other + utilities such as cookie handling are documented in the `werkzeug.utils` + module. + + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import base64 +import re +import warnings +from datetime import datetime +from datetime import timedelta +from hashlib import md5 +from time import gmtime +from time import time + +from ._compat import integer_types +from ._compat import iteritems +from ._compat import PY2 +from ._compat import string_types +from ._compat import text_type +from ._compat import to_bytes +from ._compat import to_unicode +from ._compat import try_coerce_native +from ._internal import _cookie_parse_impl +from ._internal import _cookie_quote +from ._internal import _make_cookie_domain + +try: + from email.utils import parsedate_tz +except ImportError: + from email.Utils import parsedate_tz + +try: + from urllib.request import parse_http_list as _parse_list_header + from urllib.parse import unquote_to_bytes as _unquote +except ImportError: + from urllib2 import parse_http_list as _parse_list_header + from urllib2 import unquote as _unquote + +_cookie_charset = "latin1" +_basic_auth_charset = "utf-8" +# for explanation of "media-range", etc. see Sections 5.3.{1,2} of RFC 7231 +_accept_re = re.compile( + r""" + ( # media-range capturing-parenthesis + [^\s;,]+ # type/subtype + (?:[ \t]*;[ \t]* # ";" + (?: # parameter non-capturing-parenthesis + [^\s;,q][^\s;,]* # token that doesn't start with "q" + | # or + q[^\s;,=][^\s;,]* # token that is more than just "q" + ) + )* # zero or more parameters + ) # end of media-range + (?:[ \t]*;[ \t]*q= # weight is a "q" parameter + (\d*(?:\.\d+)?) # qvalue capturing-parentheses + [^,]* # "extension" accept params: who cares? + )? # accept params are optional + """, + re.VERBOSE, +) +_token_chars = frozenset( + "!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~" +) +_etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)') +_unsafe_header_chars = set('()<>@,;:"/[]?={} \t') +_option_header_piece_re = re.compile( + r""" + ;\s*,?\s* # newlines were replaced with commas + (?P + "[^"\\]*(?:\\.[^"\\]*)*" # quoted string + | + [^\s;,=*]+ # token + ) + (?:\*(?P\d+))? # *1, optional continuation index + \s* + (?: # optionally followed by =value + (?: # equals sign, possibly with encoding + \*\s*=\s* # * indicates extended notation + (?: # optional encoding + (?P[^\s]+?) + '(?P[^\s]*?)' + )? + | + =\s* # basic notation + ) + (?P + "[^"\\]*(?:\\.[^"\\]*)*" # quoted string + | + [^;,]+ # token + )? + )? + \s* + """, + flags=re.VERBOSE, +) +_option_header_start_mime_type = re.compile(r",\s*([^;,\s]+)([;,]\s*.+)?") + +_entity_headers = frozenset( + [ + "allow", + "content-encoding", + "content-language", + "content-length", + "content-location", + "content-md5", + "content-range", + "content-type", + "expires", + "last-modified", + ] +) +_hop_by_hop_headers = frozenset( + [ + "connection", + "keep-alive", + "proxy-authenticate", + "proxy-authorization", + "te", + "trailer", + "transfer-encoding", + "upgrade", + ] +) + + +HTTP_STATUS_CODES = { + 100: "Continue", + 101: "Switching Protocols", + 102: "Processing", + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 207: "Multi Status", + 226: "IM Used", # see RFC 3229 + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 307: "Temporary Redirect", + 308: "Permanent Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", # unused + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Request Entity Too Large", + 414: "Request URI Too Long", + 415: "Unsupported Media Type", + 416: "Requested Range Not Satisfiable", + 417: "Expectation Failed", + 418: "I'm a teapot", # see RFC 2324 + 421: "Misdirected Request", # see RFC 7540 + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 426: "Upgrade Required", + 428: "Precondition Required", # see RFC 6585 + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 449: "Retry With", # proprietary MS extension + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 507: "Insufficient Storage", + 510: "Not Extended", +} + + +def wsgi_to_bytes(data): + """coerce wsgi unicode represented bytes to real ones""" + if isinstance(data, bytes): + return data + return data.encode("latin1") # XXX: utf8 fallback? + + +def bytes_to_wsgi(data): + assert isinstance(data, bytes), "data must be bytes" + if isinstance(data, str): + return data + else: + return data.decode("latin1") + + +def quote_header_value(value, extra_chars="", allow_token=True): + """Quote a header value if necessary. + + .. versionadded:: 0.5 + + :param value: the value to quote. + :param extra_chars: a list of extra characters to skip quoting. + :param allow_token: if this is enabled token values are returned + unchanged. + """ + if isinstance(value, bytes): + value = bytes_to_wsgi(value) + value = str(value) + if allow_token: + token_chars = _token_chars | set(extra_chars) + if set(value).issubset(token_chars): + return value + return '"%s"' % value.replace("\\", "\\\\").replace('"', '\\"') + + +def unquote_header_value(value, is_filename=False): + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + .. versionadded:: 0.5 + + :param value: the header value to unquote. + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # replace sequence below on a UNC path has the effect of turning + # the leading double slash into a single slash and then + # _fix_ie_filename() doesn't work correctly. See #458. + if not is_filename or value[:2] != "\\\\": + return value.replace("\\\\", "\\").replace('\\"', '"') + return value + + +def dump_options_header(header, options): + """The reverse function to :func:`parse_options_header`. + + :param header: the header to dump + :param options: a dict of options to append. + """ + segments = [] + if header is not None: + segments.append(header) + for key, value in iteritems(options): + if value is None: + segments.append(key) + else: + segments.append("%s=%s" % (key, quote_header_value(value))) + return "; ".join(segments) + + +def dump_header(iterable, allow_token=True): + """Dump an HTTP header again. This is the reversal of + :func:`parse_list_header`, :func:`parse_set_header` and + :func:`parse_dict_header`. This also quotes strings that include an + equals sign unless you pass it as dict of key, value pairs. + + >>> dump_header({'foo': 'bar baz'}) + 'foo="bar baz"' + >>> dump_header(('foo', 'bar baz')) + 'foo, "bar baz"' + + :param iterable: the iterable or dict of values to quote. + :param allow_token: if set to `False` tokens as values are disallowed. + See :func:`quote_header_value` for more details. + """ + if isinstance(iterable, dict): + items = [] + for key, value in iteritems(iterable): + if value is None: + items.append(key) + else: + items.append( + "%s=%s" % (key, quote_header_value(value, allow_token=allow_token)) + ) + else: + items = [quote_header_value(x, allow_token=allow_token) for x in iterable] + return ", ".join(items) + + +def parse_list_header(value): + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + """ + result = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +def parse_dict_header(value, cls=dict): + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict (or any other mapping object created from + the type with a dict like interface provided by the `cls` argument): + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + .. versionchanged:: 0.9 + Added support for `cls` argument. + + :param value: a string with a dict header. + :param cls: callable to use for storage of parsed results. + :return: an instance of `cls` + """ + result = cls() + if not isinstance(value, text_type): + # XXX: validate + value = bytes_to_wsgi(value) + for item in _parse_list_header(value): + if "=" not in item: + result[item] = None + continue + name, value = item.split("=", 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +def parse_options_header(value, multiple=False): + """Parse a ``Content-Type`` like header into a tuple with the content + type and the options: + + >>> parse_options_header('text/html; charset=utf8') + ('text/html', {'charset': 'utf8'}) + + This should not be used to parse ``Cache-Control`` like headers that use + a slightly different format. For these headers use the + :func:`parse_dict_header` function. + + .. versionchanged:: 0.15 + :rfc:`2231` parameter continuations are handled. + + .. versionadded:: 0.5 + + :param value: the header to parse. + :param multiple: Whether try to parse and return multiple MIME types + :return: (mimetype, options) or (mimetype, options, mimetype, options, …) + if multiple=True + """ + if not value: + return "", {} + + result = [] + + value = "," + value.replace("\n", ",") + while value: + match = _option_header_start_mime_type.match(value) + if not match: + break + result.append(match.group(1)) # mimetype + options = {} + # Parse options + rest = match.group(2) + continued_encoding = None + while rest: + optmatch = _option_header_piece_re.match(rest) + if not optmatch: + break + option, count, encoding, language, option_value = optmatch.groups() + # Continuations don't have to supply the encoding after the + # first line. If we're in a continuation, track the current + # encoding to use for subsequent lines. Reset it when the + # continuation ends. + if not count: + continued_encoding = None + else: + if not encoding: + encoding = continued_encoding + continued_encoding = encoding + option = unquote_header_value(option) + if option_value is not None: + option_value = unquote_header_value(option_value, option == "filename") + if encoding is not None: + option_value = _unquote(option_value).decode(encoding) + if count: + # Continuations append to the existing value. For + # simplicity, this ignores the possibility of + # out-of-order indices, which shouldn't happen anyway. + options[option] = options.get(option, "") + option_value + else: + options[option] = option_value + rest = rest[optmatch.end() :] + result.append(options) + if multiple is False: + return tuple(result) + value = rest + + return tuple(result) if result else ("", {}) + + +def parse_accept_header(value, cls=None): + """Parses an HTTP Accept-* header. This does not implement a complete + valid algorithm but one that supports at least value and quality + extraction. + + Returns a new :class:`Accept` object (basically a list of ``(value, quality)`` + tuples sorted by the quality with some additional accessor methods). + + The second parameter can be a subclass of :class:`Accept` that is created + with the parsed values and returned. + + :param value: the accept header string to be parsed. + :param cls: the wrapper class for the return value (can be + :class:`Accept` or a subclass thereof) + :return: an instance of `cls`. + """ + if cls is None: + cls = Accept + + if not value: + return cls(None) + + result = [] + for match in _accept_re.finditer(value): + quality = match.group(2) + if not quality: + quality = 1 + else: + quality = max(min(float(quality), 1), 0) + result.append((match.group(1), quality)) + return cls(result) + + +def parse_cache_control_header(value, on_update=None, cls=None): + """Parse a cache control header. The RFC differs between response and + request cache control, this method does not. It's your responsibility + to not use the wrong control statements. + + .. versionadded:: 0.5 + The `cls` was added. If not specified an immutable + :class:`~werkzeug.datastructures.RequestCacheControl` is returned. + + :param value: a cache control header to be parsed. + :param on_update: an optional callable that is called every time a value + on the :class:`~werkzeug.datastructures.CacheControl` + object is changed. + :param cls: the class for the returned object. By default + :class:`~werkzeug.datastructures.RequestCacheControl` is used. + :return: a `cls` object. + """ + if cls is None: + cls = RequestCacheControl + if not value: + return cls(None, on_update) + return cls(parse_dict_header(value), on_update) + + +def parse_set_header(value, on_update=None): + """Parse a set-like header and return a + :class:`~werkzeug.datastructures.HeaderSet` object: + + >>> hs = parse_set_header('token, "quoted value"') + + The return value is an object that treats the items case-insensitively + and keeps the order of the items: + + >>> 'TOKEN' in hs + True + >>> hs.index('quoted value') + 1 + >>> hs + HeaderSet(['token', 'quoted value']) + + To create a header from the :class:`HeaderSet` again, use the + :func:`dump_header` function. + + :param value: a set header to be parsed. + :param on_update: an optional callable that is called every time a + value on the :class:`~werkzeug.datastructures.HeaderSet` + object is changed. + :return: a :class:`~werkzeug.datastructures.HeaderSet` + """ + if not value: + return HeaderSet(None, on_update) + return HeaderSet(parse_list_header(value), on_update) + + +def parse_authorization_header(value): + """Parse an HTTP basic/digest authorization header transmitted by the web + browser. The return value is either `None` if the header was invalid or + not given, otherwise an :class:`~werkzeug.datastructures.Authorization` + object. + + :param value: the authorization header to parse. + :return: a :class:`~werkzeug.datastructures.Authorization` object or `None`. + """ + if not value: + return + value = wsgi_to_bytes(value) + try: + auth_type, auth_info = value.split(None, 1) + auth_type = auth_type.lower() + except ValueError: + return + if auth_type == b"basic": + try: + username, password = base64.b64decode(auth_info).split(b":", 1) + except Exception: + return + return Authorization( + "basic", + { + "username": to_unicode(username, _basic_auth_charset), + "password": to_unicode(password, _basic_auth_charset), + }, + ) + elif auth_type == b"digest": + auth_map = parse_dict_header(auth_info) + for key in "username", "realm", "nonce", "uri", "response": + if key not in auth_map: + return + if "qop" in auth_map: + if not auth_map.get("nc") or not auth_map.get("cnonce"): + return + return Authorization("digest", auth_map) + + +def parse_www_authenticate_header(value, on_update=None): + """Parse an HTTP WWW-Authenticate header into a + :class:`~werkzeug.datastructures.WWWAuthenticate` object. + + :param value: a WWW-Authenticate header to parse. + :param on_update: an optional callable that is called every time a value + on the :class:`~werkzeug.datastructures.WWWAuthenticate` + object is changed. + :return: a :class:`~werkzeug.datastructures.WWWAuthenticate` object. + """ + if not value: + return WWWAuthenticate(on_update=on_update) + try: + auth_type, auth_info = value.split(None, 1) + auth_type = auth_type.lower() + except (ValueError, AttributeError): + return WWWAuthenticate(value.strip().lower(), on_update=on_update) + return WWWAuthenticate(auth_type, parse_dict_header(auth_info), on_update) + + +def parse_if_range_header(value): + """Parses an if-range header which can be an etag or a date. Returns + a :class:`~werkzeug.datastructures.IfRange` object. + + .. versionadded:: 0.7 + """ + if not value: + return IfRange() + date = parse_date(value) + if date is not None: + return IfRange(date=date) + # drop weakness information + return IfRange(unquote_etag(value)[0]) + + +def parse_range_header(value, make_inclusive=True): + """Parses a range header into a :class:`~werkzeug.datastructures.Range` + object. If the header is missing or malformed `None` is returned. + `ranges` is a list of ``(start, stop)`` tuples where the ranges are + non-inclusive. + + .. versionadded:: 0.7 + """ + if not value or "=" not in value: + return None + + ranges = [] + last_end = 0 + units, rng = value.split("=", 1) + units = units.strip().lower() + + for item in rng.split(","): + item = item.strip() + if "-" not in item: + return None + if item.startswith("-"): + if last_end < 0: + return None + try: + begin = int(item) + except ValueError: + return None + end = None + last_end = -1 + elif "-" in item: + begin, end = item.split("-", 1) + begin = begin.strip() + end = end.strip() + if not begin.isdigit(): + return None + begin = int(begin) + if begin < last_end or last_end < 0: + return None + if end: + if not end.isdigit(): + return None + end = int(end) + 1 + if begin >= end: + return None + else: + end = None + last_end = end + ranges.append((begin, end)) + + return Range(units, ranges) + + +def parse_content_range_header(value, on_update=None): + """Parses a range header into a + :class:`~werkzeug.datastructures.ContentRange` object or `None` if + parsing is not possible. + + .. versionadded:: 0.7 + + :param value: a content range header to be parsed. + :param on_update: an optional callable that is called every time a value + on the :class:`~werkzeug.datastructures.ContentRange` + object is changed. + """ + if value is None: + return None + try: + units, rangedef = (value or "").strip().split(None, 1) + except ValueError: + return None + + if "/" not in rangedef: + return None + rng, length = rangedef.split("/", 1) + if length == "*": + length = None + elif length.isdigit(): + length = int(length) + else: + return None + + if rng == "*": + return ContentRange(units, None, None, length, on_update=on_update) + elif "-" not in rng: + return None + + start, stop = rng.split("-", 1) + try: + start = int(start) + stop = int(stop) + 1 + except ValueError: + return None + + if is_byte_range_valid(start, stop, length): + return ContentRange(units, start, stop, length, on_update=on_update) + + +def quote_etag(etag, weak=False): + """Quote an etag. + + :param etag: the etag to quote. + :param weak: set to `True` to tag it "weak". + """ + if '"' in etag: + raise ValueError("invalid etag") + etag = '"%s"' % etag + if weak: + etag = "W/" + etag + return etag + + +def unquote_etag(etag): + """Unquote a single etag: + + >>> unquote_etag('W/"bar"') + ('bar', True) + >>> unquote_etag('"bar"') + ('bar', False) + + :param etag: the etag identifier to unquote. + :return: a ``(etag, weak)`` tuple. + """ + if not etag: + return None, None + etag = etag.strip() + weak = False + if etag.startswith(("W/", "w/")): + weak = True + etag = etag[2:] + if etag[:1] == etag[-1:] == '"': + etag = etag[1:-1] + return etag, weak + + +def parse_etags(value): + """Parse an etag header. + + :param value: the tag header to parse + :return: an :class:`~werkzeug.datastructures.ETags` object. + """ + if not value: + return ETags() + strong = [] + weak = [] + end = len(value) + pos = 0 + while pos < end: + match = _etag_re.match(value, pos) + if match is None: + break + is_weak, quoted, raw = match.groups() + if raw == "*": + return ETags(star_tag=True) + elif quoted: + raw = quoted + if is_weak: + weak.append(raw) + else: + strong.append(raw) + pos = match.end() + return ETags(strong, weak) + + +def generate_etag(data): + """Generate an etag for some data.""" + return md5(data).hexdigest() + + +def parse_date(value): + """Parse one of the following date formats into a datetime object: + + .. sourcecode:: text + + Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + + If parsing fails the return value is `None`. + + :param value: a string with a supported date format. + :return: a :class:`datetime.datetime` object. + """ + if value: + t = parsedate_tz(value.strip()) + if t is not None: + try: + year = t[0] + # unfortunately that function does not tell us if two digit + # years were part of the string, or if they were prefixed + # with two zeroes. So what we do is to assume that 69-99 + # refer to 1900, and everything below to 2000 + if year >= 0 and year <= 68: + year += 2000 + elif year >= 69 and year <= 99: + year += 1900 + return datetime(*((year,) + t[1:7])) - timedelta(seconds=t[-1] or 0) + except (ValueError, OverflowError): + return None + + +def _dump_date(d, delim): + """Used for `http_date` and `cookie_date`.""" + if d is None: + d = gmtime() + elif isinstance(d, datetime): + d = d.utctimetuple() + elif isinstance(d, (integer_types, float)): + d = gmtime(d) + return "%s, %02d%s%s%s%s %02d:%02d:%02d GMT" % ( + ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")[d.tm_wday], + d.tm_mday, + delim, + ( + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + )[d.tm_mon - 1], + delim, + str(d.tm_year), + d.tm_hour, + d.tm_min, + d.tm_sec, + ) + + +def cookie_date(expires=None): + """Formats the time to ensure compatibility with Netscape's cookie + standard. + + Accepts a floating point number expressed in seconds since the epoch in, a + datetime object or a timetuple. All times in UTC. The :func:`parse_date` + function can be used to parse such a date. + + Outputs a string in the format ``Wdy, DD-Mon-YYYY HH:MM:SS GMT``. + + :param expires: If provided that date is used, otherwise the current. + """ + return _dump_date(expires, "-") + + +def http_date(timestamp=None): + """Formats the time to match the RFC1123 date format. + + Accepts a floating point number expressed in seconds since the epoch in, a + datetime object or a timetuple. All times in UTC. The :func:`parse_date` + function can be used to parse such a date. + + Outputs a string in the format ``Wdy, DD Mon YYYY HH:MM:SS GMT``. + + :param timestamp: If provided that date is used, otherwise the current. + """ + return _dump_date(timestamp, " ") + + +def parse_age(value=None): + """Parses a base-10 integer count of seconds into a timedelta. + + If parsing fails, the return value is `None`. + + :param value: a string consisting of an integer represented in base-10 + :return: a :class:`datetime.timedelta` object or `None`. + """ + if not value: + return None + try: + seconds = int(value) + except ValueError: + return None + if seconds < 0: + return None + try: + return timedelta(seconds=seconds) + except OverflowError: + return None + + +def dump_age(age=None): + """Formats the duration as a base-10 integer. + + :param age: should be an integer number of seconds, + a :class:`datetime.timedelta` object, or, + if the age is unknown, `None` (default). + """ + if age is None: + return + if isinstance(age, timedelta): + # do the equivalent of Python 2.7's timedelta.total_seconds(), + # but disregarding fractional seconds + age = age.seconds + (age.days * 24 * 3600) + + age = int(age) + if age < 0: + raise ValueError("age cannot be negative") + + return str(age) + + +def is_resource_modified( + environ, etag=None, data=None, last_modified=None, ignore_if_range=True +): + """Convenience method for conditional requests. + + :param environ: the WSGI environment of the request to be checked. + :param etag: the etag for the response for comparison. + :param data: or alternatively the data of the response to automatically + generate an etag using :func:`generate_etag`. + :param last_modified: an optional date of the last modification. + :param ignore_if_range: If `False`, `If-Range` header will be taken into + account. + :return: `True` if the resource was modified, otherwise `False`. + """ + if etag is None and data is not None: + etag = generate_etag(data) + elif data is not None: + raise TypeError("both data and etag given") + if environ["REQUEST_METHOD"] not in ("GET", "HEAD"): + return False + + unmodified = False + if isinstance(last_modified, string_types): + last_modified = parse_date(last_modified) + + # ensure that microsecond is zero because the HTTP spec does not transmit + # that either and we might have some false positives. See issue #39 + if last_modified is not None: + last_modified = last_modified.replace(microsecond=0) + + if_range = None + if not ignore_if_range and "HTTP_RANGE" in environ: + # https://tools.ietf.org/html/rfc7233#section-3.2 + # A server MUST ignore an If-Range header field received in a request + # that does not contain a Range header field. + if_range = parse_if_range_header(environ.get("HTTP_IF_RANGE")) + + if if_range is not None and if_range.date is not None: + modified_since = if_range.date + else: + modified_since = parse_date(environ.get("HTTP_IF_MODIFIED_SINCE")) + + if modified_since and last_modified and last_modified <= modified_since: + unmodified = True + + if etag: + etag, _ = unquote_etag(etag) + if if_range is not None and if_range.etag is not None: + unmodified = parse_etags(if_range.etag).contains(etag) + else: + if_none_match = parse_etags(environ.get("HTTP_IF_NONE_MATCH")) + if if_none_match: + # https://tools.ietf.org/html/rfc7232#section-3.2 + # "A recipient MUST use the weak comparison function when comparing + # entity-tags for If-None-Match" + unmodified = if_none_match.contains_weak(etag) + + # https://tools.ietf.org/html/rfc7232#section-3.1 + # "Origin server MUST use the strong comparison function when + # comparing entity-tags for If-Match" + if_match = parse_etags(environ.get("HTTP_IF_MATCH")) + if if_match: + unmodified = not if_match.is_strong(etag) + + return not unmodified + + +def remove_entity_headers(headers, allowed=("expires", "content-location")): + """Remove all entity headers from a list or :class:`Headers` object. This + operation works in-place. `Expires` and `Content-Location` headers are + by default not removed. The reason for this is :rfc:`2616` section + 10.3.5 which specifies some entity headers that should be sent. + + .. versionchanged:: 0.5 + added `allowed` parameter. + + :param headers: a list or :class:`Headers` object. + :param allowed: a list of headers that should still be allowed even though + they are entity headers. + """ + allowed = set(x.lower() for x in allowed) + headers[:] = [ + (key, value) + for key, value in headers + if not is_entity_header(key) or key.lower() in allowed + ] + + +def remove_hop_by_hop_headers(headers): + """Remove all HTTP/1.1 "Hop-by-Hop" headers from a list or + :class:`Headers` object. This operation works in-place. + + .. versionadded:: 0.5 + + :param headers: a list or :class:`Headers` object. + """ + headers[:] = [ + (key, value) for key, value in headers if not is_hop_by_hop_header(key) + ] + + +def is_entity_header(header): + """Check if a header is an entity header. + + .. versionadded:: 0.5 + + :param header: the header to test. + :return: `True` if it's an entity header, `False` otherwise. + """ + return header.lower() in _entity_headers + + +def is_hop_by_hop_header(header): + """Check if a header is an HTTP/1.1 "Hop-by-Hop" header. + + .. versionadded:: 0.5 + + :param header: the header to test. + :return: `True` if it's an HTTP/1.1 "Hop-by-Hop" header, `False` otherwise. + """ + return header.lower() in _hop_by_hop_headers + + +def parse_cookie(header, charset="utf-8", errors="replace", cls=None): + """Parse a cookie. Either from a string or WSGI environ. + + Per default encoding errors are ignored. If you want a different behavior + you can set `errors` to ``'replace'`` or ``'strict'``. In strict mode a + :exc:`HTTPUnicodeError` is raised. + + .. versionchanged:: 0.5 + This function now returns a :class:`TypeConversionDict` instead of a + regular dict. The `cls` parameter was added. + + :param header: the header to be used to parse the cookie. Alternatively + this can be a WSGI environment. + :param charset: the charset for the cookie values. + :param errors: the error behavior for the charset decoding. + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`TypeConversionDict` is + used. + """ + if isinstance(header, dict): + header = header.get("HTTP_COOKIE", "") + elif header is None: + header = "" + + # If the value is an unicode string it's mangled through latin1. This + # is done because on PEP 3333 on Python 3 all headers are assumed latin1 + # which however is incorrect for cookies, which are sent in page encoding. + # As a result we + if isinstance(header, text_type): + header = header.encode("latin1", "replace") + + if cls is None: + cls = TypeConversionDict + + def _parse_pairs(): + for key, val in _cookie_parse_impl(header): + key = to_unicode(key, charset, errors, allow_none_charset=True) + if not key: + continue + val = to_unicode(val, charset, errors, allow_none_charset=True) + yield try_coerce_native(key), val + + return cls(_parse_pairs()) + + +def dump_cookie( + key, + value="", + max_age=None, + expires=None, + path="/", + domain=None, + secure=False, + httponly=False, + charset="utf-8", + sync_expires=True, + max_size=4093, + samesite=None, +): + """Creates a new Set-Cookie header without the ``Set-Cookie`` prefix + The parameters are the same as in the cookie Morsel object in the + Python standard library but it accepts unicode data, too. + + On Python 3 the return value of this function will be a unicode + string, on Python 2 it will be a native string. In both cases the + return value is usually restricted to ascii as the vast majority of + values are properly escaped, but that is no guarantee. If a unicode + string is returned it's tunneled through latin1 as required by + PEP 3333. + + The return value is not ASCII safe if the key contains unicode + characters. This is technically against the specification but + happens in the wild. It's strongly recommended to not use + non-ASCII values for the keys. + + :param max_age: should be a number of seconds, or `None` (default) if + the cookie should last only as long as the client's + browser session. Additionally `timedelta` objects + are accepted, too. + :param expires: should be a `datetime` object or unix timestamp. + :param path: limits the cookie to a given path, per default it will + span the whole domain. + :param domain: Use this if you want to set a cross-domain cookie. For + example, ``domain=".example.com"`` will set a cookie + that is readable by the domain ``www.example.com``, + ``foo.example.com`` etc. Otherwise, a cookie will only + be readable by the domain that set it. + :param secure: The cookie will only be available via HTTPS + :param httponly: disallow JavaScript to access the cookie. This is an + extension to the cookie standard and probably not + supported by all browsers. + :param charset: the encoding for unicode values. + :param sync_expires: automatically set expires if max_age is defined + but expires not. + :param max_size: Warn if the final header value exceeds this size. The + default, 4093, should be safely `supported by most browsers + `_. Set to 0 to disable this check. + :param samesite: Limits the scope of the cookie such that it will only + be attached to requests if those requests are "same-site". + + .. _`cookie`: http://browsercookielimits.squawky.net/ + """ + key = to_bytes(key, charset) + value = to_bytes(value, charset) + + if path is not None: + from .urls import iri_to_uri + + path = iri_to_uri(path, charset) + domain = _make_cookie_domain(domain) + if isinstance(max_age, timedelta): + max_age = (max_age.days * 60 * 60 * 24) + max_age.seconds + if expires is not None: + if not isinstance(expires, string_types): + expires = cookie_date(expires) + elif max_age is not None and sync_expires: + expires = to_bytes(cookie_date(time() + max_age)) + + samesite = samesite.title() if samesite else None + if samesite not in ("Strict", "Lax", None): + raise ValueError("invalid SameSite value; must be 'Strict', 'Lax' or None") + + buf = [key + b"=" + _cookie_quote(value)] + + # XXX: In theory all of these parameters that are not marked with `None` + # should be quoted. Because stdlib did not quote it before I did not + # want to introduce quoting there now. + for k, v, q in ( + (b"Domain", domain, True), + (b"Expires", expires, False), + (b"Max-Age", max_age, False), + (b"Secure", secure, None), + (b"HttpOnly", httponly, None), + (b"Path", path, False), + (b"SameSite", samesite, False), + ): + if q is None: + if v: + buf.append(k) + continue + + if v is None: + continue + + tmp = bytearray(k) + if not isinstance(v, (bytes, bytearray)): + v = to_bytes(text_type(v), charset) + if q: + v = _cookie_quote(v) + tmp += b"=" + v + buf.append(bytes(tmp)) + + # The return value will be an incorrectly encoded latin1 header on + # Python 3 for consistency with the headers object and a bytestring + # on Python 2 because that's how the API makes more sense. + rv = b"; ".join(buf) + if not PY2: + rv = rv.decode("latin1") + + # Warn if the final value of the cookie is less than the limit. If the + # cookie is too large, then it may be silently ignored, which can be quite + # hard to debug. + cookie_size = len(rv) + + if max_size and cookie_size > max_size: + value_size = len(value) + warnings.warn( + 'The "{key}" cookie is too large: the value was {value_size} bytes' + " but the header required {extra_size} extra bytes. The final size" + " was {cookie_size} bytes but the limit is {max_size} bytes." + " Browsers may silently ignore cookies larger than this.".format( + key=key, + value_size=value_size, + extra_size=cookie_size - value_size, + cookie_size=cookie_size, + max_size=max_size, + ), + stacklevel=2, + ) + + return rv + + +def is_byte_range_valid(start, stop, length): + """Checks if a given byte content range is valid for the given length. + + .. versionadded:: 0.7 + """ + if (start is None) != (stop is None): + return False + elif start is None: + return length is None or length >= 0 + elif length is None: + return 0 <= start < stop + elif start >= stop: + return False + return 0 <= start < length + + +# circular dependencies +from .datastructures import Accept +from .datastructures import Authorization +from .datastructures import ContentRange +from .datastructures import ETags +from .datastructures import HeaderSet +from .datastructures import IfRange +from .datastructures import Range +from .datastructures import RequestCacheControl +from .datastructures import TypeConversionDict +from .datastructures import WWWAuthenticate + +from werkzeug import _DeprecatedImportModule + +_DeprecatedImportModule( + __name__, + {".datastructures": ["CharsetAccept", "Headers", "LanguageAccept", "MIMEAccept"]}, + "Werkzeug 1.0", +) +del _DeprecatedImportModule diff --git a/venv/lib/python2.7/site-packages/werkzeug/local.py b/venv/lib/python2.7/site-packages/werkzeug/local.py new file mode 100644 index 00000000..9a6088cc --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/local.py @@ -0,0 +1,421 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.local + ~~~~~~~~~~~~~~ + + This module implements context-local objects. + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import copy +from functools import update_wrapper + +from ._compat import implements_bool +from ._compat import PY2 +from .wsgi import ClosingIterator + +# since each thread has its own greenlet we can just use those as identifiers +# for the context. If greenlets are not available we fall back to the +# current thread ident depending on where it is. +try: + from greenlet import getcurrent as get_ident +except ImportError: + try: + from thread import get_ident + except ImportError: + from _thread import get_ident + + +def release_local(local): + """Releases the contents of the local for the current context. + This makes it possible to use locals without a manager. + + Example:: + + >>> loc = Local() + >>> loc.foo = 42 + >>> release_local(loc) + >>> hasattr(loc, 'foo') + False + + With this function one can release :class:`Local` objects as well + as :class:`LocalStack` objects. However it is not possible to + release data held by proxies that way, one always has to retain + a reference to the underlying local object in order to be able + to release it. + + .. versionadded:: 0.6.1 + """ + local.__release_local__() + + +class Local(object): + __slots__ = ("__storage__", "__ident_func__") + + def __init__(self): + object.__setattr__(self, "__storage__", {}) + object.__setattr__(self, "__ident_func__", get_ident) + + def __iter__(self): + return iter(self.__storage__.items()) + + def __call__(self, proxy): + """Create a proxy for a name.""" + return LocalProxy(self, proxy) + + def __release_local__(self): + self.__storage__.pop(self.__ident_func__(), None) + + def __getattr__(self, name): + try: + return self.__storage__[self.__ident_func__()][name] + except KeyError: + raise AttributeError(name) + + def __setattr__(self, name, value): + ident = self.__ident_func__() + storage = self.__storage__ + try: + storage[ident][name] = value + except KeyError: + storage[ident] = {name: value} + + def __delattr__(self, name): + try: + del self.__storage__[self.__ident_func__()][name] + except KeyError: + raise AttributeError(name) + + +class LocalStack(object): + """This class works similar to a :class:`Local` but keeps a stack + of objects instead. This is best explained with an example:: + + >>> ls = LocalStack() + >>> ls.push(42) + >>> ls.top + 42 + >>> ls.push(23) + >>> ls.top + 23 + >>> ls.pop() + 23 + >>> ls.top + 42 + + They can be force released by using a :class:`LocalManager` or with + the :func:`release_local` function but the correct way is to pop the + item from the stack after using. When the stack is empty it will + no longer be bound to the current context (and as such released). + + By calling the stack without arguments it returns a proxy that resolves to + the topmost item on the stack. + + .. versionadded:: 0.6.1 + """ + + def __init__(self): + self._local = Local() + + def __release_local__(self): + self._local.__release_local__() + + def _get__ident_func__(self): + return self._local.__ident_func__ + + def _set__ident_func__(self, value): + object.__setattr__(self._local, "__ident_func__", value) + + __ident_func__ = property(_get__ident_func__, _set__ident_func__) + del _get__ident_func__, _set__ident_func__ + + def __call__(self): + def _lookup(): + rv = self.top + if rv is None: + raise RuntimeError("object unbound") + return rv + + return LocalProxy(_lookup) + + def push(self, obj): + """Pushes a new item to the stack""" + rv = getattr(self._local, "stack", None) + if rv is None: + self._local.stack = rv = [] + rv.append(obj) + return rv + + def pop(self): + """Removes the topmost item from the stack, will return the + old value or `None` if the stack was already empty. + """ + stack = getattr(self._local, "stack", None) + if stack is None: + return None + elif len(stack) == 1: + release_local(self._local) + return stack[-1] + else: + return stack.pop() + + @property + def top(self): + """The topmost item on the stack. If the stack is empty, + `None` is returned. + """ + try: + return self._local.stack[-1] + except (AttributeError, IndexError): + return None + + +class LocalManager(object): + """Local objects cannot manage themselves. For that you need a local + manager. You can pass a local manager multiple locals or add them later + by appending them to `manager.locals`. Every time the manager cleans up, + it will clean up all the data left in the locals for this context. + + The `ident_func` parameter can be added to override the default ident + function for the wrapped locals. + + .. versionchanged:: 0.6.1 + Instead of a manager the :func:`release_local` function can be used + as well. + + .. versionchanged:: 0.7 + `ident_func` was added. + """ + + def __init__(self, locals=None, ident_func=None): + if locals is None: + self.locals = [] + elif isinstance(locals, Local): + self.locals = [locals] + else: + self.locals = list(locals) + if ident_func is not None: + self.ident_func = ident_func + for local in self.locals: + object.__setattr__(local, "__ident_func__", ident_func) + else: + self.ident_func = get_ident + + def get_ident(self): + """Return the context identifier the local objects use internally for + this context. You cannot override this method to change the behavior + but use it to link other context local objects (such as SQLAlchemy's + scoped sessions) to the Werkzeug locals. + + .. versionchanged:: 0.7 + You can pass a different ident function to the local manager that + will then be propagated to all the locals passed to the + constructor. + """ + return self.ident_func() + + def cleanup(self): + """Manually clean up the data in the locals for this context. Call + this at the end of the request or use `make_middleware()`. + """ + for local in self.locals: + release_local(local) + + def make_middleware(self, app): + """Wrap a WSGI application so that cleaning up happens after + request end. + """ + + def application(environ, start_response): + return ClosingIterator(app(environ, start_response), self.cleanup) + + return application + + def middleware(self, func): + """Like `make_middleware` but for decorating functions. + + Example usage:: + + @manager.middleware + def application(environ, start_response): + ... + + The difference to `make_middleware` is that the function passed + will have all the arguments copied from the inner application + (name, docstring, module). + """ + return update_wrapper(self.make_middleware(func), func) + + def __repr__(self): + return "<%s storages: %d>" % (self.__class__.__name__, len(self.locals)) + + +@implements_bool +class LocalProxy(object): + """Acts as a proxy for a werkzeug local. Forwards all operations to + a proxied object. The only operations not supported for forwarding + are right handed operands and any kind of assignment. + + Example usage:: + + from werkzeug.local import Local + l = Local() + + # these are proxies + request = l('request') + user = l('user') + + + from werkzeug.local import LocalStack + _response_local = LocalStack() + + # this is a proxy + response = _response_local() + + Whenever something is bound to l.user / l.request the proxy objects + will forward all operations. If no object is bound a :exc:`RuntimeError` + will be raised. + + To create proxies to :class:`Local` or :class:`LocalStack` objects, + call the object as shown above. If you want to have a proxy to an + object looked up by a function, you can (as of Werkzeug 0.6.1) pass + a function to the :class:`LocalProxy` constructor:: + + session = LocalProxy(lambda: get_current_request().session) + + .. versionchanged:: 0.6.1 + The class can be instantiated with a callable as well now. + """ + + __slots__ = ("__local", "__dict__", "__name__", "__wrapped__") + + def __init__(self, local, name=None): + object.__setattr__(self, "_LocalProxy__local", local) + object.__setattr__(self, "__name__", name) + if callable(local) and not hasattr(local, "__release_local__"): + # "local" is a callable that is not an instance of Local or + # LocalManager: mark it as a wrapped function. + object.__setattr__(self, "__wrapped__", local) + + def _get_current_object(self): + """Return the current object. This is useful if you want the real + object behind the proxy at a time for performance reasons or because + you want to pass the object into a different context. + """ + if not hasattr(self.__local, "__release_local__"): + return self.__local() + try: + return getattr(self.__local, self.__name__) + except AttributeError: + raise RuntimeError("no object bound to %s" % self.__name__) + + @property + def __dict__(self): + try: + return self._get_current_object().__dict__ + except RuntimeError: + raise AttributeError("__dict__") + + def __repr__(self): + try: + obj = self._get_current_object() + except RuntimeError: + return "<%s unbound>" % self.__class__.__name__ + return repr(obj) + + def __bool__(self): + try: + return bool(self._get_current_object()) + except RuntimeError: + return False + + def __unicode__(self): + try: + return unicode(self._get_current_object()) # noqa + except RuntimeError: + return repr(self) + + def __dir__(self): + try: + return dir(self._get_current_object()) + except RuntimeError: + return [] + + def __getattr__(self, name): + if name == "__members__": + return dir(self._get_current_object()) + return getattr(self._get_current_object(), name) + + def __setitem__(self, key, value): + self._get_current_object()[key] = value + + def __delitem__(self, key): + del self._get_current_object()[key] + + if PY2: + __getslice__ = lambda x, i, j: x._get_current_object()[i:j] + + def __setslice__(self, i, j, seq): + self._get_current_object()[i:j] = seq + + def __delslice__(self, i, j): + del self._get_current_object()[i:j] + + __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v) + __delattr__ = lambda x, n: delattr(x._get_current_object(), n) + __str__ = lambda x: str(x._get_current_object()) + __lt__ = lambda x, o: x._get_current_object() < o + __le__ = lambda x, o: x._get_current_object() <= o + __eq__ = lambda x, o: x._get_current_object() == o + __ne__ = lambda x, o: x._get_current_object() != o + __gt__ = lambda x, o: x._get_current_object() > o + __ge__ = lambda x, o: x._get_current_object() >= o + __cmp__ = lambda x, o: cmp(x._get_current_object(), o) # noqa + __hash__ = lambda x: hash(x._get_current_object()) + __call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw) + __len__ = lambda x: len(x._get_current_object()) + __getitem__ = lambda x, i: x._get_current_object()[i] + __iter__ = lambda x: iter(x._get_current_object()) + __contains__ = lambda x, i: i in x._get_current_object() + __add__ = lambda x, o: x._get_current_object() + o + __sub__ = lambda x, o: x._get_current_object() - o + __mul__ = lambda x, o: x._get_current_object() * o + __floordiv__ = lambda x, o: x._get_current_object() // o + __mod__ = lambda x, o: x._get_current_object() % o + __divmod__ = lambda x, o: x._get_current_object().__divmod__(o) + __pow__ = lambda x, o: x._get_current_object() ** o + __lshift__ = lambda x, o: x._get_current_object() << o + __rshift__ = lambda x, o: x._get_current_object() >> o + __and__ = lambda x, o: x._get_current_object() & o + __xor__ = lambda x, o: x._get_current_object() ^ o + __or__ = lambda x, o: x._get_current_object() | o + __div__ = lambda x, o: x._get_current_object().__div__(o) + __truediv__ = lambda x, o: x._get_current_object().__truediv__(o) + __neg__ = lambda x: -(x._get_current_object()) + __pos__ = lambda x: +(x._get_current_object()) + __abs__ = lambda x: abs(x._get_current_object()) + __invert__ = lambda x: ~(x._get_current_object()) + __complex__ = lambda x: complex(x._get_current_object()) + __int__ = lambda x: int(x._get_current_object()) + __long__ = lambda x: long(x._get_current_object()) # noqa + __float__ = lambda x: float(x._get_current_object()) + __oct__ = lambda x: oct(x._get_current_object()) + __hex__ = lambda x: hex(x._get_current_object()) + __index__ = lambda x: x._get_current_object().__index__() + __coerce__ = lambda x, o: x._get_current_object().__coerce__(x, o) + __enter__ = lambda x: x._get_current_object().__enter__() + __exit__ = lambda x, *a, **kw: x._get_current_object().__exit__(*a, **kw) + __radd__ = lambda x, o: o + x._get_current_object() + __rsub__ = lambda x, o: o - x._get_current_object() + __rmul__ = lambda x, o: o * x._get_current_object() + __rdiv__ = lambda x, o: o / x._get_current_object() + if PY2: + __rtruediv__ = lambda x, o: x._get_current_object().__rtruediv__(o) + else: + __rtruediv__ = __rdiv__ + __rfloordiv__ = lambda x, o: o // x._get_current_object() + __rmod__ = lambda x, o: o % x._get_current_object() + __rdivmod__ = lambda x, o: x._get_current_object().__rdivmod__(o) + __copy__ = lambda x: copy.copy(x._get_current_object()) + __deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo) diff --git a/venv/lib/python2.7/site-packages/werkzeug/middleware/__init__.py b/venv/lib/python2.7/site-packages/werkzeug/middleware/__init__.py new file mode 100644 index 00000000..5e049f5e --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/middleware/__init__.py @@ -0,0 +1,25 @@ +""" +Middleware +========== + +A WSGI middleware is a WSGI application that wraps another application +in order to observe or change its behavior. Werkzeug provides some +middleware for common use cases. + +.. toctree:: + :maxdepth: 1 + + proxy_fix + shared_data + dispatcher + http_proxy + lint + profiler + +The :doc:`interactive debugger ` is also a middleware that can +be applied manually, although it is typically used automatically with +the :doc:`development server `. + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" diff --git a/venv/lib/python2.7/site-packages/werkzeug/middleware/dispatcher.py b/venv/lib/python2.7/site-packages/werkzeug/middleware/dispatcher.py new file mode 100644 index 00000000..2eb173e0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/middleware/dispatcher.py @@ -0,0 +1,66 @@ +""" +Application Dispatcher +====================== + +This middleware creates a single WSGI application that dispatches to +multiple other WSGI applications mounted at different URL paths. + +A common example is writing a Single Page Application, where you have a +backend API and a frontend written in JavaScript that does the routing +in the browser rather than requesting different pages from the server. +The frontend is a single HTML and JS file that should be served for any +path besides "/api". + +This example dispatches to an API app under "/api", an admin app +under "/admin", and an app that serves frontend files for all other +requests:: + + app = DispatcherMiddleware(serve_frontend, { + '/api': api_app, + '/admin': admin_app, + }) + +In production, you might instead handle this at the HTTP server level, +serving files or proxying to application servers based on location. The +API and admin apps would each be deployed with a separate WSGI server, +and the static files would be served directly by the HTTP server. + +.. autoclass:: DispatcherMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" + + +class DispatcherMiddleware(object): + """Combine multiple applications as a single WSGI application. + Requests are dispatched to an application based on the path it is + mounted under. + + :param app: The WSGI application to dispatch to if the request + doesn't match a mounted path. + :param mounts: Maps path prefixes to applications for dispatching. + """ + + def __init__(self, app, mounts=None): + self.app = app + self.mounts = mounts or {} + + def __call__(self, environ, start_response): + script = environ.get("PATH_INFO", "") + path_info = "" + + while "/" in script: + if script in self.mounts: + app = self.mounts[script] + break + + script, last_item = script.rsplit("/", 1) + path_info = "/%s%s" % (last_item, path_info) + else: + app = self.mounts.get(script, self.app) + + original_script_name = environ.get("SCRIPT_NAME", "") + environ["SCRIPT_NAME"] = original_script_name + script + environ["PATH_INFO"] = path_info + return app(environ, start_response) diff --git a/venv/lib/python2.7/site-packages/werkzeug/middleware/http_proxy.py b/venv/lib/python2.7/site-packages/werkzeug/middleware/http_proxy.py new file mode 100644 index 00000000..bfdc0712 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/middleware/http_proxy.py @@ -0,0 +1,219 @@ +""" +Basic HTTP Proxy +================ + +.. autoclass:: ProxyMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import socket + +from ..datastructures import EnvironHeaders +from ..http import is_hop_by_hop_header +from ..urls import url_parse +from ..urls import url_quote +from ..wsgi import get_input_stream + +try: + from http import client +except ImportError: + import httplib as client + + +class ProxyMiddleware(object): + """Proxy requests under a path to an external server, routing other + requests to the app. + + This middleware can only proxy HTTP requests, as that is the only + protocol handled by the WSGI server. Other protocols, such as + websocket requests, cannot be proxied at this layer. This should + only be used for development, in production a real proxying server + should be used. + + The middleware takes a dict that maps a path prefix to a dict + describing the host to be proxied to:: + + app = ProxyMiddleware(app, { + "/static/": { + "target": "http://127.0.0.1:5001/", + } + }) + + Each host has the following options: + + ``target``: + The target URL to dispatch to. This is required. + ``remove_prefix``: + Whether to remove the prefix from the URL before dispatching it + to the target. The default is ``False``. + ``host``: + ``""`` (default): + The host header is automatically rewritten to the URL of the + target. + ``None``: + The host header is unmodified from the client request. + Any other value: + The host header is overwritten with the value. + ``headers``: + A dictionary of headers to be sent with the request to the + target. The default is ``{}``. + ``ssl_context``: + A :class:`ssl.SSLContext` defining how to verify requests if the + target is HTTPS. The default is ``None``. + + In the example above, everything under ``"/static/"`` is proxied to + the server on port 5001. The host header is rewritten to the target, + and the ``"/static/"`` prefix is removed from the URLs. + + :param app: The WSGI application to wrap. + :param targets: Proxy target configurations. See description above. + :param chunk_size: Size of chunks to read from input stream and + write to target. + :param timeout: Seconds before an operation to a target fails. + + .. versionadded:: 0.14 + """ + + def __init__(self, app, targets, chunk_size=2 << 13, timeout=10): + def _set_defaults(opts): + opts.setdefault("remove_prefix", False) + opts.setdefault("host", "") + opts.setdefault("headers", {}) + opts.setdefault("ssl_context", None) + return opts + + self.app = app + self.targets = dict( + ("/%s/" % k.strip("/"), _set_defaults(v)) for k, v in targets.items() + ) + self.chunk_size = chunk_size + self.timeout = timeout + + def proxy_to(self, opts, path, prefix): + target = url_parse(opts["target"]) + + def application(environ, start_response): + headers = list(EnvironHeaders(environ).items()) + headers[:] = [ + (k, v) + for k, v in headers + if not is_hop_by_hop_header(k) + and k.lower() not in ("content-length", "host") + ] + headers.append(("Connection", "close")) + + if opts["host"] == "": + headers.append(("Host", target.ascii_host)) + elif opts["host"] is None: + headers.append(("Host", environ["HTTP_HOST"])) + else: + headers.append(("Host", opts["host"])) + + headers.extend(opts["headers"].items()) + remote_path = path + + if opts["remove_prefix"]: + remote_path = "%s/%s" % ( + target.path.rstrip("/"), + remote_path[len(prefix) :].lstrip("/"), + ) + + content_length = environ.get("CONTENT_LENGTH") + chunked = False + + if content_length not in ("", None): + headers.append(("Content-Length", content_length)) + elif content_length is not None: + headers.append(("Transfer-Encoding", "chunked")) + chunked = True + + try: + if target.scheme == "http": + con = client.HTTPConnection( + target.ascii_host, target.port or 80, timeout=self.timeout + ) + elif target.scheme == "https": + con = client.HTTPSConnection( + target.ascii_host, + target.port or 443, + timeout=self.timeout, + context=opts["ssl_context"], + ) + else: + raise RuntimeError( + "Target scheme must be 'http' or 'https', got '{}'.".format( + target.scheme + ) + ) + + con.connect() + remote_url = url_quote(remote_path) + querystring = environ["QUERY_STRING"] + + if querystring: + remote_url = remote_url + "?" + querystring + + con.putrequest(environ["REQUEST_METHOD"], remote_url, skip_host=True) + + for k, v in headers: + if k.lower() == "connection": + v = "close" + + con.putheader(k, v) + + con.endheaders() + stream = get_input_stream(environ) + + while 1: + data = stream.read(self.chunk_size) + + if not data: + break + + if chunked: + con.send(b"%x\r\n%s\r\n" % (len(data), data)) + else: + con.send(data) + + resp = con.getresponse() + except socket.error: + from ..exceptions import BadGateway + + return BadGateway()(environ, start_response) + + start_response( + "%d %s" % (resp.status, resp.reason), + [ + (k.title(), v) + for k, v in resp.getheaders() + if not is_hop_by_hop_header(k) + ], + ) + + def read(): + while 1: + try: + data = resp.read(self.chunk_size) + except socket.error: + break + + if not data: + break + + yield data + + return read() + + return application + + def __call__(self, environ, start_response): + path = environ["PATH_INFO"] + app = self.app + + for prefix, opts in self.targets.items(): + if path.startswith(prefix): + app = self.proxy_to(opts, path, prefix) + break + + return app(environ, start_response) diff --git a/venv/lib/python2.7/site-packages/werkzeug/middleware/lint.py b/venv/lib/python2.7/site-packages/werkzeug/middleware/lint.py new file mode 100644 index 00000000..98f95817 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/middleware/lint.py @@ -0,0 +1,408 @@ +""" +WSGI Protocol Linter +==================== + +This module provides a middleware that performs sanity checks on the +behavior of the WSGI server and application. It checks that the +:pep:`3333` WSGI spec is properly implemented. It also warns on some +common HTTP errors such as non-empty responses for 304 status codes. + +.. autoclass:: LintMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +from warnings import warn + +from .._compat import implements_iterator +from .._compat import PY2 +from .._compat import string_types +from ..datastructures import Headers +from ..http import is_entity_header +from ..wsgi import FileWrapper + +try: + from urllib.parse import urlparse +except ImportError: + from urlparse import urlparse + + +class WSGIWarning(Warning): + """Warning class for WSGI warnings.""" + + +class HTTPWarning(Warning): + """Warning class for HTTP warnings.""" + + +def check_string(context, obj, stacklevel=3): + if type(obj) is not str: + warn( + "'%s' requires strings, got '%s'" % (context, type(obj).__name__), + WSGIWarning, + ) + + +class InputStream(object): + def __init__(self, stream): + self._stream = stream + + def read(self, *args): + if len(args) == 0: + warn( + "WSGI does not guarantee an EOF marker on the input stream, thus making" + " calls to 'wsgi.input.read()' unsafe. Conforming servers may never" + " return from this call.", + WSGIWarning, + stacklevel=2, + ) + elif len(args) != 1: + warn( + "Too many parameters passed to 'wsgi.input.read()'.", + WSGIWarning, + stacklevel=2, + ) + return self._stream.read(*args) + + def readline(self, *args): + if len(args) == 0: + warn( + "Calls to 'wsgi.input.readline()' without arguments are unsafe. Use" + " 'wsgi.input.read()' instead.", + WSGIWarning, + stacklevel=2, + ) + elif len(args) == 1: + warn( + "'wsgi.input.readline()' was called with a size hint. WSGI does not" + " support this, although it's available on all major servers.", + WSGIWarning, + stacklevel=2, + ) + else: + raise TypeError("Too many arguments passed to 'wsgi.input.readline()'.") + return self._stream.readline(*args) + + def __iter__(self): + try: + return iter(self._stream) + except TypeError: + warn("'wsgi.input' is not iterable.", WSGIWarning, stacklevel=2) + return iter(()) + + def close(self): + warn("The application closed the input stream!", WSGIWarning, stacklevel=2) + self._stream.close() + + +class ErrorStream(object): + def __init__(self, stream): + self._stream = stream + + def write(self, s): + check_string("wsgi.error.write()", s) + self._stream.write(s) + + def flush(self): + self._stream.flush() + + def writelines(self, seq): + for line in seq: + self.write(line) + + def close(self): + warn("The application closed the error stream!", WSGIWarning, stacklevel=2) + self._stream.close() + + +class GuardedWrite(object): + def __init__(self, write, chunks): + self._write = write + self._chunks = chunks + + def __call__(self, s): + check_string("write()", s) + self._write.write(s) + self._chunks.append(len(s)) + + +@implements_iterator +class GuardedIterator(object): + def __init__(self, iterator, headers_set, chunks): + self._iterator = iterator + if PY2: + self._next = iter(iterator).next + else: + self._next = iter(iterator).__next__ + self.closed = False + self.headers_set = headers_set + self.chunks = chunks + + def __iter__(self): + return self + + def __next__(self): + if self.closed: + warn("Iterated over closed 'app_iter'.", WSGIWarning, stacklevel=2) + + rv = self._next() + + if not self.headers_set: + warn( + "The application returned before it started the response.", + WSGIWarning, + stacklevel=2, + ) + + check_string("application iterator items", rv) + self.chunks.append(len(rv)) + return rv + + def close(self): + self.closed = True + + if hasattr(self._iterator, "close"): + self._iterator.close() + + if self.headers_set: + status_code, headers = self.headers_set + bytes_sent = sum(self.chunks) + content_length = headers.get("content-length", type=int) + + if status_code == 304: + for key, _value in headers: + key = key.lower() + if key not in ("expires", "content-location") and is_entity_header( + key + ): + warn( + "Entity header %r found in 304 response." % key, HTTPWarning + ) + if bytes_sent: + warn("304 responses must not have a body.", HTTPWarning) + elif 100 <= status_code < 200 or status_code == 204: + if content_length != 0: + warn( + "%r responses must have an empty content length." % status_code, + HTTPWarning, + ) + if bytes_sent: + warn( + "%r responses must not have a body." % status_code, HTTPWarning + ) + elif content_length is not None and content_length != bytes_sent: + warn( + "Content-Length and the number of bytes sent to the client do not" + " match.", + WSGIWarning, + ) + + def __del__(self): + if not self.closed: + try: + warn( + "Iterator was garbage collected before it was closed.", WSGIWarning + ) + except Exception: + pass + + +class LintMiddleware(object): + """Warns about common errors in the WSGI and HTTP behavior of the + server and wrapped application. Some of the issues it check are: + + - invalid status codes + - non-bytestrings sent to the WSGI server + - strings returned from the WSGI application + - non-empty conditional responses + - unquoted etags + - relative URLs in the Location header + - unsafe calls to wsgi.input + - unclosed iterators + + Error information is emitted using the :mod:`warnings` module. + + :param app: The WSGI application to wrap. + + .. code-block:: python + + from werkzeug.middleware.lint import LintMiddleware + app = LintMiddleware(app) + """ + + def __init__(self, app): + self.app = app + + def check_environ(self, environ): + if type(environ) is not dict: + warn( + "WSGI environment is not a standard Python dict.", + WSGIWarning, + stacklevel=4, + ) + for key in ( + "REQUEST_METHOD", + "SERVER_NAME", + "SERVER_PORT", + "wsgi.version", + "wsgi.input", + "wsgi.errors", + "wsgi.multithread", + "wsgi.multiprocess", + "wsgi.run_once", + ): + if key not in environ: + warn( + "Required environment key %r not found" % key, + WSGIWarning, + stacklevel=3, + ) + if environ["wsgi.version"] != (1, 0): + warn("Environ is not a WSGI 1.0 environ.", WSGIWarning, stacklevel=3) + + script_name = environ.get("SCRIPT_NAME", "") + path_info = environ.get("PATH_INFO", "") + + if script_name and script_name[0] != "/": + warn( + "'SCRIPT_NAME' does not start with a slash: %r" % script_name, + WSGIWarning, + stacklevel=3, + ) + + if path_info and path_info[0] != "/": + warn( + "'PATH_INFO' does not start with a slash: %r" % path_info, + WSGIWarning, + stacklevel=3, + ) + + def check_start_response(self, status, headers, exc_info): + check_string("status", status) + status_code = status.split(None, 1)[0] + + if len(status_code) != 3 or not status_code.isdigit(): + warn(WSGIWarning("Status code must be three digits"), stacklevel=3) + + if len(status) < 4 or status[3] != " ": + warn( + WSGIWarning( + "Invalid value for status %r. Valid " + "status strings are three digits, a space " + "and a status explanation" + ), + stacklevel=3, + ) + + status_code = int(status_code) + + if status_code < 100: + warn(WSGIWarning("status code < 100 detected"), stacklevel=3) + + if type(headers) is not list: + warn(WSGIWarning("header list is not a list"), stacklevel=3) + + for item in headers: + if type(item) is not tuple or len(item) != 2: + warn(WSGIWarning("Headers must tuple 2-item tuples"), stacklevel=3) + name, value = item + if type(name) is not str or type(value) is not str: + warn(WSGIWarning("header items must be strings"), stacklevel=3) + if name.lower() == "status": + warn( + WSGIWarning( + "The status header is not supported due to " + "conflicts with the CGI spec." + ), + stacklevel=3, + ) + + if exc_info is not None and not isinstance(exc_info, tuple): + warn(WSGIWarning("invalid value for exc_info"), stacklevel=3) + + headers = Headers(headers) + self.check_headers(headers) + + return status_code, headers + + def check_headers(self, headers): + etag = headers.get("etag") + + if etag is not None: + if etag.startswith(("W/", "w/")): + if etag.startswith("w/"): + warn( + HTTPWarning("weak etag indicator should be upcase."), + stacklevel=4, + ) + + etag = etag[2:] + + if not (etag[:1] == etag[-1:] == '"'): + warn(HTTPWarning("unquoted etag emitted."), stacklevel=4) + + location = headers.get("location") + + if location is not None: + if not urlparse(location).netloc: + warn( + HTTPWarning("absolute URLs required for location header"), + stacklevel=4, + ) + + def check_iterator(self, app_iter): + if isinstance(app_iter, string_types): + warn( + "The application returned astring. The response will send one character" + " at a time to the client, which will kill performance. Return a list" + " or iterable instead.", + WSGIWarning, + stacklevel=3, + ) + + def __call__(self, *args, **kwargs): + if len(args) != 2: + warn("A WSGI app takes two arguments.", WSGIWarning, stacklevel=2) + + if kwargs: + warn( + "A WSGI app does not take keyword arguments.", WSGIWarning, stacklevel=2 + ) + + environ, start_response = args + + self.check_environ(environ) + environ["wsgi.input"] = InputStream(environ["wsgi.input"]) + environ["wsgi.errors"] = ErrorStream(environ["wsgi.errors"]) + + # Hook our own file wrapper in so that applications will always + # iterate to the end and we can check the content length. + environ["wsgi.file_wrapper"] = FileWrapper + + headers_set = [] + chunks = [] + + def checking_start_response(*args, **kwargs): + if len(args) not in (2, 3): + warn( + "Invalid number of arguments: %s, expected 2 or 3." % len(args), + WSGIWarning, + stacklevel=2, + ) + + if kwargs: + warn("'start_response' does not take keyword arguments.", WSGIWarning) + + status, headers = args[:2] + + if len(args) == 3: + exc_info = args[2] + else: + exc_info = None + + headers_set[:] = self.check_start_response(status, headers, exc_info) + return GuardedWrite(start_response(status, headers, exc_info), chunks) + + app_iter = self.app(environ, checking_start_response) + self.check_iterator(app_iter) + return GuardedIterator(app_iter, headers_set, chunks) diff --git a/venv/lib/python2.7/site-packages/werkzeug/middleware/profiler.py b/venv/lib/python2.7/site-packages/werkzeug/middleware/profiler.py new file mode 100644 index 00000000..32a14d9f --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/middleware/profiler.py @@ -0,0 +1,132 @@ +""" +Application Profiler +==================== + +This module provides a middleware that profiles each request with the +:mod:`cProfile` module. This can help identify bottlenecks in your code +that may be slowing down your application. + +.. autoclass:: ProfilerMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +from __future__ import print_function + +import os.path +import sys +import time +from pstats import Stats + +try: + from cProfile import Profile +except ImportError: + from profile import Profile + + +class ProfilerMiddleware(object): + """Wrap a WSGI application and profile the execution of each + request. Responses are buffered so that timings are more exact. + + If ``stream`` is given, :class:`pstats.Stats` are written to it + after each request. If ``profile_dir`` is given, :mod:`cProfile` + data files are saved to that directory, one file per request. + + The filename can be customized by passing ``filename_format``. If + it is a string, it will be formatted using :meth:`str.format` with + the following fields available: + + - ``{method}`` - The request method; GET, POST, etc. + - ``{path}`` - The request path or 'root' should one not exist. + - ``{elapsed}`` - The elapsed time of the request. + - ``{time}`` - The time of the request. + + If it is a callable, it will be called with the WSGI ``environ`` + dict and should return a filename. + + :param app: The WSGI application to wrap. + :param stream: Write stats to this stream. Disable with ``None``. + :param sort_by: A tuple of columns to sort stats by. See + :meth:`pstats.Stats.sort_stats`. + :param restrictions: A tuple of restrictions to filter stats by. See + :meth:`pstats.Stats.print_stats`. + :param profile_dir: Save profile data files to this directory. + :param filename_format: Format string for profile data file names, + or a callable returning a name. See explanation above. + + .. code-block:: python + + from werkzeug.middleware.profiler import ProfilerMiddleware + app = ProfilerMiddleware(app) + + .. versionchanged:: 0.15 + Stats are written even if ``profile_dir`` is given, and can be + disable by passing ``stream=None``. + + .. versionadded:: 0.15 + Added ``filename_format``. + + .. versionadded:: 0.9 + Added ``restrictions`` and ``profile_dir``. + """ + + def __init__( + self, + app, + stream=sys.stdout, + sort_by=("time", "calls"), + restrictions=(), + profile_dir=None, + filename_format="{method}.{path}.{elapsed:.0f}ms.{time:.0f}.prof", + ): + self._app = app + self._stream = stream + self._sort_by = sort_by + self._restrictions = restrictions + self._profile_dir = profile_dir + self._filename_format = filename_format + + def __call__(self, environ, start_response): + response_body = [] + + def catching_start_response(status, headers, exc_info=None): + start_response(status, headers, exc_info) + return response_body.append + + def runapp(): + app_iter = self._app(environ, catching_start_response) + response_body.extend(app_iter) + + if hasattr(app_iter, "close"): + app_iter.close() + + profile = Profile() + start = time.time() + profile.runcall(runapp) + body = b"".join(response_body) + elapsed = time.time() - start + + if self._profile_dir is not None: + if callable(self._filename_format): + filename = self._filename_format(environ) + else: + filename = self._filename_format.format( + method=environ["REQUEST_METHOD"], + path=( + environ.get("PATH_INFO").strip("/").replace("/", ".") or "root" + ), + elapsed=elapsed * 1000.0, + time=time.time(), + ) + filename = os.path.join(self._profile_dir, filename) + profile.dump_stats(filename) + + if self._stream is not None: + stats = Stats(profile, stream=self._stream) + stats.sort_stats(*self._sort_by) + print("-" * 80, file=self._stream) + print("PATH: {!r}".format(environ.get("PATH_INFO", "")), file=self._stream) + stats.print_stats(*self._restrictions) + print("-" * 80 + "\n", file=self._stream) + + return [body] diff --git a/venv/lib/python2.7/site-packages/werkzeug/middleware/proxy_fix.py b/venv/lib/python2.7/site-packages/werkzeug/middleware/proxy_fix.py new file mode 100644 index 00000000..bbe18140 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/middleware/proxy_fix.py @@ -0,0 +1,232 @@ +""" +X-Forwarded-For Proxy Fix +========================= + +This module provides a middleware that adjusts the WSGI environ based on +``X-Forwarded-`` headers that proxies in front of an application may +set. + +When an application is running behind a proxy server, WSGI may see the +request as coming from that server rather than the real client. Proxies +set various headers to track where the request actually came from. + +This middleware should only be applied if the application is actually +behind such a proxy, and should be configured with the number of proxies +that are chained in front of it. Not all proxies set all the headers. +Since incoming headers can be faked, you must set how many proxies are +setting each header so the middleware knows what to trust. + +.. autoclass:: ProxyFix + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import warnings + + +class ProxyFix(object): + """Adjust the WSGI environ based on ``X-Forwarded-`` that proxies in + front of the application may set. + + - ``X-Forwarded-For`` sets ``REMOTE_ADDR``. + - ``X-Forwarded-Proto`` sets ``wsgi.url_scheme``. + - ``X-Forwarded-Host`` sets ``HTTP_HOST``, ``SERVER_NAME``, and + ``SERVER_PORT``. + - ``X-Forwarded-Port`` sets ``HTTP_HOST`` and ``SERVER_PORT``. + - ``X-Forwarded-Prefix`` sets ``SCRIPT_NAME``. + + You must tell the middleware how many proxies set each header so it + knows what values to trust. It is a security issue to trust values + that came from the client rather than a proxy. + + The original values of the headers are stored in the WSGI + environ as ``werkzeug.proxy_fix.orig``, a dict. + + :param app: The WSGI application to wrap. + :param x_for: Number of values to trust for ``X-Forwarded-For``. + :param x_proto: Number of values to trust for ``X-Forwarded-Proto``. + :param x_host: Number of values to trust for ``X-Forwarded-Host``. + :param x_port: Number of values to trust for ``X-Forwarded-Port``. + :param x_prefix: Number of values to trust for + ``X-Forwarded-Prefix``. + :param num_proxies: Deprecated, use ``x_for`` instead. + + .. code-block:: python + + from werkzeug.middleware.proxy_fix import ProxyFix + # App is behind one proxy that sets the -For and -Host headers. + app = ProxyFix(app, x_for=1, x_host=1) + + .. versionchanged:: 0.15 + All headers support multiple values. The ``num_proxies`` + argument is deprecated. Each header is configured with a + separate number of trusted proxies. + + .. versionchanged:: 0.15 + Original WSGI environ values are stored in the + ``werkzeug.proxy_fix.orig`` dict. ``orig_remote_addr``, + ``orig_wsgi_url_scheme``, and ``orig_http_host`` are deprecated + and will be removed in 1.0. + + .. versionchanged:: 0.15 + Support ``X-Forwarded-Port`` and ``X-Forwarded-Prefix``. + + .. versionchanged:: 0.15 + ``X-Fowarded-Host`` and ``X-Forwarded-Port`` modify + ``SERVER_NAME`` and ``SERVER_PORT``. + """ + + def __init__( + self, app, num_proxies=None, x_for=1, x_proto=1, x_host=0, x_port=0, x_prefix=0 + ): + self.app = app + self.x_for = x_for + self.x_proto = x_proto + self.x_host = x_host + self.x_port = x_port + self.x_prefix = x_prefix + self.num_proxies = num_proxies + + @property + def num_proxies(self): + """The number of proxies setting ``X-Forwarded-For`` in front + of the application. + + .. deprecated:: 0.15 + A separate number of trusted proxies is configured for each + header. ``num_proxies`` maps to ``x_for``. This method will + be removed in 1.0. + + :internal: + """ + warnings.warn( + "'num_proxies' is deprecated as of version 0.15 and will be" + " removed in version 1.0. Use 'x_for' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.x_for + + @num_proxies.setter + def num_proxies(self, value): + if value is not None: + warnings.warn( + "'num_proxies' is deprecated as of version 0.15 and" + " will be removed in version 1.0. Use" + " 'x_for={value}, x_proto={value}, x_host={value}'" + " instead.".format(value=value), + DeprecationWarning, + stacklevel=2, + ) + self.x_for = value + self.x_proto = value + self.x_host = value + + def get_remote_addr(self, forwarded_for): + """Get the real ``remote_addr`` by looking backwards ``x_for`` + number of values in the ``X-Forwarded-For`` header. + + :param forwarded_for: List of values parsed from the + ``X-Forwarded-For`` header. + :return: The real ``remote_addr``, or ``None`` if there were not + at least ``x_for`` values. + + .. deprecated:: 0.15 + This is handled internally for each header. This method will + be removed in 1.0. + + .. versionchanged:: 0.9 + Use ``num_proxies`` instead of always picking the first + value. + + .. versionadded:: 0.8 + """ + warnings.warn( + "'get_remote_addr' is deprecated as of version 0.15 and" + " will be removed in version 1.0. It is now handled" + " internally for each header.", + DeprecationWarning, + ) + return self._get_trusted_comma(self.x_for, ",".join(forwarded_for)) + + def _get_trusted_comma(self, trusted, value): + """Get the real value from a comma-separated header based on the + configured number of trusted proxies. + + :param trusted: Number of values to trust in the header. + :param value: Header value to parse. + :return: The real value, or ``None`` if there are fewer values + than the number of trusted proxies. + + .. versionadded:: 0.15 + """ + if not (trusted and value): + return + values = [x.strip() for x in value.split(",")] + if len(values) >= trusted: + return values[-trusted] + + def __call__(self, environ, start_response): + """Modify the WSGI environ based on the various ``Forwarded`` + headers before calling the wrapped application. Store the + original environ values in ``werkzeug.proxy_fix.orig_{key}``. + """ + environ_get = environ.get + orig_remote_addr = environ_get("REMOTE_ADDR") + orig_wsgi_url_scheme = environ_get("wsgi.url_scheme") + orig_http_host = environ_get("HTTP_HOST") + environ.update( + { + "werkzeug.proxy_fix.orig": { + "REMOTE_ADDR": orig_remote_addr, + "wsgi.url_scheme": orig_wsgi_url_scheme, + "HTTP_HOST": orig_http_host, + "SERVER_NAME": environ_get("SERVER_NAME"), + "SERVER_PORT": environ_get("SERVER_PORT"), + "SCRIPT_NAME": environ_get("SCRIPT_NAME"), + }, + # todo: remove deprecated keys + "werkzeug.proxy_fix.orig_remote_addr": orig_remote_addr, + "werkzeug.proxy_fix.orig_wsgi_url_scheme": orig_wsgi_url_scheme, + "werkzeug.proxy_fix.orig_http_host": orig_http_host, + } + ) + + x_for = self._get_trusted_comma(self.x_for, environ_get("HTTP_X_FORWARDED_FOR")) + if x_for: + environ["REMOTE_ADDR"] = x_for + + x_proto = self._get_trusted_comma( + self.x_proto, environ_get("HTTP_X_FORWARDED_PROTO") + ) + if x_proto: + environ["wsgi.url_scheme"] = x_proto + + x_host = self._get_trusted_comma( + self.x_host, environ_get("HTTP_X_FORWARDED_HOST") + ) + if x_host: + environ["HTTP_HOST"] = x_host + parts = x_host.split(":", 1) + environ["SERVER_NAME"] = parts[0] + if len(parts) == 2: + environ["SERVER_PORT"] = parts[1] + + x_port = self._get_trusted_comma( + self.x_port, environ_get("HTTP_X_FORWARDED_PORT") + ) + if x_port: + host = environ.get("HTTP_HOST") + if host: + parts = host.split(":", 1) + host = parts[0] if len(parts) == 2 else host + environ["HTTP_HOST"] = "%s:%s" % (host, x_port) + environ["SERVER_PORT"] = x_port + + x_prefix = self._get_trusted_comma( + self.x_prefix, environ_get("HTTP_X_FORWARDED_PREFIX") + ) + if x_prefix: + environ["SCRIPT_NAME"] = x_prefix + + return self.app(environ, start_response) diff --git a/venv/lib/python2.7/site-packages/werkzeug/middleware/shared_data.py b/venv/lib/python2.7/site-packages/werkzeug/middleware/shared_data.py new file mode 100644 index 00000000..088504a9 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/middleware/shared_data.py @@ -0,0 +1,253 @@ +""" +Serve Shared Static Files +========================= + +.. autoclass:: SharedDataMiddleware + :members: is_allowed + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import mimetypes +import os +import posixpath +from datetime import datetime +from io import BytesIO +from time import mktime +from time import time +from zlib import adler32 + +from .._compat import PY2 +from .._compat import string_types +from ..filesystem import get_filesystem_encoding +from ..http import http_date +from ..http import is_resource_modified +from ..security import safe_join +from ..wsgi import get_path_info +from ..wsgi import wrap_file + + +class SharedDataMiddleware(object): + + """A WSGI middleware that provides static content for development + environments or simple server setups. Usage is quite simple:: + + import os + from werkzeug.wsgi import SharedDataMiddleware + + app = SharedDataMiddleware(app, { + '/static': os.path.join(os.path.dirname(__file__), 'static') + }) + + The contents of the folder ``./shared`` will now be available on + ``http://example.com/shared/``. This is pretty useful during development + because a standalone media server is not required. One can also mount + files on the root folder and still continue to use the application because + the shared data middleware forwards all unhandled requests to the + application, even if the requests are below one of the shared folders. + + If `pkg_resources` is available you can also tell the middleware to serve + files from package data:: + + app = SharedDataMiddleware(app, { + '/static': ('myapplication', 'static') + }) + + This will then serve the ``static`` folder in the `myapplication` + Python package. + + The optional `disallow` parameter can be a list of :func:`~fnmatch.fnmatch` + rules for files that are not accessible from the web. If `cache` is set to + `False` no caching headers are sent. + + Currently the middleware does not support non ASCII filenames. If the + encoding on the file system happens to be the encoding of the URI it may + work but this could also be by accident. We strongly suggest using ASCII + only file names for static files. + + The middleware will guess the mimetype using the Python `mimetype` + module. If it's unable to figure out the charset it will fall back + to `fallback_mimetype`. + + .. versionchanged:: 0.5 + The cache timeout is configurable now. + + .. versionadded:: 0.6 + The `fallback_mimetype` parameter was added. + + :param app: the application to wrap. If you don't want to wrap an + application you can pass it :exc:`NotFound`. + :param exports: a list or dict of exported files and folders. + :param disallow: a list of :func:`~fnmatch.fnmatch` rules. + :param fallback_mimetype: the fallback mimetype for unknown files. + :param cache: enable or disable caching headers. + :param cache_timeout: the cache timeout in seconds for the headers. + """ + + def __init__( + self, + app, + exports, + disallow=None, + cache=True, + cache_timeout=60 * 60 * 12, + fallback_mimetype="text/plain", + ): + self.app = app + self.exports = [] + self.cache = cache + self.cache_timeout = cache_timeout + + if hasattr(exports, "items"): + exports = exports.items() + + for key, value in exports: + if isinstance(value, tuple): + loader = self.get_package_loader(*value) + elif isinstance(value, string_types): + if os.path.isfile(value): + loader = self.get_file_loader(value) + else: + loader = self.get_directory_loader(value) + else: + raise TypeError("unknown def %r" % value) + + self.exports.append((key, loader)) + + if disallow is not None: + from fnmatch import fnmatch + + self.is_allowed = lambda x: not fnmatch(x, disallow) + + self.fallback_mimetype = fallback_mimetype + + def is_allowed(self, filename): + """Subclasses can override this method to disallow the access to + certain files. However by providing `disallow` in the constructor + this method is overwritten. + """ + return True + + def _opener(self, filename): + return lambda: ( + open(filename, "rb"), + datetime.utcfromtimestamp(os.path.getmtime(filename)), + int(os.path.getsize(filename)), + ) + + def get_file_loader(self, filename): + return lambda x: (os.path.basename(filename), self._opener(filename)) + + def get_package_loader(self, package, package_path): + from pkg_resources import DefaultProvider, ResourceManager, get_provider + + loadtime = datetime.utcnow() + provider = get_provider(package) + manager = ResourceManager() + filesystem_bound = isinstance(provider, DefaultProvider) + + def loader(path): + if path is None: + return None, None + + path = safe_join(package_path, path) + + if not provider.has_resource(path): + return None, None + + basename = posixpath.basename(path) + + if filesystem_bound: + return ( + basename, + self._opener(provider.get_resource_filename(manager, path)), + ) + + s = provider.get_resource_string(manager, path) + return basename, lambda: (BytesIO(s), loadtime, len(s)) + + return loader + + def get_directory_loader(self, directory): + def loader(path): + if path is not None: + path = safe_join(directory, path) + else: + path = directory + + if os.path.isfile(path): + return os.path.basename(path), self._opener(path) + + return None, None + + return loader + + def generate_etag(self, mtime, file_size, real_filename): + if not isinstance(real_filename, bytes): + real_filename = real_filename.encode(get_filesystem_encoding()) + + return "wzsdm-%d-%s-%s" % ( + mktime(mtime.timetuple()), + file_size, + adler32(real_filename) & 0xFFFFFFFF, + ) + + def __call__(self, environ, start_response): + path = get_path_info(environ) + + if PY2: + path = path.encode(get_filesystem_encoding()) + + file_loader = None + + for search_path, loader in self.exports: + if search_path == path: + real_filename, file_loader = loader(None) + + if file_loader is not None: + break + + if not search_path.endswith("/"): + search_path += "/" + + if path.startswith(search_path): + real_filename, file_loader = loader(path[len(search_path) :]) + + if file_loader is not None: + break + + if file_loader is None or not self.is_allowed(real_filename): + return self.app(environ, start_response) + + guessed_type = mimetypes.guess_type(real_filename) + mime_type = guessed_type[0] or self.fallback_mimetype + f, mtime, file_size = file_loader() + + headers = [("Date", http_date())] + + if self.cache: + timeout = self.cache_timeout + etag = self.generate_etag(mtime, file_size, real_filename) + headers += [ + ("Etag", '"%s"' % etag), + ("Cache-Control", "max-age=%d, public" % timeout), + ] + + if not is_resource_modified(environ, etag, last_modified=mtime): + f.close() + start_response("304 Not Modified", headers) + return [] + + headers.append(("Expires", http_date(time() + timeout))) + else: + headers.append(("Cache-Control", "public")) + + headers.extend( + ( + ("Content-Type", mime_type), + ("Content-Length", str(file_size)), + ("Last-Modified", http_date(mtime)), + ) + ) + start_response("200 OK", headers) + return wrap_file(environ, f) diff --git a/venv/lib/python2.7/site-packages/werkzeug/posixemulation.py b/venv/lib/python2.7/site-packages/werkzeug/posixemulation.py new file mode 100644 index 00000000..696b4562 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/posixemulation.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +r""" + werkzeug.posixemulation + ~~~~~~~~~~~~~~~~~~~~~~~ + + Provides a POSIX emulation for some features that are relevant to + web applications. The main purpose is to simplify support for + systems such as Windows NT that are not 100% POSIX compatible. + + Currently this only implements a :func:`rename` function that + follows POSIX semantics. Eg: if the target file already exists it + will be replaced without asking. + + This module was introduced in 0.6.1 and is not a public interface. + It might become one in later versions of Werkzeug. + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import errno +import os +import random +import sys +import time + +from ._compat import to_unicode +from .filesystem import get_filesystem_encoding + +can_rename_open_file = False + +if os.name == "nt": + try: + import ctypes + + _MOVEFILE_REPLACE_EXISTING = 0x1 + _MOVEFILE_WRITE_THROUGH = 0x8 + _MoveFileEx = ctypes.windll.kernel32.MoveFileExW + + def _rename(src, dst): + src = to_unicode(src, get_filesystem_encoding()) + dst = to_unicode(dst, get_filesystem_encoding()) + if _rename_atomic(src, dst): + return True + retry = 0 + rv = False + while not rv and retry < 100: + rv = _MoveFileEx( + src, dst, _MOVEFILE_REPLACE_EXISTING | _MOVEFILE_WRITE_THROUGH + ) + if not rv: + time.sleep(0.001) + retry += 1 + return rv + + # new in Vista and Windows Server 2008 + _CreateTransaction = ctypes.windll.ktmw32.CreateTransaction + _CommitTransaction = ctypes.windll.ktmw32.CommitTransaction + _MoveFileTransacted = ctypes.windll.kernel32.MoveFileTransactedW + _CloseHandle = ctypes.windll.kernel32.CloseHandle + can_rename_open_file = True + + def _rename_atomic(src, dst): + ta = _CreateTransaction(None, 0, 0, 0, 0, 1000, "Werkzeug rename") + if ta == -1: + return False + try: + retry = 0 + rv = False + while not rv and retry < 100: + rv = _MoveFileTransacted( + src, + dst, + None, + None, + _MOVEFILE_REPLACE_EXISTING | _MOVEFILE_WRITE_THROUGH, + ta, + ) + if rv: + rv = _CommitTransaction(ta) + break + else: + time.sleep(0.001) + retry += 1 + return rv + finally: + _CloseHandle(ta) + + except Exception: + + def _rename(src, dst): + return False + + def _rename_atomic(src, dst): + return False + + def rename(src, dst): + # Try atomic or pseudo-atomic rename + if _rename(src, dst): + return + # Fall back to "move away and replace" + try: + os.rename(src, dst) + except OSError as e: + if e.errno != errno.EEXIST: + raise + old = "%s-%08x" % (dst, random.randint(0, sys.maxsize)) + os.rename(dst, old) + os.rename(src, dst) + try: + os.unlink(old) + except Exception: + pass + + +else: + rename = os.rename + can_rename_open_file = True diff --git a/venv/lib/python2.7/site-packages/werkzeug/routing.py b/venv/lib/python2.7/site-packages/werkzeug/routing.py new file mode 100644 index 00000000..8ff7df18 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/routing.py @@ -0,0 +1,2039 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.routing + ~~~~~~~~~~~~~~~~ + + When it comes to combining multiple controller or view functions (however + you want to call them) you need a dispatcher. A simple way would be + applying regular expression tests on the ``PATH_INFO`` and calling + registered callback functions that return the value then. + + This module implements a much more powerful system than simple regular + expression matching because it can also convert values in the URLs and + build URLs. + + Here a simple example that creates an URL map for an application with + two subdomains (www and kb) and some URL rules: + + >>> m = Map([ + ... # Static URLs + ... Rule('/', endpoint='static/index'), + ... Rule('/about', endpoint='static/about'), + ... Rule('/help', endpoint='static/help'), + ... # Knowledge Base + ... Subdomain('kb', [ + ... Rule('/', endpoint='kb/index'), + ... Rule('/browse/', endpoint='kb/browse'), + ... Rule('/browse//', endpoint='kb/browse'), + ... Rule('/browse//', endpoint='kb/browse') + ... ]) + ... ], default_subdomain='www') + + If the application doesn't use subdomains it's perfectly fine to not set + the default subdomain and not use the `Subdomain` rule factory. The endpoint + in the rules can be anything, for example import paths or unique + identifiers. The WSGI application can use those endpoints to get the + handler for that URL. It doesn't have to be a string at all but it's + recommended. + + Now it's possible to create a URL adapter for one of the subdomains and + build URLs: + + >>> c = m.bind('example.com') + >>> c.build("kb/browse", dict(id=42)) + 'http://kb.example.com/browse/42/' + >>> c.build("kb/browse", dict()) + 'http://kb.example.com/browse/' + >>> c.build("kb/browse", dict(id=42, page=3)) + 'http://kb.example.com/browse/42/3' + >>> c.build("static/about") + '/about' + >>> c.build("static/index", force_external=True) + 'http://www.example.com/' + + >>> c = m.bind('example.com', subdomain='kb') + >>> c.build("static/about") + 'http://www.example.com/about' + + The first argument to bind is the server name *without* the subdomain. + Per default it will assume that the script is mounted on the root, but + often that's not the case so you can provide the real mount point as + second argument: + + >>> c = m.bind('example.com', '/applications/example') + + The third argument can be the subdomain, if not given the default + subdomain is used. For more details about binding have a look at the + documentation of the `MapAdapter`. + + And here is how you can match URLs: + + >>> c = m.bind('example.com') + >>> c.match("/") + ('static/index', {}) + >>> c.match("/about") + ('static/about', {}) + >>> c = m.bind('example.com', '/', 'kb') + >>> c.match("/") + ('kb/index', {}) + >>> c.match("/browse/42/23") + ('kb/browse', {'id': 42, 'page': 23}) + + If matching fails you get a `NotFound` exception, if the rule thinks + it's a good idea to redirect (for example because the URL was defined + to have a slash at the end but the request was missing that slash) it + will raise a `RequestRedirect` exception. Both are subclasses of the + `HTTPException` so you can use those errors as responses in the + application. + + If matching succeeded but the URL rule was incompatible to the given + method (for example there were only rules for `GET` and `HEAD` and + routing system tried to match a `POST` request) a `MethodNotAllowed` + exception is raised. + + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import ast +import difflib +import posixpath +import re +import uuid +from pprint import pformat +from threading import Lock + +from ._compat import implements_to_string +from ._compat import iteritems +from ._compat import itervalues +from ._compat import native_string_result +from ._compat import string_types +from ._compat import text_type +from ._compat import to_bytes +from ._compat import to_unicode +from ._compat import wsgi_decoding_dance +from ._internal import _encode_idna +from ._internal import _get_environ +from .datastructures import ImmutableDict +from .datastructures import MultiDict +from .exceptions import BadHost +from .exceptions import HTTPException +from .exceptions import MethodNotAllowed +from .exceptions import NotFound +from .urls import _fast_url_quote +from .urls import url_encode +from .urls import url_join +from .urls import url_quote +from .utils import cached_property +from .utils import format_string +from .utils import redirect +from .wsgi import get_host + +_rule_re = re.compile( + r""" + (?P[^<]*) # static rule data + < + (?: + (?P[a-zA-Z_][a-zA-Z0-9_]*) # converter name + (?:\((?P.*?)\))? # converter arguments + \: # variable delimiter + )? + (?P[a-zA-Z_][a-zA-Z0-9_]*) # variable name + > + """, + re.VERBOSE, +) +_simple_rule_re = re.compile(r"<([^>]+)>") +_converter_args_re = re.compile( + r""" + ((?P\w+)\s*=\s*)? + (?P + True|False| + \d+.\d+| + \d+.| + \d+| + [\w\d_.]+| + [urUR]?(?P"[^"]*?"|'[^']*') + )\s*, + """, + re.VERBOSE | re.UNICODE, +) + + +_PYTHON_CONSTANTS = {"None": None, "True": True, "False": False} + + +def _pythonize(value): + if value in _PYTHON_CONSTANTS: + return _PYTHON_CONSTANTS[value] + for convert in int, float: + try: + return convert(value) + except ValueError: + pass + if value[:1] == value[-1:] and value[0] in "\"'": + value = value[1:-1] + return text_type(value) + + +def parse_converter_args(argstr): + argstr += "," + args = [] + kwargs = {} + + for item in _converter_args_re.finditer(argstr): + value = item.group("stringval") + if value is None: + value = item.group("value") + value = _pythonize(value) + if not item.group("name"): + args.append(value) + else: + name = item.group("name") + kwargs[name] = value + + return tuple(args), kwargs + + +def parse_rule(rule): + """Parse a rule and return it as generator. Each iteration yields tuples + in the form ``(converter, arguments, variable)``. If the converter is + `None` it's a static url part, otherwise it's a dynamic one. + + :internal: + """ + pos = 0 + end = len(rule) + do_match = _rule_re.match + used_names = set() + while pos < end: + m = do_match(rule, pos) + if m is None: + break + data = m.groupdict() + if data["static"]: + yield None, None, data["static"] + variable = data["variable"] + converter = data["converter"] or "default" + if variable in used_names: + raise ValueError("variable name %r used twice." % variable) + used_names.add(variable) + yield converter, data["args"] or None, variable + pos = m.end() + if pos < end: + remaining = rule[pos:] + if ">" in remaining or "<" in remaining: + raise ValueError("malformed url rule: %r" % rule) + yield None, None, remaining + + +class RoutingException(Exception): + """Special exceptions that require the application to redirect, notifying + about missing urls, etc. + + :internal: + """ + + +class RequestRedirect(HTTPException, RoutingException): + """Raise if the map requests a redirect. This is for example the case if + `strict_slashes` are activated and an url that requires a trailing slash. + + The attribute `new_url` contains the absolute destination url. + """ + + code = 308 + + def __init__(self, new_url): + RoutingException.__init__(self, new_url) + self.new_url = new_url + + def get_response(self, environ): + return redirect(self.new_url, self.code) + + +class RequestSlash(RoutingException): + """Internal exception.""" + + +class RequestAliasRedirect(RoutingException): # noqa: B903 + """This rule is an alias and wants to redirect to the canonical URL.""" + + def __init__(self, matched_values): + self.matched_values = matched_values + + +@implements_to_string +class BuildError(RoutingException, LookupError): + """Raised if the build system cannot find a URL for an endpoint with the + values provided. + """ + + def __init__(self, endpoint, values, method, adapter=None): + LookupError.__init__(self, endpoint, values, method) + self.endpoint = endpoint + self.values = values + self.method = method + self.adapter = adapter + + @cached_property + def suggested(self): + return self.closest_rule(self.adapter) + + def closest_rule(self, adapter): + def _score_rule(rule): + return sum( + [ + 0.98 + * difflib.SequenceMatcher( + None, rule.endpoint, self.endpoint + ).ratio(), + 0.01 * bool(set(self.values or ()).issubset(rule.arguments)), + 0.01 * bool(rule.methods and self.method in rule.methods), + ] + ) + + if adapter and adapter.map._rules: + return max(adapter.map._rules, key=_score_rule) + + def __str__(self): + message = [] + message.append("Could not build url for endpoint %r" % self.endpoint) + if self.method: + message.append(" (%r)" % self.method) + if self.values: + message.append(" with values %r" % sorted(self.values.keys())) + message.append(".") + if self.suggested: + if self.endpoint == self.suggested.endpoint: + if self.method and self.method not in self.suggested.methods: + message.append( + " Did you mean to use methods %r?" + % sorted(self.suggested.methods) + ) + missing_values = self.suggested.arguments.union( + set(self.suggested.defaults or ()) + ) - set(self.values.keys()) + if missing_values: + message.append( + " Did you forget to specify values %r?" % sorted(missing_values) + ) + else: + message.append(" Did you mean %r instead?" % self.suggested.endpoint) + return u"".join(message) + + +class ValidationError(ValueError): + """Validation error. If a rule converter raises this exception the rule + does not match the current URL and the next URL is tried. + """ + + +class RuleFactory(object): + """As soon as you have more complex URL setups it's a good idea to use rule + factories to avoid repetitive tasks. Some of them are builtin, others can + be added by subclassing `RuleFactory` and overriding `get_rules`. + """ + + def get_rules(self, map): + """Subclasses of `RuleFactory` have to override this method and return + an iterable of rules.""" + raise NotImplementedError() + + +class Subdomain(RuleFactory): + """All URLs provided by this factory have the subdomain set to a + specific domain. For example if you want to use the subdomain for + the current language this can be a good setup:: + + url_map = Map([ + Rule('/', endpoint='#select_language'), + Subdomain('', [ + Rule('/', endpoint='index'), + Rule('/about', endpoint='about'), + Rule('/help', endpoint='help') + ]) + ]) + + All the rules except for the ``'#select_language'`` endpoint will now + listen on a two letter long subdomain that holds the language code + for the current request. + """ + + def __init__(self, subdomain, rules): + self.subdomain = subdomain + self.rules = rules + + def get_rules(self, map): + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + rule = rule.empty() + rule.subdomain = self.subdomain + yield rule + + +class Submount(RuleFactory): + """Like `Subdomain` but prefixes the URL rule with a given string:: + + url_map = Map([ + Rule('/', endpoint='index'), + Submount('/blog', [ + Rule('/', endpoint='blog/index'), + Rule('/entry/', endpoint='blog/show') + ]) + ]) + + Now the rule ``'blog/show'`` matches ``/blog/entry/``. + """ + + def __init__(self, path, rules): + self.path = path.rstrip("/") + self.rules = rules + + def get_rules(self, map): + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + rule = rule.empty() + rule.rule = self.path + rule.rule + yield rule + + +class EndpointPrefix(RuleFactory): + """Prefixes all endpoints (which must be strings for this factory) with + another string. This can be useful for sub applications:: + + url_map = Map([ + Rule('/', endpoint='index'), + EndpointPrefix('blog/', [Submount('/blog', [ + Rule('/', endpoint='index'), + Rule('/entry/', endpoint='show') + ])]) + ]) + """ + + def __init__(self, prefix, rules): + self.prefix = prefix + self.rules = rules + + def get_rules(self, map): + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + rule = rule.empty() + rule.endpoint = self.prefix + rule.endpoint + yield rule + + +class RuleTemplate(object): + """Returns copies of the rules wrapped and expands string templates in + the endpoint, rule, defaults or subdomain sections. + + Here a small example for such a rule template:: + + from werkzeug.routing import Map, Rule, RuleTemplate + + resource = RuleTemplate([ + Rule('/$name/', endpoint='$name.list'), + Rule('/$name/', endpoint='$name.show') + ]) + + url_map = Map([resource(name='user'), resource(name='page')]) + + When a rule template is called the keyword arguments are used to + replace the placeholders in all the string parameters. + """ + + def __init__(self, rules): + self.rules = list(rules) + + def __call__(self, *args, **kwargs): + return RuleTemplateFactory(self.rules, dict(*args, **kwargs)) + + +class RuleTemplateFactory(RuleFactory): + """A factory that fills in template variables into rules. Used by + `RuleTemplate` internally. + + :internal: + """ + + def __init__(self, rules, context): + self.rules = rules + self.context = context + + def get_rules(self, map): + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + new_defaults = subdomain = None + if rule.defaults: + new_defaults = {} + for key, value in iteritems(rule.defaults): + if isinstance(value, string_types): + value = format_string(value, self.context) + new_defaults[key] = value + if rule.subdomain is not None: + subdomain = format_string(rule.subdomain, self.context) + new_endpoint = rule.endpoint + if isinstance(new_endpoint, string_types): + new_endpoint = format_string(new_endpoint, self.context) + yield Rule( + format_string(rule.rule, self.context), + new_defaults, + subdomain, + rule.methods, + rule.build_only, + new_endpoint, + rule.strict_slashes, + ) + + +def _prefix_names(src): + """ast parse and prefix names with `.` to avoid collision with user vars""" + tree = ast.parse(src).body[0] + if isinstance(tree, ast.Expr): + tree = tree.value + for node in ast.walk(tree): + if isinstance(node, ast.Name): + node.id = "." + node.id + return tree + + +_CALL_CONVERTER_CODE_FMT = "self._converters[{elem!r}].to_url()" +_IF_KWARGS_URL_ENCODE_CODE = """\ +if kwargs: + q = '?' + params = self._encode_query_vars(kwargs) +else: + q = params = '' +""" +_IF_KWARGS_URL_ENCODE_AST = _prefix_names(_IF_KWARGS_URL_ENCODE_CODE) +_URL_ENCODE_AST_NAMES = (_prefix_names("q"), _prefix_names("params")) + + +@implements_to_string +class Rule(RuleFactory): + """A Rule represents one URL pattern. There are some options for `Rule` + that change the way it behaves and are passed to the `Rule` constructor. + Note that besides the rule-string all arguments *must* be keyword arguments + in order to not break the application on Werkzeug upgrades. + + `string` + Rule strings basically are just normal URL paths with placeholders in + the format ```` where the converter and the + arguments are optional. If no converter is defined the `default` + converter is used which means `string` in the normal configuration. + + URL rules that end with a slash are branch URLs, others are leaves. + If you have `strict_slashes` enabled (which is the default), all + branch URLs that are matched without a trailing slash will trigger a + redirect to the same URL with the missing slash appended. + + The converters are defined on the `Map`. + + `endpoint` + The endpoint for this rule. This can be anything. A reference to a + function, a string, a number etc. The preferred way is using a string + because the endpoint is used for URL generation. + + `defaults` + An optional dict with defaults for other rules with the same endpoint. + This is a bit tricky but useful if you want to have unique URLs:: + + url_map = Map([ + Rule('/all/', defaults={'page': 1}, endpoint='all_entries'), + Rule('/all/page/', endpoint='all_entries') + ]) + + If a user now visits ``http://example.com/all/page/1`` he will be + redirected to ``http://example.com/all/``. If `redirect_defaults` is + disabled on the `Map` instance this will only affect the URL + generation. + + `subdomain` + The subdomain rule string for this rule. If not specified the rule + only matches for the `default_subdomain` of the map. If the map is + not bound to a subdomain this feature is disabled. + + Can be useful if you want to have user profiles on different subdomains + and all subdomains are forwarded to your application:: + + url_map = Map([ + Rule('/', subdomain='', endpoint='user/homepage'), + Rule('/stats', subdomain='', endpoint='user/stats') + ]) + + `methods` + A sequence of http methods this rule applies to. If not specified, all + methods are allowed. For example this can be useful if you want different + endpoints for `POST` and `GET`. If methods are defined and the path + matches but the method matched against is not in this list or in the + list of another rule for that path the error raised is of the type + `MethodNotAllowed` rather than `NotFound`. If `GET` is present in the + list of methods and `HEAD` is not, `HEAD` is added automatically. + + .. versionchanged:: 0.6.1 + `HEAD` is now automatically added to the methods if `GET` is + present. The reason for this is that existing code often did not + work properly in servers not rewriting `HEAD` to `GET` + automatically and it was not documented how `HEAD` should be + treated. This was considered a bug in Werkzeug because of that. + + `strict_slashes` + Override the `Map` setting for `strict_slashes` only for this rule. If + not specified the `Map` setting is used. + + `build_only` + Set this to True and the rule will never match but will create a URL + that can be build. This is useful if you have resources on a subdomain + or folder that are not handled by the WSGI application (like static data) + + `redirect_to` + If given this must be either a string or callable. In case of a + callable it's called with the url adapter that triggered the match and + the values of the URL as keyword arguments and has to return the target + for the redirect, otherwise it has to be a string with placeholders in + rule syntax:: + + def foo_with_slug(adapter, id): + # ask the database for the slug for the old id. this of + # course has nothing to do with werkzeug. + return 'foo/' + Foo.get_slug_for_id(id) + + url_map = Map([ + Rule('/foo/', endpoint='foo'), + Rule('/some/old/url/', redirect_to='foo/'), + Rule('/other/old/url/', redirect_to=foo_with_slug) + ]) + + When the rule is matched the routing system will raise a + `RequestRedirect` exception with the target for the redirect. + + Keep in mind that the URL will be joined against the URL root of the + script so don't use a leading slash on the target URL unless you + really mean root of that domain. + + `alias` + If enabled this rule serves as an alias for another rule with the same + endpoint and arguments. + + `host` + If provided and the URL map has host matching enabled this can be + used to provide a match rule for the whole host. This also means + that the subdomain feature is disabled. + + .. versionadded:: 0.7 + The `alias` and `host` parameters were added. + """ + + def __init__( + self, + string, + defaults=None, + subdomain=None, + methods=None, + build_only=False, + endpoint=None, + strict_slashes=None, + redirect_to=None, + alias=False, + host=None, + ): + if not string.startswith("/"): + raise ValueError("urls must start with a leading slash") + self.rule = string + self.is_leaf = not string.endswith("/") + + self.map = None + self.strict_slashes = strict_slashes + self.subdomain = subdomain + self.host = host + self.defaults = defaults + self.build_only = build_only + self.alias = alias + if methods is None: + self.methods = None + else: + if isinstance(methods, str): + raise TypeError("param `methods` should be `Iterable[str]`, not `str`") + self.methods = set([x.upper() for x in methods]) + if "HEAD" not in self.methods and "GET" in self.methods: + self.methods.add("HEAD") + self.endpoint = endpoint + self.redirect_to = redirect_to + + if defaults: + self.arguments = set(map(str, defaults)) + else: + self.arguments = set() + self._trace = self._converters = self._regex = self._argument_weights = None + + def empty(self): + """ + Return an unbound copy of this rule. + + This can be useful if want to reuse an already bound URL for another + map. See ``get_empty_kwargs`` to override what keyword arguments are + provided to the new copy. + """ + return type(self)(self.rule, **self.get_empty_kwargs()) + + def get_empty_kwargs(self): + """ + Provides kwargs for instantiating empty copy with empty() + + Use this method to provide custom keyword arguments to the subclass of + ``Rule`` when calling ``some_rule.empty()``. Helpful when the subclass + has custom keyword arguments that are needed at instantiation. + + Must return a ``dict`` that will be provided as kwargs to the new + instance of ``Rule``, following the initial ``self.rule`` value which + is always provided as the first, required positional argument. + """ + defaults = None + if self.defaults: + defaults = dict(self.defaults) + return dict( + defaults=defaults, + subdomain=self.subdomain, + methods=self.methods, + build_only=self.build_only, + endpoint=self.endpoint, + strict_slashes=self.strict_slashes, + redirect_to=self.redirect_to, + alias=self.alias, + host=self.host, + ) + + def get_rules(self, map): + yield self + + def refresh(self): + """Rebinds and refreshes the URL. Call this if you modified the + rule in place. + + :internal: + """ + self.bind(self.map, rebind=True) + + def bind(self, map, rebind=False): + """Bind the url to a map and create a regular expression based on + the information from the rule itself and the defaults from the map. + + :internal: + """ + if self.map is not None and not rebind: + raise RuntimeError("url rule %r already bound to map %r" % (self, self.map)) + self.map = map + if self.strict_slashes is None: + self.strict_slashes = map.strict_slashes + if self.subdomain is None: + self.subdomain = map.default_subdomain + self.compile() + + def get_converter(self, variable_name, converter_name, args, kwargs): + """Looks up the converter for the given parameter. + + .. versionadded:: 0.9 + """ + if converter_name not in self.map.converters: + raise LookupError("the converter %r does not exist" % converter_name) + return self.map.converters[converter_name](self.map, *args, **kwargs) + + def _encode_query_vars(self, query_vars): + return url_encode( + query_vars, + charset=self.map.charset, + sort=self.map.sort_parameters, + key=self.map.sort_key, + ) + + def compile(self): + """Compiles the regular expression and stores it.""" + assert self.map is not None, "rule not bound" + + if self.map.host_matching: + domain_rule = self.host or "" + else: + domain_rule = self.subdomain or "" + + self._trace = [] + self._converters = {} + self._static_weights = [] + self._argument_weights = [] + regex_parts = [] + + def _build_regex(rule): + index = 0 + for converter, arguments, variable in parse_rule(rule): + if converter is None: + regex_parts.append(re.escape(variable)) + self._trace.append((False, variable)) + for part in variable.split("/"): + if part: + self._static_weights.append((index, -len(part))) + else: + if arguments: + c_args, c_kwargs = parse_converter_args(arguments) + else: + c_args = () + c_kwargs = {} + convobj = self.get_converter(variable, converter, c_args, c_kwargs) + regex_parts.append("(?P<%s>%s)" % (variable, convobj.regex)) + self._converters[variable] = convobj + self._trace.append((True, variable)) + self._argument_weights.append(convobj.weight) + self.arguments.add(str(variable)) + index = index + 1 + + _build_regex(domain_rule) + regex_parts.append("\\|") + self._trace.append((False, "|")) + _build_regex(self.rule if self.is_leaf else self.rule.rstrip("/")) + if not self.is_leaf: + self._trace.append((False, "/")) + + self._build = self._compile_builder(False).__get__(self, None) + self._build_unknown = self._compile_builder(True).__get__(self, None) + + if self.build_only: + return + regex = r"^%s%s$" % ( + u"".join(regex_parts), + (not self.is_leaf or not self.strict_slashes) + and "(?/?)" + or "", + ) + self._regex = re.compile(regex, re.UNICODE) + + def match(self, path, method=None): + """Check if the rule matches a given path. Path is a string in the + form ``"subdomain|/path"`` and is assembled by the map. If + the map is doing host matching the subdomain part will be the host + instead. + + If the rule matches a dict with the converted values is returned, + otherwise the return value is `None`. + + :internal: + """ + if not self.build_only: + m = self._regex.search(path) + if m is not None: + groups = m.groupdict() + # we have a folder like part of the url without a trailing + # slash and strict slashes enabled. raise an exception that + # tells the map to redirect to the same url but with a + # trailing slash + if ( + self.strict_slashes + and not self.is_leaf + and not groups.pop("__suffix__") + and ( + method is None or self.methods is None or method in self.methods + ) + ): + raise RequestSlash() + # if we are not in strict slashes mode we have to remove + # a __suffix__ + elif not self.strict_slashes: + del groups["__suffix__"] + + result = {} + for name, value in iteritems(groups): + try: + value = self._converters[name].to_python(value) + except ValidationError: + return + result[str(name)] = value + if self.defaults: + result.update(self.defaults) + + if self.alias and self.map.redirect_defaults: + raise RequestAliasRedirect(result) + + return result + + @staticmethod + def _get_func_code(code, name): + globs, locs = {}, {} + exec(code, globs, locs) + return locs[name] + + def _compile_builder(self, append_unknown=True): + defaults = self.defaults or {} + dom_ops = [] + url_ops = [] + + opl = dom_ops + for is_dynamic, data in self._trace: + if data == "|" and opl is dom_ops: + opl = url_ops + continue + # this seems like a silly case to ever come up but: + # if a default is given for a value that appears in the rule, + # resolve it to a constant ahead of time + if is_dynamic and data in defaults: + data = self._converters[data].to_url(defaults[data]) + opl.append((False, data)) + elif not is_dynamic: + opl.append( + (False, url_quote(to_bytes(data, self.map.charset), safe="/:|+")) + ) + else: + opl.append((True, data)) + + def _convert(elem): + ret = _prefix_names(_CALL_CONVERTER_CODE_FMT.format(elem=elem)) + ret.args = [ast.Name(str(elem), ast.Load())] # str for py2 + return ret + + def _parts(ops): + parts = [ + _convert(elem) if is_dynamic else ast.Str(s=elem) + for is_dynamic, elem in ops + ] + parts = parts or [ast.Str("")] + # constant fold + ret = [parts[0]] + for p in parts[1:]: + if isinstance(p, ast.Str) and isinstance(ret[-1], ast.Str): + ret[-1] = ast.Str(ret[-1].s + p.s) + else: + ret.append(p) + return ret + + dom_parts = _parts(dom_ops) + url_parts = _parts(url_ops) + if not append_unknown: + body = [] + else: + body = [_IF_KWARGS_URL_ENCODE_AST] + url_parts.extend(_URL_ENCODE_AST_NAMES) + + def _join(parts): + if len(parts) == 1: # shortcut + return parts[0] + elif hasattr(ast, "JoinedStr"): # py36+ + return ast.JoinedStr(parts) + else: + call = _prefix_names('"".join()') + call.args = [ast.Tuple(parts, ast.Load())] + return call + + body.append( + ast.Return(ast.Tuple([_join(dom_parts), _join(url_parts)], ast.Load())) + ) + + # str is necessary for python2 + pargs = [ + str(elem) + for is_dynamic, elem in dom_ops + url_ops + if is_dynamic and elem not in defaults + ] + kargs = [str(k) for k in defaults] + + func_ast = _prefix_names("def _(): pass") + func_ast.name = "".format(self.rule) + if hasattr(ast, "arg"): # py3 + func_ast.args.args.append(ast.arg(".self", None)) + for arg in pargs + kargs: + func_ast.args.args.append(ast.arg(arg, None)) + func_ast.args.kwarg = ast.arg(".kwargs", None) + else: + func_ast.args.args.append(ast.Name(".self", ast.Param())) + for arg in pargs + kargs: + func_ast.args.args.append(ast.Name(arg, ast.Param())) + func_ast.args.kwarg = ".kwargs" + for _ in kargs: + func_ast.args.defaults.append(ast.Str("")) + func_ast.body = body + + # use `ast.parse` instead of `ast.Module` for better portability + # python3.8 changes the signature of `ast.Module` + module = ast.parse("") + module.body = [func_ast] + + # mark everything as on line 1, offset 0 + # less error-prone than `ast.fix_missing_locations` + # bad line numbers cause an assert to fail in debug builds + for node in ast.walk(module): + if "lineno" in node._attributes: + node.lineno = 1 + if "col_offset" in node._attributes: + node.col_offset = 0 + + code = compile(module, "", "exec") + return self._get_func_code(code, func_ast.name) + + def build(self, values, append_unknown=True): + """Assembles the relative url for that rule and the subdomain. + If building doesn't work for some reasons `None` is returned. + + :internal: + """ + try: + if append_unknown: + return self._build_unknown(**values) + else: + return self._build(**values) + except ValidationError: + return None + + def provides_defaults_for(self, rule): + """Check if this rule has defaults for a given rule. + + :internal: + """ + return ( + not self.build_only + and self.defaults + and self.endpoint == rule.endpoint + and self != rule + and self.arguments == rule.arguments + ) + + def suitable_for(self, values, method=None): + """Check if the dict of values has enough data for url generation. + + :internal: + """ + # if a method was given explicitly and that method is not supported + # by this rule, this rule is not suitable. + if ( + method is not None + and self.methods is not None + and method not in self.methods + ): + return False + + defaults = self.defaults or () + + # all arguments required must be either in the defaults dict or + # the value dictionary otherwise it's not suitable + for key in self.arguments: + if key not in defaults and key not in values: + return False + + # in case defaults are given we ensure that either the value was + # skipped or the value is the same as the default value. + if defaults: + for key, value in iteritems(defaults): + if key in values and value != values[key]: + return False + + return True + + def match_compare_key(self): + """The match compare key for sorting. + + Current implementation: + + 1. rules without any arguments come first for performance + reasons only as we expect them to match faster and some + common ones usually don't have any arguments (index pages etc.) + 2. rules with more static parts come first so the second argument + is the negative length of the number of the static weights. + 3. we order by static weights, which is a combination of index + and length + 4. The more complex rules come first so the next argument is the + negative length of the number of argument weights. + 5. lastly we order by the actual argument weights. + + :internal: + """ + return ( + bool(self.arguments), + -len(self._static_weights), + self._static_weights, + -len(self._argument_weights), + self._argument_weights, + ) + + def build_compare_key(self): + """The build compare key for sorting. + + :internal: + """ + return 1 if self.alias else 0, -len(self.arguments), -len(self.defaults or ()) + + def __eq__(self, other): + return self.__class__ is other.__class__ and self._trace == other._trace + + __hash__ = None + + def __ne__(self, other): + return not self.__eq__(other) + + def __str__(self): + return self.rule + + @native_string_result + def __repr__(self): + if self.map is None: + return u"<%s (unbound)>" % self.__class__.__name__ + tmp = [] + for is_dynamic, data in self._trace: + if is_dynamic: + tmp.append(u"<%s>" % data) + else: + tmp.append(data) + return u"<%s %s%s -> %s>" % ( + self.__class__.__name__, + repr((u"".join(tmp)).lstrip(u"|")).lstrip(u"u"), + self.methods is not None and u" (%s)" % u", ".join(self.methods) or u"", + self.endpoint, + ) + + +class BaseConverter(object): + """Base class for all converters.""" + + regex = "[^/]+" + weight = 100 + + def __init__(self, map): + self.map = map + + def to_python(self, value): + return value + + def to_url(self, value): + if isinstance(value, (bytes, bytearray)): + return _fast_url_quote(value) + return _fast_url_quote(text_type(value).encode(self.map.charset)) + + +class UnicodeConverter(BaseConverter): + """This converter is the default converter and accepts any string but + only one path segment. Thus the string can not include a slash. + + This is the default validator. + + Example:: + + Rule('/pages/'), + Rule('/') + + :param map: the :class:`Map`. + :param minlength: the minimum length of the string. Must be greater + or equal 1. + :param maxlength: the maximum length of the string. + :param length: the exact length of the string. + """ + + def __init__(self, map, minlength=1, maxlength=None, length=None): + BaseConverter.__init__(self, map) + if length is not None: + length = "{%d}" % int(length) + else: + if maxlength is None: + maxlength = "" + else: + maxlength = int(maxlength) + length = "{%s,%s}" % (int(minlength), maxlength) + self.regex = "[^/]" + length + + +class AnyConverter(BaseConverter): + """Matches one of the items provided. Items can either be Python + identifiers or strings:: + + Rule('/') + + :param map: the :class:`Map`. + :param items: this function accepts the possible items as positional + arguments. + """ + + def __init__(self, map, *items): + BaseConverter.__init__(self, map) + self.regex = "(?:%s)" % "|".join([re.escape(x) for x in items]) + + +class PathConverter(BaseConverter): + """Like the default :class:`UnicodeConverter`, but it also matches + slashes. This is useful for wikis and similar applications:: + + Rule('/') + Rule('//edit') + + :param map: the :class:`Map`. + """ + + regex = "[^/].*?" + weight = 200 + + +class NumberConverter(BaseConverter): + """Baseclass for `IntegerConverter` and `FloatConverter`. + + :internal: + """ + + weight = 50 + + def __init__(self, map, fixed_digits=0, min=None, max=None, signed=False): + if signed: + self.regex = self.signed_regex + BaseConverter.__init__(self, map) + self.fixed_digits = fixed_digits + self.min = min + self.max = max + self.signed = signed + + def to_python(self, value): + if self.fixed_digits and len(value) != self.fixed_digits: + raise ValidationError() + value = self.num_convert(value) + if (self.min is not None and value < self.min) or ( + self.max is not None and value > self.max + ): + raise ValidationError() + return value + + def to_url(self, value): + value = self.num_convert(value) + if self.fixed_digits: + value = ("%%0%sd" % self.fixed_digits) % value + return str(value) + + @property + def signed_regex(self): + return r"-?" + self.regex + + +class IntegerConverter(NumberConverter): + """This converter only accepts integer values:: + + Rule("/page/") + + By default it only accepts unsigned, positive values. The ``signed`` + parameter will enable signed, negative values. :: + + Rule("/page/") + + :param map: The :class:`Map`. + :param fixed_digits: The number of fixed digits in the URL. If you + set this to ``4`` for example, the rule will only match if the + URL looks like ``/0001/``. The default is variable length. + :param min: The minimal value. + :param max: The maximal value. + :param signed: Allow signed (negative) values. + + .. versionadded:: 0.15 + The ``signed`` parameter. + """ + + regex = r"\d+" + num_convert = int + + +class FloatConverter(NumberConverter): + """This converter only accepts floating point values:: + + Rule("/probability/") + + By default it only accepts unsigned, positive values. The ``signed`` + parameter will enable signed, negative values. :: + + Rule("/offset/") + + :param map: The :class:`Map`. + :param min: The minimal value. + :param max: The maximal value. + :param signed: Allow signed (negative) values. + + .. versionadded:: 0.15 + The ``signed`` parameter. + """ + + regex = r"\d+\.\d+" + num_convert = float + + def __init__(self, map, min=None, max=None, signed=False): + NumberConverter.__init__(self, map, min=min, max=max, signed=signed) + + +class UUIDConverter(BaseConverter): + """This converter only accepts UUID strings:: + + Rule('/object/') + + .. versionadded:: 0.10 + + :param map: the :class:`Map`. + """ + + regex = ( + r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-" + r"[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}" + ) + + def to_python(self, value): + return uuid.UUID(value) + + def to_url(self, value): + return str(value) + + +#: the default converter mapping for the map. +DEFAULT_CONVERTERS = { + "default": UnicodeConverter, + "string": UnicodeConverter, + "any": AnyConverter, + "path": PathConverter, + "int": IntegerConverter, + "float": FloatConverter, + "uuid": UUIDConverter, +} + + +class Map(object): + """The map class stores all the URL rules and some configuration + parameters. Some of the configuration values are only stored on the + `Map` instance since those affect all rules, others are just defaults + and can be overridden for each rule. Note that you have to specify all + arguments besides the `rules` as keyword arguments! + + :param rules: sequence of url rules for this map. + :param default_subdomain: The default subdomain for rules without a + subdomain defined. + :param charset: charset of the url. defaults to ``"utf-8"`` + :param strict_slashes: Take care of trailing slashes. + :param redirect_defaults: This will redirect to the default rule if it + wasn't visited that way. This helps creating + unique URLs. + :param converters: A dict of converters that adds additional converters + to the list of converters. If you redefine one + converter this will override the original one. + :param sort_parameters: If set to `True` the url parameters are sorted. + See `url_encode` for more details. + :param sort_key: The sort key function for `url_encode`. + :param encoding_errors: the error method to use for decoding + :param host_matching: if set to `True` it enables the host matching + feature and disables the subdomain one. If + enabled the `host` parameter to rules is used + instead of the `subdomain` one. + + .. versionadded:: 0.5 + `sort_parameters` and `sort_key` was added. + + .. versionadded:: 0.7 + `encoding_errors` and `host_matching` was added. + """ + + #: A dict of default converters to be used. + default_converters = ImmutableDict(DEFAULT_CONVERTERS) + + def __init__( + self, + rules=None, + default_subdomain="", + charset="utf-8", + strict_slashes=True, + redirect_defaults=True, + converters=None, + sort_parameters=False, + sort_key=None, + encoding_errors="replace", + host_matching=False, + ): + self._rules = [] + self._rules_by_endpoint = {} + self._remap = True + self._remap_lock = Lock() + + self.default_subdomain = default_subdomain + self.charset = charset + self.encoding_errors = encoding_errors + self.strict_slashes = strict_slashes + self.redirect_defaults = redirect_defaults + self.host_matching = host_matching + + self.converters = self.default_converters.copy() + if converters: + self.converters.update(converters) + + self.sort_parameters = sort_parameters + self.sort_key = sort_key + + for rulefactory in rules or (): + self.add(rulefactory) + + def is_endpoint_expecting(self, endpoint, *arguments): + """Iterate over all rules and check if the endpoint expects + the arguments provided. This is for example useful if you have + some URLs that expect a language code and others that do not and + you want to wrap the builder a bit so that the current language + code is automatically added if not provided but endpoints expect + it. + + :param endpoint: the endpoint to check. + :param arguments: this function accepts one or more arguments + as positional arguments. Each one of them is + checked. + """ + self.update() + arguments = set(arguments) + for rule in self._rules_by_endpoint[endpoint]: + if arguments.issubset(rule.arguments): + return True + return False + + def iter_rules(self, endpoint=None): + """Iterate over all rules or the rules of an endpoint. + + :param endpoint: if provided only the rules for that endpoint + are returned. + :return: an iterator + """ + self.update() + if endpoint is not None: + return iter(self._rules_by_endpoint[endpoint]) + return iter(self._rules) + + def add(self, rulefactory): + """Add a new rule or factory to the map and bind it. Requires that the + rule is not bound to another map. + + :param rulefactory: a :class:`Rule` or :class:`RuleFactory` + """ + for rule in rulefactory.get_rules(self): + rule.bind(self) + self._rules.append(rule) + self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule) + self._remap = True + + def bind( + self, + server_name, + script_name=None, + subdomain=None, + url_scheme="http", + default_method="GET", + path_info=None, + query_args=None, + ): + """Return a new :class:`MapAdapter` with the details specified to the + call. Note that `script_name` will default to ``'/'`` if not further + specified or `None`. The `server_name` at least is a requirement + because the HTTP RFC requires absolute URLs for redirects and so all + redirect exceptions raised by Werkzeug will contain the full canonical + URL. + + If no path_info is passed to :meth:`match` it will use the default path + info passed to bind. While this doesn't really make sense for + manual bind calls, it's useful if you bind a map to a WSGI + environment which already contains the path info. + + `subdomain` will default to the `default_subdomain` for this map if + no defined. If there is no `default_subdomain` you cannot use the + subdomain feature. + + .. versionadded:: 0.7 + `query_args` added + + .. versionadded:: 0.8 + `query_args` can now also be a string. + + .. versionchanged:: 0.15 + ``path_info`` defaults to ``'/'`` if ``None``. + """ + server_name = server_name.lower() + if self.host_matching: + if subdomain is not None: + raise RuntimeError("host matching enabled and a subdomain was provided") + elif subdomain is None: + subdomain = self.default_subdomain + if script_name is None: + script_name = "/" + if path_info is None: + path_info = "/" + try: + server_name = _encode_idna(server_name) + except UnicodeError: + raise BadHost() + return MapAdapter( + self, + server_name, + script_name, + subdomain, + url_scheme, + path_info, + default_method, + query_args, + ) + + def bind_to_environ(self, environ, server_name=None, subdomain=None): + """Like :meth:`bind` but you can pass it an WSGI environment and it + will fetch the information from that dictionary. Note that because of + limitations in the protocol there is no way to get the current + subdomain and real `server_name` from the environment. If you don't + provide it, Werkzeug will use `SERVER_NAME` and `SERVER_PORT` (or + `HTTP_HOST` if provided) as used `server_name` with disabled subdomain + feature. + + If `subdomain` is `None` but an environment and a server name is + provided it will calculate the current subdomain automatically. + Example: `server_name` is ``'example.com'`` and the `SERVER_NAME` + in the wsgi `environ` is ``'staging.dev.example.com'`` the calculated + subdomain will be ``'staging.dev'``. + + If the object passed as environ has an environ attribute, the value of + this attribute is used instead. This allows you to pass request + objects. Additionally `PATH_INFO` added as a default of the + :class:`MapAdapter` so that you don't have to pass the path info to + the match method. + + .. versionchanged:: 0.5 + previously this method accepted a bogus `calculate_subdomain` + parameter that did not have any effect. It was removed because + of that. + + .. versionchanged:: 0.8 + This will no longer raise a ValueError when an unexpected server + name was passed. + + :param environ: a WSGI environment. + :param server_name: an optional server name hint (see above). + :param subdomain: optionally the current subdomain (see above). + """ + environ = _get_environ(environ) + + wsgi_server_name = get_host(environ).lower() + + if server_name is None: + server_name = wsgi_server_name + else: + server_name = server_name.lower() + + if subdomain is None and not self.host_matching: + cur_server_name = wsgi_server_name.split(".") + real_server_name = server_name.split(".") + offset = -len(real_server_name) + if cur_server_name[offset:] != real_server_name: + # This can happen even with valid configs if the server was + # accesssed directly by IP address under some situations. + # Instead of raising an exception like in Werkzeug 0.7 or + # earlier we go by an invalid subdomain which will result + # in a 404 error on matching. + subdomain = "" + else: + subdomain = ".".join(filter(None, cur_server_name[:offset])) + + def _get_wsgi_string(name): + val = environ.get(name) + if val is not None: + return wsgi_decoding_dance(val, self.charset) + + script_name = _get_wsgi_string("SCRIPT_NAME") + path_info = _get_wsgi_string("PATH_INFO") + query_args = _get_wsgi_string("QUERY_STRING") + return Map.bind( + self, + server_name, + script_name, + subdomain, + environ["wsgi.url_scheme"], + environ["REQUEST_METHOD"], + path_info, + query_args=query_args, + ) + + def update(self): + """Called before matching and building to keep the compiled rules + in the correct order after things changed. + """ + if not self._remap: + return + + with self._remap_lock: + if not self._remap: + return + + self._rules.sort(key=lambda x: x.match_compare_key()) + for rules in itervalues(self._rules_by_endpoint): + rules.sort(key=lambda x: x.build_compare_key()) + self._remap = False + + def __repr__(self): + rules = self.iter_rules() + return "%s(%s)" % (self.__class__.__name__, pformat(list(rules))) + + +class MapAdapter(object): + + """Returned by :meth:`Map.bind` or :meth:`Map.bind_to_environ` and does + the URL matching and building based on runtime information. + """ + + def __init__( + self, + map, + server_name, + script_name, + subdomain, + url_scheme, + path_info, + default_method, + query_args=None, + ): + self.map = map + self.server_name = to_unicode(server_name) + script_name = to_unicode(script_name) + if not script_name.endswith(u"/"): + script_name += u"/" + self.script_name = script_name + self.subdomain = to_unicode(subdomain) + self.url_scheme = to_unicode(url_scheme) + self.path_info = to_unicode(path_info) + self.default_method = to_unicode(default_method) + self.query_args = query_args + + def dispatch( + self, view_func, path_info=None, method=None, catch_http_exceptions=False + ): + """Does the complete dispatching process. `view_func` is called with + the endpoint and a dict with the values for the view. It should + look up the view function, call it, and return a response object + or WSGI application. http exceptions are not caught by default + so that applications can display nicer error messages by just + catching them by hand. If you want to stick with the default + error messages you can pass it ``catch_http_exceptions=True`` and + it will catch the http exceptions. + + Here a small example for the dispatch usage:: + + from werkzeug.wrappers import Request, Response + from werkzeug.wsgi import responder + from werkzeug.routing import Map, Rule + + def on_index(request): + return Response('Hello from the index') + + url_map = Map([Rule('/', endpoint='index')]) + views = {'index': on_index} + + @responder + def application(environ, start_response): + request = Request(environ) + urls = url_map.bind_to_environ(environ) + return urls.dispatch(lambda e, v: views[e](request, **v), + catch_http_exceptions=True) + + Keep in mind that this method might return exception objects, too, so + use :class:`Response.force_type` to get a response object. + + :param view_func: a function that is called with the endpoint as + first argument and the value dict as second. Has + to dispatch to the actual view function with this + information. (see above) + :param path_info: the path info to use for matching. Overrides the + path info specified on binding. + :param method: the HTTP method used for matching. Overrides the + method specified on binding. + :param catch_http_exceptions: set to `True` to catch any of the + werkzeug :class:`HTTPException`\\s. + """ + try: + try: + endpoint, args = self.match(path_info, method) + except RequestRedirect as e: + return e + return view_func(endpoint, args) + except HTTPException as e: + if catch_http_exceptions: + return e + raise + + def match(self, path_info=None, method=None, return_rule=False, query_args=None): + """The usage is simple: you just pass the match method the current + path info as well as the method (which defaults to `GET`). The + following things can then happen: + + - you receive a `NotFound` exception that indicates that no URL is + matching. A `NotFound` exception is also a WSGI application you + can call to get a default page not found page (happens to be the + same object as `werkzeug.exceptions.NotFound`) + + - you receive a `MethodNotAllowed` exception that indicates that there + is a match for this URL but not for the current request method. + This is useful for RESTful applications. + + - you receive a `RequestRedirect` exception with a `new_url` + attribute. This exception is used to notify you about a request + Werkzeug requests from your WSGI application. This is for example the + case if you request ``/foo`` although the correct URL is ``/foo/`` + You can use the `RequestRedirect` instance as response-like object + similar to all other subclasses of `HTTPException`. + + - you get a tuple in the form ``(endpoint, arguments)`` if there is + a match (unless `return_rule` is True, in which case you get a tuple + in the form ``(rule, arguments)``) + + If the path info is not passed to the match method the default path + info of the map is used (defaults to the root URL if not defined + explicitly). + + All of the exceptions raised are subclasses of `HTTPException` so they + can be used as WSGI responses. They will all render generic error or + redirect pages. + + Here is a small example for matching: + + >>> m = Map([ + ... Rule('/', endpoint='index'), + ... Rule('/downloads/', endpoint='downloads/index'), + ... Rule('/downloads/', endpoint='downloads/show') + ... ]) + >>> urls = m.bind("example.com", "/") + >>> urls.match("/", "GET") + ('index', {}) + >>> urls.match("/downloads/42") + ('downloads/show', {'id': 42}) + + And here is what happens on redirect and missing URLs: + + >>> urls.match("/downloads") + Traceback (most recent call last): + ... + RequestRedirect: http://example.com/downloads/ + >>> urls.match("/missing") + Traceback (most recent call last): + ... + NotFound: 404 Not Found + + :param path_info: the path info to use for matching. Overrides the + path info specified on binding. + :param method: the HTTP method used for matching. Overrides the + method specified on binding. + :param return_rule: return the rule that matched instead of just the + endpoint (defaults to `False`). + :param query_args: optional query arguments that are used for + automatic redirects as string or dictionary. It's + currently not possible to use the query arguments + for URL matching. + + .. versionadded:: 0.6 + `return_rule` was added. + + .. versionadded:: 0.7 + `query_args` was added. + + .. versionchanged:: 0.8 + `query_args` can now also be a string. + """ + self.map.update() + if path_info is None: + path_info = self.path_info + else: + path_info = to_unicode(path_info, self.map.charset) + if query_args is None: + query_args = self.query_args + method = (method or self.default_method).upper() + + path = u"%s|%s" % ( + self.map.host_matching and self.server_name or self.subdomain, + path_info and "/%s" % path_info.lstrip("/"), + ) + + have_match_for = set() + for rule in self.map._rules: + try: + rv = rule.match(path, method) + except RequestSlash: + raise RequestRedirect( + self.make_redirect_url( + url_quote(path_info, self.map.charset, safe="/:|+") + "/", + query_args, + ) + ) + except RequestAliasRedirect as e: + raise RequestRedirect( + self.make_alias_redirect_url( + path, rule.endpoint, e.matched_values, method, query_args + ) + ) + if rv is None: + continue + if rule.methods is not None and method not in rule.methods: + have_match_for.update(rule.methods) + continue + + if self.map.redirect_defaults: + redirect_url = self.get_default_redirect(rule, method, rv, query_args) + if redirect_url is not None: + raise RequestRedirect(redirect_url) + + if rule.redirect_to is not None: + if isinstance(rule.redirect_to, string_types): + + def _handle_match(match): + value = rv[match.group(1)] + return rule._converters[match.group(1)].to_url(value) + + redirect_url = _simple_rule_re.sub(_handle_match, rule.redirect_to) + else: + redirect_url = rule.redirect_to(self, **rv) + raise RequestRedirect( + str( + url_join( + "%s://%s%s%s" + % ( + self.url_scheme or "http", + self.subdomain + "." if self.subdomain else "", + self.server_name, + self.script_name, + ), + redirect_url, + ) + ) + ) + + if return_rule: + return rule, rv + else: + return rule.endpoint, rv + + if have_match_for: + raise MethodNotAllowed(valid_methods=list(have_match_for)) + raise NotFound() + + def test(self, path_info=None, method=None): + """Test if a rule would match. Works like `match` but returns `True` + if the URL matches, or `False` if it does not exist. + + :param path_info: the path info to use for matching. Overrides the + path info specified on binding. + :param method: the HTTP method used for matching. Overrides the + method specified on binding. + """ + try: + self.match(path_info, method) + except RequestRedirect: + pass + except HTTPException: + return False + return True + + def allowed_methods(self, path_info=None): + """Returns the valid methods that match for a given path. + + .. versionadded:: 0.7 + """ + try: + self.match(path_info, method="--") + except MethodNotAllowed as e: + return e.valid_methods + except HTTPException: + pass + return [] + + def get_host(self, domain_part): + """Figures out the full host name for the given domain part. The + domain part is a subdomain in case host matching is disabled or + a full host name. + """ + if self.map.host_matching: + if domain_part is None: + return self.server_name + return to_unicode(domain_part, "ascii") + subdomain = domain_part + if subdomain is None: + subdomain = self.subdomain + else: + subdomain = to_unicode(subdomain, "ascii") + return (subdomain + u"." if subdomain else u"") + self.server_name + + def get_default_redirect(self, rule, method, values, query_args): + """A helper that returns the URL to redirect to if it finds one. + This is used for default redirecting only. + + :internal: + """ + assert self.map.redirect_defaults + for r in self.map._rules_by_endpoint[rule.endpoint]: + # every rule that comes after this one, including ourself + # has a lower priority for the defaults. We order the ones + # with the highest priority up for building. + if r is rule: + break + if r.provides_defaults_for(rule) and r.suitable_for(values, method): + values.update(r.defaults) + domain_part, path = r.build(values) + return self.make_redirect_url(path, query_args, domain_part=domain_part) + + def encode_query_args(self, query_args): + if not isinstance(query_args, string_types): + query_args = url_encode(query_args, self.map.charset) + return query_args + + def make_redirect_url(self, path_info, query_args=None, domain_part=None): + """Creates a redirect URL. + + :internal: + """ + suffix = "" + if query_args: + suffix = "?" + self.encode_query_args(query_args) + return str( + "%s://%s/%s%s" + % ( + self.url_scheme or "http", + self.get_host(domain_part), + posixpath.join( + self.script_name[:-1].lstrip("/"), path_info.lstrip("/") + ), + suffix, + ) + ) + + def make_alias_redirect_url(self, path, endpoint, values, method, query_args): + """Internally called to make an alias redirect URL.""" + url = self.build( + endpoint, values, method, append_unknown=False, force_external=True + ) + if query_args: + url += "?" + self.encode_query_args(query_args) + assert url != path, "detected invalid alias setting. No canonical URL found" + return url + + def _partial_build(self, endpoint, values, method, append_unknown): + """Helper for :meth:`build`. Returns subdomain and path for the + rule that accepts this endpoint, values and method. + + :internal: + """ + # in case the method is none, try with the default method first + if method is None: + rv = self._partial_build( + endpoint, values, self.default_method, append_unknown + ) + if rv is not None: + return rv + + # default method did not match or a specific method is passed, + # check all and go with first result. + for rule in self.map._rules_by_endpoint.get(endpoint, ()): + if rule.suitable_for(values, method): + rv = rule.build(values, append_unknown) + if rv is not None: + return rv + + def build( + self, + endpoint, + values=None, + method=None, + force_external=False, + append_unknown=True, + ): + """Building URLs works pretty much the other way round. Instead of + `match` you call `build` and pass it the endpoint and a dict of + arguments for the placeholders. + + The `build` function also accepts an argument called `force_external` + which, if you set it to `True` will force external URLs. Per default + external URLs (include the server name) will only be used if the + target URL is on a different subdomain. + + >>> m = Map([ + ... Rule('/', endpoint='index'), + ... Rule('/downloads/', endpoint='downloads/index'), + ... Rule('/downloads/', endpoint='downloads/show') + ... ]) + >>> urls = m.bind("example.com", "/") + >>> urls.build("index", {}) + '/' + >>> urls.build("downloads/show", {'id': 42}) + '/downloads/42' + >>> urls.build("downloads/show", {'id': 42}, force_external=True) + 'http://example.com/downloads/42' + + Because URLs cannot contain non ASCII data you will always get + bytestrings back. Non ASCII characters are urlencoded with the + charset defined on the map instance. + + Additional values are converted to unicode and appended to the URL as + URL querystring parameters: + + >>> urls.build("index", {'q': 'My Searchstring'}) + '/?q=My+Searchstring' + + When processing those additional values, lists are furthermore + interpreted as multiple values (as per + :py:class:`werkzeug.datastructures.MultiDict`): + + >>> urls.build("index", {'q': ['a', 'b', 'c']}) + '/?q=a&q=b&q=c' + + Passing a ``MultiDict`` will also add multiple values: + + >>> urls.build("index", MultiDict((('p', 'z'), ('q', 'a'), ('q', 'b')))) + '/?p=z&q=a&q=b' + + If a rule does not exist when building a `BuildError` exception is + raised. + + The build method accepts an argument called `method` which allows you + to specify the method you want to have an URL built for if you have + different methods for the same endpoint specified. + + .. versionadded:: 0.6 + the `append_unknown` parameter was added. + + :param endpoint: the endpoint of the URL to build. + :param values: the values for the URL to build. Unhandled values are + appended to the URL as query parameters. + :param method: the HTTP method for the rule if there are different + URLs for different methods on the same endpoint. + :param force_external: enforce full canonical external URLs. If the URL + scheme is not provided, this will generate + a protocol-relative URL. + :param append_unknown: unknown parameters are appended to the generated + URL as query string argument. Disable this + if you want the builder to ignore those. + """ + self.map.update() + + if values: + if isinstance(values, MultiDict): + temp_values = {} + # iteritems(dict, values) is like `values.lists()` + # without the call or `list()` coercion overhead. + for key, value in iteritems(dict, values): + if not value: + continue + if len(value) == 1: # flatten single item lists + value = value[0] + if value is None: # drop None + continue + temp_values[key] = value + values = temp_values + else: + # drop None + values = dict(i for i in iteritems(values) if i[1] is not None) + else: + values = {} + + rv = self._partial_build(endpoint, values, method, append_unknown) + if rv is None: + raise BuildError(endpoint, values, method, self) + domain_part, path = rv + + host = self.get_host(domain_part) + + # shortcut this. + if not force_external and ( + (self.map.host_matching and host == self.server_name) + or (not self.map.host_matching and domain_part == self.subdomain) + ): + return "%s/%s" % (self.script_name.rstrip("/"), path.lstrip("/")) + return str( + "%s//%s%s/%s" + % ( + self.url_scheme + ":" if self.url_scheme else "", + host, + self.script_name[:-1], + path.lstrip("/"), + ) + ) diff --git a/venv/lib/python2.7/site-packages/werkzeug/security.py b/venv/lib/python2.7/site-packages/werkzeug/security.py new file mode 100644 index 00000000..2308040d --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/security.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.security + ~~~~~~~~~~~~~~~~~ + + Security related helpers such as secure password hashing tools. + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import codecs +import hashlib +import hmac +import os +import posixpath +from random import SystemRandom +from struct import Struct + +from ._compat import izip +from ._compat import PY2 +from ._compat import range_type +from ._compat import text_type +from ._compat import to_bytes +from ._compat import to_native + +SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +DEFAULT_PBKDF2_ITERATIONS = 150000 + +_pack_int = Struct(">I").pack +_builtin_safe_str_cmp = getattr(hmac, "compare_digest", None) +_sys_rng = SystemRandom() +_os_alt_seps = list( + sep for sep in [os.path.sep, os.path.altsep] if sep not in (None, "/") +) + + +def pbkdf2_hex( + data, salt, iterations=DEFAULT_PBKDF2_ITERATIONS, keylen=None, hashfunc=None +): + """Like :func:`pbkdf2_bin`, but returns a hex-encoded string. + + .. versionadded:: 0.9 + + :param data: the data to derive. + :param salt: the salt for the derivation. + :param iterations: the number of iterations. + :param keylen: the length of the resulting key. If not provided, + the digest size will be used. + :param hashfunc: the hash function to use. This can either be the + string name of a known hash function, or a function + from the hashlib module. Defaults to sha256. + """ + rv = pbkdf2_bin(data, salt, iterations, keylen, hashfunc) + return to_native(codecs.encode(rv, "hex_codec")) + + +def pbkdf2_bin( + data, salt, iterations=DEFAULT_PBKDF2_ITERATIONS, keylen=None, hashfunc=None +): + """Returns a binary digest for the PBKDF2 hash algorithm of `data` + with the given `salt`. It iterates `iterations` times and produces a + key of `keylen` bytes. By default, SHA-256 is used as hash function; + a different hashlib `hashfunc` can be provided. + + .. versionadded:: 0.9 + + :param data: the data to derive. + :param salt: the salt for the derivation. + :param iterations: the number of iterations. + :param keylen: the length of the resulting key. If not provided + the digest size will be used. + :param hashfunc: the hash function to use. This can either be the + string name of a known hash function or a function + from the hashlib module. Defaults to sha256. + """ + if not hashfunc: + hashfunc = "sha256" + + data = to_bytes(data) + salt = to_bytes(salt) + + if callable(hashfunc): + _test_hash = hashfunc() + hash_name = getattr(_test_hash, "name", None) + else: + hash_name = hashfunc + return hashlib.pbkdf2_hmac(hash_name, data, salt, iterations, keylen) + + +def safe_str_cmp(a, b): + """This function compares strings in somewhat constant time. This + requires that the length of at least one string is known in advance. + + Returns `True` if the two strings are equal, or `False` if they are not. + + .. versionadded:: 0.7 + """ + if isinstance(a, text_type): + a = a.encode("utf-8") + if isinstance(b, text_type): + b = b.encode("utf-8") + + if _builtin_safe_str_cmp is not None: + return _builtin_safe_str_cmp(a, b) + + if len(a) != len(b): + return False + + rv = 0 + if PY2: + for x, y in izip(a, b): + rv |= ord(x) ^ ord(y) + else: + for x, y in izip(a, b): + rv |= x ^ y + + return rv == 0 + + +def gen_salt(length): + """Generate a random string of SALT_CHARS with specified ``length``.""" + if length <= 0: + raise ValueError("Salt length must be positive") + return "".join(_sys_rng.choice(SALT_CHARS) for _ in range_type(length)) + + +def _hash_internal(method, salt, password): + """Internal password hash helper. Supports plaintext without salt, + unsalted and salted passwords. In case salted passwords are used + hmac is used. + """ + if method == "plain": + return password, method + + if isinstance(password, text_type): + password = password.encode("utf-8") + + if method.startswith("pbkdf2:"): + args = method[7:].split(":") + if len(args) not in (1, 2): + raise ValueError("Invalid number of arguments for PBKDF2") + method = args.pop(0) + iterations = args and int(args[0] or 0) or DEFAULT_PBKDF2_ITERATIONS + is_pbkdf2 = True + actual_method = "pbkdf2:%s:%d" % (method, iterations) + else: + is_pbkdf2 = False + actual_method = method + + if is_pbkdf2: + if not salt: + raise ValueError("Salt is required for PBKDF2") + rv = pbkdf2_hex(password, salt, iterations, hashfunc=method) + elif salt: + if isinstance(salt, text_type): + salt = salt.encode("utf-8") + mac = _create_mac(salt, password, method) + rv = mac.hexdigest() + else: + rv = hashlib.new(method, password).hexdigest() + return rv, actual_method + + +def _create_mac(key, msg, method): + if callable(method): + return hmac.HMAC(key, msg, method) + + def hashfunc(d=b""): + return hashlib.new(method, d) + + # Python 2.7 used ``hasattr(digestmod, '__call__')`` + # to detect if hashfunc is callable + hashfunc.__call__ = hashfunc + return hmac.HMAC(key, msg, hashfunc) + + +def generate_password_hash(password, method="pbkdf2:sha256", salt_length=8): + """Hash a password with the given method and salt with a string of + the given length. The format of the string returned includes the method + that was used so that :func:`check_password_hash` can check the hash. + + The format for the hashed string looks like this:: + + method$salt$hash + + This method can **not** generate unsalted passwords but it is possible + to set param method='plain' in order to enforce plaintext passwords. + If a salt is used, hmac is used internally to salt the password. + + If PBKDF2 is wanted it can be enabled by setting the method to + ``pbkdf2:method:iterations`` where iterations is optional:: + + pbkdf2:sha256:80000$salt$hash + pbkdf2:sha256$salt$hash + + :param password: the password to hash. + :param method: the hash method to use (one that hashlib supports). Can + optionally be in the format ``pbkdf2:[:iterations]`` + to enable PBKDF2. + :param salt_length: the length of the salt in letters. + """ + salt = gen_salt(salt_length) if method != "plain" else "" + h, actual_method = _hash_internal(method, salt, password) + return "%s$%s$%s" % (actual_method, salt, h) + + +def check_password_hash(pwhash, password): + """check a password against a given salted and hashed password value. + In order to support unsalted legacy passwords this method supports + plain text passwords, md5 and sha1 hashes (both salted and unsalted). + + Returns `True` if the password matched, `False` otherwise. + + :param pwhash: a hashed string like returned by + :func:`generate_password_hash`. + :param password: the plaintext password to compare against the hash. + """ + if pwhash.count("$") < 2: + return False + method, salt, hashval = pwhash.split("$", 2) + return safe_str_cmp(_hash_internal(method, salt, password)[0], hashval) + + +def safe_join(directory, *pathnames): + """Safely join zero or more untrusted path components to a base + directory to avoid escaping the base directory. + + :param directory: The trusted base directory. + :param pathnames: The untrusted path components relative to the + base directory. + :return: A safe path, otherwise ``None``. + """ + parts = [directory] + + for filename in pathnames: + if filename != "": + filename = posixpath.normpath(filename) + + if ( + any(sep in filename for sep in _os_alt_seps) + or os.path.isabs(filename) + or filename == ".." + or filename.startswith("../") + ): + return None + + parts.append(filename) + + return posixpath.join(*parts) diff --git a/venv/lib/python2.7/site-packages/werkzeug/serving.py b/venv/lib/python2.7/site-packages/werkzeug/serving.py new file mode 100644 index 00000000..d817120f --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/serving.py @@ -0,0 +1,1075 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.serving + ~~~~~~~~~~~~~~~~ + + There are many ways to serve a WSGI application. While you're developing + it you usually don't want a full blown webserver like Apache but a simple + standalone one. From Python 2.5 onwards there is the `wsgiref`_ server in + the standard library. If you're using older versions of Python you can + download the package from the cheeseshop. + + However there are some caveats. Sourcecode won't reload itself when + changed and each time you kill the server using ``^C`` you get an + `KeyboardInterrupt` error. While the latter is easy to solve the first + one can be a pain in the ass in some situations. + + The easiest way is creating a small ``start-myproject.py`` that runs the + application:: + + #!/usr/bin/env python + # -*- coding: utf-8 -*- + from myproject import make_app + from werkzeug.serving import run_simple + + app = make_app(...) + run_simple('localhost', 8080, app, use_reloader=True) + + You can also pass it a `extra_files` keyword argument with a list of + additional files (like configuration files) you want to observe. + + For bigger applications you should consider using `click` + (http://click.pocoo.org) instead of a simple start file. + + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import io +import os +import signal +import socket +import sys + +from ._compat import PY2 +from ._compat import reraise +from ._compat import WIN +from ._compat import wsgi_encoding_dance +from ._internal import _log +from .exceptions import InternalServerError +from .urls import uri_to_iri +from .urls import url_parse +from .urls import url_unquote + +try: + import socketserver + from http.server import BaseHTTPRequestHandler + from http.server import HTTPServer +except ImportError: + import SocketServer as socketserver + from BaseHTTPServer import HTTPServer + from BaseHTTPServer import BaseHTTPRequestHandler + +try: + import ssl +except ImportError: + + class _SslDummy(object): + def __getattr__(self, name): + raise RuntimeError("SSL support unavailable") + + ssl = _SslDummy() + +try: + import termcolor +except ImportError: + termcolor = None + + +def _get_openssl_crypto_module(): + try: + from OpenSSL import crypto + except ImportError: + raise TypeError("Using ad-hoc certificates requires the pyOpenSSL library.") + else: + return crypto + + +ThreadingMixIn = socketserver.ThreadingMixIn +can_fork = hasattr(os, "fork") + +if can_fork: + ForkingMixIn = socketserver.ForkingMixIn +else: + + class ForkingMixIn(object): + pass + + +try: + af_unix = socket.AF_UNIX +except AttributeError: + af_unix = None + + +LISTEN_QUEUE = 128 +can_open_by_fd = not WIN and hasattr(socket, "fromfd") + +# On Python 3, ConnectionError represents the same errnos as +# socket.error from Python 2, while socket.error is an alias for the +# more generic OSError. +if PY2: + _ConnectionError = socket.error +else: + _ConnectionError = ConnectionError + + +class DechunkedInput(io.RawIOBase): + """An input stream that handles Transfer-Encoding 'chunked'""" + + def __init__(self, rfile): + self._rfile = rfile + self._done = False + self._len = 0 + + def readable(self): + return True + + def read_chunk_len(self): + try: + line = self._rfile.readline().decode("latin1") + _len = int(line.strip(), 16) + except ValueError: + raise IOError("Invalid chunk header") + if _len < 0: + raise IOError("Negative chunk length not allowed") + return _len + + def readinto(self, buf): + read = 0 + while not self._done and read < len(buf): + if self._len == 0: + # This is the first chunk or we fully consumed the previous + # one. Read the next length of the next chunk + self._len = self.read_chunk_len() + + if self._len == 0: + # Found the final chunk of size 0. The stream is now exhausted, + # but there is still a final newline that should be consumed + self._done = True + + if self._len > 0: + # There is data (left) in this chunk, so append it to the + # buffer. If this operation fully consumes the chunk, this will + # reset self._len to 0. + n = min(len(buf), self._len) + buf[read : read + n] = self._rfile.read(n) + self._len -= n + read += n + + if self._len == 0: + # Skip the terminating newline of a chunk that has been fully + # consumed. This also applies to the 0-sized final chunk + terminator = self._rfile.readline() + if terminator not in (b"\n", b"\r\n", b"\r"): + raise IOError("Missing chunk terminating newline") + + return read + + +class WSGIRequestHandler(BaseHTTPRequestHandler, object): + + """A request handler that implements WSGI dispatching.""" + + @property + def server_version(self): + from . import __version__ + + return "Werkzeug/" + __version__ + + def make_environ(self): + request_url = url_parse(self.path) + + def shutdown_server(): + self.server.shutdown_signal = True + + url_scheme = "http" if self.server.ssl_context is None else "https" + if not self.client_address: + self.client_address = "" + if isinstance(self.client_address, str): + self.client_address = (self.client_address, 0) + else: + pass + path_info = url_unquote(request_url.path) + + environ = { + "wsgi.version": (1, 0), + "wsgi.url_scheme": url_scheme, + "wsgi.input": self.rfile, + "wsgi.errors": sys.stderr, + "wsgi.multithread": self.server.multithread, + "wsgi.multiprocess": self.server.multiprocess, + "wsgi.run_once": False, + "werkzeug.server.shutdown": shutdown_server, + "SERVER_SOFTWARE": self.server_version, + "REQUEST_METHOD": self.command, + "SCRIPT_NAME": "", + "PATH_INFO": wsgi_encoding_dance(path_info), + "QUERY_STRING": wsgi_encoding_dance(request_url.query), + # Non-standard, added by mod_wsgi, uWSGI + "REQUEST_URI": wsgi_encoding_dance(self.path), + # Non-standard, added by gunicorn + "RAW_URI": wsgi_encoding_dance(self.path), + "REMOTE_ADDR": self.address_string(), + "REMOTE_PORT": self.port_integer(), + "SERVER_NAME": self.server.server_address[0], + "SERVER_PORT": str(self.server.server_address[1]), + "SERVER_PROTOCOL": self.request_version, + } + + for key, value in self.get_header_items(): + key = key.upper().replace("-", "_") + value = value.replace("\r\n", "") + if key not in ("CONTENT_TYPE", "CONTENT_LENGTH"): + key = "HTTP_" + key + if key in environ: + value = "{},{}".format(environ[key], value) + environ[key] = value + + if environ.get("HTTP_TRANSFER_ENCODING", "").strip().lower() == "chunked": + environ["wsgi.input_terminated"] = True + environ["wsgi.input"] = DechunkedInput(environ["wsgi.input"]) + + if request_url.scheme and request_url.netloc: + environ["HTTP_HOST"] = request_url.netloc + + return environ + + def run_wsgi(self): + if self.headers.get("Expect", "").lower().strip() == "100-continue": + self.wfile.write(b"HTTP/1.1 100 Continue\r\n\r\n") + + self.environ = environ = self.make_environ() + headers_set = [] + headers_sent = [] + + def write(data): + assert headers_set, "write() before start_response" + if not headers_sent: + status, response_headers = headers_sent[:] = headers_set + try: + code, msg = status.split(None, 1) + except ValueError: + code, msg = status, "" + code = int(code) + self.send_response(code, msg) + header_keys = set() + for key, value in response_headers: + self.send_header(key, value) + key = key.lower() + header_keys.add(key) + if not ( + "content-length" in header_keys + or environ["REQUEST_METHOD"] == "HEAD" + or code < 200 + or code in (204, 304) + ): + self.close_connection = True + self.send_header("Connection", "close") + if "server" not in header_keys: + self.send_header("Server", self.version_string()) + if "date" not in header_keys: + self.send_header("Date", self.date_time_string()) + self.end_headers() + + assert isinstance(data, bytes), "applications must write bytes" + self.wfile.write(data) + self.wfile.flush() + + def start_response(status, response_headers, exc_info=None): + if exc_info: + try: + if headers_sent: + reraise(*exc_info) + finally: + exc_info = None + elif headers_set: + raise AssertionError("Headers already set") + headers_set[:] = [status, response_headers] + return write + + def execute(app): + application_iter = app(environ, start_response) + try: + for data in application_iter: + write(data) + if not headers_sent: + write(b"") + finally: + if hasattr(application_iter, "close"): + application_iter.close() + application_iter = None + + try: + execute(self.server.app) + except (_ConnectionError, socket.timeout) as e: + self.connection_dropped(e, environ) + except Exception: + if self.server.passthrough_errors: + raise + from .debug.tbtools import get_current_traceback + + traceback = get_current_traceback(ignore_system_exceptions=True) + try: + # if we haven't yet sent the headers but they are set + # we roll back to be able to set them again. + if not headers_sent: + del headers_set[:] + execute(InternalServerError()) + except Exception: + pass + self.server.log("error", "Error on request:\n%s", traceback.plaintext) + + def handle(self): + """Handles a request ignoring dropped connections.""" + rv = None + try: + rv = BaseHTTPRequestHandler.handle(self) + except (_ConnectionError, socket.timeout) as e: + self.connection_dropped(e) + except Exception as e: + if self.server.ssl_context is None or not is_ssl_error(e): + raise + if self.server.shutdown_signal: + self.initiate_shutdown() + return rv + + def initiate_shutdown(self): + """A horrible, horrible way to kill the server for Python 2.6 and + later. It's the best we can do. + """ + # Windows does not provide SIGKILL, go with SIGTERM then. + sig = getattr(signal, "SIGKILL", signal.SIGTERM) + # reloader active + if is_running_from_reloader(): + os.kill(os.getpid(), sig) + # python 2.7 + self.server._BaseServer__shutdown_request = True + # python 2.6 + self.server._BaseServer__serving = False + + def connection_dropped(self, error, environ=None): + """Called if the connection was closed by the client. By default + nothing happens. + """ + + def handle_one_request(self): + """Handle a single HTTP request.""" + self.raw_requestline = self.rfile.readline() + if not self.raw_requestline: + self.close_connection = 1 + elif self.parse_request(): + return self.run_wsgi() + + def send_response(self, code, message=None): + """Send the response header and log the response code.""" + self.log_request(code) + if message is None: + message = code in self.responses and self.responses[code][0] or "" + if self.request_version != "HTTP/0.9": + hdr = "%s %d %s\r\n" % (self.protocol_version, code, message) + self.wfile.write(hdr.encode("ascii")) + + def version_string(self): + return BaseHTTPRequestHandler.version_string(self).strip() + + def address_string(self): + if getattr(self, "environ", None): + return self.environ["REMOTE_ADDR"] + elif not self.client_address: + return "" + elif isinstance(self.client_address, str): + return self.client_address + else: + return self.client_address[0] + + def port_integer(self): + return self.client_address[1] + + def log_request(self, code="-", size="-"): + try: + path = uri_to_iri(self.path) + msg = "%s %s %s" % (self.command, path, self.request_version) + except AttributeError: + # path isn't set if the requestline was bad + msg = self.requestline + + code = str(code) + + if termcolor: + color = termcolor.colored + + if code[0] == "1": # 1xx - Informational + msg = color(msg, attrs=["bold"]) + elif code[0] == "2": # 2xx - Success + msg = color(msg, color="white") + elif code == "304": # 304 - Resource Not Modified + msg = color(msg, color="cyan") + elif code[0] == "3": # 3xx - Redirection + msg = color(msg, color="green") + elif code == "404": # 404 - Resource Not Found + msg = color(msg, color="yellow") + elif code[0] == "4": # 4xx - Client Error + msg = color(msg, color="red", attrs=["bold"]) + else: # 5xx, or any other response + msg = color(msg, color="magenta", attrs=["bold"]) + + self.log("info", '"%s" %s %s', msg, code, size) + + def log_error(self, *args): + self.log("error", *args) + + def log_message(self, format, *args): + self.log("info", format, *args) + + def log(self, type, message, *args): + _log( + type, + "%s - - [%s] %s\n" + % (self.address_string(), self.log_date_time_string(), message % args), + ) + + def get_header_items(self): + """ + Get an iterable list of key/value pairs representing headers. + + This function provides Python 2/3 compatibility as related to the + parsing of request headers. Python 2.7 is not compliant with + RFC 3875 Section 4.1.18 which requires multiple values for headers + to be provided or RFC 2616 which allows for folding of multi-line + headers. This function will return a matching list regardless + of Python version. It can be removed once Python 2.7 support + is dropped. + + :return: List of tuples containing header hey/value pairs + """ + if PY2: + # For Python 2, process the headers manually according to + # W3C RFC 2616 Section 4.2. + items = [] + for header in self.headers.headers: + # Remove "\r\n" from the header and split on ":" to get + # the field name and value. + try: + key, value = header[0:-2].split(":", 1) + except ValueError: + # If header could not be slit with : but starts with white + # space and it follows an existing header, it's a folded + # header. + if header[0] in ("\t", " ") and items: + # Pop off the last header + key, value = items.pop() + # Append the current header to the value of the last + # header which will be placed back on the end of the + # list + value = value + header + # Otherwise it's just a bad header and should error + else: + # Re-raise the value error + raise + + # Add the key and the value once stripped of leading + # white space. The specification allows for stripping + # trailing white space but the Python 3 code does not + # strip trailing white space. Therefore, trailing space + # will be left as is to match the Python 3 behavior. + items.append((key, value.lstrip())) + else: + items = self.headers.items() + + return items + + +#: backwards compatible name if someone is subclassing it +BaseRequestHandler = WSGIRequestHandler + + +def generate_adhoc_ssl_pair(cn=None): + from random import random + + crypto = _get_openssl_crypto_module() + + # pretty damn sure that this is not actually accepted by anyone + if cn is None: + cn = "*" + + cert = crypto.X509() + cert.set_serial_number(int(random() * sys.maxsize)) + cert.gmtime_adj_notBefore(0) + cert.gmtime_adj_notAfter(60 * 60 * 24 * 365) + + subject = cert.get_subject() + subject.CN = cn + subject.O = "Dummy Certificate" # noqa: E741 + + issuer = cert.get_issuer() + issuer.CN = subject.CN + issuer.O = subject.O # noqa: E741 + + pkey = crypto.PKey() + pkey.generate_key(crypto.TYPE_RSA, 2048) + cert.set_pubkey(pkey) + cert.sign(pkey, "sha256") + + return cert, pkey + + +def make_ssl_devcert(base_path, host=None, cn=None): + """Creates an SSL key for development. This should be used instead of + the ``'adhoc'`` key which generates a new cert on each server start. + It accepts a path for where it should store the key and cert and + either a host or CN. If a host is given it will use the CN + ``*.host/CN=host``. + + For more information see :func:`run_simple`. + + .. versionadded:: 0.9 + + :param base_path: the path to the certificate and key. The extension + ``.crt`` is added for the certificate, ``.key`` is + added for the key. + :param host: the name of the host. This can be used as an alternative + for the `cn`. + :param cn: the `CN` to use. + """ + from OpenSSL import crypto + + if host is not None: + cn = "*.%s/CN=%s" % (host, host) + cert, pkey = generate_adhoc_ssl_pair(cn=cn) + + cert_file = base_path + ".crt" + pkey_file = base_path + ".key" + + with open(cert_file, "wb") as f: + f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) + with open(pkey_file, "wb") as f: + f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)) + + return cert_file, pkey_file + + +def generate_adhoc_ssl_context(): + """Generates an adhoc SSL context for the development server.""" + crypto = _get_openssl_crypto_module() + import tempfile + import atexit + + cert, pkey = generate_adhoc_ssl_pair() + cert_handle, cert_file = tempfile.mkstemp() + pkey_handle, pkey_file = tempfile.mkstemp() + atexit.register(os.remove, pkey_file) + atexit.register(os.remove, cert_file) + + os.write(cert_handle, crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) + os.write(pkey_handle, crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)) + os.close(cert_handle) + os.close(pkey_handle) + ctx = load_ssl_context(cert_file, pkey_file) + return ctx + + +def load_ssl_context(cert_file, pkey_file=None, protocol=None): + """Loads SSL context from cert/private key files and optional protocol. + Many parameters are directly taken from the API of + :py:class:`ssl.SSLContext`. + + :param cert_file: Path of the certificate to use. + :param pkey_file: Path of the private key to use. If not given, the key + will be obtained from the certificate file. + :param protocol: One of the ``PROTOCOL_*`` constants in the stdlib ``ssl`` + module. Defaults to ``PROTOCOL_SSLv23``. + """ + if protocol is None: + protocol = ssl.PROTOCOL_SSLv23 + ctx = _SSLContext(protocol) + ctx.load_cert_chain(cert_file, pkey_file) + return ctx + + +class _SSLContext(object): + + """A dummy class with a small subset of Python3's ``ssl.SSLContext``, only + intended to be used with and by Werkzeug.""" + + def __init__(self, protocol): + self._protocol = protocol + self._certfile = None + self._keyfile = None + self._password = None + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._certfile = certfile + self._keyfile = keyfile or certfile + self._password = password + + def wrap_socket(self, sock, **kwargs): + return ssl.wrap_socket( + sock, + keyfile=self._keyfile, + certfile=self._certfile, + ssl_version=self._protocol, + **kwargs + ) + + +def is_ssl_error(error=None): + """Checks if the given error (or the current one) is an SSL error.""" + exc_types = (ssl.SSLError,) + try: + from OpenSSL.SSL import Error + + exc_types += (Error,) + except ImportError: + pass + + if error is None: + error = sys.exc_info()[1] + return isinstance(error, exc_types) + + +def select_address_family(host, port): + """Return ``AF_INET4``, ``AF_INET6``, or ``AF_UNIX`` depending on + the host and port.""" + # disabled due to problems with current ipv6 implementations + # and various operating systems. Probably this code also is + # not supposed to work, but I can't come up with any other + # ways to implement this. + # try: + # info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, + # socket.SOCK_STREAM, 0, + # socket.AI_PASSIVE) + # if info: + # return info[0][0] + # except socket.gaierror: + # pass + if host.startswith("unix://"): + return socket.AF_UNIX + elif ":" in host and hasattr(socket, "AF_INET6"): + return socket.AF_INET6 + return socket.AF_INET + + +def get_sockaddr(host, port, family): + """Return a fully qualified socket address that can be passed to + :func:`socket.bind`.""" + if family == af_unix: + return host.split("://", 1)[1] + try: + res = socket.getaddrinfo( + host, port, family, socket.SOCK_STREAM, socket.IPPROTO_TCP + ) + except socket.gaierror: + return host, port + return res[0][4] + + +class BaseWSGIServer(HTTPServer, object): + + """Simple single-threaded, single-process WSGI server.""" + + multithread = False + multiprocess = False + request_queue_size = LISTEN_QUEUE + + def __init__( + self, + host, + port, + app, + handler=None, + passthrough_errors=False, + ssl_context=None, + fd=None, + ): + if handler is None: + handler = WSGIRequestHandler + + self.address_family = select_address_family(host, port) + + if fd is not None: + real_sock = socket.fromfd(fd, self.address_family, socket.SOCK_STREAM) + port = 0 + + server_address = get_sockaddr(host, int(port), self.address_family) + + # remove socket file if it already exists + if self.address_family == af_unix and os.path.exists(server_address): + os.unlink(server_address) + HTTPServer.__init__(self, server_address, handler) + + self.app = app + self.passthrough_errors = passthrough_errors + self.shutdown_signal = False + self.host = host + self.port = self.socket.getsockname()[1] + + # Patch in the original socket. + if fd is not None: + self.socket.close() + self.socket = real_sock + self.server_address = self.socket.getsockname() + + if ssl_context is not None: + if isinstance(ssl_context, tuple): + ssl_context = load_ssl_context(*ssl_context) + if ssl_context == "adhoc": + ssl_context = generate_adhoc_ssl_context() + # If we are on Python 2 the return value from socket.fromfd + # is an internal socket object but what we need for ssl wrap + # is the wrapper around it :( + sock = self.socket + if PY2 and not isinstance(sock, socket.socket): + sock = socket.socket(sock.family, sock.type, sock.proto, sock) + self.socket = ssl_context.wrap_socket(sock, server_side=True) + self.ssl_context = ssl_context + else: + self.ssl_context = None + + def log(self, type, message, *args): + _log(type, message, *args) + + def serve_forever(self): + self.shutdown_signal = False + try: + HTTPServer.serve_forever(self) + except KeyboardInterrupt: + pass + finally: + self.server_close() + + def handle_error(self, request, client_address): + if self.passthrough_errors: + raise + # Python 2 still causes a socket.error after the earlier + # handling, so silence it here. + if isinstance(sys.exc_info()[1], _ConnectionError): + return + return HTTPServer.handle_error(self, request, client_address) + + def get_request(self): + con, info = self.socket.accept() + return con, info + + +class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer): + + """A WSGI server that does threading.""" + + multithread = True + daemon_threads = True + + +class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer): + + """A WSGI server that does forking.""" + + multiprocess = True + + def __init__( + self, + host, + port, + app, + processes=40, + handler=None, + passthrough_errors=False, + ssl_context=None, + fd=None, + ): + if not can_fork: + raise ValueError("Your platform does not support forking.") + BaseWSGIServer.__init__( + self, host, port, app, handler, passthrough_errors, ssl_context, fd + ) + self.max_children = processes + + +def make_server( + host=None, + port=None, + app=None, + threaded=False, + processes=1, + request_handler=None, + passthrough_errors=False, + ssl_context=None, + fd=None, +): + """Create a new server instance that is either threaded, or forks + or just processes one request after another. + """ + if threaded and processes > 1: + raise ValueError("cannot have a multithreaded and multi process server.") + elif threaded: + return ThreadedWSGIServer( + host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd + ) + elif processes > 1: + return ForkingWSGIServer( + host, + port, + app, + processes, + request_handler, + passthrough_errors, + ssl_context, + fd=fd, + ) + else: + return BaseWSGIServer( + host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd + ) + + +def is_running_from_reloader(): + """Checks if the application is running from within the Werkzeug + reloader subprocess. + + .. versionadded:: 0.10 + """ + return os.environ.get("WERKZEUG_RUN_MAIN") == "true" + + +def run_simple( + hostname, + port, + application, + use_reloader=False, + use_debugger=False, + use_evalex=True, + extra_files=None, + reloader_interval=1, + reloader_type="auto", + threaded=False, + processes=1, + request_handler=None, + static_files=None, + passthrough_errors=False, + ssl_context=None, +): + """Start a WSGI application. Optional features include a reloader, + multithreading and fork support. + + This function has a command-line interface too:: + + python -m werkzeug.serving --help + + .. versionadded:: 0.5 + `static_files` was added to simplify serving of static files as well + as `passthrough_errors`. + + .. versionadded:: 0.6 + support for SSL was added. + + .. versionadded:: 0.8 + Added support for automatically loading a SSL context from certificate + file and private key. + + .. versionadded:: 0.9 + Added command-line interface. + + .. versionadded:: 0.10 + Improved the reloader and added support for changing the backend + through the `reloader_type` parameter. See :ref:`reloader` + for more information. + + .. versionchanged:: 0.15 + Bind to a Unix socket by passing a path that starts with + ``unix://`` as the ``hostname``. + + :param hostname: The host to bind to, for example ``'localhost'``. + If the value is a path that starts with ``unix://`` it will bind + to a Unix socket instead of a TCP socket.. + :param port: The port for the server. eg: ``8080`` + :param application: the WSGI application to execute + :param use_reloader: should the server automatically restart the python + process if modules were changed? + :param use_debugger: should the werkzeug debugging system be used? + :param use_evalex: should the exception evaluation feature be enabled? + :param extra_files: a list of files the reloader should watch + additionally to the modules. For example configuration + files. + :param reloader_interval: the interval for the reloader in seconds. + :param reloader_type: the type of reloader to use. The default is + auto detection. Valid values are ``'stat'`` and + ``'watchdog'``. See :ref:`reloader` for more + information. + :param threaded: should the process handle each request in a separate + thread? + :param processes: if greater than 1 then handle each request in a new process + up to this maximum number of concurrent processes. + :param request_handler: optional parameter that can be used to replace + the default one. You can use this to replace it + with a different + :class:`~BaseHTTPServer.BaseHTTPRequestHandler` + subclass. + :param static_files: a list or dict of paths for static files. This works + exactly like :class:`SharedDataMiddleware`, it's actually + just wrapping the application in that middleware before + serving. + :param passthrough_errors: set this to `True` to disable the error catching. + This means that the server will die on errors but + it can be useful to hook debuggers in (pdb etc.) + :param ssl_context: an SSL context for the connection. Either an + :class:`ssl.SSLContext`, a tuple in the form + ``(cert_file, pkey_file)``, the string ``'adhoc'`` if + the server should automatically create one, or ``None`` + to disable SSL (which is the default). + """ + if not isinstance(port, int): + raise TypeError("port must be an integer") + if use_debugger: + from .debug import DebuggedApplication + + application = DebuggedApplication(application, use_evalex) + if static_files: + from .middleware.shared_data import SharedDataMiddleware + + application = SharedDataMiddleware(application, static_files) + + def log_startup(sock): + display_hostname = hostname if hostname not in ("", "*") else "localhost" + quit_msg = "(Press CTRL+C to quit)" + if sock.family == af_unix: + _log("info", " * Running on %s %s", display_hostname, quit_msg) + else: + if ":" in display_hostname: + display_hostname = "[%s]" % display_hostname + port = sock.getsockname()[1] + _log( + "info", + " * Running on %s://%s:%d/ %s", + "http" if ssl_context is None else "https", + display_hostname, + port, + quit_msg, + ) + + def inner(): + try: + fd = int(os.environ["WERKZEUG_SERVER_FD"]) + except (LookupError, ValueError): + fd = None + srv = make_server( + hostname, + port, + application, + threaded, + processes, + request_handler, + passthrough_errors, + ssl_context, + fd=fd, + ) + if fd is None: + log_startup(srv.socket) + srv.serve_forever() + + if use_reloader: + # If we're not running already in the subprocess that is the + # reloader we want to open up a socket early to make sure the + # port is actually available. + if not is_running_from_reloader(): + if port == 0 and not can_open_by_fd: + raise ValueError( + "Cannot bind to a random port with enabled " + "reloader if the Python interpreter does " + "not support socket opening by fd." + ) + + # Create and destroy a socket so that any exceptions are + # raised before we spawn a separate Python interpreter and + # lose this ability. + address_family = select_address_family(hostname, port) + server_address = get_sockaddr(hostname, port, address_family) + s = socket.socket(address_family, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(server_address) + if hasattr(s, "set_inheritable"): + s.set_inheritable(True) + + # If we can open the socket by file descriptor, then we can just + # reuse this one and our socket will survive the restarts. + if can_open_by_fd: + os.environ["WERKZEUG_SERVER_FD"] = str(s.fileno()) + s.listen(LISTEN_QUEUE) + log_startup(s) + else: + s.close() + if address_family == af_unix: + _log("info", "Unlinking %s" % server_address) + os.unlink(server_address) + + # Do not use relative imports, otherwise "python -m werkzeug.serving" + # breaks. + from ._reloader import run_with_reloader + + run_with_reloader(inner, extra_files, reloader_interval, reloader_type) + else: + inner() + + +def run_with_reloader(*args, **kwargs): + # People keep using undocumented APIs. Do not use this function + # please, we do not guarantee that it continues working. + from ._reloader import run_with_reloader + + return run_with_reloader(*args, **kwargs) + + +def main(): + """A simple command-line interface for :py:func:`run_simple`.""" + + # in contrast to argparse, this works at least under Python < 2.7 + import optparse + from .utils import import_string + + parser = optparse.OptionParser(usage="Usage: %prog [options] app_module:app_object") + parser.add_option( + "-b", + "--bind", + dest="address", + help="The hostname:port the app should listen on.", + ) + parser.add_option( + "-d", + "--debug", + dest="use_debugger", + action="store_true", + default=False, + help="Use Werkzeug's debugger.", + ) + parser.add_option( + "-r", + "--reload", + dest="use_reloader", + action="store_true", + default=False, + help="Reload Python process if modules change.", + ) + options, args = parser.parse_args() + + hostname, port = None, None + if options.address: + address = options.address.split(":") + hostname = address[0] + if len(address) > 1: + port = address[1] + + if len(args) != 1: + sys.stdout.write("No application supplied, or too much. See --help\n") + sys.exit(1) + app = import_string(args[0]) + + run_simple( + hostname=(hostname or "127.0.0.1"), + port=int(port or 5000), + application=app, + use_reloader=options.use_reloader, + use_debugger=options.use_debugger, + ) + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python2.7/site-packages/werkzeug/test.py b/venv/lib/python2.7/site-packages/werkzeug/test.py new file mode 100644 index 00000000..61486656 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/test.py @@ -0,0 +1,1146 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.test + ~~~~~~~~~~~~~ + + This module implements a client to WSGI applications for testing. + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import mimetypes +import sys +from io import BytesIO +from itertools import chain +from random import random +from tempfile import TemporaryFile +from time import time + +from ._compat import iteritems +from ._compat import iterlists +from ._compat import itervalues +from ._compat import make_literal_wrapper +from ._compat import reraise +from ._compat import string_types +from ._compat import text_type +from ._compat import to_bytes +from ._compat import wsgi_encoding_dance +from ._internal import _get_environ +from .datastructures import CallbackDict +from .datastructures import CombinedMultiDict +from .datastructures import EnvironHeaders +from .datastructures import FileMultiDict +from .datastructures import FileStorage +from .datastructures import Headers +from .datastructures import MultiDict +from .http import dump_cookie +from .http import dump_options_header +from .http import parse_options_header +from .urls import iri_to_uri +from .urls import url_encode +from .urls import url_fix +from .urls import url_parse +from .urls import url_unparse +from .urls import url_unquote +from .utils import get_content_type +from .wrappers import BaseRequest +from .wsgi import ClosingIterator +from .wsgi import get_current_url + +try: + from urllib.request import Request as U2Request +except ImportError: + from urllib2 import Request as U2Request + +try: + from http.cookiejar import CookieJar +except ImportError: + from cookielib import CookieJar + + +def stream_encode_multipart( + values, use_tempfile=True, threshold=1024 * 500, boundary=None, charset="utf-8" +): + """Encode a dict of values (either strings or file descriptors or + :class:`FileStorage` objects.) into a multipart encoded string stored + in a file descriptor. + """ + if boundary is None: + boundary = "---------------WerkzeugFormPart_%s%s" % (time(), random()) + _closure = [BytesIO(), 0, False] + + if use_tempfile: + + def write_binary(string): + stream, total_length, on_disk = _closure + if on_disk: + stream.write(string) + else: + length = len(string) + if length + _closure[1] <= threshold: + stream.write(string) + else: + new_stream = TemporaryFile("wb+") + new_stream.write(stream.getvalue()) + new_stream.write(string) + _closure[0] = new_stream + _closure[2] = True + _closure[1] = total_length + length + + else: + write_binary = _closure[0].write + + def write(string): + write_binary(string.encode(charset)) + + if not isinstance(values, MultiDict): + values = MultiDict(values) + + for key, values in iterlists(values): + for value in values: + write('--%s\r\nContent-Disposition: form-data; name="%s"' % (boundary, key)) + reader = getattr(value, "read", None) + if reader is not None: + filename = getattr(value, "filename", getattr(value, "name", None)) + content_type = getattr(value, "content_type", None) + if content_type is None: + content_type = ( + filename + and mimetypes.guess_type(filename)[0] + or "application/octet-stream" + ) + if filename is not None: + write('; filename="%s"\r\n' % filename) + else: + write("\r\n") + write("Content-Type: %s\r\n\r\n" % content_type) + while 1: + chunk = reader(16384) + if not chunk: + break + write_binary(chunk) + else: + if not isinstance(value, string_types): + value = str(value) + + value = to_bytes(value, charset) + write("\r\n\r\n") + write_binary(value) + write("\r\n") + write("--%s--\r\n" % boundary) + + length = int(_closure[0].tell()) + _closure[0].seek(0) + return _closure[0], length, boundary + + +def encode_multipart(values, boundary=None, charset="utf-8"): + """Like `stream_encode_multipart` but returns a tuple in the form + (``boundary``, ``data``) where data is a bytestring. + """ + stream, length, boundary = stream_encode_multipart( + values, use_tempfile=False, boundary=boundary, charset=charset + ) + return boundary, stream.read() + + +def File(fd, filename=None, mimetype=None): + """Backwards compat. + + .. deprecated:: 0.5 + """ + from warnings import warn + + warn( + "'werkzeug.test.File' is deprecated as of version 0.5 and will" + " be removed in version 1.0. Use 'EnvironBuilder' or" + " 'FileStorage' instead.", + DeprecationWarning, + stacklevel=2, + ) + return FileStorage(fd, filename=filename, content_type=mimetype) + + +class _TestCookieHeaders(object): + + """A headers adapter for cookielib + """ + + def __init__(self, headers): + self.headers = headers + + def getheaders(self, name): + headers = [] + name = name.lower() + for k, v in self.headers: + if k.lower() == name: + headers.append(v) + return headers + + def get_all(self, name, default=None): + rv = [] + for k, v in self.headers: + if k.lower() == name.lower(): + rv.append(v) + return rv or default or [] + + +class _TestCookieResponse(object): + + """Something that looks like a httplib.HTTPResponse, but is actually just an + adapter for our test responses to make them available for cookielib. + """ + + def __init__(self, headers): + self.headers = _TestCookieHeaders(headers) + + def info(self): + return self.headers + + +class _TestCookieJar(CookieJar): + + """A cookielib.CookieJar modified to inject and read cookie headers from + and to wsgi environments, and wsgi application responses. + """ + + def inject_wsgi(self, environ): + """Inject the cookies as client headers into the server's wsgi + environment. + """ + cvals = ["%s=%s" % (c.name, c.value) for c in self] + + if cvals: + environ["HTTP_COOKIE"] = "; ".join(cvals) + else: + environ.pop("HTTP_COOKIE", None) + + def extract_wsgi(self, environ, headers): + """Extract the server's set-cookie headers as cookies into the + cookie jar. + """ + self.extract_cookies( + _TestCookieResponse(headers), U2Request(get_current_url(environ)) + ) + + +def _iter_data(data): + """Iterates over a `dict` or :class:`MultiDict` yielding all keys and + values. + This is used to iterate over the data passed to the + :class:`EnvironBuilder`. + """ + if isinstance(data, MultiDict): + for key, values in iterlists(data): + for value in values: + yield key, value + else: + for key, values in iteritems(data): + if isinstance(values, list): + for value in values: + yield key, value + else: + yield key, values + + +class EnvironBuilder(object): + """This class can be used to conveniently create a WSGI environment + for testing purposes. It can be used to quickly create WSGI environments + or request objects from arbitrary data. + + The signature of this class is also used in some other places as of + Werkzeug 0.5 (:func:`create_environ`, :meth:`BaseResponse.from_values`, + :meth:`Client.open`). Because of this most of the functionality is + available through the constructor alone. + + Files and regular form data can be manipulated independently of each + other with the :attr:`form` and :attr:`files` attributes, but are + passed with the same argument to the constructor: `data`. + + `data` can be any of these values: + + - a `str` or `bytes` object: The object is converted into an + :attr:`input_stream`, the :attr:`content_length` is set and you have to + provide a :attr:`content_type`. + - a `dict` or :class:`MultiDict`: The keys have to be strings. The values + have to be either any of the following objects, or a list of any of the + following objects: + + - a :class:`file`-like object: These are converted into + :class:`FileStorage` objects automatically. + - a `tuple`: The :meth:`~FileMultiDict.add_file` method is called + with the key and the unpacked `tuple` items as positional + arguments. + - a `str`: The string is set as form data for the associated key. + - a file-like object: The object content is loaded in memory and then + handled like a regular `str` or a `bytes`. + + :param path: the path of the request. In the WSGI environment this will + end up as `PATH_INFO`. If the `query_string` is not defined + and there is a question mark in the `path` everything after + it is used as query string. + :param base_url: the base URL is a URL that is used to extract the WSGI + URL scheme, host (server name + server port) and the + script root (`SCRIPT_NAME`). + :param query_string: an optional string or dict with URL parameters. + :param method: the HTTP method to use, defaults to `GET`. + :param input_stream: an optional input stream. Do not specify this and + `data`. As soon as an input stream is set you can't + modify :attr:`args` and :attr:`files` unless you + set the :attr:`input_stream` to `None` again. + :param content_type: The content type for the request. As of 0.5 you + don't have to provide this when specifying files + and form data via `data`. + :param content_length: The content length for the request. You don't + have to specify this when providing data via + `data`. + :param errors_stream: an optional error stream that is used for + `wsgi.errors`. Defaults to :data:`stderr`. + :param multithread: controls `wsgi.multithread`. Defaults to `False`. + :param multiprocess: controls `wsgi.multiprocess`. Defaults to `False`. + :param run_once: controls `wsgi.run_once`. Defaults to `False`. + :param headers: an optional list or :class:`Headers` object of headers. + :param data: a string or dict of form data or a file-object. + See explanation above. + :param json: An object to be serialized and assigned to ``data``. + Defaults the content type to ``"application/json"``. + Serialized with the function assigned to :attr:`json_dumps`. + :param environ_base: an optional dict of environment defaults. + :param environ_overrides: an optional dict of environment overrides. + :param charset: the charset used to encode unicode data. + + .. versionadded:: 0.15 + The ``json`` param and :meth:`json_dumps` method. + + .. versionadded:: 0.15 + The environ has keys ``REQUEST_URI`` and ``RAW_URI`` containing + the path before perecent-decoding. This is not part of the WSGI + PEP, but many WSGI servers include it. + + .. versionchanged:: 0.6 + ``path`` and ``base_url`` can now be unicode strings that are + encoded with :func:`iri_to_uri`. + """ + + #: the server protocol to use. defaults to HTTP/1.1 + server_protocol = "HTTP/1.1" + + #: the wsgi version to use. defaults to (1, 0) + wsgi_version = (1, 0) + + #: the default request class for :meth:`get_request` + request_class = BaseRequest + + import json + + #: The serialization function used when ``json`` is passed. + json_dumps = staticmethod(json.dumps) + del json + + def __init__( + self, + path="/", + base_url=None, + query_string=None, + method="GET", + input_stream=None, + content_type=None, + content_length=None, + errors_stream=None, + multithread=False, + multiprocess=False, + run_once=False, + headers=None, + data=None, + environ_base=None, + environ_overrides=None, + charset="utf-8", + mimetype=None, + json=None, + ): + path_s = make_literal_wrapper(path) + if query_string is not None and path_s("?") in path: + raise ValueError("Query string is defined in the path and as an argument") + if query_string is None and path_s("?") in path: + path, query_string = path.split(path_s("?"), 1) + self.charset = charset + self.path = iri_to_uri(path) + if base_url is not None: + base_url = url_fix(iri_to_uri(base_url, charset), charset) + self.base_url = base_url + if isinstance(query_string, (bytes, text_type)): + self.query_string = query_string + else: + if query_string is None: + query_string = MultiDict() + elif not isinstance(query_string, MultiDict): + query_string = MultiDict(query_string) + self.args = query_string + self.method = method + if headers is None: + headers = Headers() + elif not isinstance(headers, Headers): + headers = Headers(headers) + self.headers = headers + if content_type is not None: + self.content_type = content_type + if errors_stream is None: + errors_stream = sys.stderr + self.errors_stream = errors_stream + self.multithread = multithread + self.multiprocess = multiprocess + self.run_once = run_once + self.environ_base = environ_base + self.environ_overrides = environ_overrides + self.input_stream = input_stream + self.content_length = content_length + self.closed = False + + if json is not None: + if data is not None: + raise TypeError("can't provide both json and data") + + data = self.json_dumps(json) + + if self.content_type is None: + self.content_type = "application/json" + + if data: + if input_stream is not None: + raise TypeError("can't provide input stream and data") + if hasattr(data, "read"): + data = data.read() + if isinstance(data, text_type): + data = data.encode(self.charset) + if isinstance(data, bytes): + self.input_stream = BytesIO(data) + if self.content_length is None: + self.content_length = len(data) + else: + for key, value in _iter_data(data): + if isinstance(value, (tuple, dict)) or hasattr(value, "read"): + self._add_file_from_data(key, value) + else: + self.form.setlistdefault(key).append(value) + + if mimetype is not None: + self.mimetype = mimetype + + @classmethod + def from_environ(cls, environ, **kwargs): + """Turn an environ dict back into a builder. Any extra kwargs + override the args extracted from the environ. + + .. versionadded:: 0.15 + """ + headers = Headers(EnvironHeaders(environ)) + out = { + "path": environ["PATH_INFO"], + "base_url": cls._make_base_url( + environ["wsgi.url_scheme"], headers.pop("Host"), environ["SCRIPT_NAME"] + ), + "query_string": environ["QUERY_STRING"], + "method": environ["REQUEST_METHOD"], + "input_stream": environ["wsgi.input"], + "content_type": headers.pop("Content-Type", None), + "content_length": headers.pop("Content-Length", None), + "errors_stream": environ["wsgi.errors"], + "multithread": environ["wsgi.multithread"], + "multiprocess": environ["wsgi.multiprocess"], + "run_once": environ["wsgi.run_once"], + "headers": headers, + } + out.update(kwargs) + return cls(**out) + + def _add_file_from_data(self, key, value): + """Called in the EnvironBuilder to add files from the data dict.""" + if isinstance(value, tuple): + self.files.add_file(key, *value) + elif isinstance(value, dict): + from warnings import warn + + warn( + "Passing a dict as file data is deprecated as of" + " version 0.5 and will be removed in version 1.0. Use" + " a tuple or 'FileStorage' object instead.", + DeprecationWarning, + stacklevel=2, + ) + value = dict(value) + mimetype = value.pop("mimetype", None) + if mimetype is not None: + value["content_type"] = mimetype + self.files.add_file(key, **value) + else: + self.files.add_file(key, value) + + @staticmethod + def _make_base_url(scheme, host, script_root): + return url_unparse((scheme, host, script_root, "", "")).rstrip("/") + "/" + + @property + def base_url(self): + """The base URL is used to extract the URL scheme, host name, + port, and root path. + """ + return self._make_base_url(self.url_scheme, self.host, self.script_root) + + @base_url.setter + def base_url(self, value): + if value is None: + scheme = "http" + netloc = "localhost" + script_root = "" + else: + scheme, netloc, script_root, qs, anchor = url_parse(value) + if qs or anchor: + raise ValueError("base url must not contain a query string or fragment") + self.script_root = script_root.rstrip("/") + self.host = netloc + self.url_scheme = scheme + + def _get_content_type(self): + ct = self.headers.get("Content-Type") + if ct is None and not self._input_stream: + if self._files: + return "multipart/form-data" + elif self._form: + return "application/x-www-form-urlencoded" + return None + return ct + + def _set_content_type(self, value): + if value is None: + self.headers.pop("Content-Type", None) + else: + self.headers["Content-Type"] = value + + content_type = property( + _get_content_type, + _set_content_type, + doc="""The content type for the request. Reflected from and to + the :attr:`headers`. Do not set if you set :attr:`files` or + :attr:`form` for auto detection.""", + ) + del _get_content_type, _set_content_type + + def _get_content_length(self): + return self.headers.get("Content-Length", type=int) + + def _get_mimetype(self): + ct = self.content_type + if ct: + return ct.split(";")[0].strip() + + def _set_mimetype(self, value): + self.content_type = get_content_type(value, self.charset) + + def _get_mimetype_params(self): + def on_update(d): + self.headers["Content-Type"] = dump_options_header(self.mimetype, d) + + d = parse_options_header(self.headers.get("content-type", ""))[1] + return CallbackDict(d, on_update) + + mimetype = property( + _get_mimetype, + _set_mimetype, + doc="""The mimetype (content type without charset etc.) + + .. versionadded:: 0.14 + """, + ) + mimetype_params = property( + _get_mimetype_params, + doc=""" The mimetype parameters as dict. For example if the + content type is ``text/html; charset=utf-8`` the params would be + ``{'charset': 'utf-8'}``. + + .. versionadded:: 0.14 + """, + ) + del _get_mimetype, _set_mimetype, _get_mimetype_params + + def _set_content_length(self, value): + if value is None: + self.headers.pop("Content-Length", None) + else: + self.headers["Content-Length"] = str(value) + + content_length = property( + _get_content_length, + _set_content_length, + doc="""The content length as integer. Reflected from and to the + :attr:`headers`. Do not set if you set :attr:`files` or + :attr:`form` for auto detection.""", + ) + del _get_content_length, _set_content_length + + def form_property(name, storage, doc): # noqa: B902 + key = "_" + name + + def getter(self): + if self._input_stream is not None: + raise AttributeError("an input stream is defined") + rv = getattr(self, key) + if rv is None: + rv = storage() + setattr(self, key, rv) + + return rv + + def setter(self, value): + self._input_stream = None + setattr(self, key, value) + + return property(getter, setter, doc=doc) + + form = form_property("form", MultiDict, doc="A :class:`MultiDict` of form values.") + files = form_property( + "files", + FileMultiDict, + doc="""A :class:`FileMultiDict` of uploaded files. You can use + the :meth:`~FileMultiDict.add_file` method to add new files to + the dict.""", + ) + del form_property + + def _get_input_stream(self): + return self._input_stream + + def _set_input_stream(self, value): + self._input_stream = value + self._form = self._files = None + + input_stream = property( + _get_input_stream, + _set_input_stream, + doc="""An optional input stream. If you set this it will clear + :attr:`form` and :attr:`files`.""", + ) + del _get_input_stream, _set_input_stream + + def _get_query_string(self): + if self._query_string is None: + if self._args is not None: + return url_encode(self._args, charset=self.charset) + return "" + return self._query_string + + def _set_query_string(self, value): + self._query_string = value + self._args = None + + query_string = property( + _get_query_string, + _set_query_string, + doc="""The query string. If you set this to a string + :attr:`args` will no longer be available.""", + ) + del _get_query_string, _set_query_string + + def _get_args(self): + if self._query_string is not None: + raise AttributeError("a query string is defined") + if self._args is None: + self._args = MultiDict() + return self._args + + def _set_args(self, value): + self._query_string = None + self._args = value + + args = property( + _get_args, _set_args, doc="The URL arguments as :class:`MultiDict`." + ) + del _get_args, _set_args + + @property + def server_name(self): + """The server name (read-only, use :attr:`host` to set)""" + return self.host.split(":", 1)[0] + + @property + def server_port(self): + """The server port as integer (read-only, use :attr:`host` to set)""" + pieces = self.host.split(":", 1) + if len(pieces) == 2 and pieces[1].isdigit(): + return int(pieces[1]) + elif self.url_scheme == "https": + return 443 + return 80 + + def __del__(self): + try: + self.close() + except Exception: + pass + + def close(self): + """Closes all files. If you put real :class:`file` objects into the + :attr:`files` dict you can call this method to automatically close + them all in one go. + """ + if self.closed: + return + try: + files = itervalues(self.files) + except AttributeError: + files = () + for f in files: + try: + f.close() + except Exception: + pass + self.closed = True + + def get_environ(self): + """Return the built environ. + + .. versionchanged:: 0.15 + The content type and length headers are set based on + input stream detection. Previously this only set the WSGI + keys. + """ + input_stream = self.input_stream + content_length = self.content_length + + mimetype = self.mimetype + content_type = self.content_type + + if input_stream is not None: + start_pos = input_stream.tell() + input_stream.seek(0, 2) + end_pos = input_stream.tell() + input_stream.seek(start_pos) + content_length = end_pos - start_pos + elif mimetype == "multipart/form-data": + values = CombinedMultiDict([self.form, self.files]) + input_stream, content_length, boundary = stream_encode_multipart( + values, charset=self.charset + ) + content_type = mimetype + '; boundary="%s"' % boundary + elif mimetype == "application/x-www-form-urlencoded": + # XXX: py2v3 review + values = url_encode(self.form, charset=self.charset) + values = values.encode("ascii") + content_length = len(values) + input_stream = BytesIO(values) + else: + input_stream = BytesIO() + + result = {} + if self.environ_base: + result.update(self.environ_base) + + def _path_encode(x): + return wsgi_encoding_dance(url_unquote(x, self.charset), self.charset) + + qs = wsgi_encoding_dance(self.query_string) + + result.update( + { + "REQUEST_METHOD": self.method, + "SCRIPT_NAME": _path_encode(self.script_root), + "PATH_INFO": _path_encode(self.path), + "QUERY_STRING": qs, + # Non-standard, added by mod_wsgi, uWSGI + "REQUEST_URI": wsgi_encoding_dance(self.path), + # Non-standard, added by gunicorn + "RAW_URI": wsgi_encoding_dance(self.path), + "SERVER_NAME": self.server_name, + "SERVER_PORT": str(self.server_port), + "HTTP_HOST": self.host, + "SERVER_PROTOCOL": self.server_protocol, + "wsgi.version": self.wsgi_version, + "wsgi.url_scheme": self.url_scheme, + "wsgi.input": input_stream, + "wsgi.errors": self.errors_stream, + "wsgi.multithread": self.multithread, + "wsgi.multiprocess": self.multiprocess, + "wsgi.run_once": self.run_once, + } + ) + + headers = self.headers.copy() + + if content_type is not None: + result["CONTENT_TYPE"] = content_type + headers.set("Content-Type", content_type) + + if content_length is not None: + result["CONTENT_LENGTH"] = str(content_length) + headers.set("Content-Length", content_length) + + for key, value in headers.to_wsgi_list(): + result["HTTP_%s" % key.upper().replace("-", "_")] = value + + if self.environ_overrides: + result.update(self.environ_overrides) + + return result + + def get_request(self, cls=None): + """Returns a request with the data. If the request class is not + specified :attr:`request_class` is used. + + :param cls: The request wrapper to use. + """ + if cls is None: + cls = self.request_class + return cls(self.get_environ()) + + +class ClientRedirectError(Exception): + """If a redirect loop is detected when using follow_redirects=True with + the :cls:`Client`, then this exception is raised. + """ + + +class Client(object): + """This class allows you to send requests to a wrapped application. + + The response wrapper can be a class or factory function that takes + three arguments: app_iter, status and headers. The default response + wrapper just returns a tuple. + + Example:: + + class ClientResponse(BaseResponse): + ... + + client = Client(MyApplication(), response_wrapper=ClientResponse) + + The use_cookies parameter indicates whether cookies should be stored and + sent for subsequent requests. This is True by default, but passing False + will disable this behaviour. + + If you want to request some subdomain of your application you may set + `allow_subdomain_redirects` to `True` as if not no external redirects + are allowed. + + .. versionadded:: 0.5 + `use_cookies` is new in this version. Older versions did not provide + builtin cookie support. + + .. versionadded:: 0.14 + The `mimetype` parameter was added. + + .. versionadded:: 0.15 + The ``json`` parameter. + """ + + def __init__( + self, + application, + response_wrapper=None, + use_cookies=True, + allow_subdomain_redirects=False, + ): + self.application = application + self.response_wrapper = response_wrapper + if use_cookies: + self.cookie_jar = _TestCookieJar() + else: + self.cookie_jar = None + self.allow_subdomain_redirects = allow_subdomain_redirects + + def set_cookie( + self, + server_name, + key, + value="", + max_age=None, + expires=None, + path="/", + domain=None, + secure=None, + httponly=False, + charset="utf-8", + ): + """Sets a cookie in the client's cookie jar. The server name + is required and has to match the one that is also passed to + the open call. + """ + assert self.cookie_jar is not None, "cookies disabled" + header = dump_cookie( + key, value, max_age, expires, path, domain, secure, httponly, charset + ) + environ = create_environ(path, base_url="http://" + server_name) + headers = [("Set-Cookie", header)] + self.cookie_jar.extract_wsgi(environ, headers) + + def delete_cookie(self, server_name, key, path="/", domain=None): + """Deletes a cookie in the test client.""" + self.set_cookie( + server_name, key, expires=0, max_age=0, path=path, domain=domain + ) + + def run_wsgi_app(self, environ, buffered=False): + """Runs the wrapped WSGI app with the given environment.""" + if self.cookie_jar is not None: + self.cookie_jar.inject_wsgi(environ) + rv = run_wsgi_app(self.application, environ, buffered=buffered) + if self.cookie_jar is not None: + self.cookie_jar.extract_wsgi(environ, rv[2]) + return rv + + def resolve_redirect(self, response, new_location, environ, buffered=False): + """Perform a new request to the location given by the redirect + response to the previous request. + """ + scheme, netloc, path, qs, anchor = url_parse(new_location) + builder = EnvironBuilder.from_environ(environ, query_string=qs) + + to_name_parts = netloc.split(":", 1)[0].split(".") + from_name_parts = builder.server_name.split(".") + + if to_name_parts != [""]: + # The new location has a host, use it for the base URL. + builder.url_scheme = scheme + builder.host = netloc + else: + # A local redirect with autocorrect_location_header=False + # doesn't have a host, so use the request's host. + to_name_parts = from_name_parts + + # Explain why a redirect to a different server name won't be followed. + if to_name_parts != from_name_parts: + if to_name_parts[-len(from_name_parts) :] == from_name_parts: + if not self.allow_subdomain_redirects: + raise RuntimeError("Following subdomain redirects is not enabled.") + else: + raise RuntimeError("Following external redirects is not supported.") + + path_parts = path.split("/") + root_parts = builder.script_root.split("/") + + if path_parts[: len(root_parts)] == root_parts: + # Strip the script root from the path. + builder.path = path[len(builder.script_root) :] + else: + # The new location is not under the script root, so use the + # whole path and clear the previous root. + builder.path = path + builder.script_root = "" + + status_code = int(response[1].split(None, 1)[0]) + + # Only 307 and 308 preserve all of the original request. + if status_code not in {307, 308}: + # HEAD is preserved, everything else becomes GET. + if builder.method != "HEAD": + builder.method = "GET" + + # Clear the body and the headers that describe it. + builder.input_stream = None + builder.content_type = None + builder.content_length = None + builder.headers.pop("Transfer-Encoding", None) + + # Disable the response wrapper while handling redirects. Not + # thread safe, but the client should not be shared anyway. + old_response_wrapper = self.response_wrapper + self.response_wrapper = None + + try: + return self.open(builder, as_tuple=True, buffered=buffered) + finally: + self.response_wrapper = old_response_wrapper + + def open(self, *args, **kwargs): + """Takes the same arguments as the :class:`EnvironBuilder` class with + some additions: You can provide a :class:`EnvironBuilder` or a WSGI + environment as only argument instead of the :class:`EnvironBuilder` + arguments and two optional keyword arguments (`as_tuple`, `buffered`) + that change the type of the return value or the way the application is + executed. + + .. versionchanged:: 0.5 + If a dict is provided as file in the dict for the `data` parameter + the content type has to be called `content_type` now instead of + `mimetype`. This change was made for consistency with + :class:`werkzeug.FileWrapper`. + + The `follow_redirects` parameter was added to :func:`open`. + + Additional parameters: + + :param as_tuple: Returns a tuple in the form ``(environ, result)`` + :param buffered: Set this to True to buffer the application run. + This will automatically close the application for + you as well. + :param follow_redirects: Set this to True if the `Client` should + follow HTTP redirects. + """ + as_tuple = kwargs.pop("as_tuple", False) + buffered = kwargs.pop("buffered", False) + follow_redirects = kwargs.pop("follow_redirects", False) + environ = None + if not kwargs and len(args) == 1: + if isinstance(args[0], EnvironBuilder): + environ = args[0].get_environ() + elif isinstance(args[0], dict): + environ = args[0] + if environ is None: + builder = EnvironBuilder(*args, **kwargs) + try: + environ = builder.get_environ() + finally: + builder.close() + + response = self.run_wsgi_app(environ.copy(), buffered=buffered) + + # handle redirects + redirect_chain = [] + while 1: + status_code = int(response[1].split(None, 1)[0]) + if ( + status_code not in {301, 302, 303, 305, 307, 308} + or not follow_redirects + ): + break + + # Exhaust intermediate response bodies to ensure middleware + # that returns an iterator runs any cleanup code. + if not buffered: + for _ in response[0]: + pass + + new_location = response[2]["location"] + new_redirect_entry = (new_location, status_code) + if new_redirect_entry in redirect_chain: + raise ClientRedirectError("loop detected") + redirect_chain.append(new_redirect_entry) + environ, response = self.resolve_redirect( + response, new_location, environ, buffered=buffered + ) + + if self.response_wrapper is not None: + response = self.response_wrapper(*response) + if as_tuple: + return environ, response + return response + + def get(self, *args, **kw): + """Like open but method is enforced to GET.""" + kw["method"] = "GET" + return self.open(*args, **kw) + + def patch(self, *args, **kw): + """Like open but method is enforced to PATCH.""" + kw["method"] = "PATCH" + return self.open(*args, **kw) + + def post(self, *args, **kw): + """Like open but method is enforced to POST.""" + kw["method"] = "POST" + return self.open(*args, **kw) + + def head(self, *args, **kw): + """Like open but method is enforced to HEAD.""" + kw["method"] = "HEAD" + return self.open(*args, **kw) + + def put(self, *args, **kw): + """Like open but method is enforced to PUT.""" + kw["method"] = "PUT" + return self.open(*args, **kw) + + def delete(self, *args, **kw): + """Like open but method is enforced to DELETE.""" + kw["method"] = "DELETE" + return self.open(*args, **kw) + + def options(self, *args, **kw): + """Like open but method is enforced to OPTIONS.""" + kw["method"] = "OPTIONS" + return self.open(*args, **kw) + + def trace(self, *args, **kw): + """Like open but method is enforced to TRACE.""" + kw["method"] = "TRACE" + return self.open(*args, **kw) + + def __repr__(self): + return "<%s %r>" % (self.__class__.__name__, self.application) + + +def create_environ(*args, **kwargs): + """Create a new WSGI environ dict based on the values passed. The first + parameter should be the path of the request which defaults to '/'. The + second one can either be an absolute path (in that case the host is + localhost:80) or a full path to the request with scheme, netloc port and + the path to the script. + + This accepts the same arguments as the :class:`EnvironBuilder` + constructor. + + .. versionchanged:: 0.5 + This function is now a thin wrapper over :class:`EnvironBuilder` which + was added in 0.5. The `headers`, `environ_base`, `environ_overrides` + and `charset` parameters were added. + """ + builder = EnvironBuilder(*args, **kwargs) + try: + return builder.get_environ() + finally: + builder.close() + + +def run_wsgi_app(app, environ, buffered=False): + """Return a tuple in the form (app_iter, status, headers) of the + application output. This works best if you pass it an application that + returns an iterator all the time. + + Sometimes applications may use the `write()` callable returned + by the `start_response` function. This tries to resolve such edge + cases automatically. But if you don't get the expected output you + should set `buffered` to `True` which enforces buffering. + + If passed an invalid WSGI application the behavior of this function is + undefined. Never pass non-conforming WSGI applications to this function. + + :param app: the application to execute. + :param buffered: set to `True` to enforce buffering. + :return: tuple in the form ``(app_iter, status, headers)`` + """ + environ = _get_environ(environ) + response = [] + buffer = [] + + def start_response(status, headers, exc_info=None): + if exc_info is not None: + reraise(*exc_info) + response[:] = [status, headers] + return buffer.append + + app_rv = app(environ, start_response) + close_func = getattr(app_rv, "close", None) + app_iter = iter(app_rv) + + # when buffering we emit the close call early and convert the + # application iterator into a regular list + if buffered: + try: + app_iter = list(app_iter) + finally: + if close_func is not None: + close_func() + + # otherwise we iterate the application iter until we have a response, chain + # the already received data with the already collected data and wrap it in + # a new `ClosingIterator` if we need to restore a `close` callable from the + # original return value. + else: + for item in app_iter: + buffer.append(item) + if response: + break + if buffer: + app_iter = chain(buffer, app_iter) + if close_func is not None and app_iter is not app_rv: + app_iter = ClosingIterator(app_iter, close_func) + + return app_iter, response[0], Headers(response[1]) diff --git a/venv/lib/python2.7/site-packages/werkzeug/testapp.py b/venv/lib/python2.7/site-packages/werkzeug/testapp.py new file mode 100644 index 00000000..5ea85490 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/testapp.py @@ -0,0 +1,241 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.testapp + ~~~~~~~~~~~~~~~~ + + Provide a small test application that can be used to test a WSGI server + and check it for WSGI compliance. + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import base64 +import os +import sys +from textwrap import wrap + +from . import __version__ as _werkzeug_version +from .utils import escape +from .wrappers import BaseRequest as Request +from .wrappers import BaseResponse as Response + +logo = Response( + base64.b64decode( + """ +R0lGODlhoACgAOMIAAEDACwpAEpCAGdgAJaKAM28AOnVAP3rAP///////// +//////////////////////yH5BAEKAAgALAAAAACgAKAAAAT+EMlJq704680R+F0ojmRpnuj0rWnrv +nB8rbRs33gu0bzu/0AObxgsGn3D5HHJbCUFyqZ0ukkSDlAidctNFg7gbI9LZlrBaHGtzAae0eloe25 +7w9EDOX2fst/xenyCIn5/gFqDiVVDV4aGeYiKkhSFjnCQY5OTlZaXgZp8nJ2ekaB0SQOjqphrpnOiq +ncEn65UsLGytLVmQ6m4sQazpbtLqL/HwpnER8bHyLrLOc3Oz8PRONPU1crXN9na263dMt/g4SzjMeX +m5yDpLqgG7OzJ4u8lT/P69ej3JPn69kHzN2OIAHkB9RUYSFCFQYQJFTIkCDBiwoXWGnowaLEjRm7+G +p9A7Hhx4rUkAUaSLJlxHMqVMD/aSycSZkyTplCqtGnRAM5NQ1Ly5OmzZc6gO4d6DGAUKA+hSocWYAo +SlM6oUWX2O/o0KdaVU5vuSQLAa0ADwQgMEMB2AIECZhVSnTno6spgbtXmHcBUrQACcc2FrTrWS8wAf +78cMFBgwIBgbN+qvTt3ayikRBk7BoyGAGABAdYyfdzRQGV3l4coxrqQ84GpUBmrdR3xNIDUPAKDBSA +ADIGDhhqTZIWaDcrVX8EsbNzbkvCOxG8bN5w8ly9H8jyTJHC6DFndQydbguh2e/ctZJFXRxMAqqPVA +tQH5E64SPr1f0zz7sQYjAHg0In+JQ11+N2B0XXBeeYZgBZFx4tqBToiTCPv0YBgQv8JqA6BEf6RhXx +w1ENhRBnWV8ctEX4Ul2zc3aVGcQNC2KElyTDYyYUWvShdjDyMOGMuFjqnII45aogPhz/CodUHFwaDx +lTgsaOjNyhGWJQd+lFoAGk8ObghI0kawg+EV5blH3dr+digkYuAGSaQZFHFz2P/cTaLmhF52QeSb45 +Jwxd+uSVGHlqOZpOeJpCFZ5J+rkAkFjQ0N1tah7JJSZUFNsrkeJUJMIBi8jyaEKIhKPomnC91Uo+NB +yyaJ5umnnpInIFh4t6ZSpGaAVmizqjpByDegYl8tPE0phCYrhcMWSv+uAqHfgH88ak5UXZmlKLVJhd +dj78s1Fxnzo6yUCrV6rrDOkluG+QzCAUTbCwf9SrmMLzK6p+OPHx7DF+bsfMRq7Ec61Av9i6GLw23r +idnZ+/OO0a99pbIrJkproCQMA17OPG6suq3cca5ruDfXCCDoS7BEdvmJn5otdqscn+uogRHHXs8cbh +EIfYaDY1AkrC0cqwcZpnM6ludx72x0p7Fo/hZAcpJDjax0UdHavMKAbiKltMWCF3xxh9k25N/Viud8 +ba78iCvUkt+V6BpwMlErmcgc502x+u1nSxJSJP9Mi52awD1V4yB/QHONsnU3L+A/zR4VL/indx/y64 +gqcj+qgTeweM86f0Qy1QVbvmWH1D9h+alqg254QD8HJXHvjQaGOqEqC22M54PcftZVKVSQG9jhkv7C +JyTyDoAJfPdu8v7DRZAxsP/ky9MJ3OL36DJfCFPASC3/aXlfLOOON9vGZZHydGf8LnxYJuuVIbl83y +Az5n/RPz07E+9+zw2A2ahz4HxHo9Kt79HTMx1Q7ma7zAzHgHqYH0SoZWyTuOLMiHwSfZDAQTn0ajk9 +YQqodnUYjByQZhZak9Wu4gYQsMyEpIOAOQKze8CmEF45KuAHTvIDOfHJNipwoHMuGHBnJElUoDmAyX +c2Qm/R8Ah/iILCCJOEokGowdhDYc/yoL+vpRGwyVSCWFYZNljkhEirGXsalWcAgOdeAdoXcktF2udb +qbUhjWyMQxYO01o6KYKOr6iK3fE4MaS+DsvBsGOBaMb0Y6IxADaJhFICaOLmiWTlDAnY1KzDG4ambL +cWBA8mUzjJsN2KjSaSXGqMCVXYpYkj33mcIApyhQf6YqgeNAmNvuC0t4CsDbSshZJkCS1eNisKqlyG +cF8G2JeiDX6tO6Mv0SmjCa3MFb0bJaGPMU0X7c8XcpvMaOQmCajwSeY9G0WqbBmKv34DsMIEztU6Y2 +KiDlFdt6jnCSqx7Dmt6XnqSKaFFHNO5+FmODxMCWBEaco77lNDGXBM0ECYB/+s7nKFdwSF5hgXumQe +EZ7amRg39RHy3zIjyRCykQh8Zo2iviRKyTDn/zx6EefptJj2Cw+Ep2FSc01U5ry4KLPYsTyWnVGnvb +UpyGlhjBUljyjHhWpf8OFaXwhp9O4T1gU9UeyPPa8A2l0p1kNqPXEVRm1AOs1oAGZU596t6SOR2mcB +Oco1srWtkaVrMUzIErrKri85keKqRQYX9VX0/eAUK1hrSu6HMEX3Qh2sCh0q0D2CtnUqS4hj62sE/z +aDs2Sg7MBS6xnQeooc2R2tC9YrKpEi9pLXfYXp20tDCpSP8rKlrD4axprb9u1Df5hSbz9QU0cRpfgn +kiIzwKucd0wsEHlLpe5yHXuc6FrNelOl7pY2+11kTWx7VpRu97dXA3DO1vbkhcb4zyvERYajQgAADs +=""" + ), + mimetype="image/png", +) + + +TEMPLATE = u"""\ + +WSGI Information + +
    + +

    WSGI Information

    +

    + This page displays all available information about the WSGI server and + the underlying Python interpreter. +

    Python Interpreter

    + + + + + + +
    Python Version + %(python_version)s +
    Platform + %(platform)s [%(os)s] +
    API Version + %(api_version)s +
    Byteorder + %(byteorder)s +
    Werkzeug Version + %(werkzeug_version)s +
    +

    WSGI Environment

    + %(wsgi_env)s
    +

    Installed Eggs

    +

    + The following python packages were installed on the system as + Python eggs: +

      %(python_eggs)s
    +

    System Path

    +

    + The following paths are the current contents of the load path. The + following entries are looked up for Python packages. Note that not + all items in this path are folders. Gray and underlined items are + entries pointing to invalid resources or used by custom import hooks + such as the zip importer. +

    + Items with a bright background were expanded for display from a relative + path. If you encounter such paths in the output you might want to check + your setup as relative paths are usually problematic in multithreaded + environments. +

      %(sys_path)s
    +
    +""" + + +def iter_sys_path(): + if os.name == "posix": + + def strip(x): + prefix = os.path.expanduser("~") + if x.startswith(prefix): + x = "~" + x[len(prefix) :] + return x + + else: + + def strip(x): + return x + + cwd = os.path.abspath(os.getcwd()) + for item in sys.path: + path = os.path.join(cwd, item or os.path.curdir) + yield strip(os.path.normpath(path)), not os.path.isdir(path), path != item + + +def render_testapp(req): + try: + import pkg_resources + except ImportError: + eggs = () + else: + eggs = sorted(pkg_resources.working_set, key=lambda x: x.project_name.lower()) + python_eggs = [] + for egg in eggs: + try: + version = egg.version + except (ValueError, AttributeError): + version = "unknown" + python_eggs.append( + "
  • %s [%s]" % (escape(egg.project_name), escape(version)) + ) + + wsgi_env = [] + sorted_environ = sorted(req.environ.items(), key=lambda x: repr(x[0]).lower()) + for key, value in sorted_environ: + wsgi_env.append( + "%s%s" + % (escape(str(key)), " ".join(wrap(escape(repr(value))))) + ) + + sys_path = [] + for item, virtual, expanded in iter_sys_path(): + class_ = [] + if virtual: + class_.append("virtual") + if expanded: + class_.append("exp") + sys_path.append( + "%s" + % (' class="%s"' % " ".join(class_) if class_ else "", escape(item)) + ) + + return ( + TEMPLATE + % { + "python_version": "
    ".join(escape(sys.version).splitlines()), + "platform": escape(sys.platform), + "os": escape(os.name), + "api_version": sys.api_version, + "byteorder": sys.byteorder, + "werkzeug_version": _werkzeug_version, + "python_eggs": "\n".join(python_eggs), + "wsgi_env": "\n".join(wsgi_env), + "sys_path": "\n".join(sys_path), + } + ).encode("utf-8") + + +def test_app(environ, start_response): + """Simple test application that dumps the environment. You can use + it to check if Werkzeug is working properly: + + .. sourcecode:: pycon + + >>> from werkzeug.serving import run_simple + >>> from werkzeug.testapp import test_app + >>> run_simple('localhost', 3000, test_app) + * Running on http://localhost:3000/ + + The application displays important information from the WSGI environment, + the Python interpreter and the installed libraries. + """ + req = Request(environ, populate_request=False) + if req.args.get("resource") == "logo": + response = logo + else: + response = Response(render_testapp(req), mimetype="text/html") + return response(environ, start_response) + + +if __name__ == "__main__": + from .serving import run_simple + + run_simple("localhost", 5000, test_app, use_reloader=True) diff --git a/venv/lib/python2.7/site-packages/werkzeug/urls.py b/venv/lib/python2.7/site-packages/werkzeug/urls.py new file mode 100644 index 00000000..566017d7 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/urls.py @@ -0,0 +1,1138 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.urls + ~~~~~~~~~~~~~ + + ``werkzeug.urls`` used to provide several wrapper functions for Python 2 + urlparse, whose main purpose were to work around the behavior of the Py2 + stdlib and its lack of unicode support. While this was already a somewhat + inconvenient situation, it got even more complicated because Python 3's + ``urllib.parse`` actually does handle unicode properly. In other words, + this module would wrap two libraries with completely different behavior. So + now this module contains a 2-and-3-compatible backport of Python 3's + ``urllib.parse``, which is mostly API-compatible. + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import codecs +import os +import re +from collections import namedtuple + +from ._compat import fix_tuple_repr +from ._compat import implements_to_string +from ._compat import make_literal_wrapper +from ._compat import normalize_string_tuple +from ._compat import PY2 +from ._compat import text_type +from ._compat import to_native +from ._compat import to_unicode +from ._compat import try_coerce_native +from ._internal import _decode_idna +from ._internal import _encode_idna + +# A regular expression for what a valid schema looks like +_scheme_re = re.compile(r"^[a-zA-Z0-9+-.]+$") + +# Characters that are safe in any part of an URL. +_always_safe = frozenset( + bytearray( + b"abcdefghijklmnopqrstuvwxyz" + b"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + b"0123456789" + b"-._~" + ) +) + +_hexdigits = "0123456789ABCDEFabcdef" +_hextobyte = dict( + ((a + b).encode(), int(a + b, 16)) for a in _hexdigits for b in _hexdigits +) +_bytetohex = [("%%%02X" % char).encode("ascii") for char in range(256)] + + +_URLTuple = fix_tuple_repr( + namedtuple("_URLTuple", ["scheme", "netloc", "path", "query", "fragment"]) +) + + +class BaseURL(_URLTuple): + """Superclass of :py:class:`URL` and :py:class:`BytesURL`.""" + + __slots__ = () + + def replace(self, **kwargs): + """Return an URL with the same values, except for those parameters + given new values by whichever keyword arguments are specified.""" + return self._replace(**kwargs) + + @property + def host(self): + """The host part of the URL if available, otherwise `None`. The + host is either the hostname or the IP address mentioned in the + URL. It will not contain the port. + """ + return self._split_host()[0] + + @property + def ascii_host(self): + """Works exactly like :attr:`host` but will return a result that + is restricted to ASCII. If it finds a netloc that is not ASCII + it will attempt to idna decode it. This is useful for socket + operations when the URL might include internationalized characters. + """ + rv = self.host + if rv is not None and isinstance(rv, text_type): + try: + rv = _encode_idna(rv) + except UnicodeError: + rv = rv.encode("ascii", "ignore") + return to_native(rv, "ascii", "ignore") + + @property + def port(self): + """The port in the URL as an integer if it was present, `None` + otherwise. This does not fill in default ports. + """ + try: + rv = int(to_native(self._split_host()[1])) + if 0 <= rv <= 65535: + return rv + except (ValueError, TypeError): + pass + + @property + def auth(self): + """The authentication part in the URL if available, `None` + otherwise. + """ + return self._split_netloc()[0] + + @property + def username(self): + """The username if it was part of the URL, `None` otherwise. + This undergoes URL decoding and will always be a unicode string. + """ + rv = self._split_auth()[0] + if rv is not None: + return _url_unquote_legacy(rv) + + @property + def raw_username(self): + """The username if it was part of the URL, `None` otherwise. + Unlike :attr:`username` this one is not being decoded. + """ + return self._split_auth()[0] + + @property + def password(self): + """The password if it was part of the URL, `None` otherwise. + This undergoes URL decoding and will always be a unicode string. + """ + rv = self._split_auth()[1] + if rv is not None: + return _url_unquote_legacy(rv) + + @property + def raw_password(self): + """The password if it was part of the URL, `None` otherwise. + Unlike :attr:`password` this one is not being decoded. + """ + return self._split_auth()[1] + + def decode_query(self, *args, **kwargs): + """Decodes the query part of the URL. Ths is a shortcut for + calling :func:`url_decode` on the query argument. The arguments and + keyword arguments are forwarded to :func:`url_decode` unchanged. + """ + return url_decode(self.query, *args, **kwargs) + + def join(self, *args, **kwargs): + """Joins this URL with another one. This is just a convenience + function for calling into :meth:`url_join` and then parsing the + return value again. + """ + return url_parse(url_join(self, *args, **kwargs)) + + def to_url(self): + """Returns a URL string or bytes depending on the type of the + information stored. This is just a convenience function + for calling :meth:`url_unparse` for this URL. + """ + return url_unparse(self) + + def decode_netloc(self): + """Decodes the netloc part into a string.""" + rv = _decode_idna(self.host or "") + + if ":" in rv: + rv = "[%s]" % rv + port = self.port + if port is not None: + rv = "%s:%d" % (rv, port) + auth = ":".join( + filter( + None, + [ + _url_unquote_legacy(self.raw_username or "", "/:%@"), + _url_unquote_legacy(self.raw_password or "", "/:%@"), + ], + ) + ) + if auth: + rv = "%s@%s" % (auth, rv) + return rv + + def to_uri_tuple(self): + """Returns a :class:`BytesURL` tuple that holds a URI. This will + encode all the information in the URL properly to ASCII using the + rules a web browser would follow. + + It's usually more interesting to directly call :meth:`iri_to_uri` which + will return a string. + """ + return url_parse(iri_to_uri(self).encode("ascii")) + + def to_iri_tuple(self): + """Returns a :class:`URL` tuple that holds a IRI. This will try + to decode as much information as possible in the URL without + losing information similar to how a web browser does it for the + URL bar. + + It's usually more interesting to directly call :meth:`uri_to_iri` which + will return a string. + """ + return url_parse(uri_to_iri(self)) + + def get_file_location(self, pathformat=None): + """Returns a tuple with the location of the file in the form + ``(server, location)``. If the netloc is empty in the URL or + points to localhost, it's represented as ``None``. + + The `pathformat` by default is autodetection but needs to be set + when working with URLs of a specific system. The supported values + are ``'windows'`` when working with Windows or DOS paths and + ``'posix'`` when working with posix paths. + + If the URL does not point to a local file, the server and location + are both represented as ``None``. + + :param pathformat: The expected format of the path component. + Currently ``'windows'`` and ``'posix'`` are + supported. Defaults to ``None`` which is + autodetect. + """ + if self.scheme != "file": + return None, None + + path = url_unquote(self.path) + host = self.netloc or None + + if pathformat is None: + if os.name == "nt": + pathformat = "windows" + else: + pathformat = "posix" + + if pathformat == "windows": + if path[:1] == "/" and path[1:2].isalpha() and path[2:3] in "|:": + path = path[1:2] + ":" + path[3:] + windows_share = path[:3] in ("\\" * 3, "/" * 3) + import ntpath + + path = ntpath.normpath(path) + # Windows shared drives are represented as ``\\host\\directory``. + # That results in a URL like ``file://///host/directory``, and a + # path like ``///host/directory``. We need to special-case this + # because the path contains the hostname. + if windows_share and host is None: + parts = path.lstrip("\\").split("\\", 1) + if len(parts) == 2: + host, path = parts + else: + host = parts[0] + path = "" + elif pathformat == "posix": + import posixpath + + path = posixpath.normpath(path) + else: + raise TypeError("Invalid path format %s" % repr(pathformat)) + + if host in ("127.0.0.1", "::1", "localhost"): + host = None + + return host, path + + def _split_netloc(self): + if self._at in self.netloc: + return self.netloc.split(self._at, 1) + return None, self.netloc + + def _split_auth(self): + auth = self._split_netloc()[0] + if not auth: + return None, None + if self._colon not in auth: + return auth, None + return auth.split(self._colon, 1) + + def _split_host(self): + rv = self._split_netloc()[1] + if not rv: + return None, None + + if not rv.startswith(self._lbracket): + if self._colon in rv: + return rv.split(self._colon, 1) + return rv, None + + idx = rv.find(self._rbracket) + if idx < 0: + return rv, None + + host = rv[1:idx] + rest = rv[idx + 1 :] + if rest.startswith(self._colon): + return host, rest[1:] + return host, None + + +@implements_to_string +class URL(BaseURL): + """Represents a parsed URL. This behaves like a regular tuple but + also has some extra attributes that give further insight into the + URL. + """ + + __slots__ = () + _at = "@" + _colon = ":" + _lbracket = "[" + _rbracket = "]" + + def __str__(self): + return self.to_url() + + def encode_netloc(self): + """Encodes the netloc part to an ASCII safe URL as bytes.""" + rv = self.ascii_host or "" + if ":" in rv: + rv = "[%s]" % rv + port = self.port + if port is not None: + rv = "%s:%d" % (rv, port) + auth = ":".join( + filter( + None, + [ + url_quote(self.raw_username or "", "utf-8", "strict", "/:%"), + url_quote(self.raw_password or "", "utf-8", "strict", "/:%"), + ], + ) + ) + if auth: + rv = "%s@%s" % (auth, rv) + return to_native(rv) + + def encode(self, charset="utf-8", errors="replace"): + """Encodes the URL to a tuple made out of bytes. The charset is + only being used for the path, query and fragment. + """ + return BytesURL( + self.scheme.encode("ascii"), + self.encode_netloc(), + self.path.encode(charset, errors), + self.query.encode(charset, errors), + self.fragment.encode(charset, errors), + ) + + +class BytesURL(BaseURL): + """Represents a parsed URL in bytes.""" + + __slots__ = () + _at = b"@" + _colon = b":" + _lbracket = b"[" + _rbracket = b"]" + + def __str__(self): + return self.to_url().decode("utf-8", "replace") + + def encode_netloc(self): + """Returns the netloc unchanged as bytes.""" + return self.netloc + + def decode(self, charset="utf-8", errors="replace"): + """Decodes the URL to a tuple made out of strings. The charset is + only being used for the path, query and fragment. + """ + return URL( + self.scheme.decode("ascii"), + self.decode_netloc(), + self.path.decode(charset, errors), + self.query.decode(charset, errors), + self.fragment.decode(charset, errors), + ) + + +_unquote_maps = {frozenset(): _hextobyte} + + +def _unquote_to_bytes(string, unsafe=""): + if isinstance(string, text_type): + string = string.encode("utf-8") + + if isinstance(unsafe, text_type): + unsafe = unsafe.encode("utf-8") + + unsafe = frozenset(bytearray(unsafe)) + groups = iter(string.split(b"%")) + result = bytearray(next(groups, b"")) + + try: + hex_to_byte = _unquote_maps[unsafe] + except KeyError: + hex_to_byte = _unquote_maps[unsafe] = { + h: b for h, b in _hextobyte.items() if b not in unsafe + } + + for group in groups: + code = group[:2] + + if code in hex_to_byte: + result.append(hex_to_byte[code]) + result.extend(group[2:]) + else: + result.append(37) # % + result.extend(group) + + return bytes(result) + + +def _url_encode_impl(obj, charset, encode_keys, sort, key): + from .datastructures import iter_multi_items + + iterable = iter_multi_items(obj) + if sort: + iterable = sorted(iterable, key=key) + for key, value in iterable: + if value is None: + continue + if not isinstance(key, bytes): + key = text_type(key).encode(charset) + if not isinstance(value, bytes): + value = text_type(value).encode(charset) + yield _fast_url_quote_plus(key) + "=" + _fast_url_quote_plus(value) + + +def _url_unquote_legacy(value, unsafe=""): + try: + return url_unquote(value, charset="utf-8", errors="strict", unsafe=unsafe) + except UnicodeError: + return url_unquote(value, charset="latin1", unsafe=unsafe) + + +def url_parse(url, scheme=None, allow_fragments=True): + """Parses a URL from a string into a :class:`URL` tuple. If the URL + is lacking a scheme it can be provided as second argument. Otherwise, + it is ignored. Optionally fragments can be stripped from the URL + by setting `allow_fragments` to `False`. + + The inverse of this function is :func:`url_unparse`. + + :param url: the URL to parse. + :param scheme: the default schema to use if the URL is schemaless. + :param allow_fragments: if set to `False` a fragment will be removed + from the URL. + """ + s = make_literal_wrapper(url) + is_text_based = isinstance(url, text_type) + + if scheme is None: + scheme = s("") + netloc = query = fragment = s("") + i = url.find(s(":")) + if i > 0 and _scheme_re.match(to_native(url[:i], errors="replace")): + # make sure "iri" is not actually a port number (in which case + # "scheme" is really part of the path) + rest = url[i + 1 :] + if not rest or any(c not in s("0123456789") for c in rest): + # not a port number + scheme, url = url[:i].lower(), rest + + if url[:2] == s("//"): + delim = len(url) + for c in s("/?#"): + wdelim = url.find(c, 2) + if wdelim >= 0: + delim = min(delim, wdelim) + netloc, url = url[2:delim], url[delim:] + if (s("[") in netloc and s("]") not in netloc) or ( + s("]") in netloc and s("[") not in netloc + ): + raise ValueError("Invalid IPv6 URL") + + if allow_fragments and s("#") in url: + url, fragment = url.split(s("#"), 1) + if s("?") in url: + url, query = url.split(s("?"), 1) + + result_type = URL if is_text_based else BytesURL + return result_type(scheme, netloc, url, query, fragment) + + +def _make_fast_url_quote(charset="utf-8", errors="strict", safe="/:", unsafe=""): + """Precompile the translation table for a URL encoding function. + + Unlike :func:`url_quote`, the generated function only takes the + string to quote. + + :param charset: The charset to encode the result with. + :param errors: How to handle encoding errors. + :param safe: An optional sequence of safe characters to never encode. + :param unsafe: An optional sequence of unsafe characters to always encode. + """ + if isinstance(safe, text_type): + safe = safe.encode(charset, errors) + + if isinstance(unsafe, text_type): + unsafe = unsafe.encode(charset, errors) + + safe = (frozenset(bytearray(safe)) | _always_safe) - frozenset(bytearray(unsafe)) + table = [chr(c) if c in safe else "%%%02X" % c for c in range(256)] + + if not PY2: + + def quote(string): + return "".join([table[c] for c in string]) + + else: + + def quote(string): + return "".join([table[c] for c in bytearray(string)]) + + return quote + + +_fast_url_quote = _make_fast_url_quote() +_fast_quote_plus = _make_fast_url_quote(safe=" ", unsafe="+") + + +def _fast_url_quote_plus(string): + return _fast_quote_plus(string).replace(" ", "+") + + +def url_quote(string, charset="utf-8", errors="strict", safe="/:", unsafe=""): + """URL encode a single string with a given encoding. + + :param s: the string to quote. + :param charset: the charset to be used. + :param safe: an optional sequence of safe characters. + :param unsafe: an optional sequence of unsafe characters. + + .. versionadded:: 0.9.2 + The `unsafe` parameter was added. + """ + if not isinstance(string, (text_type, bytes, bytearray)): + string = text_type(string) + if isinstance(string, text_type): + string = string.encode(charset, errors) + if isinstance(safe, text_type): + safe = safe.encode(charset, errors) + if isinstance(unsafe, text_type): + unsafe = unsafe.encode(charset, errors) + safe = (frozenset(bytearray(safe)) | _always_safe) - frozenset(bytearray(unsafe)) + rv = bytearray() + for char in bytearray(string): + if char in safe: + rv.append(char) + else: + rv.extend(_bytetohex[char]) + return to_native(bytes(rv)) + + +def url_quote_plus(string, charset="utf-8", errors="strict", safe=""): + """URL encode a single string with the given encoding and convert + whitespace to "+". + + :param s: The string to quote. + :param charset: The charset to be used. + :param safe: An optional sequence of safe characters. + """ + return url_quote(string, charset, errors, safe + " ", "+").replace(" ", "+") + + +def url_unparse(components): + """The reverse operation to :meth:`url_parse`. This accepts arbitrary + as well as :class:`URL` tuples and returns a URL as a string. + + :param components: the parsed URL as tuple which should be converted + into a URL string. + """ + scheme, netloc, path, query, fragment = normalize_string_tuple(components) + s = make_literal_wrapper(scheme) + url = s("") + + # We generally treat file:///x and file:/x the same which is also + # what browsers seem to do. This also allows us to ignore a schema + # register for netloc utilization or having to differenciate between + # empty and missing netloc. + if netloc or (scheme and path.startswith(s("/"))): + if path and path[:1] != s("/"): + path = s("/") + path + url = s("//") + (netloc or s("")) + path + elif path: + url += path + if scheme: + url = scheme + s(":") + url + if query: + url = url + s("?") + query + if fragment: + url = url + s("#") + fragment + return url + + +def url_unquote(string, charset="utf-8", errors="replace", unsafe=""): + """URL decode a single string with a given encoding. If the charset + is set to `None` no unicode decoding is performed and raw bytes + are returned. + + :param s: the string to unquote. + :param charset: the charset of the query string. If set to `None` + no unicode decoding will take place. + :param errors: the error handling for the charset decoding. + """ + rv = _unquote_to_bytes(string, unsafe) + if charset is not None: + rv = rv.decode(charset, errors) + return rv + + +def url_unquote_plus(s, charset="utf-8", errors="replace"): + """URL decode a single string with the given `charset` and decode "+" to + whitespace. + + Per default encoding errors are ignored. If you want a different behavior + you can set `errors` to ``'replace'`` or ``'strict'``. In strict mode a + :exc:`HTTPUnicodeError` is raised. + + :param s: The string to unquote. + :param charset: the charset of the query string. If set to `None` + no unicode decoding will take place. + :param errors: The error handling for the `charset` decoding. + """ + if isinstance(s, text_type): + s = s.replace(u"+", u" ") + else: + s = s.replace(b"+", b" ") + return url_unquote(s, charset, errors) + + +def url_fix(s, charset="utf-8"): + r"""Sometimes you get an URL by a user that just isn't a real URL because + it contains unsafe characters like ' ' and so on. This function can fix + some of the problems in a similar way browsers handle data entered by the + user: + + >>> url_fix(u'http://de.wikipedia.org/wiki/Elf (Begriffskl\xe4rung)') + 'http://de.wikipedia.org/wiki/Elf%20(Begriffskl%C3%A4rung)' + + :param s: the string with the URL to fix. + :param charset: The target charset for the URL if the url was given as + unicode string. + """ + # First step is to switch to unicode processing and to convert + # backslashes (which are invalid in URLs anyways) to slashes. This is + # consistent with what Chrome does. + s = to_unicode(s, charset, "replace").replace("\\", "/") + + # For the specific case that we look like a malformed windows URL + # we want to fix this up manually: + if s.startswith("file://") and s[7:8].isalpha() and s[8:10] in (":/", "|/"): + s = "file:///" + s[7:] + + url = url_parse(s) + path = url_quote(url.path, charset, safe="/%+$!*'(),") + qs = url_quote_plus(url.query, charset, safe=":&%=+$!*'(),") + anchor = url_quote_plus(url.fragment, charset, safe=":&%=+$!*'(),") + return to_native(url_unparse((url.scheme, url.encode_netloc(), path, qs, anchor))) + + +# not-unreserved characters remain quoted when unquoting to IRI +_to_iri_unsafe = "".join([chr(c) for c in range(128) if c not in _always_safe]) + + +def _codec_error_url_quote(e): + """Used in :func:`uri_to_iri` after unquoting to re-quote any + invalid bytes. + """ + out = _fast_url_quote(e.object[e.start : e.end]) + + if PY2: + out = out.decode("utf-8") + + return out, e.end + + +codecs.register_error("werkzeug.url_quote", _codec_error_url_quote) + + +def uri_to_iri(uri, charset="utf-8", errors="werkzeug.url_quote"): + """Convert a URI to an IRI. All valid UTF-8 characters are unquoted, + leaving all reserved and invalid characters quoted. If the URL has + a domain, it is decoded from Punycode. + + >>> uri_to_iri("http://xn--n3h.net/p%C3%A5th?q=%C3%A8ry%DF") + 'http://\\u2603.net/p\\xe5th?q=\\xe8ry%DF' + + :param uri: The URI to convert. + :param charset: The encoding to encode unquoted bytes with. + :param errors: Error handler to use during ``bytes.encode``. By + default, invalid bytes are left quoted. + + .. versionchanged:: 0.15 + All reserved and invalid characters remain quoted. Previously, + only some reserved characters were preserved, and invalid bytes + were replaced instead of left quoted. + + .. versionadded:: 0.6 + """ + if isinstance(uri, tuple): + uri = url_unparse(uri) + + uri = url_parse(to_unicode(uri, charset)) + path = url_unquote(uri.path, charset, errors, _to_iri_unsafe) + query = url_unquote(uri.query, charset, errors, _to_iri_unsafe) + fragment = url_unquote(uri.fragment, charset, errors, _to_iri_unsafe) + return url_unparse((uri.scheme, uri.decode_netloc(), path, query, fragment)) + + +# reserved characters remain unquoted when quoting to URI +_to_uri_safe = ":/?#[]@!$&'()*+,;=%" + + +def iri_to_uri(iri, charset="utf-8", errors="strict", safe_conversion=False): + """Convert an IRI to a URI. All non-ASCII and unsafe characters are + quoted. If the URL has a domain, it is encoded to Punycode. + + >>> iri_to_uri('http://\\u2603.net/p\\xe5th?q=\\xe8ry%DF') + 'http://xn--n3h.net/p%C3%A5th?q=%C3%A8ry%DF' + + :param iri: The IRI to convert. + :param charset: The encoding of the IRI. + :param errors: Error handler to use during ``bytes.encode``. + :param safe_conversion: Return the URL unchanged if it only contains + ASCII characters and no whitespace. See the explanation below. + + There is a general problem with IRI conversion with some protocols + that are in violation of the URI specification. Consider the + following two IRIs:: + + magnet:?xt=uri:whatever + itms-services://?action=download-manifest + + After parsing, we don't know if the scheme requires the ``//``, + which is dropped if empty, but conveys different meanings in the + final URL if it's present or not. In this case, you can use + ``safe_conversion``, which will return the URL unchanged if it only + contains ASCII characters and no whitespace. This can result in a + URI with unquoted characters if it was not already quoted correctly, + but preserves the URL's semantics. Werkzeug uses this for the + ``Location`` header for redirects. + + .. versionchanged:: 0.15 + All reserved characters remain unquoted. Previously, only some + reserved characters were left unquoted. + + .. versionchanged:: 0.9.6 + The ``safe_conversion`` parameter was added. + + .. versionadded:: 0.6 + """ + if isinstance(iri, tuple): + iri = url_unparse(iri) + + if safe_conversion: + # If we're not sure if it's safe to convert the URL, and it only + # contains ASCII characters, return it unconverted. + try: + native_iri = to_native(iri) + ascii_iri = native_iri.encode("ascii") + + # Only return if it doesn't have whitespace. (Why?) + if len(ascii_iri.split()) == 1: + return native_iri + except UnicodeError: + pass + + iri = url_parse(to_unicode(iri, charset, errors)) + path = url_quote(iri.path, charset, errors, _to_uri_safe) + query = url_quote(iri.query, charset, errors, _to_uri_safe) + fragment = url_quote(iri.fragment, charset, errors, _to_uri_safe) + return to_native( + url_unparse((iri.scheme, iri.encode_netloc(), path, query, fragment)) + ) + + +def url_decode( + s, + charset="utf-8", + decode_keys=False, + include_empty=True, + errors="replace", + separator="&", + cls=None, +): + """ + Parse a querystring and return it as :class:`MultiDict`. There is a + difference in key decoding on different Python versions. On Python 3 + keys will always be fully decoded whereas on Python 2, keys will + remain bytestrings if they fit into ASCII. On 2.x keys can be forced + to be unicode by setting `decode_keys` to `True`. + + If the charset is set to `None` no unicode decoding will happen and + raw bytes will be returned. + + Per default a missing value for a key will default to an empty key. If + you don't want that behavior you can set `include_empty` to `False`. + + Per default encoding errors are ignored. If you want a different behavior + you can set `errors` to ``'replace'`` or ``'strict'``. In strict mode a + `HTTPUnicodeError` is raised. + + .. versionchanged:: 0.5 + In previous versions ";" and "&" could be used for url decoding. + This changed in 0.5 where only "&" is supported. If you want to + use ";" instead a different `separator` can be provided. + + The `cls` parameter was added. + + :param s: a string with the query string to decode. + :param charset: the charset of the query string. If set to `None` + no unicode decoding will take place. + :param decode_keys: Used on Python 2.x to control whether keys should + be forced to be unicode objects. If set to `True` + then keys will be unicode in all cases. Otherwise, + they remain `str` if they fit into ASCII. + :param include_empty: Set to `False` if you don't want empty values to + appear in the dict. + :param errors: the decoding error behavior. + :param separator: the pair separator to be used, defaults to ``&`` + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`MultiDict` is used. + """ + if cls is None: + from .datastructures import MultiDict + + cls = MultiDict + if isinstance(s, text_type) and not isinstance(separator, text_type): + separator = separator.decode(charset or "ascii") + elif isinstance(s, bytes) and not isinstance(separator, bytes): + separator = separator.encode(charset or "ascii") + return cls( + _url_decode_impl( + s.split(separator), charset, decode_keys, include_empty, errors + ) + ) + + +def url_decode_stream( + stream, + charset="utf-8", + decode_keys=False, + include_empty=True, + errors="replace", + separator="&", + cls=None, + limit=None, + return_iterator=False, +): + """Works like :func:`url_decode` but decodes a stream. The behavior + of stream and limit follows functions like + :func:`~werkzeug.wsgi.make_line_iter`. The generator of pairs is + directly fed to the `cls` so you can consume the data while it's + parsed. + + .. versionadded:: 0.8 + + :param stream: a stream with the encoded querystring + :param charset: the charset of the query string. If set to `None` + no unicode decoding will take place. + :param decode_keys: Used on Python 2.x to control whether keys should + be forced to be unicode objects. If set to `True`, + keys will be unicode in all cases. Otherwise, they + remain `str` if they fit into ASCII. + :param include_empty: Set to `False` if you don't want empty values to + appear in the dict. + :param errors: the decoding error behavior. + :param separator: the pair separator to be used, defaults to ``&`` + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`MultiDict` is used. + :param limit: the content length of the URL data. Not necessary if + a limited stream is provided. + :param return_iterator: if set to `True` the `cls` argument is ignored + and an iterator over all decoded pairs is + returned + """ + from .wsgi import make_chunk_iter + + pair_iter = make_chunk_iter(stream, separator, limit) + decoder = _url_decode_impl(pair_iter, charset, decode_keys, include_empty, errors) + + if return_iterator: + return decoder + + if cls is None: + from .datastructures import MultiDict + + cls = MultiDict + + return cls(decoder) + + +def _url_decode_impl(pair_iter, charset, decode_keys, include_empty, errors): + for pair in pair_iter: + if not pair: + continue + s = make_literal_wrapper(pair) + equal = s("=") + if equal in pair: + key, value = pair.split(equal, 1) + else: + if not include_empty: + continue + key = pair + value = s("") + key = url_unquote_plus(key, charset, errors) + if charset is not None and PY2 and not decode_keys: + key = try_coerce_native(key) + yield key, url_unquote_plus(value, charset, errors) + + +def url_encode( + obj, charset="utf-8", encode_keys=False, sort=False, key=None, separator=b"&" +): + """URL encode a dict/`MultiDict`. If a value is `None` it will not appear + in the result string. Per default only values are encoded into the target + charset strings. If `encode_keys` is set to ``True`` unicode keys are + supported too. + + If `sort` is set to `True` the items are sorted by `key` or the default + sorting algorithm. + + .. versionadded:: 0.5 + `sort`, `key`, and `separator` were added. + + :param obj: the object to encode into a query string. + :param charset: the charset of the query string. + :param encode_keys: set to `True` if you have unicode keys. (Ignored on + Python 3.x) + :param sort: set to `True` if you want parameters to be sorted by `key`. + :param separator: the separator to be used for the pairs. + :param key: an optional function to be used for sorting. For more details + check out the :func:`sorted` documentation. + """ + separator = to_native(separator, "ascii") + return separator.join(_url_encode_impl(obj, charset, encode_keys, sort, key)) + + +def url_encode_stream( + obj, + stream=None, + charset="utf-8", + encode_keys=False, + sort=False, + key=None, + separator=b"&", +): + """Like :meth:`url_encode` but writes the results to a stream + object. If the stream is `None` a generator over all encoded + pairs is returned. + + .. versionadded:: 0.8 + + :param obj: the object to encode into a query string. + :param stream: a stream to write the encoded object into or `None` if + an iterator over the encoded pairs should be returned. In + that case the separator argument is ignored. + :param charset: the charset of the query string. + :param encode_keys: set to `True` if you have unicode keys. (Ignored on + Python 3.x) + :param sort: set to `True` if you want parameters to be sorted by `key`. + :param separator: the separator to be used for the pairs. + :param key: an optional function to be used for sorting. For more details + check out the :func:`sorted` documentation. + """ + separator = to_native(separator, "ascii") + gen = _url_encode_impl(obj, charset, encode_keys, sort, key) + if stream is None: + return gen + for idx, chunk in enumerate(gen): + if idx: + stream.write(separator) + stream.write(chunk) + + +def url_join(base, url, allow_fragments=True): + """Join a base URL and a possibly relative URL to form an absolute + interpretation of the latter. + + :param base: the base URL for the join operation. + :param url: the URL to join. + :param allow_fragments: indicates whether fragments should be allowed. + """ + if isinstance(base, tuple): + base = url_unparse(base) + if isinstance(url, tuple): + url = url_unparse(url) + + base, url = normalize_string_tuple((base, url)) + s = make_literal_wrapper(base) + + if not base: + return url + if not url: + return base + + bscheme, bnetloc, bpath, bquery, bfragment = url_parse( + base, allow_fragments=allow_fragments + ) + scheme, netloc, path, query, fragment = url_parse(url, bscheme, allow_fragments) + if scheme != bscheme: + return url + if netloc: + return url_unparse((scheme, netloc, path, query, fragment)) + netloc = bnetloc + + if path[:1] == s("/"): + segments = path.split(s("/")) + elif not path: + segments = bpath.split(s("/")) + if not query: + query = bquery + else: + segments = bpath.split(s("/"))[:-1] + path.split(s("/")) + + # If the rightmost part is "./" we want to keep the slash but + # remove the dot. + if segments[-1] == s("."): + segments[-1] = s("") + + # Resolve ".." and "." + segments = [segment for segment in segments if segment != s(".")] + while 1: + i = 1 + n = len(segments) - 1 + while i < n: + if segments[i] == s("..") and segments[i - 1] not in (s(""), s("..")): + del segments[i - 1 : i + 1] + break + i += 1 + else: + break + + # Remove trailing ".." if the URL is absolute + unwanted_marker = [s(""), s("..")] + while segments[:2] == unwanted_marker: + del segments[1] + + path = s("/").join(segments) + return url_unparse((scheme, netloc, path, query, fragment)) + + +class Href(object): + """Implements a callable that constructs URLs with the given base. The + function can be called with any number of positional and keyword + arguments which than are used to assemble the URL. Works with URLs + and posix paths. + + Positional arguments are appended as individual segments to + the path of the URL: + + >>> href = Href('/foo') + >>> href('bar', 23) + '/foo/bar/23' + >>> href('foo', bar=23) + '/foo/foo?bar=23' + + If any of the arguments (positional or keyword) evaluates to `None` it + will be skipped. If no keyword arguments are given the last argument + can be a :class:`dict` or :class:`MultiDict` (or any other dict subclass), + otherwise the keyword arguments are used for the query parameters, cutting + off the first trailing underscore of the parameter name: + + >>> href(is_=42) + '/foo?is=42' + >>> href({'foo': 'bar'}) + '/foo?foo=bar' + + Combining of both methods is not allowed: + + >>> href({'foo': 'bar'}, bar=42) + Traceback (most recent call last): + ... + TypeError: keyword arguments and query-dicts can't be combined + + Accessing attributes on the href object creates a new href object with + the attribute name as prefix: + + >>> bar_href = href.bar + >>> bar_href("blub") + '/foo/bar/blub' + + If `sort` is set to `True` the items are sorted by `key` or the default + sorting algorithm: + + >>> href = Href("/", sort=True) + >>> href(a=1, b=2, c=3) + '/?a=1&b=2&c=3' + + .. versionadded:: 0.5 + `sort` and `key` were added. + """ + + def __init__(self, base="./", charset="utf-8", sort=False, key=None): + if not base: + base = "./" + self.base = base + self.charset = charset + self.sort = sort + self.key = key + + def __getattr__(self, name): + if name[:2] == "__": + raise AttributeError(name) + base = self.base + if base[-1:] != "/": + base += "/" + return Href(url_join(base, name), self.charset, self.sort, self.key) + + def __call__(self, *path, **query): + if path and isinstance(path[-1], dict): + if query: + raise TypeError("keyword arguments and query-dicts can't be combined") + query, path = path[-1], path[:-1] + elif query: + query = dict( + [(k.endswith("_") and k[:-1] or k, v) for k, v in query.items()] + ) + path = "/".join( + [ + to_unicode(url_quote(x, self.charset), "ascii") + for x in path + if x is not None + ] + ).lstrip("/") + rv = self.base + if path: + if not rv.endswith("/"): + rv += "/" + rv = url_join(rv, "./" + path) + if query: + rv += "?" + to_unicode( + url_encode(query, self.charset, sort=self.sort, key=self.key), "ascii" + ) + return to_native(rv) diff --git a/venv/lib/python2.7/site-packages/werkzeug/useragents.py b/venv/lib/python2.7/site-packages/werkzeug/useragents.py new file mode 100644 index 00000000..8fce4153 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/useragents.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.useragents + ~~~~~~~~~~~~~~~~~~~ + + This module provides a helper to inspect user agent strings. This module + is far from complete but should work for most of the currently available + browsers. + + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import re + + +class UserAgentParser(object): + """A simple user agent parser. Used by the `UserAgent`.""" + + platforms = ( + ("cros", "chromeos"), + ("iphone|ios", "iphone"), + ("ipad", "ipad"), + (r"darwin|mac|os\s*x", "macos"), + ("win", "windows"), + (r"android", "android"), + ("netbsd", "netbsd"), + ("openbsd", "openbsd"), + ("freebsd", "freebsd"), + ("dragonfly", "dragonflybsd"), + ("(sun|i86)os", "solaris"), + (r"x11|lin(\b|ux)?", "linux"), + (r"nintendo\s+wii", "wii"), + ("irix", "irix"), + ("hp-?ux", "hpux"), + ("aix", "aix"), + ("sco|unix_sv", "sco"), + ("bsd", "bsd"), + ("amiga", "amiga"), + ("blackberry|playbook", "blackberry"), + ("symbian", "symbian"), + ) + browsers = ( + ("googlebot", "google"), + ("msnbot", "msn"), + ("yahoo", "yahoo"), + ("ask jeeves", "ask"), + (r"aol|america\s+online\s+browser", "aol"), + ("opera", "opera"), + ("edge", "edge"), + ("chrome|crios", "chrome"), + ("seamonkey", "seamonkey"), + ("firefox|firebird|phoenix|iceweasel", "firefox"), + ("galeon", "galeon"), + ("safari|version", "safari"), + ("webkit", "webkit"), + ("camino", "camino"), + ("konqueror", "konqueror"), + ("k-meleon", "kmeleon"), + ("netscape", "netscape"), + (r"msie|microsoft\s+internet\s+explorer|trident/.+? rv:", "msie"), + ("lynx", "lynx"), + ("links", "links"), + ("Baiduspider", "baidu"), + ("bingbot", "bing"), + ("mozilla", "mozilla"), + ) + + _browser_version_re = r"(?:%s)[/\sa-z(]*(\d+[.\da-z]+)?" + _language_re = re.compile( + r"(?:;\s*|\s+)(\b\w{2}\b(?:-\b\w{2}\b)?)\s*;|" + r"(?:\(|\[|;)\s*(\b\w{2}\b(?:-\b\w{2}\b)?)\s*(?:\]|\)|;)" + ) + + def __init__(self): + self.platforms = [(b, re.compile(a, re.I)) for a, b in self.platforms] + self.browsers = [ + (b, re.compile(self._browser_version_re % a, re.I)) + for a, b in self.browsers + ] + + def __call__(self, user_agent): + for platform, regex in self.platforms: # noqa: B007 + match = regex.search(user_agent) + if match is not None: + break + else: + platform = None + for browser, regex in self.browsers: # noqa: B007 + match = regex.search(user_agent) + if match is not None: + version = match.group(1) + break + else: + browser = version = None + match = self._language_re.search(user_agent) + if match is not None: + language = match.group(1) or match.group(2) + else: + language = None + return platform, browser, version, language + + +class UserAgent(object): + """Represents a user agent. Pass it a WSGI environment or a user agent + string and you can inspect some of the details from the user agent + string via the attributes. The following attributes exist: + + .. attribute:: string + + the raw user agent string + + .. attribute:: platform + + the browser platform. The following platforms are currently + recognized: + + - `aix` + - `amiga` + - `android` + - `blackberry` + - `bsd` + - `chromeos` + - `dragonflybsd` + - `freebsd` + - `hpux` + - `ipad` + - `iphone` + - `irix` + - `linux` + - `macos` + - `netbsd` + - `openbsd` + - `sco` + - `solaris` + - `symbian` + - `wii` + - `windows` + + .. attribute:: browser + + the name of the browser. The following browsers are currently + recognized: + + - `aol` * + - `ask` * + - `baidu` * + - `bing` * + - `camino` + - `chrome` + - `edge` + - `firefox` + - `galeon` + - `google` * + - `kmeleon` + - `konqueror` + - `links` + - `lynx` + - `mozilla` + - `msie` + - `msn` + - `netscape` + - `opera` + - `safari` + - `seamonkey` + - `webkit` + - `yahoo` * + + (Browsers marked with a star (``*``) are crawlers.) + + .. attribute:: version + + the version of the browser + + .. attribute:: language + + the language of the browser + """ + + _parser = UserAgentParser() + + def __init__(self, environ_or_string): + if isinstance(environ_or_string, dict): + environ_or_string = environ_or_string.get("HTTP_USER_AGENT", "") + self.string = environ_or_string + self.platform, self.browser, self.version, self.language = self._parser( + environ_or_string + ) + + def to_header(self): + return self.string + + def __str__(self): + return self.string + + def __nonzero__(self): + return bool(self.browser) + + __bool__ = __nonzero__ + + def __repr__(self): + return "<%s %r/%s>" % (self.__class__.__name__, self.browser, self.version) + + +from werkzeug import _DeprecatedImportModule + +_DeprecatedImportModule( + __name__, {".wrappers.user_agent": ["UserAgentMixin"]}, "Werkzeug 1.0" +) +del _DeprecatedImportModule diff --git a/venv/lib/python2.7/site-packages/werkzeug/utils.py b/venv/lib/python2.7/site-packages/werkzeug/utils.py new file mode 100644 index 00000000..477164e3 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/utils.py @@ -0,0 +1,774 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.utils + ~~~~~~~~~~~~~~ + + This module implements various utilities for WSGI applications. Most of + them are used by the request and response wrappers but especially for + middleware development it makes sense to use them without the wrappers. + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import codecs +import os +import pkgutil +import re +import sys + +from ._compat import iteritems +from ._compat import PY2 +from ._compat import reraise +from ._compat import string_types +from ._compat import text_type +from ._compat import unichr +from ._internal import _DictAccessorProperty +from ._internal import _missing +from ._internal import _parse_signature + +try: + from html.entities import name2codepoint +except ImportError: + from htmlentitydefs import name2codepoint + + +_format_re = re.compile(r"\$(?:(%s)|\{(%s)\})" % (("[a-zA-Z_][a-zA-Z0-9_]*",) * 2)) +_entity_re = re.compile(r"&([^;]+);") +_filename_ascii_strip_re = re.compile(r"[^A-Za-z0-9_.-]") +_windows_device_files = ( + "CON", + "AUX", + "COM1", + "COM2", + "COM3", + "COM4", + "LPT1", + "LPT2", + "LPT3", + "PRN", + "NUL", +) + + +class cached_property(property): + """A decorator that converts a function into a lazy property. The + function wrapped is called the first time to retrieve the result + and then that calculated result is used the next time you access + the value:: + + class Foo(object): + + @cached_property + def foo(self): + # calculate something important here + return 42 + + The class has to have a `__dict__` in order for this property to + work. + """ + + # implementation detail: A subclass of python's builtin property + # decorator, we override __get__ to check for a cached value. If one + # chooses to invoke __get__ by hand the property will still work as + # expected because the lookup logic is replicated in __get__ for + # manual invocation. + + def __init__(self, func, name=None, doc=None): + self.__name__ = name or func.__name__ + self.__module__ = func.__module__ + self.__doc__ = doc or func.__doc__ + self.func = func + + def __set__(self, obj, value): + obj.__dict__[self.__name__] = value + + def __get__(self, obj, type=None): + if obj is None: + return self + value = obj.__dict__.get(self.__name__, _missing) + if value is _missing: + value = self.func(obj) + obj.__dict__[self.__name__] = value + return value + + +class environ_property(_DictAccessorProperty): + """Maps request attributes to environment variables. This works not only + for the Werzeug request object, but also any other class with an + environ attribute: + + >>> class Test(object): + ... environ = {'key': 'value'} + ... test = environ_property('key') + >>> var = Test() + >>> var.test + 'value' + + If you pass it a second value it's used as default if the key does not + exist, the third one can be a converter that takes a value and converts + it. If it raises :exc:`ValueError` or :exc:`TypeError` the default value + is used. If no default value is provided `None` is used. + + Per default the property is read only. You have to explicitly enable it + by passing ``read_only=False`` to the constructor. + """ + + read_only = True + + def lookup(self, obj): + return obj.environ + + +class header_property(_DictAccessorProperty): + """Like `environ_property` but for headers.""" + + def lookup(self, obj): + return obj.headers + + +class HTMLBuilder(object): + """Helper object for HTML generation. + + Per default there are two instances of that class. The `html` one, and + the `xhtml` one for those two dialects. The class uses keyword parameters + and positional parameters to generate small snippets of HTML. + + Keyword parameters are converted to XML/SGML attributes, positional + arguments are used as children. Because Python accepts positional + arguments before keyword arguments it's a good idea to use a list with the + star-syntax for some children: + + >>> html.p(class_='foo', *[html.a('foo', href='foo.html'), ' ', + ... html.a('bar', href='bar.html')]) + u'

    foo bar

    ' + + This class works around some browser limitations and can not be used for + arbitrary SGML/XML generation. For that purpose lxml and similar + libraries exist. + + Calling the builder escapes the string passed: + + >>> html.p(html("")) + u'

    <foo>

    ' + """ + + _entity_re = re.compile(r"&([^;]+);") + _entities = name2codepoint.copy() + _entities["apos"] = 39 + _empty_elements = { + "area", + "base", + "basefont", + "br", + "col", + "command", + "embed", + "frame", + "hr", + "img", + "input", + "keygen", + "isindex", + "link", + "meta", + "param", + "source", + "wbr", + } + _boolean_attributes = { + "selected", + "checked", + "compact", + "declare", + "defer", + "disabled", + "ismap", + "multiple", + "nohref", + "noresize", + "noshade", + "nowrap", + } + _plaintext_elements = {"textarea"} + _c_like_cdata = {"script", "style"} + + def __init__(self, dialect): + self._dialect = dialect + + def __call__(self, s): + return escape(s) + + def __getattr__(self, tag): + if tag[:2] == "__": + raise AttributeError(tag) + + def proxy(*children, **arguments): + buffer = "<" + tag + for key, value in iteritems(arguments): + if value is None: + continue + if key[-1] == "_": + key = key[:-1] + if key in self._boolean_attributes: + if not value: + continue + if self._dialect == "xhtml": + value = '="' + key + '"' + else: + value = "" + else: + value = '="' + escape(value) + '"' + buffer += " " + key + value + if not children and tag in self._empty_elements: + if self._dialect == "xhtml": + buffer += " />" + else: + buffer += ">" + return buffer + buffer += ">" + + children_as_string = "".join( + [text_type(x) for x in children if x is not None] + ) + + if children_as_string: + if tag in self._plaintext_elements: + children_as_string = escape(children_as_string) + elif tag in self._c_like_cdata and self._dialect == "xhtml": + children_as_string = ( + "/**/" + ) + buffer += children_as_string + "" + return buffer + + return proxy + + def __repr__(self): + return "<%s for %r>" % (self.__class__.__name__, self._dialect) + + +html = HTMLBuilder("html") +xhtml = HTMLBuilder("xhtml") + +# https://cgit.freedesktop.org/xdg/shared-mime-info/tree/freedesktop.org.xml.in +# https://www.iana.org/assignments/media-types/media-types.xhtml +# Types listed in the XDG mime info that have a charset in the IANA registration. +_charset_mimetypes = { + "application/ecmascript", + "application/javascript", + "application/sql", + "application/xml", + "application/xml-dtd", + "application/xml-external-parsed-entity", +} + + +def get_content_type(mimetype, charset): + """Returns the full content type string with charset for a mimetype. + + If the mimetype represents text, the charset parameter will be + appended, otherwise the mimetype is returned unchanged. + + :param mimetype: The mimetype to be used as content type. + :param charset: The charset to be appended for text mimetypes. + :return: The content type. + + .. verionchanged:: 0.15 + Any type that ends with ``+xml`` gets a charset, not just those + that start with ``application/``. Known text types such as + ``application/javascript`` are also given charsets. + """ + if ( + mimetype.startswith("text/") + or mimetype in _charset_mimetypes + or mimetype.endswith("+xml") + ): + mimetype += "; charset=" + charset + + return mimetype + + +def detect_utf_encoding(data): + """Detect which UTF encoding was used to encode the given bytes. + + The latest JSON standard (:rfc:`8259`) suggests that only UTF-8 is + accepted. Older documents allowed 8, 16, or 32. 16 and 32 can be big + or little endian. Some editors or libraries may prepend a BOM. + + :internal: + + :param data: Bytes in unknown UTF encoding. + :return: UTF encoding name + + .. versionadded:: 0.15 + """ + head = data[:4] + + if head[:3] == codecs.BOM_UTF8: + return "utf-8-sig" + + if b"\x00" not in head: + return "utf-8" + + if head in (codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE): + return "utf-32" + + if head[:2] in (codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE): + return "utf-16" + + if len(head) == 4: + if head[:3] == b"\x00\x00\x00": + return "utf-32-be" + + if head[::2] == b"\x00\x00": + return "utf-16-be" + + if head[1:] == b"\x00\x00\x00": + return "utf-32-le" + + if head[1::2] == b"\x00\x00": + return "utf-16-le" + + if len(head) == 2: + return "utf-16-be" if head.startswith(b"\x00") else "utf-16-le" + + return "utf-8" + + +def format_string(string, context): + """String-template format a string: + + >>> format_string('$foo and ${foo}s', dict(foo=42)) + '42 and 42s' + + This does not do any attribute lookup etc. For more advanced string + formattings have a look at the `werkzeug.template` module. + + :param string: the format string. + :param context: a dict with the variables to insert. + """ + + def lookup_arg(match): + x = context[match.group(1) or match.group(2)] + if not isinstance(x, string_types): + x = type(string)(x) + return x + + return _format_re.sub(lookup_arg, string) + + +def secure_filename(filename): + r"""Pass it a filename and it will return a secure version of it. This + filename can then safely be stored on a regular file system and passed + to :func:`os.path.join`. The filename returned is an ASCII only string + for maximum portability. + + On windows systems the function also makes sure that the file is not + named after one of the special device files. + + >>> secure_filename("My cool movie.mov") + 'My_cool_movie.mov' + >>> secure_filename("../../../etc/passwd") + 'etc_passwd' + >>> secure_filename(u'i contain cool \xfcml\xe4uts.txt') + 'i_contain_cool_umlauts.txt' + + The function might return an empty filename. It's your responsibility + to ensure that the filename is unique and that you abort or + generate a random filename if the function returned an empty one. + + .. versionadded:: 0.5 + + :param filename: the filename to secure + """ + if isinstance(filename, text_type): + from unicodedata import normalize + + filename = normalize("NFKD", filename).encode("ascii", "ignore") + if not PY2: + filename = filename.decode("ascii") + for sep in os.path.sep, os.path.altsep: + if sep: + filename = filename.replace(sep, " ") + filename = str(_filename_ascii_strip_re.sub("", "_".join(filename.split()))).strip( + "._" + ) + + # on nt a couple of special files are present in each folder. We + # have to ensure that the target file is not such a filename. In + # this case we prepend an underline + if ( + os.name == "nt" + and filename + and filename.split(".")[0].upper() in _windows_device_files + ): + filename = "_" + filename + + return filename + + +def escape(s, quote=None): + """Replace special characters "&", "<", ">" and (") to HTML-safe sequences. + + There is a special handling for `None` which escapes to an empty string. + + .. versionchanged:: 0.9 + `quote` is now implicitly on. + + :param s: the string to escape. + :param quote: ignored. + """ + if s is None: + return "" + elif hasattr(s, "__html__"): + return text_type(s.__html__()) + elif not isinstance(s, string_types): + s = text_type(s) + if quote is not None: + from warnings import warn + + warn( + "The 'quote' parameter is no longer used as of version 0.9" + " and will be removed in version 1.0.", + DeprecationWarning, + stacklevel=2, + ) + s = ( + s.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace('"', """) + ) + return s + + +def unescape(s): + """The reverse function of `escape`. This unescapes all the HTML + entities, not only the XML entities inserted by `escape`. + + :param s: the string to unescape. + """ + + def handle_match(m): + name = m.group(1) + if name in HTMLBuilder._entities: + return unichr(HTMLBuilder._entities[name]) + try: + if name[:2] in ("#x", "#X"): + return unichr(int(name[2:], 16)) + elif name.startswith("#"): + return unichr(int(name[1:])) + except ValueError: + pass + return u"" + + return _entity_re.sub(handle_match, s) + + +def redirect(location, code=302, Response=None): + """Returns a response object (a WSGI application) that, if called, + redirects the client to the target location. Supported codes are + 301, 302, 303, 305, 307, and 308. 300 is not supported because + it's not a real redirect and 304 because it's the answer for a + request with a request with defined If-Modified-Since headers. + + .. versionadded:: 0.6 + The location can now be a unicode string that is encoded using + the :func:`iri_to_uri` function. + + .. versionadded:: 0.10 + The class used for the Response object can now be passed in. + + :param location: the location the response should redirect to. + :param code: the redirect status code. defaults to 302. + :param class Response: a Response class to use when instantiating a + response. The default is :class:`werkzeug.wrappers.Response` if + unspecified. + """ + if Response is None: + from .wrappers import Response + + display_location = escape(location) + if isinstance(location, text_type): + # Safe conversion is necessary here as we might redirect + # to a broken URI scheme (for instance itms-services). + from .urls import iri_to_uri + + location = iri_to_uri(location, safe_conversion=True) + response = Response( + '\n' + "Redirecting...\n" + "

    Redirecting...

    \n" + "

    You should be redirected automatically to target URL: " + '%s. If not click the link.' + % (escape(location), display_location), + code, + mimetype="text/html", + ) + response.headers["Location"] = location + return response + + +def append_slash_redirect(environ, code=301): + """Redirects to the same URL but with a slash appended. The behavior + of this function is undefined if the path ends with a slash already. + + :param environ: the WSGI environment for the request that triggers + the redirect. + :param code: the status code for the redirect. + """ + new_path = environ["PATH_INFO"].strip("/") + "/" + query_string = environ.get("QUERY_STRING") + if query_string: + new_path += "?" + query_string + return redirect(new_path, code) + + +def import_string(import_name, silent=False): + """Imports an object based on a string. This is useful if you want to + use import paths as endpoints or something similar. An import path can + be specified either in dotted notation (``xml.sax.saxutils.escape``) + or with a colon as object delimiter (``xml.sax.saxutils:escape``). + + If `silent` is True the return value will be `None` if the import fails. + + :param import_name: the dotted name for the object to import. + :param silent: if set to `True` import errors are ignored and + `None` is returned instead. + :return: imported object + """ + # force the import name to automatically convert to strings + # __import__ is not able to handle unicode strings in the fromlist + # if the module is a package + import_name = str(import_name).replace(":", ".") + try: + try: + __import__(import_name) + except ImportError: + if "." not in import_name: + raise + else: + return sys.modules[import_name] + + module_name, obj_name = import_name.rsplit(".", 1) + module = __import__(module_name, globals(), locals(), [obj_name]) + try: + return getattr(module, obj_name) + except AttributeError as e: + raise ImportError(e) + + except ImportError as e: + if not silent: + reraise( + ImportStringError, ImportStringError(import_name, e), sys.exc_info()[2] + ) + + +def find_modules(import_path, include_packages=False, recursive=False): + """Finds all the modules below a package. This can be useful to + automatically import all views / controllers so that their metaclasses / + function decorators have a chance to register themselves on the + application. + + Packages are not returned unless `include_packages` is `True`. This can + also recursively list modules but in that case it will import all the + packages to get the correct load path of that module. + + :param import_path: the dotted name for the package to find child modules. + :param include_packages: set to `True` if packages should be returned, too. + :param recursive: set to `True` if recursion should happen. + :return: generator + """ + module = import_string(import_path) + path = getattr(module, "__path__", None) + if path is None: + raise ValueError("%r is not a package" % import_path) + basename = module.__name__ + "." + for _importer, modname, ispkg in pkgutil.iter_modules(path): + modname = basename + modname + if ispkg: + if include_packages: + yield modname + if recursive: + for item in find_modules(modname, include_packages, True): + yield item + else: + yield modname + + +def validate_arguments(func, args, kwargs, drop_extra=True): + """Checks if the function accepts the arguments and keyword arguments. + Returns a new ``(args, kwargs)`` tuple that can safely be passed to + the function without causing a `TypeError` because the function signature + is incompatible. If `drop_extra` is set to `True` (which is the default) + any extra positional or keyword arguments are dropped automatically. + + The exception raised provides three attributes: + + `missing` + A set of argument names that the function expected but where + missing. + + `extra` + A dict of keyword arguments that the function can not handle but + where provided. + + `extra_positional` + A list of values that where given by positional argument but the + function cannot accept. + + This can be useful for decorators that forward user submitted data to + a view function:: + + from werkzeug.utils import ArgumentValidationError, validate_arguments + + def sanitize(f): + def proxy(request): + data = request.values.to_dict() + try: + args, kwargs = validate_arguments(f, (request,), data) + except ArgumentValidationError: + raise BadRequest('The browser failed to transmit all ' + 'the data expected.') + return f(*args, **kwargs) + return proxy + + :param func: the function the validation is performed against. + :param args: a tuple of positional arguments. + :param kwargs: a dict of keyword arguments. + :param drop_extra: set to `False` if you don't want extra arguments + to be silently dropped. + :return: tuple in the form ``(args, kwargs)``. + """ + parser = _parse_signature(func) + args, kwargs, missing, extra, extra_positional = parser(args, kwargs)[:5] + if missing: + raise ArgumentValidationError(tuple(missing)) + elif (extra or extra_positional) and not drop_extra: + raise ArgumentValidationError(None, extra, extra_positional) + return tuple(args), kwargs + + +def bind_arguments(func, args, kwargs): + """Bind the arguments provided into a dict. When passed a function, + a tuple of arguments and a dict of keyword arguments `bind_arguments` + returns a dict of names as the function would see it. This can be useful + to implement a cache decorator that uses the function arguments to build + the cache key based on the values of the arguments. + + :param func: the function the arguments should be bound for. + :param args: tuple of positional arguments. + :param kwargs: a dict of keyword arguments. + :return: a :class:`dict` of bound keyword arguments. + """ + ( + args, + kwargs, + missing, + extra, + extra_positional, + arg_spec, + vararg_var, + kwarg_var, + ) = _parse_signature(func)(args, kwargs) + values = {} + for (name, _has_default, _default), value in zip(arg_spec, args): + values[name] = value + if vararg_var is not None: + values[vararg_var] = tuple(extra_positional) + elif extra_positional: + raise TypeError("too many positional arguments") + if kwarg_var is not None: + multikw = set(extra) & set([x[0] for x in arg_spec]) + if multikw: + raise TypeError( + "got multiple values for keyword argument " + repr(next(iter(multikw))) + ) + values[kwarg_var] = extra + elif extra: + raise TypeError("got unexpected keyword argument " + repr(next(iter(extra)))) + return values + + +class ArgumentValidationError(ValueError): + + """Raised if :func:`validate_arguments` fails to validate""" + + def __init__(self, missing=None, extra=None, extra_positional=None): + self.missing = set(missing or ()) + self.extra = extra or {} + self.extra_positional = extra_positional or [] + ValueError.__init__( + self, + "function arguments invalid. (%d missing, %d additional)" + % (len(self.missing), len(self.extra) + len(self.extra_positional)), + ) + + +class ImportStringError(ImportError): + """Provides information about a failed :func:`import_string` attempt.""" + + #: String in dotted notation that failed to be imported. + import_name = None + #: Wrapped exception. + exception = None + + def __init__(self, import_name, exception): + self.import_name = import_name + self.exception = exception + + msg = ( + "import_string() failed for %r. Possible reasons are:\n\n" + "- missing __init__.py in a package;\n" + "- package or module path not included in sys.path;\n" + "- duplicated package or module name taking precedence in " + "sys.path;\n" + "- missing module, class, function or variable;\n\n" + "Debugged import:\n\n%s\n\n" + "Original exception:\n\n%s: %s" + ) + + name = "" + tracked = [] + for part in import_name.replace(":", ".").split("."): + name += (name and ".") + part + imported = import_string(name, silent=True) + if imported: + tracked.append((name, getattr(imported, "__file__", None))) + else: + track = ["- %r found in %r." % (n, i) for n, i in tracked] + track.append("- %r not found." % name) + msg = msg % ( + import_name, + "\n".join(track), + exception.__class__.__name__, + str(exception), + ) + break + + ImportError.__init__(self, msg) + + def __repr__(self): + return "<%s(%r, %r)>" % ( + self.__class__.__name__, + self.import_name, + self.exception, + ) + + +from werkzeug import _DeprecatedImportModule + +_DeprecatedImportModule( + __name__, + { + ".datastructures": [ + "CombinedMultiDict", + "EnvironHeaders", + "Headers", + "MultiDict", + ], + ".http": ["dump_cookie", "parse_cookie"], + }, + "Werkzeug 1.0", +) +del _DeprecatedImportModule diff --git a/venv/lib/python2.7/site-packages/werkzeug/wrappers/__init__.py b/venv/lib/python2.7/site-packages/werkzeug/wrappers/__init__.py new file mode 100644 index 00000000..56c764ab --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/wrappers/__init__.py @@ -0,0 +1,36 @@ +""" +werkzeug.wrappers +~~~~~~~~~~~~~~~~~ + +The wrappers are simple request and response objects which you can +subclass to do whatever you want them to do. The request object contains +the information transmitted by the client (webbrowser) and the response +object contains all the information sent back to the browser. + +An important detail is that the request object is created with the WSGI +environ and will act as high-level proxy whereas the response object is an +actual WSGI application. + +Like everything else in Werkzeug these objects will work correctly with +unicode data. Incoming form data parsed by the response object will be +decoded into an unicode object if possible and if it makes sense. + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +from .accept import AcceptMixin +from .auth import AuthorizationMixin +from .auth import WWWAuthenticateMixin +from .base_request import BaseRequest +from .base_response import BaseResponse +from .common_descriptors import CommonRequestDescriptorsMixin +from .common_descriptors import CommonResponseDescriptorsMixin +from .etag import ETagRequestMixin +from .etag import ETagResponseMixin +from .request import PlainRequest +from .request import Request +from .request import StreamOnlyMixin +from .response import Response +from .response import ResponseStream +from .response import ResponseStreamMixin +from .user_agent import UserAgentMixin diff --git a/venv/lib/python2.7/site-packages/werkzeug/wrappers/accept.py b/venv/lib/python2.7/site-packages/werkzeug/wrappers/accept.py new file mode 100644 index 00000000..d0620a0a --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/wrappers/accept.py @@ -0,0 +1,50 @@ +from ..datastructures import CharsetAccept +from ..datastructures import LanguageAccept +from ..datastructures import MIMEAccept +from ..http import parse_accept_header +from ..utils import cached_property + + +class AcceptMixin(object): + """A mixin for classes with an :attr:`~BaseResponse.environ` attribute + to get all the HTTP accept headers as + :class:`~werkzeug.datastructures.Accept` objects (or subclasses + thereof). + """ + + @cached_property + def accept_mimetypes(self): + """List of mimetypes this client supports as + :class:`~werkzeug.datastructures.MIMEAccept` object. + """ + return parse_accept_header(self.environ.get("HTTP_ACCEPT"), MIMEAccept) + + @cached_property + def accept_charsets(self): + """List of charsets this client supports as + :class:`~werkzeug.datastructures.CharsetAccept` object. + """ + return parse_accept_header( + self.environ.get("HTTP_ACCEPT_CHARSET"), CharsetAccept + ) + + @cached_property + def accept_encodings(self): + """List of encodings this client accepts. Encodings in a HTTP term + are compression encodings such as gzip. For charsets have a look at + :attr:`accept_charset`. + """ + return parse_accept_header(self.environ.get("HTTP_ACCEPT_ENCODING")) + + @cached_property + def accept_languages(self): + """List of languages this client accepts as + :class:`~werkzeug.datastructures.LanguageAccept` object. + + .. versionchanged 0.5 + In previous versions this was a regular + :class:`~werkzeug.datastructures.Accept` object. + """ + return parse_accept_header( + self.environ.get("HTTP_ACCEPT_LANGUAGE"), LanguageAccept + ) diff --git a/venv/lib/python2.7/site-packages/werkzeug/wrappers/auth.py b/venv/lib/python2.7/site-packages/werkzeug/wrappers/auth.py new file mode 100644 index 00000000..714f7554 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/wrappers/auth.py @@ -0,0 +1,33 @@ +from ..http import parse_authorization_header +from ..http import parse_www_authenticate_header +from ..utils import cached_property + + +class AuthorizationMixin(object): + """Adds an :attr:`authorization` property that represents the parsed + value of the `Authorization` header as + :class:`~werkzeug.datastructures.Authorization` object. + """ + + @cached_property + def authorization(self): + """The `Authorization` object in parsed form.""" + header = self.environ.get("HTTP_AUTHORIZATION") + return parse_authorization_header(header) + + +class WWWAuthenticateMixin(object): + """Adds a :attr:`www_authenticate` property to a response object.""" + + @property + def www_authenticate(self): + """The `WWW-Authenticate` header in a parsed form.""" + + def on_update(www_auth): + if not www_auth and "www-authenticate" in self.headers: + del self.headers["www-authenticate"] + elif www_auth: + self.headers["WWW-Authenticate"] = www_auth.to_header() + + header = self.headers.get("www-authenticate") + return parse_www_authenticate_header(header, on_update) diff --git a/venv/lib/python2.7/site-packages/werkzeug/wrappers/base_request.py b/venv/lib/python2.7/site-packages/werkzeug/wrappers/base_request.py new file mode 100644 index 00000000..05a634ec --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/wrappers/base_request.py @@ -0,0 +1,695 @@ +import warnings +from functools import update_wrapper +from io import BytesIO + +from .._compat import to_native +from .._compat import to_unicode +from .._compat import wsgi_decoding_dance +from .._compat import wsgi_get_bytes +from ..datastructures import CombinedMultiDict +from ..datastructures import EnvironHeaders +from ..datastructures import ImmutableList +from ..datastructures import ImmutableMultiDict +from ..datastructures import ImmutableTypeConversionDict +from ..datastructures import iter_multi_items +from ..datastructures import MultiDict +from ..formparser import default_stream_factory +from ..formparser import FormDataParser +from ..http import parse_cookie +from ..http import parse_options_header +from ..urls import url_decode +from ..utils import cached_property +from ..utils import environ_property +from ..wsgi import get_content_length +from ..wsgi import get_current_url +from ..wsgi import get_host +from ..wsgi import get_input_stream + + +class BaseRequest(object): + """Very basic request object. This does not implement advanced stuff like + entity tag parsing or cache controls. The request object is created with + the WSGI environment as first argument and will add itself to the WSGI + environment as ``'werkzeug.request'`` unless it's created with + `populate_request` set to False. + + There are a couple of mixins available that add additional functionality + to the request object, there is also a class called `Request` which + subclasses `BaseRequest` and all the important mixins. + + It's a good idea to create a custom subclass of the :class:`BaseRequest` + and add missing functionality either via mixins or direct implementation. + Here an example for such subclasses:: + + from werkzeug.wrappers import BaseRequest, ETagRequestMixin + + class Request(BaseRequest, ETagRequestMixin): + pass + + Request objects are **read only**. As of 0.5 modifications are not + allowed in any place. Unlike the lower level parsing functions the + request object will use immutable objects everywhere possible. + + Per default the request object will assume all the text data is `utf-8` + encoded. Please refer to :doc:`the unicode chapter ` for more + details about customizing the behavior. + + Per default the request object will be added to the WSGI + environment as `werkzeug.request` to support the debugging system. + If you don't want that, set `populate_request` to `False`. + + If `shallow` is `True` the environment is initialized as shallow + object around the environ. Every operation that would modify the + environ in any way (such as consuming form data) raises an exception + unless the `shallow` attribute is explicitly set to `False`. This + is useful for middlewares where you don't want to consume the form + data by accident. A shallow request is not populated to the WSGI + environment. + + .. versionchanged:: 0.5 + read-only mode was enforced by using immutables classes for all + data. + """ + + #: the charset for the request, defaults to utf-8 + charset = "utf-8" + + #: the error handling procedure for errors, defaults to 'replace' + encoding_errors = "replace" + + #: the maximum content length. This is forwarded to the form data + #: parsing function (:func:`parse_form_data`). When set and the + #: :attr:`form` or :attr:`files` attribute is accessed and the + #: parsing fails because more than the specified value is transmitted + #: a :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised. + #: + #: Have a look at :ref:`dealing-with-request-data` for more details. + #: + #: .. versionadded:: 0.5 + max_content_length = None + + #: the maximum form field size. This is forwarded to the form data + #: parsing function (:func:`parse_form_data`). When set and the + #: :attr:`form` or :attr:`files` attribute is accessed and the + #: data in memory for post data is longer than the specified value a + #: :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised. + #: + #: Have a look at :ref:`dealing-with-request-data` for more details. + #: + #: .. versionadded:: 0.5 + max_form_memory_size = None + + #: the class to use for `args` and `form`. The default is an + #: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports + #: multiple values per key. alternatively it makes sense to use an + #: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which + #: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict` + #: which is the fastest but only remembers the last key. It is also + #: possible to use mutable structures, but this is not recommended. + #: + #: .. versionadded:: 0.6 + parameter_storage_class = ImmutableMultiDict + + #: the type to be used for list values from the incoming WSGI environment. + #: By default an :class:`~werkzeug.datastructures.ImmutableList` is used + #: (for example for :attr:`access_list`). + #: + #: .. versionadded:: 0.6 + list_storage_class = ImmutableList + + #: the type to be used for dict values from the incoming WSGI environment. + #: By default an + #: :class:`~werkzeug.datastructures.ImmutableTypeConversionDict` is used + #: (for example for :attr:`cookies`). + #: + #: .. versionadded:: 0.6 + dict_storage_class = ImmutableTypeConversionDict + + #: The form data parser that shoud be used. Can be replaced to customize + #: the form date parsing. + form_data_parser_class = FormDataParser + + #: Optionally a list of hosts that is trusted by this request. By default + #: all hosts are trusted which means that whatever the client sends the + #: host is will be accepted. + #: + #: Because `Host` and `X-Forwarded-Host` headers can be set to any value by + #: a malicious client, it is recommended to either set this property or + #: implement similar validation in the proxy (if application is being run + #: behind one). + #: + #: .. versionadded:: 0.9 + trusted_hosts = None + + #: Indicates whether the data descriptor should be allowed to read and + #: buffer up the input stream. By default it's enabled. + #: + #: .. versionadded:: 0.9 + disable_data_descriptor = False + + def __init__(self, environ, populate_request=True, shallow=False): + self.environ = environ + if populate_request and not shallow: + self.environ["werkzeug.request"] = self + self.shallow = shallow + + def __repr__(self): + # make sure the __repr__ even works if the request was created + # from an invalid WSGI environment. If we display the request + # in a debug session we don't want the repr to blow up. + args = [] + try: + args.append("'%s'" % to_native(self.url, self.url_charset)) + args.append("[%s]" % self.method) + except Exception: + args.append("(invalid WSGI environ)") + + return "<%s %s>" % (self.__class__.__name__, " ".join(args)) + + @property + def url_charset(self): + """The charset that is assumed for URLs. Defaults to the value + of :attr:`charset`. + + .. versionadded:: 0.6 + """ + return self.charset + + @classmethod + def from_values(cls, *args, **kwargs): + """Create a new request object based on the values provided. If + environ is given missing values are filled from there. This method is + useful for small scripts when you need to simulate a request from an URL. + Do not use this method for unittesting, there is a full featured client + object (:class:`Client`) that allows to create multipart requests, + support for cookies etc. + + This accepts the same options as the + :class:`~werkzeug.test.EnvironBuilder`. + + .. versionchanged:: 0.5 + This method now accepts the same arguments as + :class:`~werkzeug.test.EnvironBuilder`. Because of this the + `environ` parameter is now called `environ_overrides`. + + :return: request object + """ + from ..test import EnvironBuilder + + charset = kwargs.pop("charset", cls.charset) + kwargs["charset"] = charset + builder = EnvironBuilder(*args, **kwargs) + try: + return builder.get_request(cls) + finally: + builder.close() + + @classmethod + def application(cls, f): + """Decorate a function as responder that accepts the request as + the last argument. This works like the :func:`responder` + decorator but the function is passed the request object as the + last argument and the request object will be closed + automatically:: + + @Request.application + def my_wsgi_app(request): + return Response('Hello World!') + + As of Werkzeug 0.14 HTTP exceptions are automatically caught and + converted to responses instead of failing. + + :param f: the WSGI callable to decorate + :return: a new WSGI callable + """ + #: return a callable that wraps the -2nd argument with the request + #: and calls the function with all the arguments up to that one and + #: the request. The return value is then called with the latest + #: two arguments. This makes it possible to use this decorator for + #: both standalone WSGI functions as well as bound methods and + #: partially applied functions. + from ..exceptions import HTTPException + + def application(*args): + request = cls(args[-2]) + with request: + try: + resp = f(*args[:-2] + (request,)) + except HTTPException as e: + resp = e.get_response(args[-2]) + return resp(*args[-2:]) + + return update_wrapper(application, f) + + def _get_file_stream( + self, total_content_length, content_type, filename=None, content_length=None + ): + """Called to get a stream for the file upload. + + This must provide a file-like class with `read()`, `readline()` + and `seek()` methods that is both writeable and readable. + + The default implementation returns a temporary file if the total + content length is higher than 500KB. Because many browsers do not + provide a content length for the files only the total content + length matters. + + :param total_content_length: the total content length of all the + data in the request combined. This value + is guaranteed to be there. + :param content_type: the mimetype of the uploaded file. + :param filename: the filename of the uploaded file. May be `None`. + :param content_length: the length of this file. This value is usually + not provided because webbrowsers do not provide + this value. + """ + return default_stream_factory( + total_content_length=total_content_length, + filename=filename, + content_type=content_type, + content_length=content_length, + ) + + @property + def want_form_data_parsed(self): + """Returns True if the request method carries content. As of + Werkzeug 0.9 this will be the case if a content type is transmitted. + + .. versionadded:: 0.8 + """ + return bool(self.environ.get("CONTENT_TYPE")) + + def make_form_data_parser(self): + """Creates the form data parser. Instantiates the + :attr:`form_data_parser_class` with some parameters. + + .. versionadded:: 0.8 + """ + return self.form_data_parser_class( + self._get_file_stream, + self.charset, + self.encoding_errors, + self.max_form_memory_size, + self.max_content_length, + self.parameter_storage_class, + ) + + def _load_form_data(self): + """Method used internally to retrieve submitted data. After calling + this sets `form` and `files` on the request object to multi dicts + filled with the incoming form data. As a matter of fact the input + stream will be empty afterwards. You can also call this method to + force the parsing of the form data. + + .. versionadded:: 0.8 + """ + # abort early if we have already consumed the stream + if "form" in self.__dict__: + return + + _assert_not_shallow(self) + + if self.want_form_data_parsed: + content_type = self.environ.get("CONTENT_TYPE", "") + content_length = get_content_length(self.environ) + mimetype, options = parse_options_header(content_type) + parser = self.make_form_data_parser() + data = parser.parse( + self._get_stream_for_parsing(), mimetype, content_length, options + ) + else: + data = ( + self.stream, + self.parameter_storage_class(), + self.parameter_storage_class(), + ) + + # inject the values into the instance dict so that we bypass + # our cached_property non-data descriptor. + d = self.__dict__ + d["stream"], d["form"], d["files"] = data + + def _get_stream_for_parsing(self): + """This is the same as accessing :attr:`stream` with the difference + that if it finds cached data from calling :meth:`get_data` first it + will create a new stream out of the cached data. + + .. versionadded:: 0.9.3 + """ + cached_data = getattr(self, "_cached_data", None) + if cached_data is not None: + return BytesIO(cached_data) + return self.stream + + def close(self): + """Closes associated resources of this request object. This + closes all file handles explicitly. You can also use the request + object in a with statement which will automatically close it. + + .. versionadded:: 0.9 + """ + files = self.__dict__.get("files") + for _key, value in iter_multi_items(files or ()): + value.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + self.close() + + @cached_property + def stream(self): + """ + If the incoming form data was not encoded with a known mimetype + the data is stored unmodified in this stream for consumption. Most + of the time it is a better idea to use :attr:`data` which will give + you that data as a string. The stream only returns the data once. + + Unlike :attr:`input_stream` this stream is properly guarded that you + can't accidentally read past the length of the input. Werkzeug will + internally always refer to this stream to read data which makes it + possible to wrap this object with a stream that does filtering. + + .. versionchanged:: 0.9 + This stream is now always available but might be consumed by the + form parser later on. Previously the stream was only set if no + parsing happened. + """ + _assert_not_shallow(self) + return get_input_stream(self.environ) + + input_stream = environ_property( + "wsgi.input", + """The WSGI input stream. + + In general it's a bad idea to use this one because you can + easily read past the boundary. Use the :attr:`stream` + instead.""", + ) + + @cached_property + def args(self): + """The parsed URL parameters (the part in the URL after the question + mark). + + By default an + :class:`~werkzeug.datastructures.ImmutableMultiDict` + is returned from this function. This can be changed by setting + :attr:`parameter_storage_class` to a different type. This might + be necessary if the order of the form data is important. + """ + return url_decode( + wsgi_get_bytes(self.environ.get("QUERY_STRING", "")), + self.url_charset, + errors=self.encoding_errors, + cls=self.parameter_storage_class, + ) + + @cached_property + def data(self): + """ + Contains the incoming request data as string in case it came with + a mimetype Werkzeug does not handle. + """ + + if self.disable_data_descriptor: + raise AttributeError("data descriptor is disabled") + # XXX: this should eventually be deprecated. + + # We trigger form data parsing first which means that the descriptor + # will not cache the data that would otherwise be .form or .files + # data. This restores the behavior that was there in Werkzeug + # before 0.9. New code should use :meth:`get_data` explicitly as + # this will make behavior explicit. + return self.get_data(parse_form_data=True) + + def get_data(self, cache=True, as_text=False, parse_form_data=False): + """This reads the buffered incoming data from the client into one + bytestring. By default this is cached but that behavior can be + changed by setting `cache` to `False`. + + Usually it's a bad idea to call this method without checking the + content length first as a client could send dozens of megabytes or more + to cause memory problems on the server. + + Note that if the form data was already parsed this method will not + return anything as form data parsing does not cache the data like + this method does. To implicitly invoke form data parsing function + set `parse_form_data` to `True`. When this is done the return value + of this method will be an empty string if the form parser handles + the data. This generally is not necessary as if the whole data is + cached (which is the default) the form parser will used the cached + data to parse the form data. Please be generally aware of checking + the content length first in any case before calling this method + to avoid exhausting server memory. + + If `as_text` is set to `True` the return value will be a decoded + unicode string. + + .. versionadded:: 0.9 + """ + rv = getattr(self, "_cached_data", None) + if rv is None: + if parse_form_data: + self._load_form_data() + rv = self.stream.read() + if cache: + self._cached_data = rv + if as_text: + rv = rv.decode(self.charset, self.encoding_errors) + return rv + + @cached_property + def form(self): + """The form parameters. By default an + :class:`~werkzeug.datastructures.ImmutableMultiDict` + is returned from this function. This can be changed by setting + :attr:`parameter_storage_class` to a different type. This might + be necessary if the order of the form data is important. + + Please keep in mind that file uploads will not end up here, but instead + in the :attr:`files` attribute. + + .. versionchanged:: 0.9 + + Previous to Werkzeug 0.9 this would only contain form data for POST + and PUT requests. + """ + self._load_form_data() + return self.form + + @cached_property + def values(self): + """A :class:`werkzeug.datastructures.CombinedMultiDict` that combines + :attr:`args` and :attr:`form`.""" + args = [] + for d in self.args, self.form: + if not isinstance(d, MultiDict): + d = MultiDict(d) + args.append(d) + return CombinedMultiDict(args) + + @cached_property + def files(self): + """:class:`~werkzeug.datastructures.MultiDict` object containing + all uploaded files. Each key in :attr:`files` is the name from the + ````. Each value in :attr:`files` is a + Werkzeug :class:`~werkzeug.datastructures.FileStorage` object. + + It basically behaves like a standard file object you know from Python, + with the difference that it also has a + :meth:`~werkzeug.datastructures.FileStorage.save` function that can + store the file on the filesystem. + + Note that :attr:`files` will only contain data if the request method was + POST, PUT or PATCH and the ``

    `` that posted to the request had + ``enctype="multipart/form-data"``. It will be empty otherwise. + + See the :class:`~werkzeug.datastructures.MultiDict` / + :class:`~werkzeug.datastructures.FileStorage` documentation for + more details about the used data structure. + """ + self._load_form_data() + return self.files + + @cached_property + def cookies(self): + """A :class:`dict` with the contents of all cookies transmitted with + the request.""" + return parse_cookie( + self.environ, + self.charset, + self.encoding_errors, + cls=self.dict_storage_class, + ) + + @cached_property + def headers(self): + """The headers from the WSGI environ as immutable + :class:`~werkzeug.datastructures.EnvironHeaders`. + """ + return EnvironHeaders(self.environ) + + @cached_property + def path(self): + """Requested path as unicode. This works a bit like the regular path + info in the WSGI environment but will always include a leading slash, + even if the URL root is accessed. + """ + raw_path = wsgi_decoding_dance( + self.environ.get("PATH_INFO") or "", self.charset, self.encoding_errors + ) + return "/" + raw_path.lstrip("/") + + @cached_property + def full_path(self): + """Requested path as unicode, including the query string.""" + return self.path + u"?" + to_unicode(self.query_string, self.url_charset) + + @cached_property + def script_root(self): + """The root path of the script without the trailing slash.""" + raw_path = wsgi_decoding_dance( + self.environ.get("SCRIPT_NAME") or "", self.charset, self.encoding_errors + ) + return raw_path.rstrip("/") + + @cached_property + def url(self): + """The reconstructed current URL as IRI. + See also: :attr:`trusted_hosts`. + """ + return get_current_url(self.environ, trusted_hosts=self.trusted_hosts) + + @cached_property + def base_url(self): + """Like :attr:`url` but without the querystring + See also: :attr:`trusted_hosts`. + """ + return get_current_url( + self.environ, strip_querystring=True, trusted_hosts=self.trusted_hosts + ) + + @cached_property + def url_root(self): + """The full URL root (with hostname), this is the application + root as IRI. + See also: :attr:`trusted_hosts`. + """ + return get_current_url(self.environ, True, trusted_hosts=self.trusted_hosts) + + @cached_property + def host_url(self): + """Just the host with scheme as IRI. + See also: :attr:`trusted_hosts`. + """ + return get_current_url( + self.environ, host_only=True, trusted_hosts=self.trusted_hosts + ) + + @cached_property + def host(self): + """Just the host including the port if available. + See also: :attr:`trusted_hosts`. + """ + return get_host(self.environ, trusted_hosts=self.trusted_hosts) + + query_string = environ_property( + "QUERY_STRING", + "", + read_only=True, + load_func=wsgi_get_bytes, + doc="The URL parameters as raw bytestring.", + ) + method = environ_property( + "REQUEST_METHOD", + "GET", + read_only=True, + load_func=lambda x: x.upper(), + doc="The request method. (For example ``'GET'`` or ``'POST'``).", + ) + + @cached_property + def access_route(self): + """If a forwarded header exists this is a list of all ip addresses + from the client ip to the last proxy server. + """ + if "HTTP_X_FORWARDED_FOR" in self.environ: + addr = self.environ["HTTP_X_FORWARDED_FOR"].split(",") + return self.list_storage_class([x.strip() for x in addr]) + elif "REMOTE_ADDR" in self.environ: + return self.list_storage_class([self.environ["REMOTE_ADDR"]]) + return self.list_storage_class() + + @property + def remote_addr(self): + """The remote address of the client.""" + return self.environ.get("REMOTE_ADDR") + + remote_user = environ_property( + "REMOTE_USER", + doc="""If the server supports user authentication, and the + script is protected, this attribute contains the username the + user has authenticated as.""", + ) + + scheme = environ_property( + "wsgi.url_scheme", + doc=""" + URL scheme (http or https). + + .. versionadded:: 0.7""", + ) + + @property + def is_xhr(self): + """True if the request was triggered via a JavaScript XMLHttpRequest. + This only works with libraries that support the ``X-Requested-With`` + header and set it to "XMLHttpRequest". Libraries that do that are + prototype, jQuery and Mochikit and probably some more. + + .. deprecated:: 0.13 + ``X-Requested-With`` is not standard and is unreliable. You + may be able to use :attr:`AcceptMixin.accept_mimetypes` + instead. + """ + warnings.warn( + "'Request.is_xhr' is deprecated as of version 0.13 and will" + " be removed in version 1.0. The 'X-Requested-With' header" + " is not standard and is unreliable. You may be able to use" + " 'accept_mimetypes' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.environ.get("HTTP_X_REQUESTED_WITH", "").lower() == "xmlhttprequest" + + is_secure = property( + lambda self: self.environ["wsgi.url_scheme"] == "https", + doc="`True` if the request is secure.", + ) + is_multithread = environ_property( + "wsgi.multithread", + doc="""boolean that is `True` if the application is served by a + multithreaded WSGI server.""", + ) + is_multiprocess = environ_property( + "wsgi.multiprocess", + doc="""boolean that is `True` if the application is served by a + WSGI server that spawns multiple processes.""", + ) + is_run_once = environ_property( + "wsgi.run_once", + doc="""boolean that is `True` if the application will be + executed only once in a process lifetime. This is the case for + CGI for example, but it's not guaranteed that the execution only + happens one time.""", + ) + + +def _assert_not_shallow(request): + if request.shallow: + raise RuntimeError( + "A shallow request tried to consume form data. If you really" + " want to do that, set `shallow` to False." + ) diff --git a/venv/lib/python2.7/site-packages/werkzeug/wrappers/base_response.py b/venv/lib/python2.7/site-packages/werkzeug/wrappers/base_response.py new file mode 100644 index 00000000..d944a7d2 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/wrappers/base_response.py @@ -0,0 +1,702 @@ +import warnings + +from .._compat import integer_types +from .._compat import string_types +from .._compat import text_type +from .._compat import to_bytes +from .._compat import to_native +from ..datastructures import Headers +from ..http import dump_cookie +from ..http import HTTP_STATUS_CODES +from ..http import remove_entity_headers +from ..urls import iri_to_uri +from ..urls import url_join +from ..utils import get_content_type +from ..wsgi import ClosingIterator +from ..wsgi import get_current_url + + +def _run_wsgi_app(*args): + """This function replaces itself to ensure that the test module is not + imported unless required. DO NOT USE! + """ + global _run_wsgi_app + from ..test import run_wsgi_app as _run_wsgi_app + + return _run_wsgi_app(*args) + + +def _warn_if_string(iterable): + """Helper for the response objects to check if the iterable returned + to the WSGI server is not a string. + """ + if isinstance(iterable, string_types): + warnings.warn( + "Response iterable was set to a string. This will appear to" + " work but means that the server will send the data to the" + " client one character at a time. This is almost never" + " intended behavior, use 'response.data' to assign strings" + " to the response object.", + stacklevel=2, + ) + + +def _iter_encoded(iterable, charset): + for item in iterable: + if isinstance(item, text_type): + yield item.encode(charset) + else: + yield item + + +def _clean_accept_ranges(accept_ranges): + if accept_ranges is True: + return "bytes" + elif accept_ranges is False: + return "none" + elif isinstance(accept_ranges, text_type): + return to_native(accept_ranges) + raise ValueError("Invalid accept_ranges value") + + +class BaseResponse(object): + """Base response class. The most important fact about a response object + is that it's a regular WSGI application. It's initialized with a couple + of response parameters (headers, body, status code etc.) and will start a + valid WSGI response when called with the environ and start response + callable. + + Because it's a WSGI application itself processing usually ends before the + actual response is sent to the server. This helps debugging systems + because they can catch all the exceptions before responses are started. + + Here a small example WSGI application that takes advantage of the + response objects:: + + from werkzeug.wrappers import BaseResponse as Response + + def index(): + return Response('Index page') + + def application(environ, start_response): + path = environ.get('PATH_INFO') or '/' + if path == '/': + response = index() + else: + response = Response('Not Found', status=404) + return response(environ, start_response) + + Like :class:`BaseRequest` which object is lacking a lot of functionality + implemented in mixins. This gives you a better control about the actual + API of your response objects, so you can create subclasses and add custom + functionality. A full featured response object is available as + :class:`Response` which implements a couple of useful mixins. + + To enforce a new type of already existing responses you can use the + :meth:`force_type` method. This is useful if you're working with different + subclasses of response objects and you want to post process them with a + known interface. + + Per default the response object will assume all the text data is `utf-8` + encoded. Please refer to :doc:`the unicode chapter ` for more + details about customizing the behavior. + + Response can be any kind of iterable or string. If it's a string it's + considered being an iterable with one item which is the string passed. + Headers can be a list of tuples or a + :class:`~werkzeug.datastructures.Headers` object. + + Special note for `mimetype` and `content_type`: For most mime types + `mimetype` and `content_type` work the same, the difference affects + only 'text' mimetypes. If the mimetype passed with `mimetype` is a + mimetype starting with `text/`, the charset parameter of the response + object is appended to it. In contrast the `content_type` parameter is + always added as header unmodified. + + .. versionchanged:: 0.5 + the `direct_passthrough` parameter was added. + + :param response: a string or response iterable. + :param status: a string with a status or an integer with the status code. + :param headers: a list of headers or a + :class:`~werkzeug.datastructures.Headers` object. + :param mimetype: the mimetype for the response. See notice above. + :param content_type: the content type for the response. See notice above. + :param direct_passthrough: if set to `True` :meth:`iter_encoded` is not + called before iteration which makes it + possible to pass special iterators through + unchanged (see :func:`wrap_file` for more + details.) + """ + + #: the charset of the response. + charset = "utf-8" + + #: the default status if none is provided. + default_status = 200 + + #: the default mimetype if none is provided. + default_mimetype = "text/plain" + + #: if set to `False` accessing properties on the response object will + #: not try to consume the response iterator and convert it into a list. + #: + #: .. versionadded:: 0.6.2 + #: + #: That attribute was previously called `implicit_seqence_conversion`. + #: (Notice the typo). If you did use this feature, you have to adapt + #: your code to the name change. + implicit_sequence_conversion = True + + #: Should this response object correct the location header to be RFC + #: conformant? This is true by default. + #: + #: .. versionadded:: 0.8 + autocorrect_location_header = True + + #: Should this response object automatically set the content-length + #: header if possible? This is true by default. + #: + #: .. versionadded:: 0.8 + automatically_set_content_length = True + + #: Warn if a cookie header exceeds this size. The default, 4093, should be + #: safely `supported by most browsers `_. A cookie larger than + #: this size will still be sent, but it may be ignored or handled + #: incorrectly by some browsers. Set to 0 to disable this check. + #: + #: .. versionadded:: 0.13 + #: + #: .. _`cookie`: http://browsercookielimits.squawky.net/ + max_cookie_size = 4093 + + def __init__( + self, + response=None, + status=None, + headers=None, + mimetype=None, + content_type=None, + direct_passthrough=False, + ): + if isinstance(headers, Headers): + self.headers = headers + elif not headers: + self.headers = Headers() + else: + self.headers = Headers(headers) + + if content_type is None: + if mimetype is None and "content-type" not in self.headers: + mimetype = self.default_mimetype + if mimetype is not None: + mimetype = get_content_type(mimetype, self.charset) + content_type = mimetype + if content_type is not None: + self.headers["Content-Type"] = content_type + if status is None: + status = self.default_status + if isinstance(status, integer_types): + self.status_code = status + else: + self.status = status + + self.direct_passthrough = direct_passthrough + self._on_close = [] + + # we set the response after the headers so that if a class changes + # the charset attribute, the data is set in the correct charset. + if response is None: + self.response = [] + elif isinstance(response, (text_type, bytes, bytearray)): + self.set_data(response) + else: + self.response = response + + def call_on_close(self, func): + """Adds a function to the internal list of functions that should + be called as part of closing down the response. Since 0.7 this + function also returns the function that was passed so that this + can be used as a decorator. + + .. versionadded:: 0.6 + """ + self._on_close.append(func) + return func + + def __repr__(self): + if self.is_sequence: + body_info = "%d bytes" % sum(map(len, self.iter_encoded())) + else: + body_info = "streamed" if self.is_streamed else "likely-streamed" + return "<%s %s [%s]>" % (self.__class__.__name__, body_info, self.status) + + @classmethod + def force_type(cls, response, environ=None): + """Enforce that the WSGI response is a response object of the current + type. Werkzeug will use the :class:`BaseResponse` internally in many + situations like the exceptions. If you call :meth:`get_response` on an + exception you will get back a regular :class:`BaseResponse` object, even + if you are using a custom subclass. + + This method can enforce a given response type, and it will also + convert arbitrary WSGI callables into response objects if an environ + is provided:: + + # convert a Werkzeug response object into an instance of the + # MyResponseClass subclass. + response = MyResponseClass.force_type(response) + + # convert any WSGI application into a response object + response = MyResponseClass.force_type(response, environ) + + This is especially useful if you want to post-process responses in + the main dispatcher and use functionality provided by your subclass. + + Keep in mind that this will modify response objects in place if + possible! + + :param response: a response object or wsgi application. + :param environ: a WSGI environment object. + :return: a response object. + """ + if not isinstance(response, BaseResponse): + if environ is None: + raise TypeError( + "cannot convert WSGI application into response" + " objects without an environ" + ) + response = BaseResponse(*_run_wsgi_app(response, environ)) + response.__class__ = cls + return response + + @classmethod + def from_app(cls, app, environ, buffered=False): + """Create a new response object from an application output. This + works best if you pass it an application that returns a generator all + the time. Sometimes applications may use the `write()` callable + returned by the `start_response` function. This tries to resolve such + edge cases automatically. But if you don't get the expected output + you should set `buffered` to `True` which enforces buffering. + + :param app: the WSGI application to execute. + :param environ: the WSGI environment to execute against. + :param buffered: set to `True` to enforce buffering. + :return: a response object. + """ + return cls(*_run_wsgi_app(app, environ, buffered)) + + def _get_status_code(self): + return self._status_code + + def _set_status_code(self, code): + self._status_code = code + try: + self._status = "%d %s" % (code, HTTP_STATUS_CODES[code].upper()) + except KeyError: + self._status = "%d UNKNOWN" % code + + status_code = property( + _get_status_code, _set_status_code, doc="The HTTP Status code as number" + ) + del _get_status_code, _set_status_code + + def _get_status(self): + return self._status + + def _set_status(self, value): + try: + self._status = to_native(value) + except AttributeError: + raise TypeError("Invalid status argument") + + try: + self._status_code = int(self._status.split(None, 1)[0]) + except ValueError: + self._status_code = 0 + self._status = "0 %s" % self._status + except IndexError: + raise ValueError("Empty status argument") + + status = property(_get_status, _set_status, doc="The HTTP Status code") + del _get_status, _set_status + + def get_data(self, as_text=False): + """The string representation of the request body. Whenever you call + this property the request iterable is encoded and flattened. This + can lead to unwanted behavior if you stream big data. + + This behavior can be disabled by setting + :attr:`implicit_sequence_conversion` to `False`. + + If `as_text` is set to `True` the return value will be a decoded + unicode string. + + .. versionadded:: 0.9 + """ + self._ensure_sequence() + rv = b"".join(self.iter_encoded()) + if as_text: + rv = rv.decode(self.charset) + return rv + + def set_data(self, value): + """Sets a new string as response. The value set must either by a + unicode or bytestring. If a unicode string is set it's encoded + automatically to the charset of the response (utf-8 by default). + + .. versionadded:: 0.9 + """ + # if an unicode string is set, it's encoded directly so that we + # can set the content length + if isinstance(value, text_type): + value = value.encode(self.charset) + else: + value = bytes(value) + self.response = [value] + if self.automatically_set_content_length: + self.headers["Content-Length"] = str(len(value)) + + data = property( + get_data, + set_data, + doc="A descriptor that calls :meth:`get_data` and :meth:`set_data`.", + ) + + def calculate_content_length(self): + """Returns the content length if available or `None` otherwise.""" + try: + self._ensure_sequence() + except RuntimeError: + return None + return sum(len(x) for x in self.iter_encoded()) + + def _ensure_sequence(self, mutable=False): + """This method can be called by methods that need a sequence. If + `mutable` is true, it will also ensure that the response sequence + is a standard Python list. + + .. versionadded:: 0.6 + """ + if self.is_sequence: + # if we need a mutable object, we ensure it's a list. + if mutable and not isinstance(self.response, list): + self.response = list(self.response) + return + if self.direct_passthrough: + raise RuntimeError( + "Attempted implicit sequence conversion but the" + " response object is in direct passthrough mode." + ) + if not self.implicit_sequence_conversion: + raise RuntimeError( + "The response object required the iterable to be a" + " sequence, but the implicit conversion was disabled." + " Call make_sequence() yourself." + ) + self.make_sequence() + + def make_sequence(self): + """Converts the response iterator in a list. By default this happens + automatically if required. If `implicit_sequence_conversion` is + disabled, this method is not automatically called and some properties + might raise exceptions. This also encodes all the items. + + .. versionadded:: 0.6 + """ + if not self.is_sequence: + # if we consume an iterable we have to ensure that the close + # method of the iterable is called if available when we tear + # down the response + close = getattr(self.response, "close", None) + self.response = list(self.iter_encoded()) + if close is not None: + self.call_on_close(close) + + def iter_encoded(self): + """Iter the response encoded with the encoding of the response. + If the response object is invoked as WSGI application the return + value of this method is used as application iterator unless + :attr:`direct_passthrough` was activated. + """ + if __debug__: + _warn_if_string(self.response) + # Encode in a separate function so that self.response is fetched + # early. This allows us to wrap the response with the return + # value from get_app_iter or iter_encoded. + return _iter_encoded(self.response, self.charset) + + def set_cookie( + self, + key, + value="", + max_age=None, + expires=None, + path="/", + domain=None, + secure=False, + httponly=False, + samesite=None, + ): + """Sets a cookie. The parameters are the same as in the cookie `Morsel` + object in the Python standard library but it accepts unicode data, too. + + A warning is raised if the size of the cookie header exceeds + :attr:`max_cookie_size`, but the header will still be set. + + :param key: the key (name) of the cookie to be set. + :param value: the value of the cookie. + :param max_age: should be a number of seconds, or `None` (default) if + the cookie should last only as long as the client's + browser session. + :param expires: should be a `datetime` object or UNIX timestamp. + :param path: limits the cookie to a given path, per default it will + span the whole domain. + :param domain: if you want to set a cross-domain cookie. For example, + ``domain=".example.com"`` will set a cookie that is + readable by the domain ``www.example.com``, + ``foo.example.com`` etc. Otherwise, a cookie will only + be readable by the domain that set it. + :param secure: If `True`, the cookie will only be available via HTTPS + :param httponly: disallow JavaScript to access the cookie. This is an + extension to the cookie standard and probably not + supported by all browsers. + :param samesite: Limits the scope of the cookie such that it will only + be attached to requests if those requests are + "same-site". + """ + self.headers.add( + "Set-Cookie", + dump_cookie( + key, + value=value, + max_age=max_age, + expires=expires, + path=path, + domain=domain, + secure=secure, + httponly=httponly, + charset=self.charset, + max_size=self.max_cookie_size, + samesite=samesite, + ), + ) + + def delete_cookie(self, key, path="/", domain=None): + """Delete a cookie. Fails silently if key doesn't exist. + + :param key: the key (name) of the cookie to be deleted. + :param path: if the cookie that should be deleted was limited to a + path, the path has to be defined here. + :param domain: if the cookie that should be deleted was limited to a + domain, that domain has to be defined here. + """ + self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain) + + @property + def is_streamed(self): + """If the response is streamed (the response is not an iterable with + a length information) this property is `True`. In this case streamed + means that there is no information about the number of iterations. + This is usually `True` if a generator is passed to the response object. + + This is useful for checking before applying some sort of post + filtering that should not take place for streamed responses. + """ + try: + len(self.response) + except (TypeError, AttributeError): + return True + return False + + @property + def is_sequence(self): + """If the iterator is buffered, this property will be `True`. A + response object will consider an iterator to be buffered if the + response attribute is a list or tuple. + + .. versionadded:: 0.6 + """ + return isinstance(self.response, (tuple, list)) + + def close(self): + """Close the wrapped response if possible. You can also use the object + in a with statement which will automatically close it. + + .. versionadded:: 0.9 + Can now be used in a with statement. + """ + if hasattr(self.response, "close"): + self.response.close() + for func in self._on_close: + func() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + self.close() + + def freeze(self): + """Call this method if you want to make your response object ready for + being pickled. This buffers the generator if there is one. It will + also set the `Content-Length` header to the length of the body. + + .. versionchanged:: 0.6 + The `Content-Length` header is now set. + """ + # we explicitly set the length to a list of the *encoded* response + # iterator. Even if the implicit sequence conversion is disabled. + self.response = list(self.iter_encoded()) + self.headers["Content-Length"] = str(sum(map(len, self.response))) + + def get_wsgi_headers(self, environ): + """This is automatically called right before the response is started + and returns headers modified for the given environment. It returns a + copy of the headers from the response with some modifications applied + if necessary. + + For example the location header (if present) is joined with the root + URL of the environment. Also the content length is automatically set + to zero here for certain status codes. + + .. versionchanged:: 0.6 + Previously that function was called `fix_headers` and modified + the response object in place. Also since 0.6, IRIs in location + and content-location headers are handled properly. + + Also starting with 0.6, Werkzeug will attempt to set the content + length if it is able to figure it out on its own. This is the + case if all the strings in the response iterable are already + encoded and the iterable is buffered. + + :param environ: the WSGI environment of the request. + :return: returns a new :class:`~werkzeug.datastructures.Headers` + object. + """ + headers = Headers(self.headers) + location = None + content_location = None + content_length = None + status = self.status_code + + # iterate over the headers to find all values in one go. Because + # get_wsgi_headers is used each response that gives us a tiny + # speedup. + for key, value in headers: + ikey = key.lower() + if ikey == u"location": + location = value + elif ikey == u"content-location": + content_location = value + elif ikey == u"content-length": + content_length = value + + # make sure the location header is an absolute URL + if location is not None: + old_location = location + if isinstance(location, text_type): + # Safe conversion is necessary here as we might redirect + # to a broken URI scheme (for instance itms-services). + location = iri_to_uri(location, safe_conversion=True) + + if self.autocorrect_location_header: + current_url = get_current_url(environ, strip_querystring=True) + if isinstance(current_url, text_type): + current_url = iri_to_uri(current_url) + location = url_join(current_url, location) + if location != old_location: + headers["Location"] = location + + # make sure the content location is a URL + if content_location is not None and isinstance(content_location, text_type): + headers["Content-Location"] = iri_to_uri(content_location) + + if 100 <= status < 200 or status == 204: + # Per section 3.3.2 of RFC 7230, "a server MUST NOT send a + # Content-Length header field in any response with a status + # code of 1xx (Informational) or 204 (No Content)." + headers.remove("Content-Length") + elif status == 304: + remove_entity_headers(headers) + + # if we can determine the content length automatically, we + # should try to do that. But only if this does not involve + # flattening the iterator or encoding of unicode strings in + # the response. We however should not do that if we have a 304 + # response. + if ( + self.automatically_set_content_length + and self.is_sequence + and content_length is None + and status not in (204, 304) + and not (100 <= status < 200) + ): + try: + content_length = sum(len(to_bytes(x, "ascii")) for x in self.response) + except UnicodeError: + # aha, something non-bytestringy in there, too bad, we + # can't safely figure out the length of the response. + pass + else: + headers["Content-Length"] = str(content_length) + + return headers + + def get_app_iter(self, environ): + """Returns the application iterator for the given environ. Depending + on the request method and the current status code the return value + might be an empty response rather than the one from the response. + + If the request method is `HEAD` or the status code is in a range + where the HTTP specification requires an empty response, an empty + iterable is returned. + + .. versionadded:: 0.6 + + :param environ: the WSGI environment of the request. + :return: a response iterable. + """ + status = self.status_code + if ( + environ["REQUEST_METHOD"] == "HEAD" + or 100 <= status < 200 + or status in (204, 304) + ): + iterable = () + elif self.direct_passthrough: + if __debug__: + _warn_if_string(self.response) + return self.response + else: + iterable = self.iter_encoded() + return ClosingIterator(iterable, self.close) + + def get_wsgi_response(self, environ): + """Returns the final WSGI response as tuple. The first item in + the tuple is the application iterator, the second the status and + the third the list of headers. The response returned is created + specially for the given environment. For example if the request + method in the WSGI environment is ``'HEAD'`` the response will + be empty and only the headers and status code will be present. + + .. versionadded:: 0.6 + + :param environ: the WSGI environment of the request. + :return: an ``(app_iter, status, headers)`` tuple. + """ + headers = self.get_wsgi_headers(environ) + app_iter = self.get_app_iter(environ) + return app_iter, self.status, headers.to_wsgi_list() + + def __call__(self, environ, start_response): + """Process this response as WSGI application. + + :param environ: the WSGI environment. + :param start_response: the response callable provided by the WSGI + server. + :return: an application iterator + """ + app_iter, status, headers = self.get_wsgi_response(environ) + start_response(status, headers) + return app_iter diff --git a/venv/lib/python2.7/site-packages/werkzeug/wrappers/common_descriptors.py b/venv/lib/python2.7/site-packages/werkzeug/wrappers/common_descriptors.py new file mode 100644 index 00000000..e4107ee0 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/wrappers/common_descriptors.py @@ -0,0 +1,322 @@ +from datetime import datetime +from datetime import timedelta + +from .._compat import string_types +from ..datastructures import CallbackDict +from ..http import dump_age +from ..http import dump_header +from ..http import dump_options_header +from ..http import http_date +from ..http import parse_age +from ..http import parse_date +from ..http import parse_options_header +from ..http import parse_set_header +from ..utils import cached_property +from ..utils import environ_property +from ..utils import get_content_type +from ..utils import header_property +from ..wsgi import get_content_length + + +class CommonRequestDescriptorsMixin(object): + """A mixin for :class:`BaseRequest` subclasses. Request objects that + mix this class in will automatically get descriptors for a couple of + HTTP headers with automatic type conversion. + + .. versionadded:: 0.5 + """ + + content_type = environ_property( + "CONTENT_TYPE", + doc="""The Content-Type entity-header field indicates the media + type of the entity-body sent to the recipient or, in the case of + the HEAD method, the media type that would have been sent had + the request been a GET.""", + ) + + @cached_property + def content_length(self): + """The Content-Length entity-header field indicates the size of the + entity-body in bytes or, in the case of the HEAD method, the size of + the entity-body that would have been sent had the request been a + GET. + """ + return get_content_length(self.environ) + + content_encoding = environ_property( + "HTTP_CONTENT_ENCODING", + doc="""The Content-Encoding entity-header field is used as a + modifier to the media-type. When present, its value indicates + what additional content codings have been applied to the + entity-body, and thus what decoding mechanisms must be applied + in order to obtain the media-type referenced by the Content-Type + header field. + + .. versionadded:: 0.9""", + ) + content_md5 = environ_property( + "HTTP_CONTENT_MD5", + doc="""The Content-MD5 entity-header field, as defined in + RFC 1864, is an MD5 digest of the entity-body for the purpose of + providing an end-to-end message integrity check (MIC) of the + entity-body. (Note: a MIC is good for detecting accidental + modification of the entity-body in transit, but is not proof + against malicious attacks.) + + .. versionadded:: 0.9""", + ) + referrer = environ_property( + "HTTP_REFERER", + doc="""The Referer[sic] request-header field allows the client + to specify, for the server's benefit, the address (URI) of the + resource from which the Request-URI was obtained (the + "referrer", although the header field is misspelled).""", + ) + date = environ_property( + "HTTP_DATE", + None, + parse_date, + doc="""The Date general-header field represents the date and + time at which the message was originated, having the same + semantics as orig-date in RFC 822.""", + ) + max_forwards = environ_property( + "HTTP_MAX_FORWARDS", + None, + int, + doc="""The Max-Forwards request-header field provides a + mechanism with the TRACE and OPTIONS methods to limit the number + of proxies or gateways that can forward the request to the next + inbound server.""", + ) + + def _parse_content_type(self): + if not hasattr(self, "_parsed_content_type"): + self._parsed_content_type = parse_options_header( + self.environ.get("CONTENT_TYPE", "") + ) + + @property + def mimetype(self): + """Like :attr:`content_type`, but without parameters (eg, without + charset, type etc.) and always lowercase. For example if the content + type is ``text/HTML; charset=utf-8`` the mimetype would be + ``'text/html'``. + """ + self._parse_content_type() + return self._parsed_content_type[0].lower() + + @property + def mimetype_params(self): + """The mimetype parameters as dict. For example if the content + type is ``text/html; charset=utf-8`` the params would be + ``{'charset': 'utf-8'}``. + """ + self._parse_content_type() + return self._parsed_content_type[1] + + @cached_property + def pragma(self): + """The Pragma general-header field is used to include + implementation-specific directives that might apply to any recipient + along the request/response chain. All pragma directives specify + optional behavior from the viewpoint of the protocol; however, some + systems MAY require that behavior be consistent with the directives. + """ + return parse_set_header(self.environ.get("HTTP_PRAGMA", "")) + + +class CommonResponseDescriptorsMixin(object): + """A mixin for :class:`BaseResponse` subclasses. Response objects that + mix this class in will automatically get descriptors for a couple of + HTTP headers with automatic type conversion. + """ + + @property + def mimetype(self): + """The mimetype (content type without charset etc.)""" + ct = self.headers.get("content-type") + if ct: + return ct.split(";")[0].strip() + + @mimetype.setter + def mimetype(self, value): + self.headers["Content-Type"] = get_content_type(value, self.charset) + + @property + def mimetype_params(self): + """The mimetype parameters as dict. For example if the + content type is ``text/html; charset=utf-8`` the params would be + ``{'charset': 'utf-8'}``. + + .. versionadded:: 0.5 + """ + + def on_update(d): + self.headers["Content-Type"] = dump_options_header(self.mimetype, d) + + d = parse_options_header(self.headers.get("content-type", ""))[1] + return CallbackDict(d, on_update) + + location = header_property( + "Location", + doc="""The Location response-header field is used to redirect + the recipient to a location other than the Request-URI for + completion of the request or identification of a new + resource.""", + ) + age = header_property( + "Age", + None, + parse_age, + dump_age, + doc="""The Age response-header field conveys the sender's + estimate of the amount of time since the response (or its + revalidation) was generated at the origin server. + + Age values are non-negative decimal integers, representing time + in seconds.""", + ) + content_type = header_property( + "Content-Type", + doc="""The Content-Type entity-header field indicates the media + type of the entity-body sent to the recipient or, in the case of + the HEAD method, the media type that would have been sent had + the request been a GET.""", + ) + content_length = header_property( + "Content-Length", + None, + int, + str, + doc="""The Content-Length entity-header field indicates the size + of the entity-body, in decimal number of OCTETs, sent to the + recipient or, in the case of the HEAD method, the size of the + entity-body that would have been sent had the request been a + GET.""", + ) + content_location = header_property( + "Content-Location", + doc="""The Content-Location entity-header field MAY be used to + supply the resource location for the entity enclosed in the + message when that entity is accessible from a location separate + from the requested resource's URI.""", + ) + content_encoding = header_property( + "Content-Encoding", + doc="""The Content-Encoding entity-header field is used as a + modifier to the media-type. When present, its value indicates + what additional content codings have been applied to the + entity-body, and thus what decoding mechanisms must be applied + in order to obtain the media-type referenced by the Content-Type + header field.""", + ) + content_md5 = header_property( + "Content-MD5", + doc="""The Content-MD5 entity-header field, as defined in + RFC 1864, is an MD5 digest of the entity-body for the purpose of + providing an end-to-end message integrity check (MIC) of the + entity-body. (Note: a MIC is good for detecting accidental + modification of the entity-body in transit, but is not proof + against malicious attacks.)""", + ) + date = header_property( + "Date", + None, + parse_date, + http_date, + doc="""The Date general-header field represents the date and + time at which the message was originated, having the same + semantics as orig-date in RFC 822.""", + ) + expires = header_property( + "Expires", + None, + parse_date, + http_date, + doc="""The Expires entity-header field gives the date/time after + which the response is considered stale. A stale cache entry may + not normally be returned by a cache.""", + ) + last_modified = header_property( + "Last-Modified", + None, + parse_date, + http_date, + doc="""The Last-Modified entity-header field indicates the date + and time at which the origin server believes the variant was + last modified.""", + ) + + @property + def retry_after(self): + """The Retry-After response-header field can be used with a + 503 (Service Unavailable) response to indicate how long the + service is expected to be unavailable to the requesting client. + + Time in seconds until expiration or date. + """ + value = self.headers.get("retry-after") + if value is None: + return + elif value.isdigit(): + return datetime.utcnow() + timedelta(seconds=int(value)) + return parse_date(value) + + @retry_after.setter + def retry_after(self, value): + if value is None: + if "retry-after" in self.headers: + del self.headers["retry-after"] + return + elif isinstance(value, datetime): + value = http_date(value) + else: + value = str(value) + self.headers["Retry-After"] = value + + def _set_property(name, doc=None): # noqa: B902 + def fget(self): + def on_update(header_set): + if not header_set and name in self.headers: + del self.headers[name] + elif header_set: + self.headers[name] = header_set.to_header() + + return parse_set_header(self.headers.get(name), on_update) + + def fset(self, value): + if not value: + del self.headers[name] + elif isinstance(value, string_types): + self.headers[name] = value + else: + self.headers[name] = dump_header(value) + + return property(fget, fset, doc=doc) + + vary = _set_property( + "Vary", + doc="""The Vary field value indicates the set of request-header + fields that fully determines, while the response is fresh, + whether a cache is permitted to use the response to reply to a + subsequent request without revalidation.""", + ) + content_language = _set_property( + "Content-Language", + doc="""The Content-Language entity-header field describes the + natural language(s) of the intended audience for the enclosed + entity. Note that this might not be equivalent to all the + languages used within the entity-body.""", + ) + allow = _set_property( + "Allow", + doc="""The Allow entity-header field lists the set of methods + supported by the resource identified by the Request-URI. The + purpose of this field is strictly to inform the recipient of + valid methods associated with the resource. An Allow header + field MUST be present in a 405 (Method Not Allowed) + response.""", + ) + + del _set_property diff --git a/venv/lib/python2.7/site-packages/werkzeug/wrappers/etag.py b/venv/lib/python2.7/site-packages/werkzeug/wrappers/etag.py new file mode 100644 index 00000000..0733506f --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/wrappers/etag.py @@ -0,0 +1,304 @@ +from .._compat import string_types +from .._internal import _get_environ +from ..datastructures import ContentRange +from ..datastructures import RequestCacheControl +from ..datastructures import ResponseCacheControl +from ..http import generate_etag +from ..http import http_date +from ..http import is_resource_modified +from ..http import parse_cache_control_header +from ..http import parse_content_range_header +from ..http import parse_date +from ..http import parse_etags +from ..http import parse_if_range_header +from ..http import parse_range_header +from ..http import quote_etag +from ..http import unquote_etag +from ..utils import cached_property +from ..utils import header_property +from ..wrappers.base_response import _clean_accept_ranges +from ..wsgi import _RangeWrapper + + +class ETagRequestMixin(object): + """Add entity tag and cache descriptors to a request object or object with + a WSGI environment available as :attr:`~BaseRequest.environ`. This not + only provides access to etags but also to the cache control header. + """ + + @cached_property + def cache_control(self): + """A :class:`~werkzeug.datastructures.RequestCacheControl` object + for the incoming cache control headers. + """ + cache_control = self.environ.get("HTTP_CACHE_CONTROL") + return parse_cache_control_header(cache_control, None, RequestCacheControl) + + @cached_property + def if_match(self): + """An object containing all the etags in the `If-Match` header. + + :rtype: :class:`~werkzeug.datastructures.ETags` + """ + return parse_etags(self.environ.get("HTTP_IF_MATCH")) + + @cached_property + def if_none_match(self): + """An object containing all the etags in the `If-None-Match` header. + + :rtype: :class:`~werkzeug.datastructures.ETags` + """ + return parse_etags(self.environ.get("HTTP_IF_NONE_MATCH")) + + @cached_property + def if_modified_since(self): + """The parsed `If-Modified-Since` header as datetime object.""" + return parse_date(self.environ.get("HTTP_IF_MODIFIED_SINCE")) + + @cached_property + def if_unmodified_since(self): + """The parsed `If-Unmodified-Since` header as datetime object.""" + return parse_date(self.environ.get("HTTP_IF_UNMODIFIED_SINCE")) + + @cached_property + def if_range(self): + """The parsed `If-Range` header. + + .. versionadded:: 0.7 + + :rtype: :class:`~werkzeug.datastructures.IfRange` + """ + return parse_if_range_header(self.environ.get("HTTP_IF_RANGE")) + + @cached_property + def range(self): + """The parsed `Range` header. + + .. versionadded:: 0.7 + + :rtype: :class:`~werkzeug.datastructures.Range` + """ + return parse_range_header(self.environ.get("HTTP_RANGE")) + + +class ETagResponseMixin(object): + """Adds extra functionality to a response object for etag and cache + handling. This mixin requires an object with at least a `headers` + object that implements a dict like interface similar to + :class:`~werkzeug.datastructures.Headers`. + + If you want the :meth:`freeze` method to automatically add an etag, you + have to mixin this method before the response base class. The default + response class does not do that. + """ + + @property + def cache_control(self): + """The Cache-Control general-header field is used to specify + directives that MUST be obeyed by all caching mechanisms along the + request/response chain. + """ + + def on_update(cache_control): + if not cache_control and "cache-control" in self.headers: + del self.headers["cache-control"] + elif cache_control: + self.headers["Cache-Control"] = cache_control.to_header() + + return parse_cache_control_header( + self.headers.get("cache-control"), on_update, ResponseCacheControl + ) + + def _wrap_response(self, start, length): + """Wrap existing Response in case of Range Request context.""" + if self.status_code == 206: + self.response = _RangeWrapper(self.response, start, length) + + def _is_range_request_processable(self, environ): + """Return ``True`` if `Range` header is present and if underlying + resource is considered unchanged when compared with `If-Range` header. + """ + return ( + "HTTP_IF_RANGE" not in environ + or not is_resource_modified( + environ, + self.headers.get("etag"), + None, + self.headers.get("last-modified"), + ignore_if_range=False, + ) + ) and "HTTP_RANGE" in environ + + def _process_range_request(self, environ, complete_length=None, accept_ranges=None): + """Handle Range Request related headers (RFC7233). If `Accept-Ranges` + header is valid, and Range Request is processable, we set the headers + as described by the RFC, and wrap the underlying response in a + RangeWrapper. + + Returns ``True`` if Range Request can be fulfilled, ``False`` otherwise. + + :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable` + if `Range` header could not be parsed or satisfied. + """ + from ..exceptions import RequestedRangeNotSatisfiable + + if accept_ranges is None: + return False + self.headers["Accept-Ranges"] = accept_ranges + if not self._is_range_request_processable(environ) or complete_length is None: + return False + parsed_range = parse_range_header(environ.get("HTTP_RANGE")) + if parsed_range is None: + raise RequestedRangeNotSatisfiable(complete_length) + range_tuple = parsed_range.range_for_length(complete_length) + content_range_header = parsed_range.to_content_range_header(complete_length) + if range_tuple is None or content_range_header is None: + raise RequestedRangeNotSatisfiable(complete_length) + content_length = range_tuple[1] - range_tuple[0] + # Be sure not to send 206 response + # if requested range is the full content. + if content_length != complete_length: + self.headers["Content-Length"] = content_length + self.content_range = content_range_header + self.status_code = 206 + self._wrap_response(range_tuple[0], content_length) + return True + return False + + def make_conditional( + self, request_or_environ, accept_ranges=False, complete_length=None + ): + """Make the response conditional to the request. This method works + best if an etag was defined for the response already. The `add_etag` + method can be used to do that. If called without etag just the date + header is set. + + This does nothing if the request method in the request or environ is + anything but GET or HEAD. + + For optimal performance when handling range requests, it's recommended + that your response data object implements `seekable`, `seek` and `tell` + methods as described by :py:class:`io.IOBase`. Objects returned by + :meth:`~werkzeug.wsgi.wrap_file` automatically implement those methods. + + It does not remove the body of the response because that's something + the :meth:`__call__` function does for us automatically. + + Returns self so that you can do ``return resp.make_conditional(req)`` + but modifies the object in-place. + + :param request_or_environ: a request object or WSGI environment to be + used to make the response conditional + against. + :param accept_ranges: This parameter dictates the value of + `Accept-Ranges` header. If ``False`` (default), + the header is not set. If ``True``, it will be set + to ``"bytes"``. If ``None``, it will be set to + ``"none"``. If it's a string, it will use this + value. + :param complete_length: Will be used only in valid Range Requests. + It will set `Content-Range` complete length + value and compute `Content-Length` real value. + This parameter is mandatory for successful + Range Requests completion. + :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable` + if `Range` header could not be parsed or satisfied. + """ + environ = _get_environ(request_or_environ) + if environ["REQUEST_METHOD"] in ("GET", "HEAD"): + # if the date is not in the headers, add it now. We however + # will not override an already existing header. Unfortunately + # this header will be overriden by many WSGI servers including + # wsgiref. + if "date" not in self.headers: + self.headers["Date"] = http_date() + accept_ranges = _clean_accept_ranges(accept_ranges) + is206 = self._process_range_request(environ, complete_length, accept_ranges) + if not is206 and not is_resource_modified( + environ, + self.headers.get("etag"), + None, + self.headers.get("last-modified"), + ): + if parse_etags(environ.get("HTTP_IF_MATCH")): + self.status_code = 412 + else: + self.status_code = 304 + if ( + self.automatically_set_content_length + and "content-length" not in self.headers + ): + length = self.calculate_content_length() + if length is not None: + self.headers["Content-Length"] = length + return self + + def add_etag(self, overwrite=False, weak=False): + """Add an etag for the current response if there is none yet.""" + if overwrite or "etag" not in self.headers: + self.set_etag(generate_etag(self.get_data()), weak) + + def set_etag(self, etag, weak=False): + """Set the etag, and override the old one if there was one.""" + self.headers["ETag"] = quote_etag(etag, weak) + + def get_etag(self): + """Return a tuple in the form ``(etag, is_weak)``. If there is no + ETag the return value is ``(None, None)``. + """ + return unquote_etag(self.headers.get("ETag")) + + def freeze(self, no_etag=False): + """Call this method if you want to make your response object ready for + pickeling. This buffers the generator if there is one. This also + sets the etag unless `no_etag` is set to `True`. + """ + if not no_etag: + self.add_etag() + super(ETagResponseMixin, self).freeze() + + accept_ranges = header_property( + "Accept-Ranges", + doc="""The `Accept-Ranges` header. Even though the name would + indicate that multiple values are supported, it must be one + string token only. + + The values ``'bytes'`` and ``'none'`` are common. + + .. versionadded:: 0.7""", + ) + + def _get_content_range(self): + def on_update(rng): + if not rng: + del self.headers["content-range"] + else: + self.headers["Content-Range"] = rng.to_header() + + rv = parse_content_range_header(self.headers.get("content-range"), on_update) + # always provide a content range object to make the descriptor + # more user friendly. It provides an unset() method that can be + # used to remove the header quickly. + if rv is None: + rv = ContentRange(None, None, None, on_update=on_update) + return rv + + def _set_content_range(self, value): + if not value: + del self.headers["content-range"] + elif isinstance(value, string_types): + self.headers["Content-Range"] = value + else: + self.headers["Content-Range"] = value.to_header() + + content_range = property( + _get_content_range, + _set_content_range, + doc="""The ``Content-Range`` header as + :class:`~werkzeug.datastructures.ContentRange` object. Even if + the header is not set it wil provide such an object for easier + manipulation. + + .. versionadded:: 0.7""", + ) + del _get_content_range, _set_content_range diff --git a/venv/lib/python2.7/site-packages/werkzeug/wrappers/json.py b/venv/lib/python2.7/site-packages/werkzeug/wrappers/json.py new file mode 100644 index 00000000..6d5dc33d --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/wrappers/json.py @@ -0,0 +1,145 @@ +from __future__ import absolute_import + +import datetime +import uuid + +from .._compat import text_type +from ..exceptions import BadRequest +from ..utils import detect_utf_encoding + +try: + import simplejson as _json +except ImportError: + import json as _json + + +class _JSONModule(object): + @staticmethod + def _default(o): + if isinstance(o, datetime.date): + return o.isoformat() + + if isinstance(o, uuid.UUID): + return str(o) + + if hasattr(o, "__html__"): + return text_type(o.__html__()) + + raise TypeError() + + @classmethod + def dumps(cls, obj, **kw): + kw.setdefault("separators", (",", ":")) + kw.setdefault("default", cls._default) + kw.setdefault("sort_keys", True) + return _json.dumps(obj, **kw) + + @staticmethod + def loads(s, **kw): + if isinstance(s, bytes): + # Needed for Python < 3.6 + encoding = detect_utf_encoding(s) + s = s.decode(encoding) + + return _json.loads(s, **kw) + + +class JSONMixin(object): + """Mixin to parse :attr:`data` as JSON. Can be mixed in for both + :class:`~werkzeug.wrappers.Request` and + :class:`~werkzeug.wrappers.Response` classes. + + If `simplejson`_ is installed it is preferred over Python's built-in + :mod:`json` module. + + .. _simplejson: https://simplejson.readthedocs.io/en/latest/ + """ + + #: A module or other object that has ``dumps`` and ``loads`` + #: functions that match the API of the built-in :mod:`json` module. + json_module = _JSONModule + + @property + def json(self): + """The parsed JSON data if :attr:`mimetype` indicates JSON + (:mimetype:`application/json`, see :meth:`is_json`). + + Calls :meth:`get_json` with default arguments. + """ + return self.get_json() + + @property + def is_json(self): + """Check if the mimetype indicates JSON data, either + :mimetype:`application/json` or :mimetype:`application/*+json`. + """ + mt = self.mimetype + return ( + mt == "application/json" + or mt.startswith("application/") + and mt.endswith("+json") + ) + + def _get_data_for_json(self, cache): + try: + return self.get_data(cache=cache) + except TypeError: + # Response doesn't have cache param. + return self.get_data() + + # Cached values for ``(silent=False, silent=True)``. Initialized + # with sentinel values. + _cached_json = (Ellipsis, Ellipsis) + + def get_json(self, force=False, silent=False, cache=True): + """Parse :attr:`data` as JSON. + + If the mimetype does not indicate JSON + (:mimetype:`application/json`, see :meth:`is_json`), this + returns ``None``. + + If parsing fails, :meth:`on_json_loading_failed` is called and + its return value is used as the return value. + + :param force: Ignore the mimetype and always try to parse JSON. + :param silent: Silence parsing errors and return ``None`` + instead. + :param cache: Store the parsed JSON to return for subsequent + calls. + """ + if cache and self._cached_json[silent] is not Ellipsis: + return self._cached_json[silent] + + if not (force or self.is_json): + return None + + data = self._get_data_for_json(cache=cache) + + try: + rv = self.json_module.loads(data) + except ValueError as e: + if silent: + rv = None + + if cache: + normal_rv, _ = self._cached_json + self._cached_json = (normal_rv, rv) + else: + rv = self.on_json_loading_failed(e) + + if cache: + _, silent_rv = self._cached_json + self._cached_json = (rv, silent_rv) + else: + if cache: + self._cached_json = (rv, rv) + + return rv + + def on_json_loading_failed(self, e): + """Called if :meth:`get_json` parsing fails and isn't silenced. + If this method returns a value, it is used as the return value + for :meth:`get_json`. The default implementation raises + :exc:`~werkzeug.exceptions.BadRequest`. + """ + raise BadRequest("Failed to decode JSON object: {0}".format(e)) diff --git a/venv/lib/python2.7/site-packages/werkzeug/wrappers/request.py b/venv/lib/python2.7/site-packages/werkzeug/wrappers/request.py new file mode 100644 index 00000000..d1c71b64 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/wrappers/request.py @@ -0,0 +1,44 @@ +from .accept import AcceptMixin +from .auth import AuthorizationMixin +from .base_request import BaseRequest +from .common_descriptors import CommonRequestDescriptorsMixin +from .etag import ETagRequestMixin +from .user_agent import UserAgentMixin + + +class Request( + BaseRequest, + AcceptMixin, + ETagRequestMixin, + UserAgentMixin, + AuthorizationMixin, + CommonRequestDescriptorsMixin, +): + """Full featured request object implementing the following mixins: + + - :class:`AcceptMixin` for accept header parsing + - :class:`ETagRequestMixin` for etag and cache control handling + - :class:`UserAgentMixin` for user agent introspection + - :class:`AuthorizationMixin` for http auth handling + - :class:`CommonRequestDescriptorsMixin` for common headers + """ + + +class StreamOnlyMixin(object): + """If mixed in before the request object this will change the bahavior + of it to disable handling of form parsing. This disables the + :attr:`files`, :attr:`form` attributes and will just provide a + :attr:`stream` attribute that however is always available. + + .. versionadded:: 0.9 + """ + + disable_data_descriptor = True + want_form_data_parsed = False + + +class PlainRequest(StreamOnlyMixin, Request): + """A request object without special form parsing capabilities. + + .. versionadded:: 0.9 + """ diff --git a/venv/lib/python2.7/site-packages/werkzeug/wrappers/response.py b/venv/lib/python2.7/site-packages/werkzeug/wrappers/response.py new file mode 100644 index 00000000..cd86cacd --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/wrappers/response.py @@ -0,0 +1,78 @@ +from ..utils import cached_property +from .auth import WWWAuthenticateMixin +from .base_response import BaseResponse +from .common_descriptors import CommonResponseDescriptorsMixin +from .etag import ETagResponseMixin + + +class ResponseStream(object): + """A file descriptor like object used by the :class:`ResponseStreamMixin` to + represent the body of the stream. It directly pushes into the response + iterable of the response object. + """ + + mode = "wb+" + + def __init__(self, response): + self.response = response + self.closed = False + + def write(self, value): + if self.closed: + raise ValueError("I/O operation on closed file") + self.response._ensure_sequence(mutable=True) + self.response.response.append(value) + self.response.headers.pop("Content-Length", None) + return len(value) + + def writelines(self, seq): + for item in seq: + self.write(item) + + def close(self): + self.closed = True + + def flush(self): + if self.closed: + raise ValueError("I/O operation on closed file") + + def isatty(self): + if self.closed: + raise ValueError("I/O operation on closed file") + return False + + def tell(self): + self.response._ensure_sequence() + return sum(map(len, self.response.response)) + + @property + def encoding(self): + return self.response.charset + + +class ResponseStreamMixin(object): + """Mixin for :class:`BaseRequest` subclasses. Classes that inherit from + this mixin will automatically get a :attr:`stream` property that provides + a write-only interface to the response iterable. + """ + + @cached_property + def stream(self): + """The response iterable as write-only stream.""" + return ResponseStream(self) + + +class Response( + BaseResponse, + ETagResponseMixin, + ResponseStreamMixin, + CommonResponseDescriptorsMixin, + WWWAuthenticateMixin, +): + """Full featured response object implementing the following mixins: + + - :class:`ETagResponseMixin` for etag and cache control handling + - :class:`ResponseStreamMixin` to add support for the `stream` property + - :class:`CommonResponseDescriptorsMixin` for various HTTP descriptors + - :class:`WWWAuthenticateMixin` for HTTP authentication support + """ diff --git a/venv/lib/python2.7/site-packages/werkzeug/wrappers/user_agent.py b/venv/lib/python2.7/site-packages/werkzeug/wrappers/user_agent.py new file mode 100644 index 00000000..a32d8acd --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/wrappers/user_agent.py @@ -0,0 +1,14 @@ +from ..useragents import UserAgent +from ..utils import cached_property + + +class UserAgentMixin(object): + """Adds a `user_agent` attribute to the request object which + contains the parsed user agent of the browser that triggered the + request as a :class:`~werkzeug.useragents.UserAgent` object. + """ + + @cached_property + def user_agent(self): + """The current user agent.""" + return UserAgent(self.environ) diff --git a/venv/lib/python2.7/site-packages/werkzeug/wsgi.py b/venv/lib/python2.7/site-packages/werkzeug/wsgi.py new file mode 100644 index 00000000..74119554 --- /dev/null +++ b/venv/lib/python2.7/site-packages/werkzeug/wsgi.py @@ -0,0 +1,1013 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.wsgi + ~~~~~~~~~~~~~ + + This module implements WSGI related helpers. + + :copyright: 2007 Pallets + :license: BSD-3-Clause +""" +import io +import re +from functools import partial +from functools import update_wrapper +from itertools import chain + +from ._compat import BytesIO +from ._compat import implements_iterator +from ._compat import make_literal_wrapper +from ._compat import string_types +from ._compat import text_type +from ._compat import to_bytes +from ._compat import to_unicode +from ._compat import try_coerce_native +from ._compat import wsgi_get_bytes +from ._internal import _encode_idna +from .urls import uri_to_iri +from .urls import url_join +from .urls import url_parse +from .urls import url_quote + + +def responder(f): + """Marks a function as responder. Decorate a function with it and it + will automatically call the return value as WSGI application. + + Example:: + + @responder + def application(environ, start_response): + return Response('Hello World!') + """ + return update_wrapper(lambda *a: f(*a)(*a[-2:]), f) + + +def get_current_url( + environ, + root_only=False, + strip_querystring=False, + host_only=False, + trusted_hosts=None, +): + """A handy helper function that recreates the full URL as IRI for the + current request or parts of it. Here's an example: + + >>> from werkzeug.test import create_environ + >>> env = create_environ("/?param=foo", "http://localhost/script") + >>> get_current_url(env) + 'http://localhost/script/?param=foo' + >>> get_current_url(env, root_only=True) + 'http://localhost/script/' + >>> get_current_url(env, host_only=True) + 'http://localhost/' + >>> get_current_url(env, strip_querystring=True) + 'http://localhost/script/' + + This optionally it verifies that the host is in a list of trusted hosts. + If the host is not in there it will raise a + :exc:`~werkzeug.exceptions.SecurityError`. + + Note that the string returned might contain unicode characters as the + representation is an IRI not an URI. If you need an ASCII only + representation you can use the :func:`~werkzeug.urls.iri_to_uri` + function: + + >>> from werkzeug.urls import iri_to_uri + >>> iri_to_uri(get_current_url(env)) + 'http://localhost/script/?param=foo' + + :param environ: the WSGI environment to get the current URL from. + :param root_only: set `True` if you only want the root URL. + :param strip_querystring: set to `True` if you don't want the querystring. + :param host_only: set to `True` if the host URL should be returned. + :param trusted_hosts: a list of trusted hosts, see :func:`host_is_trusted` + for more information. + """ + tmp = [environ["wsgi.url_scheme"], "://", get_host(environ, trusted_hosts)] + cat = tmp.append + if host_only: + return uri_to_iri("".join(tmp) + "/") + cat(url_quote(wsgi_get_bytes(environ.get("SCRIPT_NAME", ""))).rstrip("/")) + cat("/") + if not root_only: + cat(url_quote(wsgi_get_bytes(environ.get("PATH_INFO", "")).lstrip(b"/"))) + if not strip_querystring: + qs = get_query_string(environ) + if qs: + cat("?" + qs) + return uri_to_iri("".join(tmp)) + + +def host_is_trusted(hostname, trusted_list): + """Checks if a host is trusted against a list. This also takes care + of port normalization. + + .. versionadded:: 0.9 + + :param hostname: the hostname to check + :param trusted_list: a list of hostnames to check against. If a + hostname starts with a dot it will match against + all subdomains as well. + """ + if not hostname: + return False + + if isinstance(trusted_list, string_types): + trusted_list = [trusted_list] + + def _normalize(hostname): + if ":" in hostname: + hostname = hostname.rsplit(":", 1)[0] + return _encode_idna(hostname) + + try: + hostname = _normalize(hostname) + except UnicodeError: + return False + for ref in trusted_list: + if ref.startswith("."): + ref = ref[1:] + suffix_match = True + else: + suffix_match = False + try: + ref = _normalize(ref) + except UnicodeError: + return False + if ref == hostname: + return True + if suffix_match and hostname.endswith(b"." + ref): + return True + return False + + +def get_host(environ, trusted_hosts=None): + """Return the host for the given WSGI environment. This first checks + the ``Host`` header. If it's not present, then ``SERVER_NAME`` and + ``SERVER_PORT`` are used. The host will only contain the port if it + is different than the standard port for the protocol. + + Optionally, verify that the host is trusted using + :func:`host_is_trusted` and raise a + :exc:`~werkzeug.exceptions.SecurityError` if it is not. + + :param environ: The WSGI environment to get the host from. + :param trusted_hosts: A list of trusted hosts. + :return: Host, with port if necessary. + :raise ~werkzeug.exceptions.SecurityError: If the host is not + trusted. + """ + if "HTTP_HOST" in environ: + rv = environ["HTTP_HOST"] + if environ["wsgi.url_scheme"] == "http" and rv.endswith(":80"): + rv = rv[:-3] + elif environ["wsgi.url_scheme"] == "https" and rv.endswith(":443"): + rv = rv[:-4] + else: + rv = environ["SERVER_NAME"] + if (environ["wsgi.url_scheme"], environ["SERVER_PORT"]) not in ( + ("https", "443"), + ("http", "80"), + ): + rv += ":" + environ["SERVER_PORT"] + if trusted_hosts is not None: + if not host_is_trusted(rv, trusted_hosts): + from .exceptions import SecurityError + + raise SecurityError('Host "%s" is not trusted' % rv) + return rv + + +def get_content_length(environ): + """Returns the content length from the WSGI environment as + integer. If it's not available or chunked transfer encoding is used, + ``None`` is returned. + + .. versionadded:: 0.9 + + :param environ: the WSGI environ to fetch the content length from. + """ + if environ.get("HTTP_TRANSFER_ENCODING", "") == "chunked": + return None + + content_length = environ.get("CONTENT_LENGTH") + if content_length is not None: + try: + return max(0, int(content_length)) + except (ValueError, TypeError): + pass + + +def get_input_stream(environ, safe_fallback=True): + """Returns the input stream from the WSGI environment and wraps it + in the most sensible way possible. The stream returned is not the + raw WSGI stream in most cases but one that is safe to read from + without taking into account the content length. + + If content length is not set, the stream will be empty for safety reasons. + If the WSGI server supports chunked or infinite streams, it should set + the ``wsgi.input_terminated`` value in the WSGI environ to indicate that. + + .. versionadded:: 0.9 + + :param environ: the WSGI environ to fetch the stream from. + :param safe_fallback: use an empty stream as a safe fallback when the + content length is not set. Disabling this allows infinite streams, + which can be a denial-of-service risk. + """ + stream = environ["wsgi.input"] + content_length = get_content_length(environ) + + # A wsgi extension that tells us if the input is terminated. In + # that case we return the stream unchanged as we know we can safely + # read it until the end. + if environ.get("wsgi.input_terminated"): + return stream + + # If the request doesn't specify a content length, returning the stream is + # potentially dangerous because it could be infinite, malicious or not. If + # safe_fallback is true, return an empty stream instead for safety. + if content_length is None: + return BytesIO() if safe_fallback else stream + + # Otherwise limit the stream to the content length + return LimitedStream(stream, content_length) + + +def get_query_string(environ): + """Returns the `QUERY_STRING` from the WSGI environment. This also takes + care about the WSGI decoding dance on Python 3 environments as a + native string. The string returned will be restricted to ASCII + characters. + + .. versionadded:: 0.9 + + :param environ: the WSGI environment object to get the query string from. + """ + qs = wsgi_get_bytes(environ.get("QUERY_STRING", "")) + # QUERY_STRING really should be ascii safe but some browsers + # will send us some unicode stuff (I am looking at you IE). + # In that case we want to urllib quote it badly. + return try_coerce_native(url_quote(qs, safe=":&%=+$!*'(),")) + + +def get_path_info(environ, charset="utf-8", errors="replace"): + """Returns the `PATH_INFO` from the WSGI environment and properly + decodes it. This also takes care about the WSGI decoding dance + on Python 3 environments. if the `charset` is set to `None` a + bytestring is returned. + + .. versionadded:: 0.9 + + :param environ: the WSGI environment object to get the path from. + :param charset: the charset for the path info, or `None` if no + decoding should be performed. + :param errors: the decoding error handling. + """ + path = wsgi_get_bytes(environ.get("PATH_INFO", "")) + return to_unicode(path, charset, errors, allow_none_charset=True) + + +def get_script_name(environ, charset="utf-8", errors="replace"): + """Returns the `SCRIPT_NAME` from the WSGI environment and properly + decodes it. This also takes care about the WSGI decoding dance + on Python 3 environments. if the `charset` is set to `None` a + bytestring is returned. + + .. versionadded:: 0.9 + + :param environ: the WSGI environment object to get the path from. + :param charset: the charset for the path, or `None` if no + decoding should be performed. + :param errors: the decoding error handling. + """ + path = wsgi_get_bytes(environ.get("SCRIPT_NAME", "")) + return to_unicode(path, charset, errors, allow_none_charset=True) + + +def pop_path_info(environ, charset="utf-8", errors="replace"): + """Removes and returns the next segment of `PATH_INFO`, pushing it onto + `SCRIPT_NAME`. Returns `None` if there is nothing left on `PATH_INFO`. + + If the `charset` is set to `None` a bytestring is returned. + + If there are empty segments (``'/foo//bar``) these are ignored but + properly pushed to the `SCRIPT_NAME`: + + >>> env = {'SCRIPT_NAME': '/foo', 'PATH_INFO': '/a/b'} + >>> pop_path_info(env) + 'a' + >>> env['SCRIPT_NAME'] + '/foo/a' + >>> pop_path_info(env) + 'b' + >>> env['SCRIPT_NAME'] + '/foo/a/b' + + .. versionadded:: 0.5 + + .. versionchanged:: 0.9 + The path is now decoded and a charset and encoding + parameter can be provided. + + :param environ: the WSGI environment that is modified. + """ + path = environ.get("PATH_INFO") + if not path: + return None + + script_name = environ.get("SCRIPT_NAME", "") + + # shift multiple leading slashes over + old_path = path + path = path.lstrip("/") + if path != old_path: + script_name += "/" * (len(old_path) - len(path)) + + if "/" not in path: + environ["PATH_INFO"] = "" + environ["SCRIPT_NAME"] = script_name + path + rv = wsgi_get_bytes(path) + else: + segment, path = path.split("/", 1) + environ["PATH_INFO"] = "/" + path + environ["SCRIPT_NAME"] = script_name + segment + rv = wsgi_get_bytes(segment) + + return to_unicode(rv, charset, errors, allow_none_charset=True) + + +def peek_path_info(environ, charset="utf-8", errors="replace"): + """Returns the next segment on the `PATH_INFO` or `None` if there + is none. Works like :func:`pop_path_info` without modifying the + environment: + + >>> env = {'SCRIPT_NAME': '/foo', 'PATH_INFO': '/a/b'} + >>> peek_path_info(env) + 'a' + >>> peek_path_info(env) + 'a' + + If the `charset` is set to `None` a bytestring is returned. + + .. versionadded:: 0.5 + + .. versionchanged:: 0.9 + The path is now decoded and a charset and encoding + parameter can be provided. + + :param environ: the WSGI environment that is checked. + """ + segments = environ.get("PATH_INFO", "").lstrip("/").split("/", 1) + if segments: + return to_unicode( + wsgi_get_bytes(segments[0]), charset, errors, allow_none_charset=True + ) + + +def extract_path_info( + environ_or_baseurl, + path_or_url, + charset="utf-8", + errors="werkzeug.url_quote", + collapse_http_schemes=True, +): + """Extracts the path info from the given URL (or WSGI environment) and + path. The path info returned is a unicode string, not a bytestring + suitable for a WSGI environment. The URLs might also be IRIs. + + If the path info could not be determined, `None` is returned. + + Some examples: + + >>> extract_path_info('http://example.com/app', '/app/hello') + u'/hello' + >>> extract_path_info('http://example.com/app', + ... 'https://example.com/app/hello') + u'/hello' + >>> extract_path_info('http://example.com/app', + ... 'https://example.com/app/hello', + ... collapse_http_schemes=False) is None + True + + Instead of providing a base URL you can also pass a WSGI environment. + + :param environ_or_baseurl: a WSGI environment dict, a base URL or + base IRI. This is the root of the + application. + :param path_or_url: an absolute path from the server root, a + relative path (in which case it's the path info) + or a full URL. Also accepts IRIs and unicode + parameters. + :param charset: the charset for byte data in URLs + :param errors: the error handling on decode + :param collapse_http_schemes: if set to `False` the algorithm does + not assume that http and https on the + same server point to the same + resource. + + .. versionchanged:: 0.15 + The ``errors`` parameter defaults to leaving invalid bytes + quoted instead of replacing them. + + .. versionadded:: 0.6 + """ + + def _normalize_netloc(scheme, netloc): + parts = netloc.split(u"@", 1)[-1].split(u":", 1) + if len(parts) == 2: + netloc, port = parts + if (scheme == u"http" and port == u"80") or ( + scheme == u"https" and port == u"443" + ): + port = None + else: + netloc = parts[0] + port = None + if port is not None: + netloc += u":" + port + return netloc + + # make sure whatever we are working on is a IRI and parse it + path = uri_to_iri(path_or_url, charset, errors) + if isinstance(environ_or_baseurl, dict): + environ_or_baseurl = get_current_url(environ_or_baseurl, root_only=True) + base_iri = uri_to_iri(environ_or_baseurl, charset, errors) + base_scheme, base_netloc, base_path = url_parse(base_iri)[:3] + cur_scheme, cur_netloc, cur_path, = url_parse(url_join(base_iri, path))[:3] + + # normalize the network location + base_netloc = _normalize_netloc(base_scheme, base_netloc) + cur_netloc = _normalize_netloc(cur_scheme, cur_netloc) + + # is that IRI even on a known HTTP scheme? + if collapse_http_schemes: + for scheme in base_scheme, cur_scheme: + if scheme not in (u"http", u"https"): + return None + else: + if not (base_scheme in (u"http", u"https") and base_scheme == cur_scheme): + return None + + # are the netlocs compatible? + if base_netloc != cur_netloc: + return None + + # are we below the application path? + base_path = base_path.rstrip(u"/") + if not cur_path.startswith(base_path): + return None + + return u"/" + cur_path[len(base_path) :].lstrip(u"/") + + +@implements_iterator +class ClosingIterator(object): + """The WSGI specification requires that all middlewares and gateways + respect the `close` callback of the iterable returned by the application. + Because it is useful to add another close action to a returned iterable + and adding a custom iterable is a boring task this class can be used for + that:: + + return ClosingIterator(app(environ, start_response), [cleanup_session, + cleanup_locals]) + + If there is just one close function it can be passed instead of the list. + + A closing iterator is not needed if the application uses response objects + and finishes the processing if the response is started:: + + try: + return response(environ, start_response) + finally: + cleanup_session() + cleanup_locals() + """ + + def __init__(self, iterable, callbacks=None): + iterator = iter(iterable) + self._next = partial(next, iterator) + if callbacks is None: + callbacks = [] + elif callable(callbacks): + callbacks = [callbacks] + else: + callbacks = list(callbacks) + iterable_close = getattr(iterable, "close", None) + if iterable_close: + callbacks.insert(0, iterable_close) + self._callbacks = callbacks + + def __iter__(self): + return self + + def __next__(self): + return self._next() + + def close(self): + for callback in self._callbacks: + callback() + + +def wrap_file(environ, file, buffer_size=8192): + """Wraps a file. This uses the WSGI server's file wrapper if available + or otherwise the generic :class:`FileWrapper`. + + .. versionadded:: 0.5 + + If the file wrapper from the WSGI server is used it's important to not + iterate over it from inside the application but to pass it through + unchanged. If you want to pass out a file wrapper inside a response + object you have to set :attr:`~BaseResponse.direct_passthrough` to `True`. + + More information about file wrappers are available in :pep:`333`. + + :param file: a :class:`file`-like object with a :meth:`~file.read` method. + :param buffer_size: number of bytes for one iteration. + """ + return environ.get("wsgi.file_wrapper", FileWrapper)(file, buffer_size) + + +@implements_iterator +class FileWrapper(object): + """This class can be used to convert a :class:`file`-like object into + an iterable. It yields `buffer_size` blocks until the file is fully + read. + + You should not use this class directly but rather use the + :func:`wrap_file` function that uses the WSGI server's file wrapper + support if it's available. + + .. versionadded:: 0.5 + + If you're using this object together with a :class:`BaseResponse` you have + to use the `direct_passthrough` mode. + + :param file: a :class:`file`-like object with a :meth:`~file.read` method. + :param buffer_size: number of bytes for one iteration. + """ + + def __init__(self, file, buffer_size=8192): + self.file = file + self.buffer_size = buffer_size + + def close(self): + if hasattr(self.file, "close"): + self.file.close() + + def seekable(self): + if hasattr(self.file, "seekable"): + return self.file.seekable() + if hasattr(self.file, "seek"): + return True + return False + + def seek(self, *args): + if hasattr(self.file, "seek"): + self.file.seek(*args) + + def tell(self): + if hasattr(self.file, "tell"): + return self.file.tell() + return None + + def __iter__(self): + return self + + def __next__(self): + data = self.file.read(self.buffer_size) + if data: + return data + raise StopIteration() + + +@implements_iterator +class _RangeWrapper(object): + # private for now, but should we make it public in the future ? + + """This class can be used to convert an iterable object into + an iterable that will only yield a piece of the underlying content. + It yields blocks until the underlying stream range is fully read. + The yielded blocks will have a size that can't exceed the original + iterator defined block size, but that can be smaller. + + If you're using this object together with a :class:`BaseResponse` you have + to use the `direct_passthrough` mode. + + :param iterable: an iterable object with a :meth:`__next__` method. + :param start_byte: byte from which read will start. + :param byte_range: how many bytes to read. + """ + + def __init__(self, iterable, start_byte=0, byte_range=None): + self.iterable = iter(iterable) + self.byte_range = byte_range + self.start_byte = start_byte + self.end_byte = None + if byte_range is not None: + self.end_byte = self.start_byte + self.byte_range + self.read_length = 0 + self.seekable = hasattr(iterable, "seekable") and iterable.seekable() + self.end_reached = False + + def __iter__(self): + return self + + def _next_chunk(self): + try: + chunk = next(self.iterable) + self.read_length += len(chunk) + return chunk + except StopIteration: + self.end_reached = True + raise + + def _first_iteration(self): + chunk = None + if self.seekable: + self.iterable.seek(self.start_byte) + self.read_length = self.iterable.tell() + contextual_read_length = self.read_length + else: + while self.read_length <= self.start_byte: + chunk = self._next_chunk() + if chunk is not None: + chunk = chunk[self.start_byte - self.read_length :] + contextual_read_length = self.start_byte + return chunk, contextual_read_length + + def _next(self): + if self.end_reached: + raise StopIteration() + chunk = None + contextual_read_length = self.read_length + if self.read_length == 0: + chunk, contextual_read_length = self._first_iteration() + if chunk is None: + chunk = self._next_chunk() + if self.end_byte is not None and self.read_length >= self.end_byte: + self.end_reached = True + return chunk[: self.end_byte - contextual_read_length] + return chunk + + def __next__(self): + chunk = self._next() + if chunk: + return chunk + self.end_reached = True + raise StopIteration() + + def close(self): + if hasattr(self.iterable, "close"): + self.iterable.close() + + +def _make_chunk_iter(stream, limit, buffer_size): + """Helper for the line and chunk iter functions.""" + if isinstance(stream, (bytes, bytearray, text_type)): + raise TypeError( + "Passed a string or byte object instead of true iterator or stream." + ) + if not hasattr(stream, "read"): + for item in stream: + if item: + yield item + return + if not isinstance(stream, LimitedStream) and limit is not None: + stream = LimitedStream(stream, limit) + _read = stream.read + while 1: + item = _read(buffer_size) + if not item: + break + yield item + + +def make_line_iter(stream, limit=None, buffer_size=10 * 1024, cap_at_buffer=False): + """Safely iterates line-based over an input stream. If the input stream + is not a :class:`LimitedStream` the `limit` parameter is mandatory. + + This uses the stream's :meth:`~file.read` method internally as opposite + to the :meth:`~file.readline` method that is unsafe and can only be used + in violation of the WSGI specification. The same problem applies to the + `__iter__` function of the input stream which calls :meth:`~file.readline` + without arguments. + + If you need line-by-line processing it's strongly recommended to iterate + over the input stream using this helper function. + + .. versionchanged:: 0.8 + This function now ensures that the limit was reached. + + .. versionadded:: 0.9 + added support for iterators as input stream. + + .. versionadded:: 0.11.10 + added support for the `cap_at_buffer` parameter. + + :param stream: the stream or iterate to iterate over. + :param limit: the limit in bytes for the stream. (Usually + content length. Not necessary if the `stream` + is a :class:`LimitedStream`. + :param buffer_size: The optional buffer size. + :param cap_at_buffer: if this is set chunks are split if they are longer + than the buffer size. Internally this is implemented + that the buffer size might be exhausted by a factor + of two however. + """ + _iter = _make_chunk_iter(stream, limit, buffer_size) + + first_item = next(_iter, "") + if not first_item: + return + + s = make_literal_wrapper(first_item) + empty = s("") + cr = s("\r") + lf = s("\n") + crlf = s("\r\n") + + _iter = chain((first_item,), _iter) + + def _iter_basic_lines(): + _join = empty.join + buffer = [] + while 1: + new_data = next(_iter, "") + if not new_data: + break + new_buf = [] + buf_size = 0 + for item in chain(buffer, new_data.splitlines(True)): + new_buf.append(item) + buf_size += len(item) + if item and item[-1:] in crlf: + yield _join(new_buf) + new_buf = [] + elif cap_at_buffer and buf_size >= buffer_size: + rv = _join(new_buf) + while len(rv) >= buffer_size: + yield rv[:buffer_size] + rv = rv[buffer_size:] + new_buf = [rv] + buffer = new_buf + if buffer: + yield _join(buffer) + + # This hackery is necessary to merge 'foo\r' and '\n' into one item + # of 'foo\r\n' if we were unlucky and we hit a chunk boundary. + previous = empty + for item in _iter_basic_lines(): + if item == lf and previous[-1:] == cr: + previous += item + item = empty + if previous: + yield previous + previous = item + if previous: + yield previous + + +def make_chunk_iter( + stream, separator, limit=None, buffer_size=10 * 1024, cap_at_buffer=False +): + """Works like :func:`make_line_iter` but accepts a separator + which divides chunks. If you want newline based processing + you should use :func:`make_line_iter` instead as it + supports arbitrary newline markers. + + .. versionadded:: 0.8 + + .. versionadded:: 0.9 + added support for iterators as input stream. + + .. versionadded:: 0.11.10 + added support for the `cap_at_buffer` parameter. + + :param stream: the stream or iterate to iterate over. + :param separator: the separator that divides chunks. + :param limit: the limit in bytes for the stream. (Usually + content length. Not necessary if the `stream` + is otherwise already limited). + :param buffer_size: The optional buffer size. + :param cap_at_buffer: if this is set chunks are split if they are longer + than the buffer size. Internally this is implemented + that the buffer size might be exhausted by a factor + of two however. + """ + _iter = _make_chunk_iter(stream, limit, buffer_size) + + first_item = next(_iter, "") + if not first_item: + return + + _iter = chain((first_item,), _iter) + if isinstance(first_item, text_type): + separator = to_unicode(separator) + _split = re.compile(r"(%s)" % re.escape(separator)).split + _join = u"".join + else: + separator = to_bytes(separator) + _split = re.compile(b"(" + re.escape(separator) + b")").split + _join = b"".join + + buffer = [] + while 1: + new_data = next(_iter, "") + if not new_data: + break + chunks = _split(new_data) + new_buf = [] + buf_size = 0 + for item in chain(buffer, chunks): + if item == separator: + yield _join(new_buf) + new_buf = [] + buf_size = 0 + else: + buf_size += len(item) + new_buf.append(item) + + if cap_at_buffer and buf_size >= buffer_size: + rv = _join(new_buf) + while len(rv) >= buffer_size: + yield rv[:buffer_size] + rv = rv[buffer_size:] + new_buf = [rv] + buf_size = len(rv) + + buffer = new_buf + if buffer: + yield _join(buffer) + + +@implements_iterator +class LimitedStream(io.IOBase): + """Wraps a stream so that it doesn't read more than n bytes. If the + stream is exhausted and the caller tries to get more bytes from it + :func:`on_exhausted` is called which by default returns an empty + string. The return value of that function is forwarded + to the reader function. So if it returns an empty string + :meth:`read` will return an empty string as well. + + The limit however must never be higher than what the stream can + output. Otherwise :meth:`readlines` will try to read past the + limit. + + .. admonition:: Note on WSGI compliance + + calls to :meth:`readline` and :meth:`readlines` are not + WSGI compliant because it passes a size argument to the + readline methods. Unfortunately the WSGI PEP is not safely + implementable without a size argument to :meth:`readline` + because there is no EOF marker in the stream. As a result + of that the use of :meth:`readline` is discouraged. + + For the same reason iterating over the :class:`LimitedStream` + is not portable. It internally calls :meth:`readline`. + + We strongly suggest using :meth:`read` only or using the + :func:`make_line_iter` which safely iterates line-based + over a WSGI input stream. + + :param stream: the stream to wrap. + :param limit: the limit for the stream, must not be longer than + what the string can provide if the stream does not + end with `EOF` (like `wsgi.input`) + """ + + def __init__(self, stream, limit): + self._read = stream.read + self._readline = stream.readline + self._pos = 0 + self.limit = limit + + def __iter__(self): + return self + + @property + def is_exhausted(self): + """If the stream is exhausted this attribute is `True`.""" + return self._pos >= self.limit + + def on_exhausted(self): + """This is called when the stream tries to read past the limit. + The return value of this function is returned from the reading + function. + """ + # Read null bytes from the stream so that we get the + # correct end of stream marker. + return self._read(0) + + def on_disconnect(self): + """What should happen if a disconnect is detected? The return + value of this function is returned from read functions in case + the client went away. By default a + :exc:`~werkzeug.exceptions.ClientDisconnected` exception is raised. + """ + from .exceptions import ClientDisconnected + + raise ClientDisconnected() + + def exhaust(self, chunk_size=1024 * 64): + """Exhaust the stream. This consumes all the data left until the + limit is reached. + + :param chunk_size: the size for a chunk. It will read the chunk + until the stream is exhausted and throw away + the results. + """ + to_read = self.limit - self._pos + chunk = chunk_size + while to_read > 0: + chunk = min(to_read, chunk) + self.read(chunk) + to_read -= chunk + + def read(self, size=None): + """Read `size` bytes or if size is not provided everything is read. + + :param size: the number of bytes read. + """ + if self._pos >= self.limit: + return self.on_exhausted() + if size is None or size == -1: # -1 is for consistence with file + size = self.limit + to_read = min(self.limit - self._pos, size) + try: + read = self._read(to_read) + except (IOError, ValueError): + return self.on_disconnect() + if to_read and len(read) != to_read: + return self.on_disconnect() + self._pos += len(read) + return read + + def readline(self, size=None): + """Reads one line from the stream.""" + if self._pos >= self.limit: + return self.on_exhausted() + if size is None: + size = self.limit - self._pos + else: + size = min(size, self.limit - self._pos) + try: + line = self._readline(size) + except (ValueError, IOError): + return self.on_disconnect() + if size and not line: + return self.on_disconnect() + self._pos += len(line) + return line + + def readlines(self, size=None): + """Reads a file into a list of strings. It calls :meth:`readline` + until the file is read to the end. It does support the optional + `size` argument if the underlaying stream supports it for + `readline`. + """ + last_pos = self._pos + result = [] + if size is not None: + end = min(self.limit, last_pos + size) + else: + end = self.limit + while 1: + if size is not None: + size -= last_pos - self._pos + if self._pos >= end: + break + result.append(self.readline(size)) + if size is not None: + last_pos = self._pos + return result + + def tell(self): + """Returns the position of the stream. + + .. versionadded:: 0.9 + """ + return self._pos + + def __next__(self): + line = self.readline() + if not line: + raise StopIteration() + return line + + def readable(self): + return True + + +from werkzeug import _DeprecatedImportModule + +_DeprecatedImportModule( + __name__, + { + ".middleware.dispatcher": ["DispatcherMiddleware"], + ".middleware.http_proxy": ["ProxyMiddleware"], + ".middleware.shared_data": ["SharedDataMiddleware"], + }, + "Werkzeug 1.0", +) diff --git a/venv/lib/python2.7/site-packages/wheel/__init__.py b/venv/lib/python2.7/site-packages/wheel/__init__.py new file mode 100644 index 00000000..5df0e1b1 --- /dev/null +++ b/venv/lib/python2.7/site-packages/wheel/__init__.py @@ -0,0 +1,2 @@ +# __variables__ with double-quoted values will be available in setup.py: +__version__ = "0.33.6" diff --git a/venv/lib/python2.7/site-packages/wheel/__main__.py b/venv/lib/python2.7/site-packages/wheel/__main__.py new file mode 100644 index 00000000..b3773a20 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/wheel/bdist_wheel.py b/venv/lib/python2.7/site-packages/wheel/bdist_wheel.py new file mode 100644 index 00000000..c79307b5 --- /dev/null +++ b/venv/lib/python2.7/site-packages/wheel/bdist_wheel.py @@ -0,0 +1,372 @@ +""" +Create a wheel (.whl) distribution. + +A wheel is a built archive format. +""" + +import os +import shutil +import sys +import re +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 + +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('-', '_') + + +class bdist_wheel(Command): + + description = 'create a wheel 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_platform()), + ('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)"), + ('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.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 + + 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: + plat_name = self.plat_name or get_platform() + 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( + 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') 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) + + 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/venv/lib/python2.7/site-packages/wheel/cli/__init__.py b/venv/lib/python2.7/site-packages/wheel/cli/__init__.py new file mode 100644 index 00000000..95740bfb --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/wheel/cli/convert.py b/venv/lib/python2.7/site-packages/wheel/cli/convert.py new file mode 100644 index 00000000..154f1b1e --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/wheel/cli/pack.py b/venv/lib/python2.7/site-packages/wheel/cli/pack.py new file mode 100644 index 00000000..af6e81c4 --- /dev/null +++ b/venv/lib/python2.7/site-packages/wheel/cli/pack.py @@ -0,0 +1,58 @@ +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$") + + +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') + + # Add the build number if specific + if build_number: + name_version += '-' + build_number + + # Read the tags from .dist-info/WHEEL + with open(os.path.join(directory, dist_info_dir, 'WHEEL')) as f: + tags = [line.split(' ')[1].rstrip() for line in f if line.startswith('Tag: ')] + if not tags: + raise WheelError('No tags present in {}/WHEEL; cannot determine target wheel filename' + .format(dist_info_dir)) + + # 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/venv/lib/python2.7/site-packages/wheel/cli/unpack.py b/venv/lib/python2.7/site-packages/wheel/cli/unpack.py new file mode 100644 index 00000000..2e9857a3 --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/wheel/metadata.py b/venv/lib/python2.7/site-packages/wheel/metadata.py new file mode 100644 index 00000000..ab0c07e5 --- /dev/null +++ b/venv/lib/python2.7/site-packages/wheel/metadata.py @@ -0,0 +1,141 @@ +""" +Tools for converting old- to new-style metadata. +""" + +import os.path +import re +import textwrap + +import pkg_resources + +from .pkginfo import read_pkg_info + +# Wheel itself is probably the only program that uses non-extras markers +# in METADATA/PKG-INFO. Support its 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): + if not hasattr(pkg_info, 'raw_items'): # Python 3.2 + return str(text) + 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/venv/lib/python2.7/site-packages/wheel/pep425tags.py b/venv/lib/python2.7/site-packages/wheel/pep425tags.py new file mode 100644 index 00000000..b9242efa --- /dev/null +++ b/venv/lib/python2.7/site-packages/wheel/pep425tags.py @@ -0,0 +1,190 @@ +"""Generate and work with PEP 425 Compatibility Tags.""" + +import distutils.util +import platform +import sys +import sysconfig +import warnings + +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 or get_abbr_impl() == 'pp': + 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.""" + if get_abbr_impl() == 'pp': + # as per https://github.com/pypa/pip/issues/2882 + return (sys.version_info[0], sys.pypy_version_info.major, + sys.pypy_version_info.minor) + else: + 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 get_platform(): + """Return our platform name 'win32', 'linux_x86_64'""" + # XXX remove distutils dependency + result = distutils.util.get_platform().replace('.', '_').replace('-', '_') + if result == "linux_x86_64" and sys.maxsize == 2147483647: + # pip pull request #3497 + result = "linux_i686" + return result + + +def get_supported(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()) + + # 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/venv/lib/python2.7/site-packages/wheel/pkginfo.py b/venv/lib/python2.7/site-packages/wheel/pkginfo.py new file mode 100644 index 00000000..115be45b --- /dev/null +++ b/venv/lib/python2.7/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/venv/lib/python2.7/site-packages/wheel/util.py b/venv/lib/python2.7/site-packages/wheel/util.py new file mode 100644 index 00000000..0afb54a4 --- /dev/null +++ b/venv/lib/python2.7/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): + 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/venv/lib/python2.7/site-packages/wheel/wheelfile.py b/venv/lib/python2.7/site-packages/wheel/wheelfile.py new file mode 100644 index 00000000..ddf8509d --- /dev/null +++ b/venv/lib/python2.7/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'): + 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=ZIP_DEFLATED, 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) + 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 = ZIP_DEFLATED + 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 = ZIP_DEFLATED + zinfo.external_attr = 0o664 << 16 + self.writestr(zinfo, as_bytes(data.getvalue())) + + ZipFile.close(self) diff --git a/venv/lib/python2.7/site.py b/venv/lib/python2.7/site.py new file mode 100644 index 00000000..ede3ca30 --- /dev/null +++ b/venv/lib/python2.7/site.py @@ -0,0 +1,851 @@ +"""Append module search paths for third-party packages to sys.path. + +**************************************************************** +* This module is automatically imported during initialization. * +**************************************************************** + +In earlier versions of Python (up to 1.5a3), scripts or modules that +needed to use site-specific modules would place ``import site'' +somewhere near the top of their code. Because of the automatic +import, this is no longer necessary (but code that does it still +works). + +This will append site-specific paths to the module search path. On +Unix, it starts with sys.prefix and sys.exec_prefix (if different) and +appends lib/python/site-packages as well as lib/site-python. +It also supports the Debian convention of +lib/python/dist-packages. On other platforms (mainly Mac and +Windows), it uses just sys.prefix (and sys.exec_prefix, if different, +but this is unlikely). The resulting directories, if they exist, are +appended to sys.path, and also inspected for path configuration files. + +FOR DEBIAN, this sys.path is augmented with directories in /usr/local. +Local addons go into /usr/local/lib/python/site-packages +(resp. /usr/local/lib/site-python), Debian addons install into +/usr/{lib,share}/python/dist-packages. + +A path configuration file is a file whose name has the form +.pth; its contents are additional directories (one per line) +to be added to sys.path. Non-existing directories (or +non-directories) are never added to sys.path; no directory is added to +sys.path more than once. Blank lines and lines beginning with +'#' are skipped. Lines starting with 'import' are executed. + +For example, suppose sys.prefix and sys.exec_prefix are set to +/usr/local and there is a directory /usr/local/lib/python2.X/site-packages +with three subdirectories, foo, bar and spam, and two path +configuration files, foo.pth and bar.pth. Assume foo.pth contains the +following: + + # foo package configuration + foo + bar + bletch + +and bar.pth contains: + + # bar package configuration + bar + +Then the following directories are added to sys.path, in this order: + + /usr/local/lib/python2.X/site-packages/bar + /usr/local/lib/python2.X/site-packages/foo + +Note that bletch is omitted because it doesn't exist; bar precedes foo +because bar.pth comes alphabetically before foo.pth; and spam is +omitted because it is not mentioned in either path configuration file. + +After these path manipulations, an attempt is made to import a module +named sitecustomize, which can perform arbitrary additional +site-specific customizations. If this import fails with an +ImportError exception, it is silently ignored. + +""" + +import os +import sys + +try: + import __builtin__ as builtins +except ImportError: + import builtins +try: + set +except NameError: + from sets import Set as set + +# Prefixes for site-packages; add additional prefixes like /usr/local here +PREFIXES = [sys.prefix, sys.exec_prefix] +# Enable per user site-packages directory +# set it to False to disable the feature or True to force the feature +ENABLE_USER_SITE = None +# for distutils.commands.install +USER_SITE = None +USER_BASE = None + +_is_64bit = (getattr(sys, "maxsize", None) or getattr(sys, "maxint")) > 2 ** 32 +_is_pypy = hasattr(sys, "pypy_version_info") +_is_jython = sys.platform[:4] == "java" +if _is_jython: + ModuleType = type(os) + + +def makepath(*paths): + dir = os.path.join(*paths) + if _is_jython and (dir == "__classpath__" or dir.startswith("__pyclasspath__")): + return dir, dir + dir = os.path.abspath(dir) + return dir, os.path.normcase(dir) + + +def abs__file__(): + """Set all module' __file__ attribute to an absolute path""" + for m in sys.modules.values(): + if (_is_jython and not isinstance(m, ModuleType)) or hasattr(m, "__loader__"): + # only modules need the abspath in Jython. and don't mess + # with a PEP 302-supplied __file__ + continue + f = getattr(m, "__file__", None) + if f is None: + continue + m.__file__ = os.path.abspath(f) + + +def removeduppaths(): + """ Remove duplicate entries from sys.path along with making them + absolute""" + # This ensures that the initial path provided by the interpreter contains + # only absolute pathnames, even if we're running from the build directory. + L = [] + known_paths = set() + for dir in sys.path: + # Filter out duplicate paths (on case-insensitive file systems also + # if they only differ in case); turn relative paths into absolute + # paths. + dir, dircase = makepath(dir) + if not dircase in known_paths: + L.append(dir) + known_paths.add(dircase) + sys.path[:] = L + return known_paths + + +# XXX This should not be part of site.py, since it is needed even when +# using the -S option for Python. See http://www.python.org/sf/586680 +def addbuilddir(): + """Append ./build/lib. in case we're running in the build dir + (especially for Guido :-)""" + from distutils.util import get_platform + + s = "build/lib.{}-{:.3}".format(get_platform(), sys.version) + if hasattr(sys, "gettotalrefcount"): + s += "-pydebug" + s = os.path.join(os.path.dirname(sys.path[-1]), s) + sys.path.append(s) + + +def _init_pathinfo(): + """Return a set containing all existing directory entries from sys.path""" + d = set() + for dir in sys.path: + try: + if os.path.isdir(dir): + dir, dircase = makepath(dir) + d.add(dircase) + except TypeError: + continue + return d + + +def addpackage(sitedir, name, known_paths): + """Add a new path to known_paths by combining sitedir and 'name' or execute + sitedir if it starts with 'import'""" + if known_paths is None: + _init_pathinfo() + reset = 1 + else: + reset = 0 + fullname = os.path.join(sitedir, name) + try: + f = open(fullname, "r") + except IOError: + return + try: + for line in f: + if line.startswith("#"): + continue + if line.startswith("import"): + exec(line) + continue + line = line.rstrip() + dir, dircase = makepath(sitedir, line) + if not dircase in known_paths and os.path.exists(dir): + sys.path.append(dir) + known_paths.add(dircase) + finally: + f.close() + if reset: + known_paths = None + return known_paths + + +def addsitedir(sitedir, known_paths=None): + """Add 'sitedir' argument to sys.path if missing and handle .pth files in + 'sitedir'""" + if known_paths is None: + known_paths = _init_pathinfo() + reset = 1 + else: + reset = 0 + sitedir, sitedircase = makepath(sitedir) + if not sitedircase in known_paths: + sys.path.append(sitedir) # Add path component + try: + names = os.listdir(sitedir) + except os.error: + return + names.sort() + for name in names: + if name.endswith(os.extsep + "pth"): + addpackage(sitedir, name, known_paths) + if reset: + known_paths = None + return known_paths + + +def addsitepackages(known_paths, sys_prefix=sys.prefix, exec_prefix=sys.exec_prefix): + """Add site-packages (and possibly site-python) to sys.path""" + prefixes = [os.path.join(sys_prefix, "local"), sys_prefix] + if exec_prefix != sys_prefix: + prefixes.append(os.path.join(exec_prefix, "local")) + + for prefix in prefixes: + if prefix: + if sys.platform in ("os2emx", "riscos") or _is_jython: + sitedirs = [os.path.join(prefix, "Lib", "site-packages")] + elif _is_pypy: + sitedirs = [os.path.join(prefix, "site-packages")] + elif sys.platform == "darwin" and prefix == sys_prefix: + + if prefix.startswith("/System/Library/Frameworks/"): # Apple's Python + + sitedirs = [ + os.path.join("/Library/Python", sys.version[:3], "site-packages"), + os.path.join(prefix, "Extras", "lib", "python"), + ] + + else: # any other Python distros on OSX work this way + sitedirs = [os.path.join(prefix, "lib", "python" + sys.version[:3], "site-packages")] + + elif os.sep == "/": + sitedirs = [ + os.path.join(prefix, "lib", "python" + sys.version[:3], "site-packages"), + os.path.join(prefix, "lib", "site-python"), + os.path.join(prefix, "python" + sys.version[:3], "lib-dynload"), + ] + lib64_dir = os.path.join(prefix, "lib64", "python" + sys.version[:3], "site-packages") + if os.path.exists(lib64_dir) and os.path.realpath(lib64_dir) not in [ + os.path.realpath(p) for p in sitedirs + ]: + if _is_64bit: + sitedirs.insert(0, lib64_dir) + else: + sitedirs.append(lib64_dir) + try: + # sys.getobjects only available in --with-pydebug build + sys.getobjects + sitedirs.insert(0, os.path.join(sitedirs[0], "debug")) + except AttributeError: + pass + # Debian-specific dist-packages directories: + sitedirs.append(os.path.join(prefix, "local/lib", "python" + sys.version[:3], "dist-packages")) + if sys.version[0] == "2": + sitedirs.append(os.path.join(prefix, "lib", "python" + sys.version[:3], "dist-packages")) + else: + sitedirs.append(os.path.join(prefix, "lib", "python" + sys.version[0], "dist-packages")) + sitedirs.append(os.path.join(prefix, "lib", "dist-python")) + else: + sitedirs = [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: + sitedirs.append(os.path.join(home, "Library", "Python", sys.version[:3], "site-packages")) + for sitedir in sitedirs: + if os.path.isdir(sitedir): + addsitedir(sitedir, known_paths) + return None + + +def check_enableusersite(): + """Check if user site directory is safe for inclusion + + The function tests for the command line flag (including environment var), + process uid/gid equal to effective uid/gid. + + None: Disabled for security reasons + False: Disabled by user (command line option) + True: Safe and enabled + """ + if hasattr(sys, "flags") and getattr(sys.flags, "no_user_site", False): + return False + + if hasattr(os, "getuid") and hasattr(os, "geteuid"): + # check process uid == effective uid + if os.geteuid() != os.getuid(): + return None + if hasattr(os, "getgid") and hasattr(os, "getegid"): + # check process gid == effective gid + if os.getegid() != os.getgid(): + return None + + return True + + +def addusersitepackages(known_paths): + """Add a per user site-package to sys.path + + Each user has its own python directory with site-packages in the + home directory. + + USER_BASE is the root directory for all Python versions + + USER_SITE is the user specific site-packages directory + + USER_SITE/.. can be used for data. + """ + global USER_BASE, USER_SITE, ENABLE_USER_SITE + env_base = os.environ.get("PYTHONUSERBASE", None) + + def joinuser(*args): + return os.path.expanduser(os.path.join(*args)) + + # if sys.platform in ('os2emx', 'riscos'): + # # Don't know what to put here + # USER_BASE = '' + # USER_SITE = '' + if os.name == "nt": + base = os.environ.get("APPDATA") or "~" + if env_base: + USER_BASE = env_base + else: + USER_BASE = joinuser(base, "Python") + USER_SITE = os.path.join(USER_BASE, "Python" + sys.version[0] + sys.version[2], "site-packages") + else: + if env_base: + USER_BASE = env_base + else: + USER_BASE = joinuser("~", ".local") + USER_SITE = os.path.join(USER_BASE, "lib", "python" + sys.version[:3], "site-packages") + + if ENABLE_USER_SITE and os.path.isdir(USER_SITE): + addsitedir(USER_SITE, known_paths) + if ENABLE_USER_SITE: + for dist_libdir in ("lib", "local/lib"): + user_site = os.path.join(USER_BASE, dist_libdir, "python" + sys.version[:3], "dist-packages") + if os.path.isdir(user_site): + addsitedir(user_site, known_paths) + return known_paths + + +def setBEGINLIBPATH(): + """The OS/2 EMX port has optional extension modules that do double duty + as DLLs (and must use the .DLL file extension) for other extensions. + The library search path needs to be amended so these will be found + during module import. Use BEGINLIBPATH so that these are at the start + of the library search path. + + """ + dllpath = os.path.join(sys.prefix, "Lib", "lib-dynload") + libpath = os.environ["BEGINLIBPATH"].split(";") + if libpath[-1]: + libpath.append(dllpath) + else: + libpath[-1] = dllpath + os.environ["BEGINLIBPATH"] = ";".join(libpath) + + +def setquit(): + """Define new built-ins 'quit' and 'exit'. + These are simply strings that display a hint on how to exit. + + """ + if os.sep == ":": + eof = "Cmd-Q" + elif os.sep == "\\": + eof = "Ctrl-Z plus Return" + else: + eof = "Ctrl-D (i.e. EOF)" + + class Quitter(object): + def __init__(self, name): + self.name = name + + def __repr__(self): + return "Use {}() or {} to exit".format(self.name, eof) + + def __call__(self, code=None): + # Shells like IDLE catch the SystemExit, but listen when their + # stdin wrapper is closed. + try: + sys.stdin.close() + except: + pass + raise SystemExit(code) + + builtins.quit = Quitter("quit") + builtins.exit = Quitter("exit") + + +class _Printer(object): + """interactive prompt objects for printing the license text, a list of + contributors and the copyright notice.""" + + MAXLINES = 23 + + def __init__(self, name, data, files=(), dirs=()): + self.__name = name + self.__data = data + self.__files = files + self.__dirs = dirs + self.__lines = None + + def __setup(self): + if self.__lines: + return + data = None + for dir in self.__dirs: + for filename in self.__files: + filename = os.path.join(dir, filename) + try: + fp = open(filename, "r") + data = fp.read() + fp.close() + break + except IOError: + pass + if data: + break + if not data: + data = self.__data + self.__lines = data.split("\n") + self.__linecnt = len(self.__lines) + + def __repr__(self): + self.__setup() + if len(self.__lines) <= self.MAXLINES: + return "\n".join(self.__lines) + else: + return "Type %s() to see the full %s text" % ((self.__name,) * 2) + + def __call__(self): + self.__setup() + prompt = "Hit Return for more, or q (and Return) to quit: " + lineno = 0 + while 1: + try: + for i in range(lineno, lineno + self.MAXLINES): + print(self.__lines[i]) + except IndexError: + break + else: + lineno += self.MAXLINES + key = None + while key is None: + try: + key = raw_input(prompt) + except NameError: + key = input(prompt) + if key not in ("", "q"): + key = None + if key == "q": + break + + +def setcopyright(): + """Set 'copyright' and 'credits' in __builtin__""" + builtins.copyright = _Printer("copyright", sys.copyright) + if _is_jython: + builtins.credits = _Printer("credits", "Jython is maintained by the Jython developers (www.jython.org).") + elif _is_pypy: + builtins.credits = _Printer("credits", "PyPy is maintained by the PyPy developers: http://pypy.org/") + else: + builtins.credits = _Printer( + "credits", + """\ + Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands + for supporting Python development. See www.python.org for more information.""", + ) + here = os.path.dirname(os.__file__) + builtins.license = _Printer( + "license", + "See https://www.python.org/psf/license/", + ["LICENSE.txt", "LICENSE"], + [sys.prefix, os.path.join(here, os.pardir), here, os.curdir], + ) + + +class _Helper(object): + """Define the built-in 'help'. + This is a wrapper around pydoc.help (with a twist). + + """ + + def __repr__(self): + return "Type help() for interactive help, " "or help(object) for help about object." + + def __call__(self, *args, **kwds): + import pydoc + + return pydoc.help(*args, **kwds) + + +def sethelper(): + builtins.help = _Helper() + + +def aliasmbcs(): + """On Windows, some default encodings are not provided by Python, + while they are always available as "mbcs" in each locale. Make + them usable by aliasing to "mbcs" in such a case.""" + if sys.platform == "win32": + import locale, codecs + + enc = locale.getdefaultlocale()[1] + if enc.startswith("cp"): # "cp***" ? + try: + codecs.lookup(enc) + except LookupError: + import encodings + + encodings._cache[enc] = encodings._unknown + encodings.aliases.aliases[enc] = "mbcs" + + +def setencoding(): + """Set the string encoding used by the Unicode implementation. The + default is 'ascii', but if you're willing to experiment, you can + change this.""" + encoding = "ascii" # Default value set by _PyUnicode_Init() + if 0: + # Enable to support locale aware default string encodings. + import locale + + loc = locale.getdefaultlocale() + if loc[1]: + encoding = loc[1] + if 0: + # Enable to switch off string to Unicode coercion and implicit + # Unicode to string conversion. + encoding = "undefined" + if encoding != "ascii": + # On Non-Unicode builds this will raise an AttributeError... + sys.setdefaultencoding(encoding) # Needs Python Unicode build ! + + +def execsitecustomize(): + """Run custom site specific code, if available.""" + try: + import sitecustomize + except ImportError: + pass + + +def virtual_install_main_packages(): + f = open(os.path.join(os.path.dirname(__file__), "orig-prefix.txt")) + sys.real_prefix = f.read().strip() + f.close() + pos = 2 + hardcoded_relative_dirs = [] + if sys.path[0] == "": + pos += 1 + if _is_jython: + paths = [os.path.join(sys.real_prefix, "Lib")] + elif _is_pypy: + if sys.version_info > (3, 2): + cpyver = "%d" % sys.version_info[0] + elif sys.pypy_version_info >= (1, 5): + cpyver = "%d.%d" % sys.version_info[:2] + else: + cpyver = "%d.%d.%d" % sys.version_info[:3] + paths = [os.path.join(sys.real_prefix, "lib_pypy"), os.path.join(sys.real_prefix, "lib-python", cpyver)] + if sys.pypy_version_info < (1, 9): + paths.insert(1, os.path.join(sys.real_prefix, "lib-python", "modified-%s" % cpyver)) + hardcoded_relative_dirs = paths[:] # for the special 'darwin' case below + # + # This is hardcoded in the Python executable, but relative to sys.prefix: + for path in paths[:]: + plat_path = os.path.join(path, "plat-%s" % sys.platform) + if os.path.exists(plat_path): + paths.append(plat_path) + elif sys.platform == "win32": + paths = [os.path.join(sys.real_prefix, "Lib"), os.path.join(sys.real_prefix, "DLLs")] + else: + paths = [os.path.join(sys.real_prefix, "lib", "python" + sys.version[:3])] + hardcoded_relative_dirs = paths[:] # for the special 'darwin' case below + lib64_path = os.path.join(sys.real_prefix, "lib64", "python" + sys.version[:3]) + if os.path.exists(lib64_path): + if _is_64bit: + paths.insert(0, lib64_path) + else: + paths.append(lib64_path) + # This is hardcoded in the Python executable, but relative to + # sys.prefix. Debian change: we need to add the multiarch triplet + # here, which is where the real stuff lives. As per PEP 421, in + # Python 3.3+, this lives in sys.implementation, while in Python 2.7 + # it lives in sys. + try: + arch = getattr(sys, "implementation", sys)._multiarch + except AttributeError: + # This is a non-multiarch aware Python. Fallback to the old way. + arch = sys.platform + plat_path = os.path.join(sys.real_prefix, "lib", "python" + sys.version[:3], "plat-%s" % arch) + if os.path.exists(plat_path): + paths.append(plat_path) + # This is hardcoded in the Python executable, but + # relative to sys.prefix, so we have to fix up: + for path in list(paths): + tk_dir = os.path.join(path, "lib-tk") + if os.path.exists(tk_dir): + paths.append(tk_dir) + + # These are hardcoded in the Apple's Python executable, + # but relative to sys.prefix, so we have to fix them up: + if sys.platform == "darwin": + hardcoded_paths = [ + os.path.join(relative_dir, module) + for relative_dir in hardcoded_relative_dirs + for module in ("plat-darwin", "plat-mac", "plat-mac/lib-scriptpackages") + ] + + for path in hardcoded_paths: + if os.path.exists(path): + paths.append(path) + + sys.path.extend(paths) + + +def force_global_eggs_after_local_site_packages(): + """ + Force easy_installed eggs in the global environment to get placed + in sys.path after all packages inside the virtualenv. This + maintains the "least surprise" result that packages in the + virtualenv always mask global packages, never the other way + around. + + """ + egginsert = getattr(sys, "__egginsert", 0) + for i, path in enumerate(sys.path): + if i > egginsert and path.startswith(sys.prefix): + egginsert = i + sys.__egginsert = egginsert + 1 + + +def virtual_addsitepackages(known_paths): + force_global_eggs_after_local_site_packages() + return addsitepackages(known_paths, sys_prefix=sys.real_prefix) + + +def fixclasspath(): + """Adjust the special classpath sys.path entries for Jython. These + entries should follow the base virtualenv lib directories. + """ + paths = [] + classpaths = [] + for path in sys.path: + if path == "__classpath__" or path.startswith("__pyclasspath__"): + classpaths.append(path) + else: + paths.append(path) + sys.path = paths + sys.path.extend(classpaths) + + +def execusercustomize(): + """Run custom user specific code, if available.""" + try: + import usercustomize + except ImportError: + pass + + +def enablerlcompleter(): + """Enable default readline configuration on interactive prompts, by + registering a sys.__interactivehook__. + If the readline module can be imported, the hook will set the Tab key + as completion key and register ~/.python_history as history file. + This can be overridden in the sitecustomize or usercustomize module, + or in a PYTHONSTARTUP file. + """ + + def register_readline(): + import atexit + + try: + import readline + import rlcompleter + except ImportError: + return + + # Reading the initialization (config) file may not be enough to set a + # completion key, so we set one first and then read the file. + readline_doc = getattr(readline, "__doc__", "") + if readline_doc is not None and "libedit" in readline_doc: + readline.parse_and_bind("bind ^I rl_complete") + else: + readline.parse_and_bind("tab: complete") + + try: + readline.read_init_file() + except OSError: + # An OSError here could have many causes, but the most likely one + # is that there's no .inputrc file (or .editrc file in the case of + # Mac OS X + libedit) in the expected location. In that case, we + # want to ignore the exception. + pass + + if readline.get_current_history_length() == 0: + # If no history was loaded, default to .python_history. + # The guard is necessary to avoid doubling history size at + # each interpreter exit when readline was already configured + # through a PYTHONSTARTUP hook, see: + # http://bugs.python.org/issue5845#msg198636 + history = os.path.join(os.path.expanduser("~"), ".python_history") + try: + readline.read_history_file(history) + except OSError: + pass + + def write_history(): + try: + readline.write_history_file(history) + except (FileNotFoundError, PermissionError): + # home directory does not exist or is not writable + # https://bugs.python.org/issue19891 + pass + + atexit.register(write_history) + + sys.__interactivehook__ = register_readline + + +if _is_pypy: + + def import_builtin_stuff(): + """PyPy specific: some built-in modules should be pre-imported because + some programs expect them to be in sys.modules on startup. This is ported + from PyPy's site.py. + """ + import encodings + + if "exceptions" in sys.builtin_module_names: + import exceptions + + if "zipimport" in sys.builtin_module_names: + import zipimport + + +def main(): + global ENABLE_USER_SITE + virtual_install_main_packages() + if _is_pypy: + import_builtin_stuff() + abs__file__() + paths_in_sys = removeduppaths() + if os.name == "posix" and sys.path and os.path.basename(sys.path[-1]) == "Modules": + addbuilddir() + if _is_jython: + fixclasspath() + GLOBAL_SITE_PACKAGES = not os.path.exists(os.path.join(os.path.dirname(__file__), "no-global-site-packages.txt")) + if not GLOBAL_SITE_PACKAGES: + ENABLE_USER_SITE = False + if ENABLE_USER_SITE is None: + ENABLE_USER_SITE = check_enableusersite() + paths_in_sys = addsitepackages(paths_in_sys) + paths_in_sys = addusersitepackages(paths_in_sys) + if GLOBAL_SITE_PACKAGES: + paths_in_sys = virtual_addsitepackages(paths_in_sys) + if sys.platform == "os2emx": + setBEGINLIBPATH() + setquit() + setcopyright() + sethelper() + if sys.version_info[0] == 3: + enablerlcompleter() + aliasmbcs() + setencoding() + execsitecustomize() + if ENABLE_USER_SITE: + execusercustomize() + # Remove sys.setdefaultencoding() so that users cannot change the + # encoding after initialization. The test for presence is needed when + # this module is run as a script, because this code is executed twice. + if hasattr(sys, "setdefaultencoding"): + del sys.setdefaultencoding + + +main() + + +def _script(): + help = """\ + %s [--user-base] [--user-site] + + Without arguments print some useful information + With arguments print the value of USER_BASE and/or USER_SITE separated + by '%s'. + + Exit codes with --user-base or --user-site: + 0 - user site directory is enabled + 1 - user site directory is disabled by user + 2 - uses site directory is disabled by super user + or for security reasons + >2 - unknown error + """ + args = sys.argv[1:] + if not args: + print("sys.path = [") + for dir in sys.path: + print(" {!r},".format(dir)) + print("]") + + def exists(path): + if os.path.isdir(path): + return "exists" + else: + return "doesn't exist" + + print("USER_BASE: {!r} ({})".format(USER_BASE, exists(USER_BASE))) + print("USER_SITE: {!r} ({})".format(USER_SITE, exists(USER_SITE))) + print("ENABLE_USER_SITE: %r" % ENABLE_USER_SITE) + sys.exit(0) + + buffer = [] + if "--user-base" in args: + buffer.append(USER_BASE) + if "--user-site" in args: + buffer.append(USER_SITE) + + if buffer: + print(os.pathsep.join(buffer)) + if ENABLE_USER_SITE: + sys.exit(0) + elif ENABLE_USER_SITE is False: + sys.exit(1) + elif ENABLE_USER_SITE is None: + sys.exit(2) + else: + sys.exit(3) + else: + import textwrap + + print(textwrap.dedent(help % (sys.argv[0], os.pathsep))) + sys.exit(10) + + +if __name__ == "__main__": + _script() diff --git a/venv/lib/python2.7/sre.py b/venv/lib/python2.7/sre.py new file mode 120000 index 00000000..282157f8 --- /dev/null +++ b/venv/lib/python2.7/sre.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sre.py \ No newline at end of file diff --git a/venv/lib/python2.7/sre_compile.py b/venv/lib/python2.7/sre_compile.py new file mode 120000 index 00000000..aae50607 --- /dev/null +++ b/venv/lib/python2.7/sre_compile.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sre_compile.py \ No newline at end of file diff --git a/venv/lib/python2.7/sre_constants.py b/venv/lib/python2.7/sre_constants.py new file mode 120000 index 00000000..ec54ee37 --- /dev/null +++ b/venv/lib/python2.7/sre_constants.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sre_constants.py \ No newline at end of file diff --git a/venv/lib/python2.7/sre_parse.py b/venv/lib/python2.7/sre_parse.py new file mode 120000 index 00000000..ee2f0c39 --- /dev/null +++ b/venv/lib/python2.7/sre_parse.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sre_parse.py \ No newline at end of file diff --git a/venv/lib/python2.7/stat.py b/venv/lib/python2.7/stat.py new file mode 120000 index 00000000..f406b15d --- /dev/null +++ b/venv/lib/python2.7/stat.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/stat.py \ No newline at end of file diff --git a/venv/lib/python2.7/types.py b/venv/lib/python2.7/types.py new file mode 120000 index 00000000..4769ef6e --- /dev/null +++ b/venv/lib/python2.7/types.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/types.py \ No newline at end of file diff --git a/venv/lib/python2.7/warnings.py b/venv/lib/python2.7/warnings.py new file mode 120000 index 00000000..68ff8e66 --- /dev/null +++ b/venv/lib/python2.7/warnings.py @@ -0,0 +1 @@ +/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/warnings.py \ No newline at end of file