refdef 0.2.0__tar.gz → 0.3.0__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 (168) hide show
  1. {refdef-0.2.0 → refdef-0.3.0}/PKG-INFO +1 -1
  2. {refdef-0.2.0 → refdef-0.3.0}/docs/CHANGELOG.md +4 -0
  3. {refdef-0.2.0 → refdef-0.3.0}/docs/api-snapshot.txt +10 -0
  4. {refdef-0.2.0 → refdef-0.3.0}/docs/guide/class-table.md +9 -0
  5. {refdef-0.2.0 → refdef-0.3.0}/docs/llms.txt +2 -0
  6. {refdef-0.2.0 → refdef-0.3.0}/pyproject.toml +1 -1
  7. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/__init__.py +3 -1
  8. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/_api.py +16 -1
  9. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/scheme.py +8 -2
  10. refdef-0.3.0/tests/test_scheme_export.py +16 -0
  11. {refdef-0.2.0 → refdef-0.3.0}/uv.lock +1 -1
  12. {refdef-0.2.0 → refdef-0.3.0}/.gitattributes +0 -0
  13. {refdef-0.2.0 → refdef-0.3.0}/.github/workflows/ci.yml +0 -0
  14. {refdef-0.2.0 → refdef-0.3.0}/.github/workflows/docs.yml +0 -0
  15. {refdef-0.2.0 → refdef-0.3.0}/.github/workflows/publish.yml +0 -0
  16. {refdef-0.2.0 → refdef-0.3.0}/.gitignore +0 -0
  17. {refdef-0.2.0 → refdef-0.3.0}/.python-version +0 -0
  18. {refdef-0.2.0 → refdef-0.3.0}/AGENTS.md +0 -0
  19. {refdef-0.2.0 → refdef-0.3.0}/CLAUDE.md +0 -0
  20. {refdef-0.2.0 → refdef-0.3.0}/DESIGN.md +0 -0
  21. {refdef-0.2.0 → refdef-0.3.0}/LICENSE +0 -0
  22. {refdef-0.2.0 → refdef-0.3.0}/README.md +0 -0
  23. {refdef-0.2.0 → refdef-0.3.0}/RULES.md +0 -0
  24. {refdef-0.2.0 → refdef-0.3.0}/docs/guide/build.md +0 -0
  25. {refdef-0.2.0 → refdef-0.3.0}/docs/guide/check.md +0 -0
  26. {refdef-0.2.0 → refdef-0.3.0}/docs/guide/index.md +0 -0
  27. {refdef-0.2.0 → refdef-0.3.0}/docs/guide/number.md +0 -0
  28. {refdef-0.2.0 → refdef-0.3.0}/docs/guide/plc.md +0 -0
  29. {refdef-0.2.0 → refdef-0.3.0}/docs/guide/print.md +0 -0
  30. {refdef-0.2.0 → refdef-0.3.0}/docs/guide/read.md +0 -0
  31. {refdef-0.2.0 → refdef-0.3.0}/docs/guide/sort.md +0 -0
  32. {refdef-0.2.0 → refdef-0.3.0}/docs/guide/terminals.md +0 -0
  33. {refdef-0.2.0 → refdef-0.3.0}/docs/guide/tree.md +0 -0
  34. {refdef-0.2.0 → refdef-0.3.0}/docs/index.md +0 -0
  35. {refdef-0.2.0 → refdef-0.3.0}/justfile +0 -0
  36. {refdef-0.2.0 → refdef-0.3.0}/mkdocs.yml +0 -0
  37. {refdef-0.2.0 → refdef-0.3.0}/scripts/build_site.py +0 -0
  38. {refdef-0.2.0 → refdef-0.3.0}/scripts/design_guard.py +0 -0
  39. {refdef-0.2.0 → refdef-0.3.0}/scripts/family_compare.py +0 -0
  40. {refdef-0.2.0 → refdef-0.3.0}/scripts/family_files.txt +0 -0
  41. {refdef-0.2.0 → refdef-0.3.0}/scripts/gen_render_corpus.py +0 -0
  42. {refdef-0.2.0 → refdef-0.3.0}/scripts/gen_table.py +0 -0
  43. {refdef-0.2.0 → refdef-0.3.0}/scripts/release_guard.py +0 -0
  44. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/__main__.py +0 -0
  45. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/_check.py +0 -0
  46. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/_heads.py +0 -0
  47. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/_parse.py +0 -0
  48. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/_render.py +0 -0
  49. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/designation.py +0 -0
  50. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/issues.py +0 -0
  51. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/level.py +0 -0
  52. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/numbering.py +0 -0
  53. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/order.py +0 -0
  54. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/plc.py +0 -0
  55. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/py.typed +0 -0
  56. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/tables/__init__.py +0 -0
  57. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/tables/standard.py +0 -0
  58. {refdef-0.2.0 → refdef-0.3.0}/src/refdef/terminal.py +0 -0
  59. {refdef-0.2.0 → refdef-0.3.0}/tests/conftest.py +0 -0
  60. {refdef-0.2.0 → refdef-0.3.0}/tests/corpus/addresses.txt +0 -0
  61. {refdef-0.2.0 → refdef-0.3.0}/tests/corpus/render.txt +0 -0
  62. {refdef-0.2.0 → refdef-0.3.0}/tests/corpus/strings.txt +0 -0
  63. {refdef-0.2.0 → refdef-0.3.0}/tests/family.py +0 -0
  64. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/check_aspect_off.py +0 -0
  65. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/check_duplicate.py +0 -0
  66. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/check_letter_count.py +0 -0
  67. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/check_never_letter.py +0 -0
  68. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/check_unknown_class.py +0 -0
  69. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_bad_label.py +0 -0
  70. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_dot_level.py +0 -0
  71. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_empty.py +0 -0
  72. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_empty_level.py +0 -0
  73. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_empty_name.py +0 -0
  74. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_empty_pin.py +0 -0
  75. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_empty_top.py +0 -0
  76. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_free_label.py +0 -0
  77. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_leading_zero.py +0 -0
  78. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_lower_case.py +0 -0
  79. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_no_sign.py +0 -0
  80. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_number_too_large.py +0 -0
  81. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_unsupported_suffix.py +0 -0
  82. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_whitespace.py +0 -0
  83. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/issues/parse_zero_number.py +0 -0
  84. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/designation_levels_list.py +0 -0
  85. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/designation_levels_not_level.py +0 -0
  86. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/designation_top_ampersand.py +0 -0
  87. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/designation_top_colon.py +0 -0
  88. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/designation_top_gt.py +0 -0
  89. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/designation_top_not_str.py +0 -0
  90. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/designation_top_outer_space.py +0 -0
  91. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/designation_top_semicolon.py +0 -0
  92. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_aspect_not_aspect.py +0 -0
  93. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_code_with_digit.py +0 -0
  94. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_coded_and_label.py +0 -0
  95. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_empty.py +0 -0
  96. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_label_angle.py +0 -0
  97. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_label_coded_shape_k1.py +0 -0
  98. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_label_coded_shape_lower.py +0 -0
  99. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_label_colon.py +0 -0
  100. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_label_digits_only.py +0 -0
  101. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_label_dot.py +0 -0
  102. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_label_outer_space.py +0 -0
  103. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_label_sign_char.py +0 -0
  104. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_letters_without_number.py +0 -0
  105. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_lower_case_code.py +0 -0
  106. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_non_ascii_code.py +0 -0
  107. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_number_and_label.py +0 -0
  108. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_number_bool.py +0 -0
  109. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_number_float.py +0 -0
  110. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_number_too_large.py +0 -0
  111. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/level_number_zero.py +0 -0
  112. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/signal_empty_designation.py +0 -0
  113. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/signal_empty_name.py +0 -0
  114. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/signal_name_not_str.py +0 -0
  115. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/signal_name_outer_space.py +0 -0
  116. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/signal_of_not_designation.py +0 -0
  117. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/terminal_empty_pin.py +0 -0
  118. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/terminal_no_pins.py +0 -0
  119. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/terminal_of_not_designation.py +0 -0
  120. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/terminal_pin_colon.py +0 -0
  121. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/terminal_pin_not_str.py +0 -0
  122. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/terminal_pin_outer_space.py +0 -0
  123. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/malformed/terminal_pins_list.py +0 -0
  124. {refdef-0.2.0 → refdef-0.3.0}/tests/fixtures/tables/classes.csv +0 -0
  125. {refdef-0.2.0 → refdef-0.3.0}/tests/fuzz.py +0 -0
  126. {refdef-0.2.0 → refdef-0.3.0}/tests/guide/test_guide.py +0 -0
  127. {refdef-0.2.0 → refdef-0.3.0}/tests/issue_hook.py +0 -0
  128. {refdef-0.2.0 → refdef-0.3.0}/tests/test_agents.py +0 -0
  129. {refdef-0.2.0 → refdef-0.3.0}/tests/test_api.py +0 -0
  130. {refdef-0.2.0 → refdef-0.3.0}/tests/test_boundary.py +0 -0
  131. {refdef-0.2.0 → refdef-0.3.0}/tests/test_boundary_r.py +0 -0
  132. {refdef-0.2.0 → refdef-0.3.0}/tests/test_check.py +0 -0
  133. {refdef-0.2.0 → refdef-0.3.0}/tests/test_deps.py +0 -0
  134. {refdef-0.2.0 → refdef-0.3.0}/tests/test_design_guard.py +0 -0
  135. {refdef-0.2.0 → refdef-0.3.0}/tests/test_designation.py +0 -0
  136. {refdef-0.2.0 → refdef-0.3.0}/tests/test_docs.py +0 -0
  137. {refdef-0.2.0 → refdef-0.3.0}/tests/test_family_compare.py +0 -0
  138. {refdef-0.2.0 → refdef-0.3.0}/tests/test_family_rules.py +0 -0
  139. {refdef-0.2.0 → refdef-0.3.0}/tests/test_gen_table.py +0 -0
  140. {refdef-0.2.0 → refdef-0.3.0}/tests/test_lean.py +0 -0
  141. {refdef-0.2.0 → refdef-0.3.0}/tests/test_level.py +0 -0
  142. {refdef-0.2.0 → refdef-0.3.0}/tests/test_licence.py +0 -0
  143. {refdef-0.2.0 → refdef-0.3.0}/tests/test_llms.py +0 -0
  144. {refdef-0.2.0 → refdef-0.3.0}/tests/test_malformed.py +0 -0
  145. {refdef-0.2.0 → refdef-0.3.0}/tests/test_numbering.py +0 -0
  146. {refdef-0.2.0 → refdef-0.3.0}/tests/test_order.py +0 -0
  147. {refdef-0.2.0 → refdef-0.3.0}/tests/test_parse.py +0 -0
  148. {refdef-0.2.0 → refdef-0.3.0}/tests/test_plc.py +0 -0
  149. {refdef-0.2.0 → refdef-0.3.0}/tests/test_pure.py +0 -0
  150. {refdef-0.2.0 → refdef-0.3.0}/tests/test_pyproject_pins.py +0 -0
  151. {refdef-0.2.0 → refdef-0.3.0}/tests/test_release_guard.py +0 -0
  152. {refdef-0.2.0 → refdef-0.3.0}/tests/test_render.py +0 -0
  153. {refdef-0.2.0 → refdef-0.3.0}/tests/test_roundtrip.py +0 -0
  154. {refdef-0.2.0 → refdef-0.3.0}/tests/test_site.py +0 -0
  155. {refdef-0.2.0 → refdef-0.3.0}/tests/test_sketch_cases.py +0 -0
  156. {refdef-0.2.0 → refdef-0.3.0}/tests/test_sketch_rd-3.py +0 -0
  157. {refdef-0.2.0 → refdef-0.3.0}/tests/test_sketch_rd-4.py +0 -0
  158. {refdef-0.2.0 → refdef-0.3.0}/tests/test_sketch_rd-5.py +0 -0
  159. {refdef-0.2.0 → refdef-0.3.0}/tests/test_sketch_rd-6b.py +0 -0
  160. {refdef-0.2.0 → refdef-0.3.0}/tests/test_sketch_rd2.py +0 -0
  161. {refdef-0.2.0 → refdef-0.3.0}/tests/test_standalone.py +0 -0
  162. {refdef-0.2.0 → refdef-0.3.0}/tests/test_surface.py +0 -0
  163. {refdef-0.2.0 → refdef-0.3.0}/tests/test_table.py +0 -0
  164. {refdef-0.2.0 → refdef-0.3.0}/tests/test_table_version.py +0 -0
  165. {refdef-0.2.0 → refdef-0.3.0}/tests/test_tree.py +0 -0
  166. {refdef-0.2.0 → refdef-0.3.0}/tests/test_tree_properties.py +0 -0
  167. {refdef-0.2.0 → refdef-0.3.0}/tests/test_vocab.py +0 -0
  168. {refdef-0.2.0 → refdef-0.3.0}/tests/test_wording.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: refdef
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Reference designations as strict, typed, immutable Python values: parse, check, print, number and order
5
5
  Project-URL: Repository, https://github.com/OleJBondahl/refdef
6
6
  Author: Ole Johan Bondahl
@@ -5,6 +5,10 @@ Every change to the public surface gets a line under Unreleased until the releas
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.3.0 (2026-10-24)
9
+
10
+ - Export `LetterRule` and `ClassTable` from `refdef`.
11
+
8
12
  ## 0.2.0 (2026-10-23)
9
13
 
10
14
  - Add `lowest_free(taken, reserved=(), *, first=1)`: the lowest free number over plain ints.
@@ -7,6 +7,11 @@ class Aspect
7
7
  class ClassEntry(code: 'str', title: 'str') -> 'None'
8
8
  One class code with its title, the typical device of the class.
9
9
 
10
+ class ClassTable(name: 'str', version: 'str', depth: 'int', entries: 'tuple[ClassEntry, ...]') -> 'None'
11
+ Versioned class entries; `depth` is how many leading letters of a code are looked up.
12
+
13
+ A caller passes its own table to a custom Scheme; `version` names the table, not the library.
14
+
10
15
  class Designation(levels: 'tuple[Level, ...]' = (), top: 'str' = '') -> 'None'
11
16
  A tuple of levels and an optional top node, built by the constructor and `child` only.
12
17
 
@@ -18,6 +23,11 @@ class Issue(code: 'IssueCode', subject: 'str', message: 'str') -> 'None'
18
23
  class IssueCode
19
24
  The 21 issue codes: the first 16 come from the readers, the last 5 from check.
20
25
 
26
+ class LetterRule(min_count: 'int' = 1, max_count: 'int' = 3, never: 'frozenset[str]' = frozenset({'I', 'O'})) -> 'None'
27
+ How many letters a code has and which letters never appear.
28
+
29
+ A caller builds a custom Scheme with it; `Scheme.extend` checks that `never` holds A-Z letters.
30
+
21
31
  class Level(aspect: 'Aspect', code: 'str' = '', number: 'int | None' = None, label: 'str' = '') -> 'None'
22
32
  One step in a tree: letters and a number (`-K2`), a number alone (`+3`) or a label (`+ER`).
23
33
 
@@ -33,3 +33,12 @@ stay barred and `extend` never lifts a letter. Any iterable of letters works; a
33
33
  strict = STANDARD.extend(never={"A"})
34
34
  print([i.code.value for i in check([parse("-A1", bare=P).value], strict)]) # ['never_letter', 'unknown_class']
35
35
  ```
36
+
37
+ A caller who builds its own scheme passes its own `ClassTable` and `LetterRule`, both public from `refdef`.
38
+
39
+ ```python
40
+ from refdef import Aspect, ClassEntry, ClassTable, LetterRule, Scheme
41
+ table = ClassTable(name="plant", version="1", depth=1, entries=(ClassEntry("A", "valve"),))
42
+ mine = Scheme("plant", frozenset({Aspect.PRODUCT}), LetterRule(1, 2, frozenset("IO")), table)
43
+ print(mine.classes.get("AB").title) # valve
44
+ ```
@@ -60,5 +60,7 @@ ChannelTag: a terminal with an optional address (refdef.plc), printed as the ter
60
60
  parse_address: reads a direct PLC address (refdef.plc), repairing outer whitespace and leading zeros with an issue each; text that is not one gives None and a NOT_ADDRESS issue.
61
61
  Scheme: what check tests against: the aspects that are on, a letter rule and a class table; extend returns a new scheme with project classes, aspects and further barred letters (never: any iterable of A-Z letters, not a str).
62
62
  ClassEntry: one class code with its title, the typical device of the class.
63
+ ClassTable: versioned class entries with the lookup depth; get(code) returns the entry or None.
64
+ LetterRule: how many letters a code has and which letters never appear.
63
65
  STANDARD: the shipped scheme (function, product and location on, one to three letters without I and O, the versioned standard class table).
64
66
  check: tests designations against a scheme and returns an issue for each finding, in order; it reads nothing but its scheme argument.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "refdef"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Reference designations as strict, typed, immutable Python values: parse, check, print, number and order"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.14"
@@ -8,16 +8,18 @@ from refdef.issues import Issue, IssueCode, Parsed
8
8
  from refdef.level import Aspect, Level
9
9
  from refdef.numbering import allocate, lowest_free, number_group
10
10
  from refdef.order import natural_key, sort_key
11
- from refdef.scheme import STANDARD, ClassEntry, Scheme
11
+ from refdef.scheme import STANDARD, ClassEntry, ClassTable, LetterRule, Scheme
12
12
  from refdef.terminal import Signal, Terminal
13
13
 
14
14
  __all__ = [
15
15
  "STANDARD",
16
16
  "Aspect",
17
17
  "ClassEntry",
18
+ "ClassTable",
18
19
  "Designation",
19
20
  "Issue",
20
21
  "IssueCode",
22
+ "LetterRule",
21
23
  "Level",
22
24
  "Parsed",
23
25
  "Scheme",
@@ -9,9 +9,24 @@ import inspect
9
9
  DOORS: tuple[str, ...] = ("refdef", "refdef.plc")
10
10
 
11
11
 
12
+ class _SortedSet:
13
+ """A set default whose repr lists its members sorted, so the hash seed never shows."""
14
+
15
+ def __init__(self, members: frozenset[object]) -> None:
16
+ self.text = "frozenset({" + ", ".join(repr(m) for m in sorted(members, key=repr)) + "})"
17
+
18
+ def __repr__(self) -> str:
19
+ return self.text
20
+
21
+
12
22
  def _signature(obj: object) -> str:
13
23
  """The signature as source text, so 3.14 and 3.15 print the same bytes."""
14
- return str(inspect.signature(obj, annotation_format=annotationlib.Format.STRING)) # ty: ignore[invalid-argument-type] - obj is callable here
24
+ sig = inspect.signature(obj, annotation_format=annotationlib.Format.STRING) # ty: ignore[invalid-argument-type] - obj is callable here
25
+ params = [
26
+ p.replace(default=_SortedSet(p.default)) if isinstance(p.default, frozenset) else p
27
+ for p in sig.parameters.values()
28
+ ]
29
+ return str(sig.replace(parameters=params))
15
30
 
16
31
 
17
32
  def _head(name: str, obj: object) -> str:
@@ -10,7 +10,10 @@ from refdef.tables import standard as _standard
10
10
 
11
11
  @dataclass(frozen=True)
12
12
  class LetterRule:
13
- """How many letters a code has and which letters never appear."""
13
+ """How many letters a code has and which letters never appear.
14
+
15
+ A caller builds a custom Scheme with it; `Scheme.extend` checks that `never` holds A-Z letters.
16
+ """
14
17
 
15
18
  min_count: int = 1
16
19
  max_count: int = 3
@@ -27,7 +30,10 @@ class ClassEntry:
27
30
 
28
31
  @dataclass(frozen=True)
29
32
  class ClassTable:
30
- """Versioned class entries; `depth` is how many leading letters of a code are looked up."""
33
+ """Versioned class entries; `depth` is how many leading letters of a code are looked up.
34
+
35
+ A caller passes its own table to a custom Scheme; `version` names the table, not the library.
36
+ """
31
37
 
32
38
  name: str
33
39
  version: str
@@ -0,0 +1,16 @@
1
+ """LetterRule and ClassTable are public names: a caller builds a Scheme from them."""
2
+
3
+ import refdef
4
+ from refdef import Aspect, ClassEntry, ClassTable, LetterRule, Scheme
5
+
6
+
7
+ def test_letter_rule_and_class_table_are_exported():
8
+ assert "LetterRule" in refdef.__all__
9
+ assert "ClassTable" in refdef.__all__
10
+
11
+
12
+ def test_a_scheme_built_from_exported_names_looks_up_its_entry():
13
+ table = ClassTable(name="t", version="1", depth=1, entries=(ClassEntry("A", "x"),))
14
+ scheme = Scheme("custom", frozenset({Aspect.PRODUCT}), LetterRule(1, 2, frozenset("I")), table)
15
+ assert scheme.classes.get("A") == ClassEntry("A", "x")
16
+ assert scheme.letters.never == frozenset("I")
@@ -321,7 +321,7 @@ wheels = [
321
321
 
322
322
  [[package]]
323
323
  name = "refdef"
324
- version = "0.2.0"
324
+ version = "0.3.0"
325
325
  source = { editable = "." }
326
326
 
327
327
  [package.dev-dependencies]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes