igs-slm 0.1.0b0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (447) hide show
  1. igs_slm-0.1.0b0.dist-info/LICENSE +21 -0
  2. igs_slm-0.1.0b0.dist-info/METADATA +151 -0
  3. igs_slm-0.1.0b0.dist-info/RECORD +447 -0
  4. igs_slm-0.1.0b0.dist-info/WHEEL +4 -0
  5. igs_slm-0.1.0b0.dist-info/entry_points.txt +3 -0
  6. igs_tools/__init__.py +0 -0
  7. igs_tools/connection.py +88 -0
  8. igs_tools/defines/__init__.py +8 -0
  9. igs_tools/defines/constellation.py +21 -0
  10. igs_tools/defines/data_center.py +75 -0
  11. igs_tools/defines/rinex.py +49 -0
  12. igs_tools/directory.py +247 -0
  13. igs_tools/utils.py +66 -0
  14. slm/__init__.py +21 -0
  15. slm/admin.py +674 -0
  16. slm/api/edit/__init__.py +0 -0
  17. slm/api/edit/serializers.py +316 -0
  18. slm/api/edit/views.py +1632 -0
  19. slm/api/fields.py +89 -0
  20. slm/api/filter.py +504 -0
  21. slm/api/pagination.py +55 -0
  22. slm/api/permissions.py +65 -0
  23. slm/api/public/__init__.py +0 -0
  24. slm/api/public/serializers.py +249 -0
  25. slm/api/public/views.py +606 -0
  26. slm/api/serializers.py +132 -0
  27. slm/api/views.py +148 -0
  28. slm/apps.py +323 -0
  29. slm/authentication.py +198 -0
  30. slm/bin/__init__.py +0 -0
  31. slm/bin/startproject.py +262 -0
  32. slm/bin/templates/{{ project_dir }}/pyproject.toml +35 -0
  33. slm/bin/templates/{{ project_dir }}/sites/__init__.py +0 -0
  34. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/__init__.py +0 -0
  35. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/base.py +15 -0
  36. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/develop/__init__.py +56 -0
  37. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/develop/local.py +4 -0
  38. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/develop/wsgi.py +16 -0
  39. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/manage.py +34 -0
  40. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/production/__init__.py +61 -0
  41. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/production/wsgi.py +16 -0
  42. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/urls.py +7 -0
  43. slm/bin/templates/{{ project_dir }}/sites/{{ site }}/validation.py +11 -0
  44. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/__init__.py +0 -0
  45. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/admin.py +5 -0
  46. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/apps.py +14 -0
  47. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/management/__init__.py +0 -0
  48. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/management/commands/__init__.py +0 -0
  49. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/management/commands/import_archive.py +64 -0
  50. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/migrations/__init__.py +0 -0
  51. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/models.py +6 -0
  52. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/templates/slm/base.html +8 -0
  53. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/urls.py +10 -0
  54. slm/bin/templates/{{ project_dir }}/{{ extension_app }}/views.py +5 -0
  55. slm/defines/AlertLevel.py +24 -0
  56. slm/defines/AntennaCalibration.py +25 -0
  57. slm/defines/AntennaFeatures.py +27 -0
  58. slm/defines/AntennaReferencePoint.py +22 -0
  59. slm/defines/Aspiration.py +13 -0
  60. slm/defines/CardinalDirection.py +19 -0
  61. slm/defines/CollocationStatus.py +12 -0
  62. slm/defines/EquipmentState.py +22 -0
  63. slm/defines/FlagSeverity.py +14 -0
  64. slm/defines/FractureSpacing.py +15 -0
  65. slm/defines/FrequencyStandardType.py +15 -0
  66. slm/defines/GeodesyMLVersion.py +48 -0
  67. slm/defines/ISOCountry.py +1194 -0
  68. slm/defines/Instrumentation.py +19 -0
  69. slm/defines/LogEntryType.py +30 -0
  70. slm/defines/SLMFileType.py +18 -0
  71. slm/defines/SiteFileUploadStatus.py +61 -0
  72. slm/defines/SiteLogFormat.py +49 -0
  73. slm/defines/SiteLogStatus.py +78 -0
  74. slm/defines/TectonicPlates.py +28 -0
  75. slm/defines/__init__.py +46 -0
  76. slm/forms.py +1126 -0
  77. slm/jinja2/slm/sitelog/ascii_9char.log +346 -0
  78. slm/jinja2/slm/sitelog/legacy.log +346 -0
  79. slm/jinja2/slm/sitelog/xsd/0.4/collocationInformation.xml +12 -0
  80. slm/jinja2/slm/sitelog/xsd/0.4/condition.xml +12 -0
  81. slm/jinja2/slm/sitelog/xsd/0.4/contact.xml +52 -0
  82. slm/jinja2/slm/sitelog/xsd/0.4/formInformation.xml +5 -0
  83. slm/jinja2/slm/sitelog/xsd/0.4/frequencyStandard.xml +12 -0
  84. slm/jinja2/slm/sitelog/xsd/0.4/gnssAntenna.xml +16 -0
  85. slm/jinja2/slm/sitelog/xsd/0.4/gnssReceiver.xml +11 -0
  86. slm/jinja2/slm/sitelog/xsd/0.4/humiditySensor.xml +13 -0
  87. slm/jinja2/slm/sitelog/xsd/0.4/localEpisodicEffect.xml +10 -0
  88. slm/jinja2/slm/sitelog/xsd/0.4/moreInformation.xml +22 -0
  89. slm/jinja2/slm/sitelog/xsd/0.4/multipathSource.xml +10 -0
  90. slm/jinja2/slm/sitelog/xsd/0.4/otherInstrumentation.xml +5 -0
  91. slm/jinja2/slm/sitelog/xsd/0.4/pressureSensor.xml +12 -0
  92. slm/jinja2/slm/sitelog/xsd/0.4/radioInterference.xml +11 -0
  93. slm/jinja2/slm/sitelog/xsd/0.4/sensor.xml +16 -0
  94. slm/jinja2/slm/sitelog/xsd/0.4/signalObstruction.xml +10 -0
  95. slm/jinja2/slm/sitelog/xsd/0.4/siteIdentification.xml +22 -0
  96. slm/jinja2/slm/sitelog/xsd/0.4/siteLocation.xml +21 -0
  97. slm/jinja2/slm/sitelog/xsd/0.4/surveyedLocalTie.xml +20 -0
  98. slm/jinja2/slm/sitelog/xsd/0.4/temperatureSensor.xml +13 -0
  99. slm/jinja2/slm/sitelog/xsd/0.4/waterVaporSensor.xml +11 -0
  100. slm/jinja2/slm/sitelog/xsd/0.5/document.xml +10 -0
  101. slm/jinja2/slm/sitelog/xsd/geodesyml_0.4.xml +99 -0
  102. slm/jinja2/slm/sitelog/xsd/geodesyml_0.5.xml +112 -0
  103. slm/management/__init__.py +0 -0
  104. slm/management/commands/__init__.py +53 -0
  105. slm/management/commands/build_index.py +96 -0
  106. slm/management/commands/generate_sinex.py +675 -0
  107. slm/management/commands/head_from_index.py +541 -0
  108. slm/management/commands/import_archive.py +908 -0
  109. slm/management/commands/import_equipment.py +351 -0
  110. slm/management/commands/set_site.py +56 -0
  111. slm/management/commands/sitelog.py +144 -0
  112. slm/management/commands/synchronize.py +60 -0
  113. slm/management/commands/update_data_availability.py +167 -0
  114. slm/management/commands/validate_db.py +186 -0
  115. slm/management/commands/validate_gml.py +73 -0
  116. slm/map/__init__.py +1 -0
  117. slm/map/admin.py +5 -0
  118. slm/map/api/__init__.py +0 -0
  119. slm/map/api/edit/__init__.py +0 -0
  120. slm/map/api/edit/serializers.py +28 -0
  121. slm/map/api/edit/views.py +46 -0
  122. slm/map/api/public/__init__.py +0 -0
  123. slm/map/api/public/serializers.py +29 -0
  124. slm/map/api/public/views.py +64 -0
  125. slm/map/apps.py +7 -0
  126. slm/map/defines.py +53 -0
  127. slm/map/migrations/0001_initial.py +115 -0
  128. slm/map/migrations/__init__.py +0 -0
  129. slm/map/models.py +63 -0
  130. slm/map/static/slm/css/map.css +86 -0
  131. slm/map/static/slm/js/map.js +159 -0
  132. slm/map/templates/slm/map.html +374 -0
  133. slm/map/templates/slm/station/base.html +11 -0
  134. slm/map/templates/slm/station/edit.html +10 -0
  135. slm/map/templates/slm/top_nav.html +17 -0
  136. slm/map/templatetags/__init__.py +0 -0
  137. slm/map/templatetags/slm_map.py +18 -0
  138. slm/map/urls.py +25 -0
  139. slm/map/views.py +36 -0
  140. slm/middleware.py +29 -0
  141. slm/migrations/0001_alter_siteantenna_marker_enu_alter_sitelocation_llh_and_more.py +47 -0
  142. slm/migrations/0001_initial.py +4826 -0
  143. slm/migrations/0002_alter_dataavailability_site.py +22 -0
  144. slm/migrations/0003_remove_logentry_slm_logentr_site_lo_7a2af7_idx_and_more.py +80 -0
  145. slm/migrations/0004_alter_logentry_timestamp_and_more.py +25 -0
  146. slm/migrations/0005_alter_logentry_options_alter_logentry_section_and_more.py +46 -0
  147. slm/migrations/0006_alter_logentry_options_alter_logentry_index_together.py +24 -0
  148. slm/migrations/0007_alter_dataavailability_rate.py +23 -0
  149. slm/migrations/0008_alter_archiveindex_options_and_more.py +64 -0
  150. slm/migrations/0009_alter_archiveindex_end.py +21 -0
  151. slm/migrations/0010_alter_dataavailability_rinex_version_and_more.py +844 -0
  152. slm/migrations/0011_alter_siteidentification_fracture_spacing.py +33 -0
  153. slm/migrations/0012_alter_logentry_type.py +36 -0
  154. slm/migrations/0013_unpublishedfilesalert.py +48 -0
  155. slm/migrations/0014_sitelogpublished.py +48 -0
  156. slm/migrations/0015_alter_siteantenna_options_and_more.py +181 -0
  157. slm/migrations/0016_alter_antenna_description_alter_radome_description_and_more.py +42 -0
  158. slm/migrations/0017_alter_logentry_unique_together_and_more.py +54 -0
  159. slm/migrations/0018_afix_deleted.py +34 -0
  160. slm/migrations/0018_alter_siteantenna_options_and_more.py +244 -0
  161. slm/migrations/0019_remove_siteantenna_marker_enu_siteantenna_marker_une_and_more.py +101 -0
  162. slm/migrations/0020_alter_manufacturer_options.py +16 -0
  163. slm/migrations/0021_alter_siteform_report_type.py +23 -0
  164. slm/migrations/0022_rename_antcal_antenna_radome_slm_antcal_antenna_20827a_idx_and_more.py +297 -0
  165. slm/migrations/0023_archivedsitelog_gml_version_and_more.py +55 -0
  166. slm/migrations/0024_alter_agency_name_alter_agency_shortname.py +24 -0
  167. slm/migrations/0025_alter_archivedsitelog_log_format_and_more.py +61 -0
  168. slm/migrations/0026_alter_archivedsitelog_log_format_and_more.py +61 -0
  169. slm/migrations/0027_importalert_file_contents_importalert_findings_and_more.py +41 -0
  170. slm/migrations/0028_antenna_replaced_manufacturer_url_radome_replaced_and_more.py +46 -0
  171. slm/migrations/0029_manufacturer_full_name.py +17 -0
  172. slm/migrations/0030_alter_antenna_state_alter_radome_state_and_more.py +43 -0
  173. slm/migrations/__init__.py +0 -0
  174. slm/migrations/load_satellitesystems.py +27 -0
  175. slm/models/__init__.py +118 -0
  176. slm/models/about.py +14 -0
  177. slm/models/alerts.py +1204 -0
  178. slm/models/data.py +58 -0
  179. slm/models/equipment.py +229 -0
  180. slm/models/help.py +14 -0
  181. slm/models/index.py +428 -0
  182. slm/models/sitelog.py +4279 -0
  183. slm/models/system.py +723 -0
  184. slm/models/user.py +304 -0
  185. slm/parsing/__init__.py +786 -0
  186. slm/parsing/legacy/__init__.py +4 -0
  187. slm/parsing/legacy/binding.py +817 -0
  188. slm/parsing/legacy/parser.py +377 -0
  189. slm/parsing/xsd/__init__.py +34 -0
  190. slm/parsing/xsd/binding.py +86 -0
  191. slm/parsing/xsd/geodesyml/0.4/commonTypes.xsd +133 -0
  192. slm/parsing/xsd/geodesyml/0.4/contact.xsd +29 -0
  193. slm/parsing/xsd/geodesyml/0.4/dataStreams.xsd +129 -0
  194. slm/parsing/xsd/geodesyml/0.4/document.xsd +64 -0
  195. slm/parsing/xsd/geodesyml/0.4/equipment.xsd +427 -0
  196. slm/parsing/xsd/geodesyml/0.4/fieldMeasurement.xsd +170 -0
  197. slm/parsing/xsd/geodesyml/0.4/geodesyML.xsd +71 -0
  198. slm/parsing/xsd/geodesyml/0.4/geodeticEquipment.xsd +343 -0
  199. slm/parsing/xsd/geodesyml/0.4/geodeticMonument.xsd +147 -0
  200. slm/parsing/xsd/geodesyml/0.4/lineage.xsd +614 -0
  201. slm/parsing/xsd/geodesyml/0.4/localInterferences.xsd +131 -0
  202. slm/parsing/xsd/geodesyml/0.4/measurement.xsd +473 -0
  203. slm/parsing/xsd/geodesyml/0.4/monumentInfo.xsd +251 -0
  204. slm/parsing/xsd/geodesyml/0.4/observationSystem.xsd +429 -0
  205. slm/parsing/xsd/geodesyml/0.4/project.xsd +38 -0
  206. slm/parsing/xsd/geodesyml/0.4/quality.xsd +176 -0
  207. slm/parsing/xsd/geodesyml/0.4/referenceFrame.xsd +194 -0
  208. slm/parsing/xsd/geodesyml/0.4/siteLog.xsd +71 -0
  209. slm/parsing/xsd/geodesyml/0.5/commonTypes.xsd +133 -0
  210. slm/parsing/xsd/geodesyml/0.5/contact.xsd +29 -0
  211. slm/parsing/xsd/geodesyml/0.5/dataStreams.xsd +129 -0
  212. slm/parsing/xsd/geodesyml/0.5/document.xsd +64 -0
  213. slm/parsing/xsd/geodesyml/0.5/equipment.xsd +427 -0
  214. slm/parsing/xsd/geodesyml/0.5/fieldMeasurement.xsd +170 -0
  215. slm/parsing/xsd/geodesyml/0.5/geodesyML.xsd +71 -0
  216. slm/parsing/xsd/geodesyml/0.5/geodeticEquipment.xsd +343 -0
  217. slm/parsing/xsd/geodesyml/0.5/geodeticMonument.xsd +147 -0
  218. slm/parsing/xsd/geodesyml/0.5/lineage.xsd +614 -0
  219. slm/parsing/xsd/geodesyml/0.5/localInterferences.xsd +131 -0
  220. slm/parsing/xsd/geodesyml/0.5/measurement.xsd +473 -0
  221. slm/parsing/xsd/geodesyml/0.5/monumentInfo.xsd +306 -0
  222. slm/parsing/xsd/geodesyml/0.5/observationSystem.xsd +429 -0
  223. slm/parsing/xsd/geodesyml/0.5/project.xsd +38 -0
  224. slm/parsing/xsd/geodesyml/0.5/quality.xsd +176 -0
  225. slm/parsing/xsd/geodesyml/0.5/referenceFrame.xsd +194 -0
  226. slm/parsing/xsd/geodesyml/0.5/siteLog.xsd +73 -0
  227. slm/parsing/xsd/parser.py +116 -0
  228. slm/parsing/xsd/resolver.py +28 -0
  229. slm/receivers/__init__.py +11 -0
  230. slm/receivers/alerts.py +87 -0
  231. slm/receivers/cleanup.py +41 -0
  232. slm/receivers/event_loggers.py +175 -0
  233. slm/receivers/index.py +67 -0
  234. slm/settings/__init__.py +55 -0
  235. slm/settings/auth.py +15 -0
  236. slm/settings/ckeditor.py +14 -0
  237. slm/settings/debug.py +47 -0
  238. slm/settings/internationalization.py +12 -0
  239. slm/settings/logging.py +113 -0
  240. slm/settings/platform/__init__.py +0 -0
  241. slm/settings/platform/darwin.py +10 -0
  242. slm/settings/rest.py +21 -0
  243. slm/settings/root.py +152 -0
  244. slm/settings/routines.py +43 -0
  245. slm/settings/secrets.py +37 -0
  246. slm/settings/security.py +5 -0
  247. slm/settings/slm.py +188 -0
  248. slm/settings/static_templates.py +53 -0
  249. slm/settings/templates.py +29 -0
  250. slm/settings/uploads.py +8 -0
  251. slm/settings/urls.py +126 -0
  252. slm/settings/validation.py +196 -0
  253. slm/signals.py +250 -0
  254. slm/singleton.py +49 -0
  255. slm/static/rest_framework/css/bootstrap-tweaks.css +204 -0
  256. slm/static/rest_framework/css/bootstrap.min.css +7 -0
  257. slm/static/rest_framework/css/bootstrap.min.css.map +1 -0
  258. slm/static/rest_framework/css/default.css +82 -0
  259. slm/static/rest_framework/css/prettify.css +30 -0
  260. slm/static/rest_framework/docs/css/base.css +344 -0
  261. slm/static/rest_framework/docs/css/highlight.css +125 -0
  262. slm/static/rest_framework/docs/css/jquery.json-view.min.css +11 -0
  263. slm/static/rest_framework/docs/img/favicon.ico +0 -0
  264. slm/static/rest_framework/docs/img/grid.png +0 -0
  265. slm/static/rest_framework/docs/js/api.js +321 -0
  266. slm/static/rest_framework/docs/js/highlight.pack.js +2 -0
  267. slm/static/rest_framework/docs/js/jquery.json-view.min.js +7 -0
  268. slm/static/rest_framework/img/grid.png +0 -0
  269. slm/static/rest_framework/js/ajax-form.js +127 -0
  270. slm/static/rest_framework/js/bootstrap.bundle.min.js +7 -0
  271. slm/static/rest_framework/js/bootstrap.bundle.min.js.map +1 -0
  272. slm/static/rest_framework/js/bootstrap.min.js.map +1 -0
  273. slm/static/rest_framework/js/coreapi-0.1.1.js +2043 -0
  274. slm/static/rest_framework/js/csrf.js +52 -0
  275. slm/static/rest_framework/js/default.js +47 -0
  276. slm/static/rest_framework/js/jquery-3.5.1.min.js +2 -0
  277. slm/static/rest_framework/js/prettify-min.js +28 -0
  278. slm/static/slm/css/admin.css +3 -0
  279. slm/static/slm/css/defines.css +82 -0
  280. slm/static/slm/css/forms.css +1 -0
  281. slm/static/slm/css/style.css +1004 -0
  282. slm/static/slm/img/email-branding.png +0 -0
  283. slm/static/slm/img/favicon.ico +0 -0
  284. slm/static/slm/img/login-bg.jpg +0 -0
  285. slm/static/slm/img/slm-logo.svg +4 -0
  286. slm/static/slm/js/autocomplete.js +341 -0
  287. slm/static/slm/js/enums.js +322 -0
  288. slm/static/slm/js/fileIcons.js +30 -0
  289. slm/static/slm/js/form.js +404 -0
  290. slm/static/slm/js/formWidget.js +23 -0
  291. slm/static/slm/js/persistable.js +33 -0
  292. slm/static/slm/js/slm.js +1028 -0
  293. slm/static/slm/js/time24.js +212 -0
  294. slm/static_templates/slm/css/defines.css +26 -0
  295. slm/static_templates/slm/js/enums.js +28 -0
  296. slm/static_templates/slm/js/fileIcons.js +16 -0
  297. slm/static_templates/slm/js/urls.js +5 -0
  298. slm/templates/account/base.html +20 -0
  299. slm/templates/account/email/base.html +43 -0
  300. slm/templates/account/email/base_message.txt +7 -0
  301. slm/templates/account/email/email_confirmation_message.html +16 -0
  302. slm/templates/account/email/email_confirmation_message.txt +7 -0
  303. slm/templates/account/email/email_confirmation_signup_message.html +1 -0
  304. slm/templates/account/email/email_confirmation_signup_message.txt +1 -0
  305. slm/templates/account/email/email_confirmation_signup_subject.txt +1 -0
  306. slm/templates/account/email/email_confirmation_subject.txt +4 -0
  307. slm/templates/account/email/password_reset_key_message.html +28 -0
  308. slm/templates/account/email/password_reset_key_message.txt +9 -0
  309. slm/templates/account/email/password_reset_key_subject.txt +4 -0
  310. slm/templates/account/email/unknown_account_message.html +25 -0
  311. slm/templates/account/email/unknown_account_message.txt +12 -0
  312. slm/templates/account/email/unknown_account_subject.txt +4 -0
  313. slm/templates/account/login.html +67 -0
  314. slm/templates/account/logout.html +38 -0
  315. slm/templates/account/password_change.html +48 -0
  316. slm/templates/account/password_reset.html +51 -0
  317. slm/templates/account/password_reset_done.html +20 -0
  318. slm/templates/account/password_reset_from_key.html +52 -0
  319. slm/templates/account/password_reset_from_key_done.html +17 -0
  320. slm/templates/admin/base.html +7 -0
  321. slm/templates/messages.html +8 -0
  322. slm/templates/rest_framework/README +16 -0
  323. slm/templates/rest_framework/admin/detail.html +10 -0
  324. slm/templates/rest_framework/admin/dict_value.html +11 -0
  325. slm/templates/rest_framework/admin/list.html +32 -0
  326. slm/templates/rest_framework/admin/list_value.html +11 -0
  327. slm/templates/rest_framework/admin/simple_list_value.html +2 -0
  328. slm/templates/rest_framework/admin.html +282 -0
  329. slm/templates/rest_framework/api.html +3 -0
  330. slm/templates/rest_framework/base.html +334 -0
  331. slm/templates/rest_framework/docs/auth/basic.html +42 -0
  332. slm/templates/rest_framework/docs/auth/session.html +40 -0
  333. slm/templates/rest_framework/docs/auth/token.html +41 -0
  334. slm/templates/rest_framework/docs/document.html +37 -0
  335. slm/templates/rest_framework/docs/error.html +71 -0
  336. slm/templates/rest_framework/docs/index.html +55 -0
  337. slm/templates/rest_framework/docs/interact.html +57 -0
  338. slm/templates/rest_framework/docs/langs/javascript-intro.html +5 -0
  339. slm/templates/rest_framework/docs/langs/javascript.html +15 -0
  340. slm/templates/rest_framework/docs/langs/python-intro.html +3 -0
  341. slm/templates/rest_framework/docs/langs/python.html +13 -0
  342. slm/templates/rest_framework/docs/langs/shell-intro.html +3 -0
  343. slm/templates/rest_framework/docs/langs/shell.html +6 -0
  344. slm/templates/rest_framework/docs/link.html +113 -0
  345. slm/templates/rest_framework/docs/sidebar.html +78 -0
  346. slm/templates/rest_framework/filters/base.html +16 -0
  347. slm/templates/rest_framework/filters/ordering.html +17 -0
  348. slm/templates/rest_framework/filters/search.html +13 -0
  349. slm/templates/rest_framework/horizontal/checkbox.html +23 -0
  350. slm/templates/rest_framework/horizontal/checkbox_multiple.html +32 -0
  351. slm/templates/rest_framework/horizontal/dict_field.html +11 -0
  352. slm/templates/rest_framework/horizontal/fieldset.html +16 -0
  353. slm/templates/rest_framework/horizontal/form.html +6 -0
  354. slm/templates/rest_framework/horizontal/input.html +21 -0
  355. slm/templates/rest_framework/horizontal/list_field.html +11 -0
  356. slm/templates/rest_framework/horizontal/list_fieldset.html +13 -0
  357. slm/templates/rest_framework/horizontal/radio.html +42 -0
  358. slm/templates/rest_framework/horizontal/select.html +36 -0
  359. slm/templates/rest_framework/horizontal/select_multiple.html +38 -0
  360. slm/templates/rest_framework/horizontal/textarea.html +21 -0
  361. slm/templates/rest_framework/inline/checkbox.html +8 -0
  362. slm/templates/rest_framework/inline/checkbox_multiple.html +14 -0
  363. slm/templates/rest_framework/inline/dict_field.html +9 -0
  364. slm/templates/rest_framework/inline/fieldset.html +6 -0
  365. slm/templates/rest_framework/inline/form.html +8 -0
  366. slm/templates/rest_framework/inline/input.html +9 -0
  367. slm/templates/rest_framework/inline/list_field.html +9 -0
  368. slm/templates/rest_framework/inline/list_fieldset.html +3 -0
  369. slm/templates/rest_framework/inline/radio.html +25 -0
  370. slm/templates/rest_framework/inline/select.html +24 -0
  371. slm/templates/rest_framework/inline/select_multiple.html +25 -0
  372. slm/templates/rest_framework/inline/textarea.html +9 -0
  373. slm/templates/rest_framework/login.html +3 -0
  374. slm/templates/rest_framework/login_base.html +65 -0
  375. slm/templates/rest_framework/pagination/numbers.html +47 -0
  376. slm/templates/rest_framework/pagination/previous_and_next.html +21 -0
  377. slm/templates/rest_framework/raw_data_form.html +11 -0
  378. slm/templates/rest_framework/schema.js +3 -0
  379. slm/templates/rest_framework/vertical/checkbox.html +16 -0
  380. slm/templates/rest_framework/vertical/checkbox_multiple.html +30 -0
  381. slm/templates/rest_framework/vertical/dict_field.html +7 -0
  382. slm/templates/rest_framework/vertical/fieldset.html +13 -0
  383. slm/templates/rest_framework/vertical/form.html +6 -0
  384. slm/templates/rest_framework/vertical/input.html +17 -0
  385. slm/templates/rest_framework/vertical/list_field.html +7 -0
  386. slm/templates/rest_framework/vertical/list_fieldset.html +7 -0
  387. slm/templates/rest_framework/vertical/radio.html +40 -0
  388. slm/templates/rest_framework/vertical/select.html +34 -0
  389. slm/templates/rest_framework/vertical/select_multiple.html +31 -0
  390. slm/templates/rest_framework/vertical/textarea.html +17 -0
  391. slm/templates/slm/about.html +21 -0
  392. slm/templates/slm/alerts/alert.html +15 -0
  393. slm/templates/slm/alerts/geodesymlinvalid.html +8 -0
  394. slm/templates/slm/alerts/importalert.html +10 -0
  395. slm/templates/slm/alerts.html +18 -0
  396. slm/templates/slm/auth_menu.html +41 -0
  397. slm/templates/slm/base.html +195 -0
  398. slm/templates/slm/emails/alert_issued.html +31 -0
  399. slm/templates/slm/emails/alert_issued.txt +9 -0
  400. slm/templates/slm/emails/base.html +6 -0
  401. slm/templates/slm/emails/changes_rejected.txt +7 -0
  402. slm/templates/slm/emails/review_requested.txt +7 -0
  403. slm/templates/slm/forms/widgets/auto_complete.html +21 -0
  404. slm/templates/slm/forms/widgets/auto_complete_multiple.html +18 -0
  405. slm/templates/slm/forms/widgets/checkbox_multiple.html +6 -0
  406. slm/templates/slm/forms/widgets/inline_multi.html +1 -0
  407. slm/templates/slm/forms/widgets/splitdatetime.html +14 -0
  408. slm/templates/slm/forms/widgets/time24.html +37 -0
  409. slm/templates/slm/help.html +54 -0
  410. slm/templates/slm/messages.html +13 -0
  411. slm/templates/slm/new_site.html +88 -0
  412. slm/templates/slm/profile.html +57 -0
  413. slm/templates/slm/register.html +40 -0
  414. slm/templates/slm/reports/file_log.html +43 -0
  415. slm/templates/slm/reports/head_log.html +23 -0
  416. slm/templates/slm/reports/head_report.html +55 -0
  417. slm/templates/slm/reports/index_log.html +23 -0
  418. slm/templates/slm/reports/index_report.html +71 -0
  419. slm/templates/slm/station/alert.html +8 -0
  420. slm/templates/slm/station/alerts.html +19 -0
  421. slm/templates/slm/station/base.html +104 -0
  422. slm/templates/slm/station/download.html +87 -0
  423. slm/templates/slm/station/edit.html +283 -0
  424. slm/templates/slm/station/form.html +110 -0
  425. slm/templates/slm/station/log.html +18 -0
  426. slm/templates/slm/station/review.html +461 -0
  427. slm/templates/slm/station/upload.html +295 -0
  428. slm/templates/slm/station/uploads/attachment.html +20 -0
  429. slm/templates/slm/station/uploads/geodesyml.html +1 -0
  430. slm/templates/slm/station/uploads/image.html +27 -0
  431. slm/templates/slm/station/uploads/json.html +0 -0
  432. slm/templates/slm/station/uploads/legacy.html +77 -0
  433. slm/templates/slm/top_nav.html +14 -0
  434. slm/templates/slm/user_activity.html +16 -0
  435. slm/templates/slm/widgets/alert_scroll.html +135 -0
  436. slm/templates/slm/widgets/filelist.html +258 -0
  437. slm/templates/slm/widgets/legend.html +12 -0
  438. slm/templates/slm/widgets/log_scroll.html +88 -0
  439. slm/templates/slm/widgets/stationlist.html +233 -0
  440. slm/templatetags/__init__.py +0 -0
  441. slm/templatetags/jinja2.py +9 -0
  442. slm/templatetags/slm.py +459 -0
  443. slm/urls.py +148 -0
  444. slm/utils.py +299 -0
  445. slm/validators.py +297 -0
  446. slm/views.py +654 -0
  447. slm/widgets.py +134 -0
@@ -0,0 +1,786 @@
1
+ import re
2
+ from dataclasses import dataclass
3
+ from datetime import date, datetime, timezone
4
+ from functools import partial
5
+ from typing import Any, Dict, List, Optional, Tuple, Union
6
+
7
+ from dateutil.parser import parse as parse_date
8
+ from dateutil.tz.tz import tzlocal
9
+ from django.contrib.gis.geos import Point
10
+
11
+ from slm.models.equipment import Antenna, Radome, Receiver, SatelliteSystem
12
+ from slm.utils import dddmmssss_to_decimal
13
+
14
+ SPECIAL_CHARACTERS = "().,-_[]{}<>+%"
15
+ NUMERIC_CHARACTERS = ".+-Ee"
16
+ SKIPPED_NUMERICS = "',()"
17
+ NULL_VALUES = [
18
+ "unknown",
19
+ "unkn",
20
+ "n/a",
21
+ "none",
22
+ "not measured",
23
+ "provisional",
24
+ "?",
25
+ "programmable",
26
+ ]
27
+
28
+ ACCURACY_PREFIXES = [
29
+ "approx.",
30
+ "approx",
31
+ "+-",
32
+ "-+",
33
+ "+/-",
34
+ "-/+",
35
+ "±",
36
+ "~",
37
+ "<",
38
+ "Better than",
39
+ "=/-",
40
+ "+/_", # common QWERTY typos
41
+ ]
42
+ METERS = ["m", "m.", "meter", "meters"]
43
+
44
+ TZ_INFOS = {"UT": timezone.utc, "UTUT": timezone.utc}
45
+
46
+
47
+ def remove_from_start(value: str, prefixes: List[str]):
48
+ pattern = re.compile(
49
+ r"^("
50
+ + "|".join(
51
+ [re.escape(prefix.strip()).replace(r"\ ", r"\s*") for prefix in prefixes]
52
+ )
53
+ + r")\s*",
54
+ re.IGNORECASE,
55
+ )
56
+ result = pattern.sub("", value).strip()
57
+ return result
58
+
59
+
60
+ @dataclass
61
+ class _Ignored:
62
+ msg: str
63
+
64
+
65
+ @dataclass
66
+ class _Warning:
67
+ """Used to wrap a warning message with a value as the return type for binding functions"""
68
+
69
+ msg: str
70
+ value: Any
71
+
72
+
73
+ def normalize(name):
74
+ """
75
+ Normalization is designed to remove any superficial variable name
76
+ mismatches. We remove all special characters and spaces and then
77
+ upper case the name.
78
+ """
79
+ for char in SPECIAL_CHARACTERS + " \t":
80
+ name = name.replace(char, "")
81
+ return name.upper().strip()
82
+
83
+
84
+ class Finding:
85
+ """
86
+ A base class for parser/binding findings.
87
+ """
88
+
89
+ priority: int = 0
90
+
91
+ def __init__(
92
+ self, lineno, parser, message, section=None, parameter=None, line=None
93
+ ):
94
+ self.lineno = lineno
95
+ self.parser = parser
96
+ self.message = message
97
+ self.section = section
98
+ self.parameter = parameter
99
+ self.line = line
100
+
101
+ def __str__(self):
102
+ return (
103
+ f"({self.lineno+1: 4}) {self.parser.lines[self.lineno]}"
104
+ f'{" " * (80-len(self.parser.lines[self.lineno]))}'
105
+ f"[{self.level.upper()}]: {self.message}"
106
+ )
107
+
108
+ @property
109
+ def level(self) -> str:
110
+ return self.__class__.__name__.lower()
111
+
112
+
113
+ class Ignored(Finding):
114
+ level = "I"
115
+
116
+
117
+ class Error(Finding):
118
+ priority = 5
119
+
120
+ level = "E"
121
+
122
+
123
+ class Warn(Finding):
124
+ priority = 4
125
+
126
+ level = "W"
127
+
128
+
129
+ class BaseParameter:
130
+ name = ""
131
+ parser = None
132
+ section = None
133
+ line_no = None
134
+ line_end = None
135
+ values = None
136
+
137
+ binding = None
138
+
139
+ @property
140
+ def is_placeholder(self):
141
+ return False
142
+
143
+ @property
144
+ def is_empty(self):
145
+ return not self.value.strip()
146
+
147
+ @property
148
+ def num_lines(self):
149
+ return self.line_end - self.line_no + 1
150
+
151
+ @property
152
+ def lines(self):
153
+ return self.parser.lines[self.line_no : self.line_end]
154
+
155
+ def __init__(self, line_no, name, values, parser, section):
156
+ self.line_no = line_no
157
+ self.line_end = line_no
158
+ self.parser = parser
159
+ self.section = section
160
+ self.name = name
161
+ self.values = values
162
+
163
+ def append(self, line_no, match):
164
+ self.line_end = line_no
165
+ self.values.append(match.group(1).strip())
166
+
167
+ @property
168
+ def value(self):
169
+ return "\n".join(self.values)
170
+
171
+ @property
172
+ def normalized_name(self):
173
+ return normalize(self.name)
174
+
175
+ def bind(self, name, value):
176
+ if self.binding is None:
177
+ self.binding = {}
178
+ self.binding[name] = value
179
+ self.section.bind(name, self, value)
180
+
181
+ def __str__(self):
182
+ if self.is_placeholder:
183
+ return f"{self.name} [PLACEHOLDER]: {self.value}"
184
+ return f"{self.name}: {self.value}"
185
+
186
+
187
+ class BaseSection:
188
+ line_no = None
189
+ line_end = None
190
+ section_number = None
191
+ subsection_number = None
192
+ order = None
193
+ header = ""
194
+
195
+ parameters: Dict[str, BaseParameter]
196
+ example = False
197
+
198
+ _param_binding_: Dict[str, List[BaseParameter]] = None
199
+ _binding_: Dict[str, Union[str, int, float, date, datetime]] = None
200
+
201
+ def get_params(self, name: str) -> List[BaseParameter]:
202
+ """
203
+ Get parameter by parsing or bound name.
204
+ """
205
+ self._param_binding_ = self._param_binding_ or {}
206
+ return self._param_binding_.get(
207
+ name,
208
+ (
209
+ [self.parameters[normalize(name)]]
210
+ if normalize(name) in self.parameters
211
+ else []
212
+ ),
213
+ )
214
+
215
+ def bind(self, name, parameter, value):
216
+ """
217
+ Bind a parameter to the given name and value.
218
+ """
219
+ self._binding_ = self._binding_ or {}
220
+ self._param_binding_ = self._param_binding_ or {}
221
+ self._param_binding_.setdefault(name, []).append(parameter)
222
+ self._binding_[name] = value
223
+
224
+ def collate(self, params, param, value):
225
+ """
226
+ Combine the given bound params into a new single binding.
227
+ """
228
+ self._binding_ = self._binding_ or {}
229
+ self._param_binding_ = self._param_binding_ or {}
230
+ self._binding_[param] = value
231
+ for collated in params:
232
+ parsed_params = self._param_binding_.get(collated, [])
233
+ self._param_binding_.setdefault(param, []).extend(parsed_params)
234
+ if parsed_params:
235
+ del self._param_binding_[collated]
236
+
237
+ @property
238
+ def binding(self) -> Optional[Dict[str, Union[str, int, float, date, datetime]]]:
239
+ return self._binding_
240
+
241
+ @property
242
+ def ordering_id(self):
243
+ if self.order:
244
+ return self.order
245
+ if self.subsection_number:
246
+ return self.subsection_number
247
+ return None
248
+
249
+ def __init__(self, line_no, section_number, subsection_number, order, parser):
250
+ self.parameters = {}
251
+ self.line_no = line_no
252
+ self.line_end = line_no
253
+ self.section_number = section_number
254
+ self.subsection_number = subsection_number
255
+ self.order = order
256
+ self.parser = parser
257
+
258
+ if isinstance(self.subsection_number, str) and self.subsection_number.isdigit():
259
+ self.subsection_number = int(self.subsection_number)
260
+
261
+ if isinstance(self.order, str) and self.order.isdigit():
262
+ self.order = int(self.order)
263
+
264
+ def __str__(self):
265
+ section_str = f"{self.index_string} {self.header}\n"
266
+ if self.example:
267
+ section_str = f"{self.index_string} {self.header} (EXAMPLE)\n"
268
+ for name, param in self.parameters.items():
269
+ section_str += f"\t{param}\n"
270
+ return section_str
271
+
272
+ @property
273
+ def contains_values(self):
274
+ """
275
+ True if any parameter in this section contains a real value that is
276
+ not a placeholder.
277
+ """
278
+ for name, param in self.parameters.items():
279
+ if not (param.is_empty or param.is_placeholder):
280
+ return True
281
+ return False
282
+
283
+ @property
284
+ def index_string(self):
285
+ index = f"{self.section_number}"
286
+ if self.subsection_number or self.example:
287
+ index += "."
288
+ if self.subsection_number:
289
+ index += str(self.subsection_number)
290
+ else:
291
+ index += "x"
292
+ if self.subsection_number and (self.order or self.example):
293
+ index += f'.{self.order if self.order else "x"}'
294
+ return index
295
+
296
+ @property
297
+ def index_tuple(self):
298
+ return self.section_number, self.subsection_number, self.order
299
+
300
+ @property
301
+ def heading_index(self):
302
+ if self.order is not None:
303
+ return self.section_number, self.subsection_number
304
+ return self.section_number
305
+
306
+ def add_parameter(self, parameter):
307
+ if parameter.normalized_name in self.parameters:
308
+ self.parser.add_finding(
309
+ Error(
310
+ parameter.line_no,
311
+ self.parser,
312
+ f"Duplicate parameter: {parameter.name}",
313
+ section=self,
314
+ )
315
+ )
316
+ else:
317
+ self.parameters[parameter.normalized_name] = parameter
318
+ if self.line_end < parameter.line_end:
319
+ self.line_end = parameter.line_end
320
+ return parameter
321
+
322
+
323
+ class BaseParser:
324
+ """
325
+ A base parser that tracks findings at specific lines.
326
+ """
327
+
328
+ lines: Optional[List[str]]
329
+
330
+ name_matched: bool = None
331
+ site_name: str = None
332
+
333
+ _findings_: Dict[int, Finding]
334
+
335
+ """
336
+ Sections indexed by section number tuple
337
+ (section_number, subsection_number, order)
338
+ """
339
+ sections: Dict[Tuple[int, Optional[int], Optional[int]], BaseSection]
340
+
341
+ @property
342
+ def findings(self) -> Dict[int, Finding]:
343
+ """
344
+ Return findings dictionary keyed by line number and ordered by
345
+ line number.
346
+ """
347
+ # dictionaries are ordered in python 3.7+
348
+ return dict(sorted(self._findings_.items()))
349
+
350
+ @property
351
+ def findings_context(self) -> Dict[int, Tuple[str, str]]:
352
+ return {
353
+ int(line): (finding.level, finding.message)
354
+ for line, finding in self.findings.items()
355
+ }
356
+
357
+ @property
358
+ def context(self):
359
+ return {"site": self.site_name, "findings": self.findings_context}
360
+
361
+ @property
362
+ def ignored(self):
363
+ return {
364
+ lineno: finding
365
+ for lineno, finding in self._findings_.items()
366
+ if isinstance(finding, Ignored)
367
+ }
368
+
369
+ @property
370
+ def errors(self):
371
+ return {
372
+ lineno: finding
373
+ for lineno, finding in self._findings_.items()
374
+ if isinstance(finding, Error)
375
+ }
376
+
377
+ @property
378
+ def warnings(self):
379
+ return {
380
+ lineno: finding
381
+ for lineno, finding in self._findings_.items()
382
+ if isinstance(finding, Warn)
383
+ }
384
+
385
+ @property
386
+ def is_valid(self):
387
+ return len(self.errors) == 0
388
+
389
+ @property
390
+ def has_warnings(self):
391
+ return len(self.warnings) == 0
392
+
393
+ def remove_finding(self, lineno):
394
+ if lineno in self._findings_:
395
+ del self._findings_[lineno]
396
+
397
+ def add_section(self, section):
398
+ if section.index_tuple in self.sections:
399
+ self.duplicate_section_error(section)
400
+ self.sections[section.index_tuple] = section
401
+ return section
402
+
403
+ def duplicate_section_error(self, duplicate_section):
404
+ for lineno in range(duplicate_section.line_no, duplicate_section.line_end):
405
+ self.add_finding(
406
+ Error(
407
+ lineno,
408
+ self,
409
+ f"Duplicate section {duplicate_section.index_string}",
410
+ section=duplicate_section,
411
+ )
412
+ )
413
+
414
+ def add_finding(self, finding):
415
+ if not isinstance(finding, Finding):
416
+ raise ValueError(
417
+ f"add_finding() expected a {Finding.__class__.__name__} "
418
+ f"object, was given: {finding.__class__.__name__}."
419
+ )
420
+ self._findings_[finding.lineno] = finding
421
+
422
+ def __init__(self, site_log: Union[str, List[str]], site_name: str = None) -> None:
423
+ """
424
+ :param site_log: The entire site log contents as a string or as a list
425
+ of lines
426
+ :param site_name: The expected 9-character site name of this site or
427
+ None (default) if name is unknown
428
+ """
429
+ self.sections = {}
430
+ self.site_name = site_name.upper() if site_name else site_name
431
+ if isinstance(site_log, str):
432
+ self.lines = site_log.split("\n")
433
+ elif isinstance(site_log, list):
434
+ self.lines = site_log
435
+ else:
436
+ raise ValueError(
437
+ f"Expected site_log input to be string or list of lines, "
438
+ f"given: {type(site_log)}."
439
+ )
440
+
441
+ self._findings_ = {}
442
+
443
+
444
+ def try_prefixes(value, converter, delimiter=" "):
445
+ """
446
+ A wrapper for converter functions that tries successively shorter strings
447
+ as determined by the given delimiter. Think of this as being robust to junk
448
+ at the end of a value.
449
+ """
450
+
451
+ def try_parse(prefix):
452
+ return converter(prefix)
453
+
454
+ parts = value.split(delimiter)
455
+ trailing = []
456
+ while parts:
457
+ try:
458
+ success = try_parse(" ".join(parts))
459
+ if trailing:
460
+ return _Warning(
461
+ msg=f"Unexpected trailing characters: {' '.join(reversed(trailing))}",
462
+ value=success.value if isinstance(success, _Warning) else success,
463
+ )
464
+ return success
465
+ except ValueError:
466
+ trailing.append(parts.pop())
467
+
468
+
469
+ def to_antenna(value):
470
+ antenna = value.strip()
471
+ parts = value.split()
472
+ if len(parts) > 1 and len(antenna) > 16:
473
+ antenna = value.rstrip(parts[-1]).strip()
474
+ try:
475
+ return Antenna.objects.get(model__iexact=antenna).model
476
+ except Antenna.DoesNotExist:
477
+ antennas = "\n".join([ant.model for ant in Antenna.objects.public()])
478
+ raise ValueError(
479
+ f"Unexpected antenna model {antenna}. Must be one of " f"\n{antennas}"
480
+ )
481
+
482
+
483
+ def to_radome(value):
484
+ radome = value.strip()
485
+ try:
486
+ return Radome.objects.get(model__iexact=radome).model
487
+ except Radome.DoesNotExist:
488
+ radomes = "\n".join([rad.model for rad in Radome.objects.public()])
489
+ raise ValueError(
490
+ f"Unexpected radome model {radome}. Must be one of " f"\n{radomes}"
491
+ )
492
+
493
+
494
+ def to_receiver(value):
495
+ receiver = value.strip()
496
+ try:
497
+ return Receiver.objects.get(model__iexact=receiver).model
498
+ except Receiver.DoesNotExist:
499
+ receivers = "\n".join([rec.model for rec in Receiver.objects.public()])
500
+ raise ValueError(
501
+ f"Unexpected receiver model {receiver}. Must be one of " f"\n{receivers}"
502
+ )
503
+
504
+
505
+ def to_satellites(value):
506
+ sats = []
507
+ bad_sats = set()
508
+ for sat in [sat for sat in value.split("+") if sat]:
509
+ try:
510
+ upper_sat = sat.upper()
511
+ if upper_sat == "GLONASS":
512
+ sat = "GLO"
513
+ elif upper_sat == "BEIDOU":
514
+ sat = "BDS"
515
+ elif upper_sat == "GALILEO":
516
+ sat = "GAL"
517
+ sats.append(SatelliteSystem.objects.get(name__iexact=sat).pk)
518
+ except SatelliteSystem.DoesNotExist:
519
+ bad_sats.add(sat)
520
+ if bad_sats:
521
+ bad_sats = " \n".join(bad_sats)
522
+ good_sats = " \n".join([sat.name for sat in SatelliteSystem.objects.all()])
523
+ raise ValueError(
524
+ f"Expected constellation list delineated by '+' (e.g. GPS+GLO). "
525
+ f"Unexpected values encountered: \n{bad_sats}\n\nMust be one of "
526
+ f"\n{good_sats}"
527
+ )
528
+ return sats
529
+
530
+
531
+ def to_enum(enum_cls, value, strict=False, blank=None, ignored=None):
532
+ if value:
533
+ ignored = ignored or []
534
+ if value.lower() in [ign.lower() for ign in ignored]:
535
+ return _Ignored(msg=f"{value} is a placeholder.")
536
+
537
+ parts = value.split()
538
+ idx = len(parts)
539
+ while idx > 0:
540
+ try:
541
+ return enum_cls(" ".join(parts[0:idx]))
542
+ except ValueError:
543
+ idx -= 1
544
+
545
+ if not strict:
546
+ return value.strip()
547
+
548
+ valid_list = " \n".join(en.label for en in enum_cls)
549
+ raise ValueError(f"Invalid value {value} must be one of:\n" f"{valid_list}")
550
+ return blank
551
+
552
+
553
+ def to_numeric(
554
+ numeric_type,
555
+ value: str,
556
+ units: Optional[List[str]] = None,
557
+ prefixes: Optional[List[str]] = None,
558
+ take_first: Optional[bool] = None,
559
+ ):
560
+ """
561
+ The strategy for converting string parameter values to numerics is to chop
562
+ the numeric off the front of the string. If there's any more numeric
563
+ characters in the remainder its an error if they are not in units. Prefixes
564
+ are also tolerated at the start. Certain delimiters are tolerated but complained about.
565
+
566
+ :param numeric_type: The python type to return
567
+ :param value: The string to parse
568
+ :param units: Expected unit strings that will not be warned about.
569
+ :param prefixes: Prefixes that if present will be removed and ignored
570
+ :param take_first: If specified and the value is a range (e.g. 2-3) return the first element if True,
571
+ return the second element if False and raise ValueError if None
572
+ :return the parsed number of type numeric_type - it may also return _Ignored or a _Warning
573
+ :raises: ValueError if no numeric could be parsed
574
+ """
575
+ if not value:
576
+ return None
577
+
578
+ if prefixes:
579
+ value = remove_from_start(value, prefixes).strip()
580
+
581
+ unit_start = None
582
+ if units:
583
+ # add parenthesis around units if they arent on
584
+ units = [
585
+ *units,
586
+ *[
587
+ f"({unit})"
588
+ for unit in units
589
+ if not (unit[0] == "(" and unit[-1] == ")")
590
+ ],
591
+ ]
592
+ for unit in units:
593
+ if value.endswith(unit):
594
+ unit_start = -len(unit)
595
+ break
596
+
597
+ digits = ""
598
+ end = None
599
+ skipped = set()
600
+ for end, char in enumerate(value[0:unit_start]):
601
+ if char == " " and (not digits or not digits.isdigit()):
602
+ continue
603
+ if char.isdigit() or char in NUMERIC_CHARACTERS:
604
+ digits += char
605
+ elif char in SKIPPED_NUMERICS:
606
+ skipped.add(char)
607
+ continue
608
+ else:
609
+ break
610
+
611
+ if not digits or digits.lower().endswith("e"):
612
+ if value.startswith("("):
613
+ return _Ignored("Looks like a placeholder.")
614
+ for null in NULL_VALUES:
615
+ if null in value.lower():
616
+ return _Ignored("Looks like a null value.")
617
+ if value.lower() in units or all(
618
+ [part in units for part in value.lower().split(" ")]
619
+ ):
620
+ # some systems spit out a unit or units for when no value is present
621
+ return _Ignored("Looks like a null value.")
622
+ raise ValueError(f"Could not convert {value} to type {numeric_type.__name__}.")
623
+
624
+ # there should not be any trailing text in the string unless they are expected units
625
+ skipped = f"Unexpected delimiters: ({' '.join(list(skipped))})" if skipped else None
626
+ if len(digits) < len(value[0:unit_start].strip()) and (
627
+ not units or value[end:].strip().lower() not in [unt.lower() for unt in units]
628
+ ):
629
+ msg = f"{skipped} and u" if skipped else "U"
630
+ return _Warning(
631
+ msg=f"{msg}nexpected trailing characters: {value[end:]}",
632
+ value=numeric_type(digits),
633
+ )
634
+ if skipped:
635
+ return _Warning(msg=skipped, value=numeric_type(digits))
636
+
637
+ try:
638
+ return numeric_type(digits)
639
+ except ValueError:
640
+ if take_first is None:
641
+ raise
642
+ numeric = numeric_type(digits.split("-")[0 if take_first else -1])
643
+ return _Warning(
644
+ msg=f"Used {'first' if take_first else 'second'} value ({numeric}).",
645
+ value=numeric,
646
+ )
647
+
648
+
649
+ def to_float(value, units=None, prefixes=None, take_first=None):
650
+ return to_numeric(
651
+ numeric_type=float,
652
+ value=value,
653
+ units=units,
654
+ prefixes=prefixes,
655
+ take_first=take_first,
656
+ )
657
+
658
+
659
+ def to_decimal_degrees(value):
660
+ """
661
+ Converts ISO6709 degrees minutes seconds into decimal degrees.
662
+ :param value:
663
+ :return:
664
+ """
665
+ flt = to_float(value)
666
+ if isinstance(flt, _Warning):
667
+ return _Warning(msg=flt.msg, value=dddmmssss_to_decimal(flt.value))
668
+ if flt is _Ignored or isinstance(flt, _Ignored):
669
+ return flt
670
+ return dddmmssss_to_decimal(to_float(value))
671
+
672
+
673
+ def to_int(value, units=None, prefixes=None):
674
+ try:
675
+ to_numeric(numeric_type=int, value=value, units=units, prefixes=prefixes)
676
+ except ValueError:
677
+ from_float = to_numeric(
678
+ numeric_type=float, value=value, units=units, prefixes=prefixes
679
+ )
680
+ return _Warning(
681
+ msg="Value should be an integer.",
682
+ value=int(
683
+ from_float.value if isinstance(from_float, _Warning) else from_float
684
+ ),
685
+ )
686
+
687
+
688
+ def to_seconds(value):
689
+ seconds = to_int(
690
+ value, units=["sec", "second", "seconds", "s.", "s"], prefixes=["every"]
691
+ )
692
+ if isinstance(seconds, _Warning):
693
+ low_val = value.lower()
694
+ if "hour" in low_val or "hr" in low_val:
695
+ seconds = seconds.value * 3600
696
+ return _Warning(msg=f"Converted to {seconds} seconds!", value=seconds)
697
+ elif "minute" in low_val or "min" in low_val:
698
+ seconds = seconds.value * 60
699
+ return _Warning(msg=f"Converted to {seconds} seconds!", value=seconds)
700
+ return seconds
701
+
702
+
703
+ def to_pressure(value):
704
+ hpa = to_float(
705
+ value, units=["%", "hPa", "% hPa"], prefixes=ACCURACY_PREFIXES, take_first=False
706
+ )
707
+ if isinstance(hpa, _Warning):
708
+ low_val = value.lower()
709
+ if "mb" in low_val or "mbar" in low_val:
710
+ hpa = hpa.value * 100
711
+ return _Warning(msg=f"Converted to {hpa:0.01f} hPa", value=hpa)
712
+ if "mm" in low_val:
713
+ hpa = hpa.value * 133.3
714
+ return _Warning(msg=f"Converted to {hpa:0.01f} hPa", value=hpa)
715
+ return hpa
716
+
717
+
718
+ def _to_date(value):
719
+ if value.strip():
720
+ if "CCYY-MM-DD" in value.upper():
721
+ return _Ignored
722
+ try:
723
+ return parse_date(value, tzinfos=TZ_INFOS).date()
724
+ except Exception as exc:
725
+ raise ValueError(
726
+ f"Unable to parse {value} into a date. Expected " f"format: CCYY-MM-DD"
727
+ ) from exc
728
+ return None
729
+
730
+
731
+ def _to_datetime(value):
732
+ if value.strip():
733
+ if "CCYY-MM-DD" in value.upper():
734
+ return _Ignored
735
+ try:
736
+ # UT and UTUT has been seen as a timezone specifier in the wild
737
+ dt = parse_date(value, tzinfos=TZ_INFOS)
738
+ if not dt.tzinfo or dt.tzinfo == tzlocal():
739
+ return dt.replace(tzinfo=timezone.utc)
740
+ return dt
741
+ except Exception as exc:
742
+ raise ValueError(
743
+ f"Unable to parse {value} into a date and time. Expected "
744
+ f"format: CCYY-MM-DDThh:mmZ"
745
+ ) from exc
746
+ return None
747
+
748
+
749
+ to_date = partial(try_prefixes, converter=_to_date)
750
+ to_datetime = partial(try_prefixes, converter=_to_datetime)
751
+
752
+
753
+ def to_point(x, y, z):
754
+ if x is None or y is None or z is None:
755
+ return None
756
+ return Point(x, y, z)
757
+
758
+
759
+ def to_alignment(value):
760
+ try:
761
+ return to_float(value, units=["deg", "degrees"])
762
+ except ValueError:
763
+ if "true north" in value.lower():
764
+ return _Warning(msg="Interpreted as zero.", value=0)
765
+ raise
766
+
767
+
768
+ def to_str(value):
769
+ if value is None:
770
+ return ""
771
+ return value
772
+
773
+
774
+ class BaseBinder:
775
+ parsed: BaseParser = None
776
+
777
+ @property
778
+ def lines(self) -> List[str]:
779
+ return self.parsed.lines
780
+
781
+ @property
782
+ def findings(self):
783
+ return self.parsed.findings
784
+
785
+ def __init__(self, parsed: BaseParser):
786
+ self.parsed = parsed