psdi-data-conversion 0.0.38__py3-none-any.whl → 0.1.0__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 (31) hide show
  1. psdi_data_conversion/app.py +93 -33
  2. psdi_data_conversion/constants.py +1 -0
  3. psdi_data_conversion/converter.py +145 -17
  4. psdi_data_conversion/converters/base.py +24 -20
  5. psdi_data_conversion/converters/c2x.py +13 -0
  6. psdi_data_conversion/converters/openbabel.py +2 -1
  7. psdi_data_conversion/database.py +46 -14
  8. psdi_data_conversion/dist.py +2 -1
  9. psdi_data_conversion/file_io.py +1 -2
  10. psdi_data_conversion/log_utility.py +1 -1
  11. psdi_data_conversion/main.py +32 -25
  12. psdi_data_conversion/static/content/index-versions/psdi-common-footer.html +13 -9
  13. psdi_data_conversion/static/content/index-versions/psdi-common-header.html +1 -1
  14. psdi_data_conversion/static/content/psdi-common-footer.html +13 -9
  15. psdi_data_conversion/static/content/psdi-common-header.html +1 -1
  16. psdi_data_conversion/static/data/data.json +617 -3
  17. psdi_data_conversion/static/javascript/convert.js +54 -6
  18. psdi_data_conversion/static/javascript/convert_common.js +16 -2
  19. psdi_data_conversion/static/javascript/data.js +18 -0
  20. psdi_data_conversion/static/styles/format.css +7 -0
  21. psdi_data_conversion/templates/index.htm +8 -9
  22. psdi_data_conversion/testing/conversion_callbacks.py +2 -2
  23. psdi_data_conversion/testing/conversion_test_specs.py +27 -7
  24. psdi_data_conversion/testing/gui.py +18 -12
  25. psdi_data_conversion/testing/utils.py +3 -3
  26. psdi_data_conversion/utils.py +21 -0
  27. {psdi_data_conversion-0.0.38.dist-info → psdi_data_conversion-0.1.0.dist-info}/METADATA +2 -2
  28. {psdi_data_conversion-0.0.38.dist-info → psdi_data_conversion-0.1.0.dist-info}/RECORD +31 -30
  29. {psdi_data_conversion-0.0.38.dist-info → psdi_data_conversion-0.1.0.dist-info}/WHEEL +0 -0
  30. {psdi_data_conversion-0.0.38.dist-info → psdi_data_conversion-0.1.0.dist-info}/entry_points.txt +0 -0
  31. {psdi_data_conversion-0.0.38.dist-info → psdi_data_conversion-0.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,16 +7,17 @@ Python module provide utilities for accessing the converter database
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import json
11
+ import os
10
12
  from dataclasses import dataclass, field
11
13
  from itertools import product
12
- import json
13
14
  from logging import getLogger
14
- import os
15
- from typing import Any, Literal
15
+ from typing import Any, Literal, overload
16
16
 
17
17
  from psdi_data_conversion import constants as const
18
- from psdi_data_conversion.converter import D_REGISTERED_CONVERTERS, D_SUPPORTED_CONVERTERS
18
+ from psdi_data_conversion.converter import D_SUPPORTED_CONVERTERS, get_registered_converter_class
19
19
  from psdi_data_conversion.converters.base import FileConverterException
20
+ from psdi_data_conversion.utils import regularize_name
20
21
 
21
22
  # Keys for top-level and general items in the database
22
23
  DB_FORMATS_KEY = "formats"
@@ -32,6 +33,7 @@ DB_URL_KEY = "url"
32
33
 
33
34
  # Keys for format general info in the database
34
35
  DB_FORMAT_EXT_KEY = "extension"
36
+ DB_FORMAT_C2X_KEY = "format"
35
37
  DB_FORMAT_NOTE_KEY = "note"
36
38
  DB_FORMAT_COMP_KEY = "composition"
37
39
  DB_FORMAT_CONN_KEY = "connections"
@@ -116,14 +118,14 @@ class ConverterInfo:
116
118
  Parameters
117
119
  ----------
118
120
  name : str
119
- The name of the converter
121
+ The regularized name of the converter
120
122
  parent : DataConversionDatabase
121
123
  The database which this belongs to
122
124
  d_data : dict[str, Any]
123
125
  The loaded database dict
124
126
  """
125
127
 
126
- self.name = name
128
+ self.name = regularize_name(name)
127
129
  self.parent = parent
128
130
 
129
131
  # Get info about the converter from the database
@@ -133,7 +135,7 @@ class ConverterInfo:
133
135
 
134
136
  # Get necessary info about the converter from the class
135
137
  try:
136
- self._key_prefix = D_REGISTERED_CONVERTERS[name].database_key_prefix
138
+ self._key_prefix = get_registered_converter_class(name).database_key_prefix
137
139
  except KeyError:
138
140
  # We'll get a KeyError for converters in the database that don't yet have their own class, which we can
139
141
  # safely ignore
@@ -428,6 +430,9 @@ class FormatInfo:
428
430
  self.id: int = d_single_format_info.get(DB_ID_KEY, -1)
429
431
  """The ID of this format"""
430
432
 
433
+ self.c2x_format: str = d_single_format_info.get(DB_FORMAT_C2X_KEY)
434
+ """The name of this format as the c2x converter expects it"""
435
+
431
436
  self.note: str = d_single_format_info.get(DB_FORMAT_NOTE_KEY, "")
432
437
  """The description of this format"""
433
438
 
@@ -525,6 +530,10 @@ class ConversionQualityInfo:
525
530
  input and output file formats and a note on the implications
526
531
  """
527
532
 
533
+ def __post_init__(self):
534
+ """Regularize the converter name"""
535
+ self.converter_name = regularize_name(self.converter_name)
536
+
528
537
 
529
538
  class ConversionsTable:
530
539
  """Class providing information on available file format conversions.
@@ -619,7 +628,7 @@ class ConversionsTable:
619
628
 
620
629
  # Check if this converter deals with ambiguous formats, so we know if we need to be strict about getting format
621
630
  # info
622
- if D_REGISTERED_CONVERTERS[converter_name].supports_ambiguous_extensions:
631
+ if get_registered_converter_class(converter_name).supports_ambiguous_extensions:
623
632
  which_format = None
624
633
  else:
625
634
  which_format = 0
@@ -708,8 +717,8 @@ class ConversionsTable:
708
717
  conversion, the second is the info of the input format for this conversion, and the third is the info of the
709
718
  output format
710
719
  """
711
- l_in_format_infos: list[FormatInfo] = self.parent.get_format_info(in_format, which="all")
712
- l_out_format_infos: list[FormatInfo] = self.parent.get_format_info(out_format, which="all")
720
+ l_in_format_infos = self.parent.get_format_info(in_format, which="all")
721
+ l_out_format_infos = self.parent.get_format_info(out_format, which="all")
713
722
 
714
723
  # Start a list of all possible conversions
715
724
  l_possible_conversions = []
@@ -801,7 +810,7 @@ class DataConversionDatabase:
801
810
  if self._d_converter_info is None:
802
811
  self._d_converter_info: dict[str, ConverterInfo] = {}
803
812
  for d_single_converter_info in self.converters:
804
- name: str = d_single_converter_info[DB_NAME_KEY]
813
+ name: str = regularize_name(d_single_converter_info[DB_NAME_KEY])
805
814
  if name in self._d_converter_info:
806
815
  logger.warning(f"Converter '{name}' appears more than once in the database. Only the first instance"
807
816
  " will be used.")
@@ -934,6 +943,16 @@ class DataConversionDatabase:
934
943
  f" of type '{type(converter_name_or_id)}'. Type must be `str` or "
935
944
  "`int`")
936
945
 
946
+ @overload
947
+ def get_format_info(self,
948
+ format_name_or_id: str | int | FormatInfo,
949
+ which: int | None = None) -> FormatInfo: ...
950
+
951
+ @overload
952
+ def get_format_info(self,
953
+ format_name_or_id: str | int | FormatInfo,
954
+ which: Literal["all"]) -> list[FormatInfo]: ...
955
+
937
956
  def get_format_info(self,
938
957
  format_name_or_id: str | int | FormatInfo,
939
958
  which: int | Literal["all"] | None = None) -> FormatInfo | list[FormatInfo]:
@@ -1081,7 +1100,17 @@ def get_converter_info(name: str) -> ConverterInfo:
1081
1100
  ConverterInfo
1082
1101
  """
1083
1102
 
1084
- return get_database().d_converter_info[name]
1103
+ return get_database().d_converter_info[regularize_name(name)]
1104
+
1105
+
1106
+ @overload
1107
+ def get_format_info(format_name_or_id: str | int | FormatInfo,
1108
+ which: int | None = None) -> FormatInfo: ...
1109
+
1110
+
1111
+ @overload
1112
+ def get_format_info(format_name_or_id: str | int | FormatInfo,
1113
+ which: Literal["all"]) -> list[FormatInfo]: ...
1085
1114
 
1086
1115
 
1087
1116
  def get_format_info(format_name_or_id: str | int | FormatInfo,
@@ -1131,7 +1160,7 @@ def get_conversion_quality(converter_name: str,
1131
1160
  `ConversionQualityInfo` object with info on the conversion
1132
1161
  """
1133
1162
 
1134
- return get_database().conversions_table.get_conversion_quality(converter_name=converter_name,
1163
+ return get_database().conversions_table.get_conversion_quality(converter_name=regularize_name(converter_name),
1135
1164
  in_format=in_format,
1136
1165
  out_format=out_format)
1137
1166
 
@@ -1186,6 +1215,9 @@ def disambiguate_formats(converter_name: str,
1186
1215
  If more than one format combination is possible for this conversion, or no conversion is possible
1187
1216
  """
1188
1217
 
1218
+ # Regularize the converter name so we don't worry about case/spacing mismatches
1219
+ converter_name = regularize_name(converter_name)
1220
+
1189
1221
  # Get all possible conversions, and see if we only have one for this converter
1190
1222
  l_possible_conversions = [x for x in get_possible_conversions(in_format, out_format)
1191
1223
  if x[0] == converter_name]
@@ -1219,7 +1251,7 @@ def get_possible_formats(converter_name: str) -> tuple[list[FormatInfo], list[Fo
1219
1251
  tuple[list[FormatInfo], list[FormatInfo]]
1220
1252
  A tuple of a list of the supported input formats and a list of the supported output formats
1221
1253
  """
1222
- return get_database().conversions_table.get_possible_formats(converter_name=converter_name)
1254
+ return get_database().conversions_table.get_possible_formats(converter_name=regularize_name(converter_name))
1223
1255
 
1224
1256
 
1225
1257
  def _find_arg(tl_args: tuple[list[FlagInfo], list[OptionInfo]],
@@ -7,9 +7,10 @@ Functions and utilities related to handling multiple user OSes and distributions
7
7
 
8
8
  import os
9
9
  import shutil
10
- import psdi_data_conversion
11
10
  import sys
12
11
 
12
+ import psdi_data_conversion
13
+
13
14
  # Labels for each platform (which we use for the folder in this project), and the head of the name each platform will
14
15
  # have in `sys.platform`
15
16
 
@@ -149,8 +149,7 @@ def pack_zip_or_tar(archive_filename: str,
149
149
  archive_format = _format
150
150
  archive_root_filename = split_archive_ext(archive_filename)[0]
151
151
  break
152
- # Check that the format was found
153
- if archive_format is None:
152
+ else:
154
153
  raise AssertionError("Invalid execution path entered - filename wasn't found with a valid archive "
155
154
  "extension, but it did pass the `is_supported_archive` check")
156
155
  else:
@@ -5,11 +5,11 @@ Created 2024-12-09 by Bryan Gillis.
5
5
  Functions and classes related to logging and other messaging for the user
6
6
  """
7
7
 
8
- from datetime import datetime
9
8
  import logging
10
9
  import os
11
10
  import re
12
11
  import sys
12
+ from datetime import datetime
13
13
 
14
14
  from psdi_data_conversion import constants as const
15
15
 
@@ -7,17 +7,19 @@ Created 2025-01-14 by Bryan Gillis.
7
7
  Entry-point file for the command-line interface for data conversion.
8
8
  """
9
9
 
10
- from itertools import product
11
10
  import logging
12
- from argparse import ArgumentParser
13
11
  import os
14
12
  import sys
15
13
  import textwrap
14
+ from argparse import ArgumentParser
15
+ from itertools import product
16
16
 
17
17
  from psdi_data_conversion import constants as const
18
18
  from psdi_data_conversion.constants import CL_SCRIPT_NAME, CONVERTER_DEFAULT, TERM_WIDTH
19
- from psdi_data_conversion.converter import (D_CONVERTER_ARGS, D_SUPPORTED_CONVERTERS, L_REGISTERED_CONVERTERS,
20
- L_SUPPORTED_CONVERTERS, run_converter)
19
+ from psdi_data_conversion.converter import (D_CONVERTER_ARGS, L_REGISTERED_CONVERTERS, L_SUPPORTED_CONVERTERS,
20
+ converter_is_registered, converter_is_supported,
21
+ get_registered_converter_class, get_supported_converter_class,
22
+ run_converter)
21
23
  from psdi_data_conversion.converters.base import (FileConverterAbortException, FileConverterException,
22
24
  FileConverterInputException)
23
25
  from psdi_data_conversion.database import (FormatInfo, get_conversion_quality, get_converter_info, get_format_info,
@@ -25,6 +27,7 @@ from psdi_data_conversion.database import (FormatInfo, get_conversion_quality, g
25
27
  get_possible_formats)
26
28
  from psdi_data_conversion.file_io import split_archive_ext
27
29
  from psdi_data_conversion.log_utility import get_log_level_from_str
30
+ from psdi_data_conversion.utils import regularize_name
28
31
 
29
32
 
30
33
  def print_wrap(s: str, newline=False, err=False, **kwargs):
@@ -58,9 +61,9 @@ class ConvertArgs:
58
61
  self._output_dir: str | None = args.out
59
62
  converter_name = getattr(args, "with")
60
63
  if isinstance(converter_name, str):
61
- self.name = converter_name
64
+ self.name = regularize_name(converter_name)
62
65
  elif converter_name:
63
- self.name: str = " ".join(converter_name)
66
+ self.name = regularize_name(" ".join(converter_name))
64
67
  else:
65
68
  self.name = None
66
69
  self.delete_input = args.delete_input
@@ -109,14 +112,14 @@ class ConvertArgs:
109
112
 
110
113
  # Get the converter name from the arguments if it wasn't provided by -w/--with
111
114
  if not self.name:
112
- self.name = " ".join(self.l_args)
115
+ self.name = regularize_name(" ".join(self.l_args))
113
116
 
114
117
  # For this operation, any other arguments can be ignored
115
118
  return
116
119
 
117
120
  # If not listing and a converter name wasn't supplied, use the default converter
118
121
  if not self.name:
119
- self.name = CONVERTER_DEFAULT
122
+ self.name = regularize_name(CONVERTER_DEFAULT)
120
123
 
121
124
  # Quiet mode is equivalent to logging mode == LOGGING_NONE, so normalize them if either is set
122
125
  if self.quiet:
@@ -147,13 +150,14 @@ class ConvertArgs:
147
150
  os.makedirs(self._output_dir, exist_ok=True)
148
151
 
149
152
  # Check the converter is recognized
150
- if self.name not in L_SUPPORTED_CONVERTERS:
153
+ if not converter_is_supported(self.name):
151
154
  msg = textwrap.fill(f"ERROR: Converter '{self.name}' not recognised", width=TERM_WIDTH)
152
155
  msg += f"\n\n{get_supported_converters()}"
153
156
  raise FileConverterInputException(msg, help=True, msg_preformatted=True)
154
- elif self.name not in L_REGISTERED_CONVERTERS:
155
- msg = textwrap.fill(f"ERROR: Converter '{self.name}' is not registered. It may be possible to register "
156
- "it by installing an appropriate binary for your platform.", width=TERM_WIDTH)
157
+ elif not converter_is_registered(self.name):
158
+ converter_name = get_supported_converter_class(self.name).name
159
+ msg = textwrap.fill(f"ERROR: Converter '{converter_name}' is not registered. It may be possible to "
160
+ "register it by installing an appropriate binary for your platform.", width=TERM_WIDTH)
157
161
  msg += f"\n\n{get_supported_converters()}"
158
162
  raise FileConverterInputException(msg, help=True, msg_preformatted=True)
159
163
 
@@ -349,9 +353,10 @@ def detail_converter_use(args: ConvertArgs):
349
353
  """
350
354
 
351
355
  converter_info = get_converter_info(args.name)
352
- converter_class = D_SUPPORTED_CONVERTERS[args.name]
356
+ converter_class = get_supported_converter_class(args.name)
357
+ converter_name = converter_class.name
353
358
 
354
- print_wrap(f"{converter_info.name}: {converter_info.description} ({converter_info.url})", break_long_words=False,
359
+ print_wrap(f"{converter_name}: {converter_info.description} ({converter_info.url})", break_long_words=False,
355
360
  break_on_hyphens=False, newline=True)
356
361
 
357
362
  if converter_class.info:
@@ -362,10 +367,10 @@ def detail_converter_use(args: ConvertArgs):
362
367
  if args.from_format is not None and args.to_format is not None:
363
368
  qual = get_conversion_quality(args.name, args.from_format, args.to_format)
364
369
  if qual is None:
365
- print_wrap(f"Conversion from '{args.from_format}' to '{args.to_format}' with {args.name} is not "
370
+ print_wrap(f"Conversion from '{args.from_format}' to '{args.to_format}' with {converter_name} is not "
366
371
  "supported.", newline=True)
367
372
  else:
368
- print_wrap(f"Conversion from '{args.from_format}' to '{args.to_format}' with {args.name} is "
373
+ print_wrap(f"Conversion from '{args.from_format}' to '{args.to_format}' with {converter_name} is "
369
374
  f"possible with {qual.qual_str} conversion quality", newline=True)
370
375
  # If there are any potential issues with the conversion, print them out
371
376
  if qual.details:
@@ -385,7 +390,7 @@ def detail_converter_use(args: ConvertArgs):
385
390
  optional_not: str = ""
386
391
  else:
387
392
  optional_not: str = "not "
388
- print_wrap(f"Conversion {to_or_from} {format_name} is {optional_not}supported by {args.name}.\n")
393
+ print_wrap(f"Conversion {to_or_from} {format_name} is {optional_not}supported by {converter_name}.\n")
389
394
 
390
395
  # List all possible formats, and which can be used for input and which for output
391
396
  s_all_formats: set[FormatInfo] = set(l_input_formats)
@@ -393,7 +398,7 @@ def detail_converter_use(args: ConvertArgs):
393
398
  l_all_formats: list[FormatInfo] = list(s_all_formats)
394
399
  l_all_formats.sort(key=lambda x: x.disambiguated_name.lower())
395
400
 
396
- print_wrap(f"File formats supported by {args.name}:", newline=True)
401
+ print_wrap(f"File formats supported by {converter_name}:", newline=True)
397
402
  max_format_length = max([len(x.disambiguated_name) for x in l_all_formats])
398
403
  print(" "*(max_format_length+4) + " INPUT OUTPUT")
399
404
  print(" "*(max_format_length+4) + " ----- ------")
@@ -472,13 +477,13 @@ def detail_converter_use(args: ConvertArgs):
472
477
  # Now at the end, bring up input/output-format-specific flags and options
473
478
  if mention_input_format and mention_output_format:
474
479
  print_wrap("For details on input/output flags and options allowed for specific formats, call:\n"
475
- f"{CL_SCRIPT_NAME} -l {args.name} -f <input_format> -t <output_format>")
480
+ f"{CL_SCRIPT_NAME} -l {converter_name} -f <input_format> -t <output_format>")
476
481
  elif mention_input_format:
477
482
  print_wrap("For details on input flags and options allowed for a specific format, call:\n"
478
- f"{CL_SCRIPT_NAME} -l {args.name} -f <input_format> [-t <output_format>]")
483
+ f"{CL_SCRIPT_NAME} -l {converter_name} -f <input_format> [-t <output_format>]")
479
484
  elif mention_output_format:
480
485
  print_wrap("For details on output flags and options allowed for a specific format, call:\n"
481
- f"{CL_SCRIPT_NAME} -l {args.name} -t <output_format> [-f <input_format>]")
486
+ f"{CL_SCRIPT_NAME} -l {converter_name} -t <output_format> [-f <input_format>]")
482
487
 
483
488
 
484
489
  def list_supported_formats(err=False):
@@ -537,7 +542,7 @@ def detail_format(format_name: str):
537
542
  """Prints details on a format
538
543
  """
539
544
 
540
- l_format_info: list[FormatInfo] = get_format_info(format_name, which="all")
545
+ l_format_info = get_format_info(format_name, which="all")
541
546
 
542
547
  if len(l_format_info) == 0:
543
548
  print_wrap(f"ERROR: Format '{format_name}' not recognised", err=True, newline=True)
@@ -600,9 +605,11 @@ def detail_formats_and_possible_converters(from_format: str, to_format: str):
600
605
  l_conversions_matching_formats = [x for x in l_possible_conversions
601
606
  if x[1] == possible_from_format and x[2] == possible_to_format]
602
607
 
603
- l_possible_registered_converters = [x[0] for x in l_conversions_matching_formats
608
+ l_possible_registered_converters = [get_registered_converter_class(x[0]).name
609
+ for x in l_conversions_matching_formats
604
610
  if x[0] in L_REGISTERED_CONVERTERS]
605
- l_possible_unregistered_converters = [x[0] for x in l_conversions_matching_formats
611
+ l_possible_unregistered_converters = [get_supported_converter_class(x[0]).name
612
+ for x in l_conversions_matching_formats
606
613
  if x[0] in L_SUPPORTED_CONVERTERS and x[0] not in L_REGISTERED_CONVERTERS]
607
614
 
608
615
  print()
@@ -640,7 +647,7 @@ def get_supported_converters():
640
647
  l_converters: list[str] = []
641
648
  any_not_registered = False
642
649
  for converter_name in L_SUPPORTED_CONVERTERS:
643
- converter_text = converter_name
650
+ converter_text = get_supported_converter_class(converter_name).name
644
651
  if converter_name not in L_REGISTERED_CONVERTERS:
645
652
  converter_text += f" {MSG_NOT_REGISTERED}"
646
653
  any_not_registered = True
@@ -17,16 +17,20 @@
17
17
  </ul>
18
18
  <ul class="footer__col footer__items clean-list">
19
19
  <li>
20
- <img class="lm-only" src="static/img/ukri-logo-lighttext.png"
21
- alt="UKRI logo">
22
- <img class="dm-only" src="static/img/ukri-logo-darktext.png"
23
- alt="UKRI logo">
20
+ <a href="https://www.ukri.org/">
21
+ <img class="lm-only" src="static/img/ukri-logo-lighttext.png"
22
+ alt="UKRI logo">
23
+ <img class="dm-only" src="static/img/ukri-logo-darktext.png"
24
+ alt="UKRI logo">
25
+ </a>
24
26
  </li>
25
27
  <li>
26
- <img class="lm-only" src="static/img/ukri-epsr-logo-lighttext.png"
27
- alt="UKRI EPSR logo">
28
- <img class="dm-only" src="static/img/ukri-epsr-logo-darktext.png"
29
- alt="UKRI EPSR logo">
28
+ <a href="https://www.ukri.org/councils/epsrc/">
29
+ <img class="lm-only" src="static/img/ukri-epsr-logo-lighttext.png"
30
+ alt="UKRI EPSR logo">
31
+ <img class="dm-only" src="static/img/ukri-epsr-logo-darktext.png"
32
+ alt="UKRI EPSR logo">
33
+ </a>
30
34
  </li>
31
35
  </ul>
32
36
  <div class="footer__hline small-screen-only">
@@ -44,7 +48,7 @@
44
48
  <hr>
45
49
  </div>
46
50
  <ul class="footer__col footer__items clean-list">
47
- <li><a href="https://psdistg.wpengine.com/">PSDI Home</a></li>
51
+ <li><a href="https://psdi.ac.uk/">PSDI Home</a></li>
48
52
  <li><a href="mailto:support@psdi.ac.uk">Contact Us</a></li>
49
53
  <li><a href="https://www.psdi.ac.uk/privacy/">Privacy</a></li>
50
54
  <li><a href="https://www.psdi.ac.uk/terms-and-conditions/">Terms and Conditions</a></li>
@@ -4,7 +4,7 @@
4
4
  <div class="max-width-box navbar">
5
5
  <div class="header-left">
6
6
  <div class="navbar__brand">
7
- <a class="navbar__logo" href="https://psdistg.wpengine.com/">
7
+ <a class="navbar__logo" href="https://psdi.ac.uk/">
8
8
  <img src="static/img/psdi-logo-darktext.png" alt="PSDI logo"
9
9
  class="lm-only">
10
10
  <img src="static/img/psdi-logo-lighttext.png" alt="PSDI logo"
@@ -17,16 +17,20 @@
17
17
  </ul>
18
18
  <ul class="footer__col footer__items clean-list">
19
19
  <li>
20
- <img class="lm-only" src="../img/ukri-logo-lighttext.png"
21
- alt="UKRI logo">
22
- <img class="dm-only" src="../img/ukri-logo-darktext.png"
23
- alt="UKRI logo">
20
+ <a href="https://www.ukri.org/">
21
+ <img class="lm-only" src="../img/ukri-logo-lighttext.png"
22
+ alt="UKRI logo">
23
+ <img class="dm-only" src="../img/ukri-logo-darktext.png"
24
+ alt="UKRI logo">
25
+ </a>
24
26
  </li>
25
27
  <li>
26
- <img class="lm-only" src="../img/ukri-epsr-logo-lighttext.png"
27
- alt="UKRI EPSR logo">
28
- <img class="dm-only" src="../img/ukri-epsr-logo-darktext.png"
29
- alt="UKRI EPSR logo">
28
+ <a href="https://www.ukri.org/councils/epsrc/">
29
+ <img class="lm-only" src="../img/ukri-epsr-logo-lighttext.png"
30
+ alt="UKRI EPSR logo">
31
+ <img class="dm-only" src="../img/ukri-epsr-logo-darktext.png"
32
+ alt="UKRI EPSR logo">
33
+ </a>
30
34
  </li>
31
35
  </ul>
32
36
  <div class="footer__hline small-screen-only">
@@ -44,7 +48,7 @@
44
48
  <hr>
45
49
  </div>
46
50
  <ul class="footer__col footer__items clean-list">
47
- <li><a href="https://psdistg.wpengine.com/">PSDI Home</a></li>
51
+ <li><a href="https://psdi.ac.uk/">PSDI Home</a></li>
48
52
  <li><a href="mailto:support@psdi.ac.uk">Contact Us</a></li>
49
53
  <li><a href="https://www.psdi.ac.uk/privacy/">Privacy</a></li>
50
54
  <li><a href="https://www.psdi.ac.uk/terms-and-conditions/">Terms and Conditions</a></li>
@@ -4,7 +4,7 @@
4
4
  <div class="max-width-box navbar">
5
5
  <div class="header-left">
6
6
  <div class="navbar__brand">
7
- <a class="navbar__logo" href="https://psdistg.wpengine.com/">
7
+ <a class="navbar__logo" href="https://psdi.ac.uk/">
8
8
  <img src="../img/psdi-logo-darktext.png" alt="PSDI logo"
9
9
  class="lm-only">
10
10
  <img src="../img/psdi-logo-lighttext.png" alt="PSDI logo"