foundrytools-cli 2.0.3__tar.gz → 2.0.5__tar.gz

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 (40) hide show
  1. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/PKG-INFO +13 -13
  2. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/README.md +2 -2
  3. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/pyproject.toml +23 -23
  4. foundrytools_cli-2.0.5/src/foundrytools_cli/__init__.py +3 -0
  5. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/cff.py +2 -1
  6. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/converter/cli.py +7 -7
  7. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/converter/ttf_to_otf.py +5 -6
  8. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/fix/cli.py +33 -0
  9. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/font.py +3 -3
  10. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/name.py +10 -10
  11. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/os_2.py +4 -4
  12. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/otf.py +2 -1
  13. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/post.py +5 -5
  14. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/print/cli.py +1 -2
  15. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/print/font_names.py +4 -5
  16. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/utils/cli.py +2 -1
  17. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/utils/__init__.py +7 -8
  18. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/utils/task_runner.py +5 -4
  19. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/utils/timer.py +5 -4
  20. foundrytools_cli-2.0.3/.gitignore +0 -161
  21. foundrytools_cli-2.0.3/LICENSE +0 -21
  22. foundrytools_cli-2.0.3/src/foundrytools_cli/__init__.py +0 -3
  23. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/__main__.py +0 -0
  24. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/__init__.py +0 -0
  25. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/cmap.py +0 -0
  26. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/converter/__init__.py +0 -0
  27. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/fix/__init__.py +0 -0
  28. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/fix/legacy_accents.py +0 -0
  29. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/fix/vertical_metrics.py +0 -0
  30. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/gsub.py +0 -0
  31. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/hhea.py +0 -0
  32. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/print/__init__.py +0 -0
  33. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/print/font_info.py +0 -0
  34. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/print/vf_instances.py +0 -0
  35. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/ttf.py +0 -0
  36. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/utils/__init__.py +0 -0
  37. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/utils/font_organizer.py +0 -0
  38. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/utils/font_renamer.py +0 -0
  39. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/commands/utils/sync_timestamps.py +0 -0
  40. {foundrytools_cli-2.0.3 → foundrytools_cli-2.0.5}/src/foundrytools_cli/utils/logger.py +0 -0
@@ -1,32 +1,32 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: foundrytools-cli
3
- Version: 2.0.3
3
+ Version: 2.0.5
4
4
  Summary: A set of command line tools to inspect, manipulate and convert font files
5
- Project-URL: Homepage, https://github.com/ftCLI/FoundryTools-CLI
5
+ Author: Cesare Gilento
6
6
  Author-email: Cesare Gilento <ftcli@proton.me>
7
7
  License-Expression: MIT
8
- License-File: LICENSE
9
8
  Classifier: Development Status :: 5 - Production/Stable
10
9
  Classifier: Intended Audience :: Developers
11
10
  Classifier: Intended Audience :: End Users/Desktop
12
- Classifier: License :: OSI Approved :: MIT License
13
11
  Classifier: Natural Language :: English
14
12
  Classifier: Operating System :: OS Independent
15
13
  Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.9
17
14
  Classifier: Programming Language :: Python :: 3.10
18
15
  Classifier: Programming Language :: Python :: 3.11
19
16
  Classifier: Programming Language :: Python :: 3.12
20
17
  Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: License :: OSI Approved :: MIT License
21
19
  Classifier: Topic :: Text Processing :: Fonts
22
- Requires-Python: >=3.9
23
- Requires-Dist: afdko>=4.0.2
24
- Requires-Dist: click>=8.1.8
25
- Requires-Dist: foundrytools>=0.1.4
20
+ Requires-Dist: afdko>=4.0.3
21
+ Requires-Dist: click>=8.3.1
22
+ Requires-Dist: foundrytools>=0.1.5
26
23
  Requires-Dist: loguru>=0.7.3
27
24
  Requires-Dist: pathvalidate>=3.3.1
28
- Requires-Dist: rich>=14.1.0
29
- Requires-Dist: win32-setctime>=1.2.0; sys_platform >= 'win32'
25
+ Requires-Dist: rich>=14.2.0
26
+ Requires-Dist: ufolib2>=0.18.1
27
+ Requires-Dist: win32-setctime>=1.2.0 ; sys_platform >= 'win32'
28
+ Requires-Python: >=3.10
29
+ Project-URL: Homepage, https://github.com/ftCLI/FoundryTools-CLI
30
30
  Description-Content-Type: text/markdown
31
31
 
32
32
  # FoundryTools-CLI
@@ -53,7 +53,7 @@ rendered by [rich](https://github.com/Textualize/rich).
53
53
 
54
54
  ## Installation
55
55
 
56
- FoundryTools-CLI requires Python 3.9 or later.
56
+ FoundryTools-CLI requires Python 3.10 or later.
57
57
 
58
58
  **IMPORTANT**: If you have Python 2.x installed in your system, you may have to use `python3`
59
59
  (instead of `python`) in the commands below.
@@ -95,7 +95,7 @@ python -m pip install -e .
95
95
 
96
96
  FoundryTools-CLI is a Terminal app where commands are logically organized into subcommands.
97
97
 
98
- Please refer to the [user documentation](https://foundrytools-cli.readthedocs.io/).
98
+ Please refer to the [user documentation](https://foundrytools-cli.readthedocs.io/en/stable/).
99
99
 
100
100
  ## License
101
101
 
@@ -22,7 +22,7 @@ rendered by [rich](https://github.com/Textualize/rich).
22
22
 
23
23
  ## Installation
24
24
 
25
- FoundryTools-CLI requires Python 3.9 or later.
25
+ FoundryTools-CLI requires Python 3.10 or later.
26
26
 
27
27
  **IMPORTANT**: If you have Python 2.x installed in your system, you may have to use `python3`
28
28
  (instead of `python`) in the commands below.
@@ -64,7 +64,7 @@ python -m pip install -e .
64
64
 
65
65
  FoundryTools-CLI is a Terminal app where commands are logically organized into subcommands.
66
66
 
67
- Please refer to the [user documentation](https://foundrytools-cli.readthedocs.io/).
67
+ Please refer to the [user documentation](https://foundrytools-cli.readthedocs.io/en/stable/).
68
68
 
69
69
  ## License
70
70
 
@@ -1,14 +1,14 @@
1
1
  [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
2
+ requires = ["uv_build>=0.9.17"]
3
+ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "foundrytools-cli"
7
- dynamic = ["version"]
7
+ version = "2.0.5"
8
8
  description = "A set of command line tools to inspect, manipulate and convert font files"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
- requires-python = ">=3.9"
11
+ requires-python = ">=3.10"
12
12
  authors = [
13
13
  { name = "Cesare Gilento", email = "ftcli@proton.me" },
14
14
  ]
@@ -20,7 +20,6 @@ classifiers = [
20
20
  "Natural Language :: English",
21
21
  "Operating System :: OS Independent",
22
22
  "Programming Language :: Python :: 3",
23
- "Programming Language :: Python :: 3.9",
24
23
  "Programming Language :: Python :: 3.10",
25
24
  "Programming Language :: Python :: 3.11",
26
25
  "Programming Language :: Python :: 3.12",
@@ -30,36 +29,37 @@ classifiers = [
30
29
  ]
31
30
 
32
31
  dependencies = [
33
- "afdko>=4.0.2",
34
- "click>=8.1.8",
35
- "foundrytools>=0.1.4",
32
+ "afdko>=4.0.3",
33
+ "click>=8.3.1",
34
+ "foundrytools>=0.1.5",
36
35
  "loguru>=0.7.3",
37
36
  "pathvalidate>=3.3.1",
38
- "rich>=14.1.0",
37
+ "rich>=14.2.0",
38
+ "ufolib2>=0.18.1",
39
39
  "win32-setctime>=1.2.0; sys_platform >= 'win32'",
40
40
  ]
41
41
 
42
+ [dependency-groups]
43
+ dev = [
44
+ "bump-my-version>=1.2.6",
45
+ "mypy>=1.19.1",
46
+ "pre-commit>=4.5.1",
47
+ "pylint>=4.0.4",
48
+ ]
49
+ docs = [
50
+ "sphinx-click>=6.2.0",
51
+ "sphinx-rtd-theme>=3.0.2",
52
+ ]
53
+
42
54
  [project.scripts]
43
55
  ftcli = "foundrytools_cli.__main__:cli"
44
56
 
45
57
  [project.urls]
46
58
  Homepage = "https://github.com/ftCLI/FoundryTools-CLI"
47
59
 
48
- [tool.hatch.version]
49
- path = "src/foundrytools_cli/__init__.py"
50
- pattern = 'VERSION = __version__ = "(?P<version>[^"]+)"'
51
-
52
- [tool.hatch.build.targets.sdist]
53
- include = [
54
- "/src",
55
- ]
56
- exclude = [
57
- "/.gitignore",
58
- ]
59
-
60
60
  [tool.bumpversion]
61
- current_version = "2.0.3"
62
- commit = true
61
+ current_version = "2.0.5"
62
+ commit = false
63
63
  tag = true
64
64
 
65
65
  [[tool.bumpversion.files]]
@@ -0,0 +1,3 @@
1
+ VERSION = __version__ = "2.0.5"
2
+
3
+ __all__ = ["VERSION"]
@@ -1,5 +1,6 @@
1
+ from collections.abc import Callable
1
2
  from pathlib import Path
2
- from typing import Any, Callable
3
+ from typing import Any
3
4
 
4
5
  import click
5
6
  from foundrytools import Font
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
- from typing import Any, Literal, Optional
2
+ from typing import Any, Literal
3
3
 
4
4
  import click
5
5
  from fontTools.misc.cliTools import makeOutputFileName
@@ -74,8 +74,8 @@ def otf_to_ttf(input_path: Path, **options: dict[str, Any]) -> None:
74
74
  def task(
75
75
  font: Font,
76
76
  tolerance: float = 1.0,
77
- target_upm: Optional[int] = None,
78
- output_dir: Optional[Path] = None,
77
+ target_upm: int | None = None,
78
+ output_dir: Path | None = None,
79
79
  overwrite: bool = True,
80
80
  ) -> None:
81
81
  flavor = font.ttfont.flavor
@@ -201,7 +201,7 @@ def ttf_to_otf(input_path: Path, **options: dict[str, Any]) -> None:
201
201
  )
202
202
  def web_to_sfnt(
203
203
  input_path: Path,
204
- in_format: Optional[Literal["woff", "woff2"]],
204
+ in_format: Literal["woff", "woff2"] | None,
205
205
  **options: dict[str, Any],
206
206
  ) -> None:
207
207
  """
@@ -236,8 +236,8 @@ def sfnt_to_web(input_path: Path, **options: dict[str, Any]) -> None:
236
236
 
237
237
  def task(
238
238
  font: Font,
239
- output_dir: Optional[Path] = None,
240
- out_format: Optional[Literal["woff", "woff2"]] = None,
239
+ output_dir: Path | None = None,
240
+ out_format: Literal["woff", "woff2"] | None = None,
241
241
  overwrite: bool = True,
242
242
  reorder_tables: bool = False,
243
243
  ) -> None:
@@ -302,7 +302,7 @@ def variable_to_static(input_path: Path, **options: dict[str, Any]) -> None:
302
302
  var_font: Font,
303
303
  select_instance: bool = False,
304
304
  overlap: int = 1,
305
- output_dir: Optional[Path] = None,
305
+ output_dir: Path | None = None,
306
306
  overwrite: bool = True,
307
307
  ) -> None:
308
308
  if select_instance:
@@ -1,5 +1,4 @@
1
1
  from pathlib import Path
2
- from typing import Optional
3
2
 
4
3
  from afdko.fdkutils import run_shell_command
5
4
  from foundrytools import Font
@@ -10,7 +9,7 @@ from foundrytools.lib.qu2cu import quadratics_to_cubics_2
10
9
  from foundrytools_cli.utils.logger import logger
11
10
 
12
11
 
13
- def _build_out_file_name(font: Font, output_dir: Optional[Path], overwrite: bool = True) -> Path:
12
+ def _build_out_file_name(font: Font, output_dir: Path | None, overwrite: bool = True) -> Path:
14
13
  """
15
14
  When converting a TrueType flavored web font to PS flavored web font, we need to add a suffix to
16
15
  the output file name to avoid overwriting the input file. This function builds the output file
@@ -39,11 +38,11 @@ def _build_out_file_name(font: Font, output_dir: Optional[Path], overwrite: bool
39
38
  def ttf2otf(
40
39
  font: Font,
41
40
  tolerance: float = 1.0,
42
- target_upm: Optional[int] = None,
41
+ target_upm: int | None = None,
43
42
  correct_contours: bool = True,
44
43
  check_outlines: bool = False,
45
44
  subroutinize: bool = True,
46
- output_dir: Optional[Path] = None,
45
+ output_dir: Path | None = None,
47
46
  overwrite: bool = True,
48
47
  ) -> None:
49
48
  """
@@ -104,11 +103,11 @@ def ttf2otf(
104
103
 
105
104
  def ttf2otf_with_tx(
106
105
  font: Font,
107
- target_upm: Optional[int] = None,
106
+ target_upm: int | None = None,
108
107
  correct_contours: bool = True,
109
108
  check_outlines: bool = False,
110
109
  subroutinize: bool = True,
111
- output_dir: Optional[Path] = None,
110
+ output_dir: Path | None = None,
112
111
  recalc_timestamp: bool = False,
113
112
  overwrite: bool = True,
114
113
  ) -> None:
@@ -344,6 +344,39 @@ def fix_monospace(input_path: Path, **options: dict[str, Any]) -> None:
344
344
  runner.run()
345
345
 
346
346
 
347
+ @cli.command("integer-ppem-if-hinted", cls=BaseCommand)
348
+ def fix_integer_ppm_if_hinted(input_path: Path, **options: dict[str, Any]) -> None:
349
+ """
350
+ Sets the ``head.flags`` bit 3 to force integer ppem for hinted fonts.
351
+
352
+ fontbakery check id: com.google.fonts/check/integer_ppem_if_hinted
353
+
354
+ Rationale:
355
+
356
+ Hinted fonts must have head table flag bit 3 set.
357
+
358
+ Per https://docs.microsoft.com/en-us/typography/opentype/spec/head, bit 3 of Head::flags decides
359
+ whether PPEM should be rounded. This bit should always be set for hinted fonts.
360
+
361
+ Note:
362
+ Bit 3 = Force ppem to integer values for all internal scaler math;
363
+
364
+ Fixing procedure:
365
+
366
+ * Check if the font is hinted by looking for the presence of the ``fpgm`` table.
367
+ * Set the ``head.flags`` bit 3 to force integer ppem for hinted fonts.
368
+ """
369
+
370
+ def task(font: Font) -> bool:
371
+ if font.ttfont.get("fpgm"):
372
+ font.t_head.set_bit("flags", 3, True)
373
+ return font.t_head.is_modified
374
+
375
+ runner = TaskRunner(input_path=input_path, task=task, **options)
376
+ runner.filter.filter_out_ps = True
377
+ runner.run()
378
+
379
+
347
380
  @cli.command("transformed-components", cls=BaseCommand)
348
381
  def fix_transformed_components(input_path: Path, **options: dict[str, Any]) -> None:
349
382
  """
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
- from typing import Any, Literal, Optional
2
+ from typing import Any, Literal
3
3
 
4
4
  import click
5
5
  from foundrytools.core.font import Font
@@ -150,8 +150,8 @@ def remove_glyphs(input_path: Path, **options: dict[str, Any]) -> None:
150
150
 
151
151
  def task(
152
152
  font: Font,
153
- glyph_names_to_remove: Optional[set[str]],
154
- glyph_ids_to_remove: Optional[set[int]],
153
+ glyph_names_to_remove: set[str] | None,
154
+ glyph_ids_to_remove: set[int] | None,
155
155
  ) -> bool:
156
156
  removed_glyphs = font.remove_glyphs(
157
157
  glyph_names_to_remove=glyph_names_to_remove,
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
- from typing import Any, Optional
2
+ from typing import Any
3
3
 
4
4
  import click
5
5
  from foundrytools import Font
@@ -55,8 +55,8 @@ def del_names(input_path: Path, **options: dict[str, Any]) -> None:
55
55
  def task(
56
56
  font: Font,
57
57
  name_ids_to_process: tuple[int],
58
- platform_id: Optional[int] = None,
59
- language_string: Optional[str] = None,
58
+ platform_id: int | None = None,
59
+ language_string: str | None = None,
60
60
  ) -> bool:
61
61
  font.t_name.remove_names(
62
62
  name_ids=name_ids_to_process, platform_id=platform_id, language_string=language_string
@@ -171,8 +171,8 @@ def find_replace(input_path: Path, **options: dict[str, Any]) -> None:
171
171
  font: Font,
172
172
  old_string: str,
173
173
  new_string: str,
174
- name_ids_to_process: Optional[tuple[int]] = None,
175
- name_ids_to_skip: Optional[tuple[int]] = None,
174
+ name_ids_to_process: tuple[int] | None = None,
175
+ name_ids_to_skip: tuple[int] | None = None,
176
176
  ) -> bool:
177
177
  font.t_name.find_replace(
178
178
  old_string=old_string,
@@ -227,7 +227,7 @@ def set_name(input_path: Path, **options: dict[str, Any]) -> None:
227
227
  font: Font,
228
228
  name_id: int,
229
229
  name_string: str,
230
- platform_id: Optional[int] = None,
230
+ platform_id: int | None = None,
231
231
  language_string: str = "en",
232
232
  ) -> bool:
233
233
  font.t_name.set_name(
@@ -289,7 +289,7 @@ def build_unique_id(input_path: Path, **options: dict[str, Any]) -> None:
289
289
  ``Font Revision;Vendor ID;PostScript Name``
290
290
  """
291
291
 
292
- def task(font: Font, platform_id: Optional[int] = None, alternate: bool = False) -> bool:
292
+ def task(font: Font, platform_id: int | None = None, alternate: bool = False) -> bool:
293
293
  font.t_name.build_unique_identifier(platform_id=platform_id, alternate=alternate)
294
294
  return font.t_name.is_modified
295
295
 
@@ -313,7 +313,7 @@ def build_full_name(input_path: Path, **options: dict[str, Any]) -> None:
313
313
  Builds the NameID 4 (Full Font Name).
314
314
  """
315
315
 
316
- def task(font: Font, platform_id: Optional[int] = None) -> bool:
316
+ def task(font: Font, platform_id: int | None = None) -> bool:
317
317
  font.t_name.build_full_font_name(platform_id=platform_id)
318
318
  return font.t_name.is_modified
319
319
 
@@ -337,7 +337,7 @@ def build_version_string(input_path: Path, **options: dict[str, Any]) -> None:
337
337
  Builds the NameID 5 (Version String).
338
338
  """
339
339
 
340
- def task(font: Font, platform_id: Optional[int] = None) -> bool:
340
+ def task(font: Font, platform_id: int | None = None) -> bool:
341
341
  font.t_name.build_version_string(platform_id=platform_id)
342
342
  return font.t_name.is_modified
343
343
 
@@ -361,7 +361,7 @@ def build_postscript_name(input_path: Path, **options: dict[str, Any]) -> None:
361
361
  Builds the NameID 6 (PostScript Name).
362
362
  """
363
363
 
364
- def task(font: Font, platform_id: Optional[int] = None) -> bool:
364
+ def task(font: Font, platform_id: int | None = None) -> bool:
365
365
  font.t_name.build_postscript_name(platform_id=platform_id)
366
366
  return font.t_name.is_modified
367
367
 
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
- from typing import Any, Optional, Union
2
+ from typing import Any
3
3
 
4
4
  import click
5
5
  from fontTools.misc.roundTools import otRound
@@ -257,7 +257,7 @@ def set_attrs(input_path: Path, **options: dict[str, Any]) -> None:
257
257
  """
258
258
  ensure_at_least_one_param(click.get_current_context())
259
259
 
260
- def task(font: Font, **kwargs: dict[str, Optional[Union[int, float, str, bool]]]) -> bool:
260
+ def task(font: Font, **kwargs: dict[str, int | float | str | bool | None]) -> bool:
261
261
  for attr, value in kwargs.items():
262
262
  if value is not None:
263
263
  try:
@@ -417,7 +417,7 @@ def set_fs_selection(input_path: Path, **options: dict[str, Any]) -> None:
417
417
  """
418
418
  ensure_at_least_one_param(click.get_current_context())
419
419
 
420
- def task(font: Font, **kwargs: dict[str, Optional[bool]]) -> bool:
420
+ def task(font: Font, **kwargs: dict[str, bool | None]) -> bool:
421
421
  for attr, value in kwargs.items():
422
422
  if value is not None:
423
423
  if hasattr(font.flags, attr):
@@ -497,7 +497,7 @@ def set_fs_type(input_path: Path, **options: dict[str, Any]) -> None:
497
497
  """
498
498
  ensure_at_least_one_param(click.get_current_context())
499
499
 
500
- def task(font: Font, **kwargs: dict[str, Optional[bool]]) -> bool:
500
+ def task(font: Font, **kwargs: dict[str, bool | None]) -> bool:
501
501
  for attr, value in kwargs.items():
502
502
  if hasattr(font.t_os_2, attr) and value is not None:
503
503
  setattr(font.t_os_2, attr, value)
@@ -1,5 +1,6 @@
1
+ from collections.abc import Callable
1
2
  from pathlib import Path
2
- from typing import Any, Callable, cast
3
+ from typing import Any, cast
3
4
 
4
5
  import click
5
6
  from foundrytools import Font
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
- from typing import Any, Optional
2
+ from typing import Any
3
3
 
4
4
  import click
5
5
  from foundrytools import Font
@@ -43,10 +43,10 @@ def cli(input_path: Path, **options: dict[str, Any]) -> None:
43
43
 
44
44
  def task(
45
45
  font: Font,
46
- italic_angle: Optional[float] = None,
47
- underline_position: Optional[int] = None,
48
- underline_thickness: Optional[int] = None,
49
- fixed_pitch: Optional[bool] = None,
46
+ italic_angle: float | None = None,
47
+ underline_position: int | None = None,
48
+ underline_thickness: int | None = None,
49
+ fixed_pitch: bool | None = None,
50
50
  ) -> bool:
51
51
  attrs = {
52
52
  "italic_angle": italic_angle,
@@ -1,5 +1,4 @@
1
1
  from pathlib import Path
2
- from typing import Optional
3
2
 
4
3
  import click
5
4
  from foundrytools import FontFinder
@@ -53,7 +52,7 @@ def font_info(input_path: Path) -> None:
53
52
  Prints a minimal set of NameRecords, omitting the ones with nameID not in 1, 2, 3, 4, 5, 6, 16,
54
53
  17, 18, 21, 22, 25""",
55
54
  )
56
- def font_names(input_path: Path, max_lines: Optional[int] = None, minimal: bool = False) -> None:
55
+ def font_names(input_path: Path, max_lines: int | None = None, minimal: bool = False) -> None:
57
56
  """
58
57
  Prints the name table.
59
58
  """
@@ -1,5 +1,4 @@
1
1
  from shutil import get_terminal_size
2
- from typing import Optional
3
2
 
4
3
  from fontTools.ttLib.tables._n_a_m_e import _MAC_LANGUAGES, _WINDOWS_LANGUAGES, NameRecord
5
4
  from foundrytools import Font
@@ -42,7 +41,7 @@ JUSTIFY = 22
42
41
 
43
42
 
44
43
  def _process_name_table(
45
- font: Font, table: Table, terminal_width: int, max_lines: Optional[int], minimal: bool
44
+ font: Font, table: Table, terminal_width: int, max_lines: int | None, minimal: bool
46
45
  ) -> None:
47
46
  name_table = NameTable(font.ttfont)
48
47
  names = name_table.table.names
@@ -63,7 +62,7 @@ def _process_name_table(
63
62
 
64
63
 
65
64
  def _add_name_row_to_table(
66
- table: Table, name: NameRecord, terminal_width: int, max_lines: Optional[int]
65
+ table: Table, name: NameRecord, terminal_width: int, max_lines: int | None
67
66
  ) -> None:
68
67
  row_string = _get_name_row(name)
69
68
  row_string = wrap_string(
@@ -77,7 +76,7 @@ def _add_name_row_to_table(
77
76
 
78
77
 
79
78
  def _process_cff_table(
80
- font: Font, table: Table, terminal_width: int, max_lines: Optional[int], minimal: bool
79
+ font: Font, table: Table, terminal_width: int, max_lines: int | None, minimal: bool
81
80
  ) -> None:
82
81
  if not font.is_ps:
83
82
  return
@@ -133,7 +132,7 @@ def _get_name_row(name: NameRecord) -> str:
133
132
  )
134
133
 
135
134
 
136
- def main(font: Font, max_lines: Optional[int] = None, minimal: bool = False) -> None:
135
+ def main(font: Font, max_lines: int | None = None, minimal: bool = False) -> None:
137
136
  """
138
137
  Prints the names of the font.
139
138
  """
@@ -1,6 +1,7 @@
1
1
  # pylint: disable=import-outside-toplevel
2
+ from collections.abc import Callable
2
3
  from pathlib import Path
3
- from typing import Any, Callable, cast
4
+ from typing import Any, cast
4
5
 
5
6
  import click
6
7
 
@@ -1,7 +1,8 @@
1
1
  import os
2
+ from collections.abc import Callable
2
3
  from pathlib import Path
3
4
  from textwrap import TextWrapper
4
- from typing import Any, Callable, Optional, Union
5
+ from typing import Any
5
6
 
6
7
  import click
7
8
 
@@ -112,8 +113,8 @@ def make_options(options: list[Callable]) -> Callable:
112
113
 
113
114
 
114
115
  def choice_to_int_callback(
115
- ctx: click.Context, _: click.Parameter, value: Union[str, tuple[str, ...]]
116
- ) -> Optional[Union[int, tuple[int, ...]]]:
116
+ ctx: click.Context, _: click.Parameter, value: str | tuple[str, ...]
117
+ ) -> int | tuple[int, ...] | None:
117
118
  """
118
119
  Callback for click options that accept a choice. Converts the choice to an integer or a tuple
119
120
  of integers.
@@ -129,7 +130,7 @@ def choice_to_int_callback(
129
130
  :param value: The value to convert
130
131
  :type value: Union[str, tuple[str, ...]]
131
132
  :return: The converted value
132
- :rtype: Optional[Union[int, tuple[int, ...]]]
133
+ :rtype: int | tuple[int, ...] | None
133
134
  """
134
135
 
135
136
  # we do not check if the values can be converted to integers here because the click.Choice
@@ -169,9 +170,7 @@ def tuple_to_set_callback(
169
170
  return set(value)
170
171
 
171
172
 
172
- def output_dir_callback(
173
- ctx: click.Context, _: click.Parameter, value: Optional[Path]
174
- ) -> Optional[Path]:
173
+ def output_dir_callback(ctx: click.Context, _: click.Parameter, value: Path | None) -> Path | None:
175
174
  """
176
175
  Callback for ``--output-dir option``.
177
176
 
@@ -227,7 +226,7 @@ def ensure_at_least_one_param(ctx: click.Context) -> None:
227
226
 
228
227
 
229
228
  def wrap_string(
230
- string: str, width: int, initial_indent: int, indent: int, max_lines: Optional[int] = None
229
+ string: str, width: int, initial_indent: int, indent: int, max_lines: int | None = None
231
230
  ) -> str:
232
231
  """
233
232
  Wraps a string to a specified width.
@@ -1,6 +1,7 @@
1
+ from collections.abc import Callable
1
2
  from dataclasses import dataclass
2
3
  from pathlib import Path
3
- from typing import Any, Callable, Optional, Union, get_type_hints
4
+ from typing import Any, get_type_hints
4
5
 
5
6
  from foundrytools import Font
6
7
  from foundrytools.lib.font_finder import (
@@ -28,9 +29,9 @@ class SaveOptions:
28
29
  A class that specifies how to save the font.
29
30
  """
30
31
 
31
- reorder_tables: Optional[bool] = True
32
+ reorder_tables: bool | None = True
32
33
  suffix: str = ""
33
- output_dir: Optional[Path] = None
34
+ output_dir: Path | None = None
34
35
  overwrite: bool = False
35
36
 
36
37
 
@@ -74,7 +75,7 @@ class TaskRunnerConfig: # pylint: disable=too-few-public-methods
74
75
 
75
76
  @staticmethod
76
77
  def _set_options(
77
- options_group: Union[dict, FinderOptions, SaveOptions], options: dict[str, Any]
78
+ options_group: dict | FinderOptions | SaveOptions, options: dict[str, Any]
78
79
  ) -> None:
79
80
  """
80
81
  Update attributes of an options_group with provided options if the attribute exists.
@@ -1,9 +1,10 @@
1
1
  # https://realpython.com/python-timer/#the-python-timer-code
2
2
 
3
3
  import time
4
+ from collections.abc import Callable
4
5
  from contextlib import ContextDecorator
5
6
  from dataclasses import dataclass, field
6
- from typing import Any, Callable, ClassVar, Optional
7
+ from typing import Any, ClassVar
7
8
 
8
9
 
9
10
  class TimerError(Exception):
@@ -15,10 +16,10 @@ class Timer(ContextDecorator):
15
16
  """Time your code using a class, context manager, or decorator"""
16
17
 
17
18
  timers: ClassVar[dict[str, float]] = {}
18
- name: Optional[str] = None
19
+ name: str | None = None
19
20
  text: str = "Elapsed time: {:0.4f} seconds"
20
- logger: Optional[Callable[[str], None]] = print
21
- _start_time: Optional[float] = field(default=None, init=False, repr=False)
21
+ logger: Callable[[str], None] | None = print
22
+ _start_time: float | None = field(default=None, init=False, repr=False)
22
23
 
23
24
  def __post_init__(self) -> None:
24
25
  """Initialization: add timer to dict of timers"""
@@ -1,161 +0,0 @@
1
- # Byte-compiled / optimized / DLL files
2
- __pycache__/
3
- *.py[cod]
4
- *$py.class
5
-
6
- # C extensions
7
- *.so
8
-
9
- # Distribution / packaging
10
- .Python
11
- build/
12
- develop-eggs/
13
- dist/
14
- downloads/
15
- eggs/
16
- .eggs/
17
- foundryToolsCLI_2/lib/
18
- lib64/
19
- parts/
20
- sdist/
21
- var/
22
- wheels/
23
- share/python-wheels/
24
- *.egg-info/
25
- .installed.cfg
26
- *.egg
27
- MANIFEST
28
-
29
- # PyInstaller
30
- # Usually these files are written by a python script from a template
31
- # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
- *.manifest
33
- *.spec
34
-
35
- # Installer logs
36
- pip-log.txt
37
- pip-delete-this-directory.txt
38
-
39
- # Unit test / coverage reports
40
- htmlcov/
41
- .tox/
42
- .nox/
43
- .coverage
44
- .coverage.*
45
- .cache
46
- nosetests.xml
47
- coverage.xml
48
- *.cover
49
- *.py,cover
50
- .hypothesis/
51
- .pytest_cache/
52
- cover/
53
-
54
- # Translations
55
- *.mo
56
- *.pot
57
-
58
- # Django stuff:
59
- *.log
60
- local_settings.py
61
- db.sqlite3
62
- db.sqlite3-journal
63
-
64
- # Flask stuff:
65
- instance/
66
- .webassets-cache
67
-
68
- # Scrapy stuff:
69
- .scrapy
70
-
71
- # Sphinx documentation
72
- docs/_build/
73
-
74
- # PyBuilder
75
- .pybuilder/
76
- target/
77
-
78
- # Jupyter Notebook
79
- .ipynb_checkpoints
80
-
81
- # IPython
82
- profile_default/
83
- ipython_config.py
84
-
85
- # pyenv
86
- # For a library or package, you might want to ignore these files since the code is
87
- # intended to run in multiple environments; otherwise, check them in:
88
- # .python-version
89
-
90
- # pipenv
91
- # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
- # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
- # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
- # install all needed dependencies.
95
- #Pipfile.lock
96
-
97
- # poetry
98
- # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
- # This is especially recommended for binary packages to ensure reproducibility, and is more
100
- # commonly ignored for libraries.
101
- # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
- #poetry.lock
103
-
104
- # pdm
105
- # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
- #pdm.lock
107
- # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
- # in version control.
109
- # https://pdm.fming.dev/#use-with-ide
110
- .pdm.toml
111
-
112
- # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
- __pypackages__/
114
-
115
- # Celery stuff
116
- celerybeat-schedule
117
- celerybeat.pid
118
-
119
- # SageMath parsed files
120
- *.sage.py
121
-
122
- # Environments
123
- .env
124
- .venv
125
- env/
126
- venv/
127
- ENV/
128
- env.bak/
129
- venv.bak/
130
-
131
- # Spyder project settings
132
- .spyderproject
133
- .spyproject
134
-
135
- # Rope project settings
136
- .ropeproject
137
-
138
- # mkdocs documentation
139
- /site
140
-
141
- # mypy
142
- .mypy_cache/
143
- .dmypy.json
144
- dmypy.json
145
-
146
- # Pyre type checker
147
- .pyre/
148
-
149
- # pytype static type analyzer
150
- .pytype/
151
-
152
- # Cython debug symbols
153
- cython_debug/
154
-
155
- # PyCharm
156
- # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
- # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158
- # and can be added to the global gitignore or merged into this file. For a more nuclear
159
- # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160
- #.idea/
161
- .qodo
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2023 ftCLI
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,3 +0,0 @@
1
- VERSION = __version__ = "2.0.3"
2
-
3
- __all__ = ["VERSION"]