diff --git a/bikeshed/cddl.py b/bikeshed/cddl.py index eb45cd016a..9286387e67 100644 --- a/bikeshed/cddl.py +++ b/bikeshed/cddl.py @@ -33,10 +33,11 @@ def serializeValue(self, prefix: str, value: str, suffix: str, node: cddlparser. # Cannot easily link member key back to a definition return name else: - # Name of created type should not include the quotes + # Create a key with and without quotes as linking text + lts = [value, name] return '{}'.format( h.escapeAttr(forName), - h.escapeAttr(value), + h.escapeAttr("|".join(lts)), name, ) elif isinstance(parent, cddlparser.ast.Operator) and parent.controller == node: @@ -49,9 +50,10 @@ def serializeValue(self, prefix: str, value: str, suffix: str, node: cddlparser. if forName is None: return name else: + lts = [value, name] return '{}'.format( h.escapeAttr(forName), - h.escapeAttr(name), + h.escapeAttr("|".join(lts)), name, ) @@ -88,9 +90,14 @@ def serializeName(self, name: str, node: cddlparser.ast.CDDLNode) -> str: # Cannot easily link member key back to a definition return name else: + lts = [] + if name[0] == '"': + lts = [name[1:-1], name] + else: + lts = [name, '"' + name + '"'] return '{}'.format( h.escapeAttr(forName), - h.escapeAttr(name), + h.escapeAttr("|".join(lts)), name, ) elif isinstance(parent, cddlparser.ast.GenericParameters): @@ -162,66 +169,96 @@ def markupCDDLBlock(pre: t.ElementT, doc: t.SpecT) -> set[t.ElementT]: Convert blocks into "dfn" or links. """ localDfns = set() - forcedDfns = [] - for x in (h.treeAttr(pre, "data-dfn-force") or "").split(): - x = x.strip() - if x.endswith(""): - x = x[:-11] - forcedDfns.append(x) + + # A CDDL definition may create duplicates, e.g. argh = [ "dupl", "dupl" ] + # To detect these duplicates, let's maintain a list of actual definitions + # contained in CDDL blocks. + cddlDfns = set() + + def recordDfns(el: t.ElementT) -> bool: + cddlType = "cddl-" + (el.get("data-cddl-type") or "") + for cddlText in (el.get("data-lt") or "").split("|"): + linkFors = t.cast("list[str|None]", config.splitForValues(el.get("data-cddl-for", ""))) or [None] + for linkFor in linkFors: + dfnText = cddlType + ">" + (linkFor or "") + "/" + cddlText + if dfnText in cddlDfns: + forText = "" if linkFor is None else f' defined in type "{linkFor}"' + m.die( + f"CDDL {cddlType[5:]} {cddlText}{forText} creates a duplicate and cannot be referenced.\nPlease create additional CDDL types to disambiguate.", + ) + return False + cddlDfns.add(dfnText) + if cddlType != "cddl-parameter": + warned = False + for cddlText in (el.get("data-lt") or "").split("|"): + linkFors = t.cast("list[str|None]", config.splitForValues(el.get("data-cddl-for", ""))) or [None] + for linkFor in linkFors: + dfnText = (linkFor or "") + "/" + cddlText + if dfnText in cddlDfns and not warned: + warned = True + forText = "" if linkFor is None else f' defined in type "{linkFor}"' + m.warn( + f"CDDL {cddlType[5:]} {cddlText}{forText} creates a duplicate with another CDDL definition.\nLink type needs to be specified to reference the term.\nConsider creating additional CDDL types to disambiguate.", + ) + cddlDfns.add(dfnText) + return True + for el in h.findAll("cddl", pre): # Prefix CDDL types with "cddl-" to avoid confusion with other # types (notably CSS ones such as "value") cddlType = "cddl-" + (el.get("data-cddl-type") or "") assert isinstance(cddlType, str) url = None - forceDfn = False ref = None cddlText = None - for cddlText in (el.get("data-lt") or "").split("|"): - if cddlType == "interface" and cddlText in forcedDfns: - forceDfn = True - linkFors = t.cast("list[str|None]", config.splitForValues(el.get("data-cddl-for", ""))) or [None] - for linkFor in linkFors: - ref = doc.refs.getRef( - cddlType, - cddlText, - linkFor=linkFor, - status="local", - el=el, - error=False, - ) + # Record the dfns that the term would create and check whether + # it creates a duplicate. If it does, let's not link the term. + if not recordDfns(el): + el.tag = "span" + else: + for cddlText in (el.get("data-lt") or "").split("|"): + linkFors = t.cast("list[str|None]", config.splitForValues(el.get("data-cddl-for", ""))) or [None] + for linkFor in linkFors: + ref = doc.refs.getRef( + cddlType, + cddlText, + linkFor=linkFor, + status="local", + el=el, + error=True, + ) + if ref: + url = ref.url + break if ref: - url = ref.url break - if ref: - break - if url is None or forceDfn: - el.tag = "dfn" - el.set("data-dfn-type", cddlType) - del el.attrib["data-cddl-type"] - if el.get("data-cddl-for"): - el.set("data-dfn-for", el.get("data-cddl-for") or "") - del el.attrib["data-cddl-for"] - else: - # Copy over the auto-generated linking text to the manual dfn. - dfn = h.find(url, doc) - # How in the hell does this work, the url is not a selector??? - assert dfn is not None - lts = combineCDDLLinkingTexts(el.get("data-lt"), dfn.get("data-lt")) - dfn.set("data-lt", lts) - localDfns.add(dfn) - - # Reset the element to be a link to the manual dfn. - el.tag = "a" - el.set("data-link-type", cddlType) - el.set("data-lt", cddlText) - del el.attrib["data-cddl-type"] - if el.get("data-cddl-for"): - el.set("data-link-for", el.get("data-cddl-for") or "") - del el.attrib["data-cddl-for"] - if el.get("id"): - # ID was defensively added by the Marker. - del el.attrib["id"] + if url is None: + el.tag = "dfn" + el.set("data-dfn-type", cddlType) + del el.attrib["data-cddl-type"] + if el.get("data-cddl-for"): + el.set("data-dfn-for", el.get("data-cddl-for") or "") + del el.attrib["data-cddl-for"] + else: + # Copy over the auto-generated linking text to the manual dfn. + dfn = h.find(url, doc) + # How in the hell does this work, the url is not a selector??? + assert dfn is not None + lts = combineCDDLLinkingTexts(el.get("data-lt"), dfn.get("data-lt")) + dfn.set("data-lt", lts) + localDfns.add(dfn) + + # Reset the element to be a link to the manual dfn. + el.tag = "a" + el.set("data-link-type", cddlType) + el.set("data-lt", cddlText) + del el.attrib["data-cddl-type"] + if el.get("data-cddl-for"): + el.set("data-link-for", el.get("data-cddl-for") or "") + del el.attrib["data-cddl-for"] + if el.get("id"): + # ID was defensively added by the Marker. + del el.attrib["id"] return localDfns diff --git a/bikeshed/refs/utils.py b/bikeshed/refs/utils.py index d0f29a7460..4b2f7ae782 100644 --- a/bikeshed/refs/utils.py +++ b/bikeshed/refs/utils.py @@ -181,13 +181,6 @@ def linkTextVariations(str: str, linkType: str | None) -> t.Generator[str, None, if str[0] != '"': yield '"' + str + '"' - if config.linkTypeIn(linkType, "cddl-key") or config.linkTypeIn(linkType, "cddl-value"): - # Allow linking to a cddl-key or cddl-value with or without quotes - if str[0] == '"': - yield str[1:-1] - if str[0] != '"': - yield '"' + str + '"' - if t.TYPE_CHECKING: U = t.TypeVar("U", bound="t.MutableMapping|t.MutableSequence") diff --git a/tests/cddl001.html b/tests/cddl001.html index da46e7d2bd..efedf3e5b6 100644 --- a/tests/cddl001.html +++ b/tests/cddl001.html @@ -602,15 +602,15 @@

Table of Contents

1. The definitions

- The attire enumeration lets you wear a "bow tie", a "necktie", an "Internet attire" or "swimwear". -

The basecolors construct defines a set of base colors, including black and white.

-

The extended-color construct extends basecolors with orange.

+ The attire enumeration lets you wear a "bow tie", a "necktie", an "Internet attire" or "swimwear". +

The basecolors construct defines a set of base colors, including black and white.

+

The extended-color construct extends basecolors with orange.

2. The CDDL

attire = "bow tie" / "necktie" / "Internet attire"
 attireBlock = (
-    "bow tie" /
-    "necktie" /
-    "Internet attire"
+    "bow tie" /
+    "necktie" /
+    "Internet attire"
 )
 attireGroup = (
     attire //
@@ -621,21 +621,21 @@ 

2. address = { delivery } delivery = ( - street: tstr, - ? number: uint, - city // po-box: uint, - city // foo: tstr / uint // per-pickup: true + street: tstr, + ? number: uint, + city // po-box: uint, + city // foo: tstr / uint // per-pickup: true ) city = ( - name: tstr, - zip-code: uint + name: tstr, + zip-code: uint ) attire /= "swimwear" delivery //= ( - lat: float, long: float, drone-type: tstr + lat: float, long: float, drone-type: tstr ) device-address = byte @@ -645,13 +645,13 @@

2. terminal-color = &basecolors basecolors = ( - black: 0, red: 1, green: 2, yellow: 3, - blue: 4, magenta: 5, cyan: 6, white: 7, + black: 0, red: 1, green: 2, yellow: 3, + blue: 4, magenta: 5, cyan: 6, white: 7, ) extended-color = &( basecolors, - orange: 8, pink: 9, purple: 10, brown: 11, + orange: 8, pink: 9, purple: 10, brown: 11, ) my_breakfast = #6.55799(breakfast) ; cbor-any is too general! @@ -664,8 +664,8 @@

2. solid = tstr apartment = { - kitchen: size, - * bedroom: size, + kitchen: size, + * bedroom: size, } size = float ; in m2 @@ -673,28 +673,28 @@

2. one-or-two-people = [1*2 person] at-least-two-people = [2* person] person = ( - name: tstr, - age: uint, + name: tstr, + age: uint, ) located-samples = { - sample-point: int, - samples: [+ float], + sample-point: int, + samples: [+ float], * equipment-type => equipment-tolerances, } -equipment-type = [name: tstr, manufacturer: tstr] +equipment-type = [name: tstr, manufacturer: tstr] equipment-tolerances = [+ [float, float]] PersonalData = { - ? displayName: tstr, + ? displayName: tstr, NameComponents, - ? age: uint, + ? age: uint, * tstr => any } NameComponents = ( - ? firstName: tstr, - ? familyName: tstr, + ? firstName: tstr, + ? familyName: tstr, ) square-roots = {* x => y} @@ -702,32 +702,39 @@

2. y = float extensible-map-example = { - ? "optional-key": int, + ? "optional-key": int, * tstr => any } tcpflagbytes = bstr .bits flags flags = &( - fin: 8, - syn: 9, - rst: 10, - psh: 11, - ack: 12, - urg: 13, - ece: 14, - cwr: 15, - ns: 0, + fin: 8, + syn: 9, + rst: 10, + psh: 11, + ack: 12, + urg: 13, + ece: 14, + cwr: 15, + ns: 0, ) / (4..7) ; data offset bits rwxbits = uint .bits rwx -rwx = &(r: 2, w: 1, x: 0) +rwx = &(r: 2, w: 1, x: 0)

Index

Terms defined by this specification

+
  • + Internet attire + +
  • "kitchen", in § 2
  • kitchen, in § 2 +
  • "lat", in § 2
  • lat, in § 2
  • liquid, in § 2
  • located-samples, in § 2 +
  • "long", in § 2
  • long, in § 2 +
  • "magenta", in § 2
  • magenta, in § 2 +
  • "manufacturer", in § 2
  • manufacturer, in § 2
  • max-byte, in § 2
  • milk, in § 2
  • my_breakfast, in § 2 +
  • + "name" +
  • name +
  • + necktie + +
  • "ns", in § 2
  • ns, in § 2 +
  • "number", in § 2
  • number, in § 2
  • one-or-two-people, in § 2 +
  • "optional-key", in § 2
  • optional-key, in § 2 +
  • "orange", in § 1
  • orange, in § 1 +
  • "per-pickup", in § 2
  • per-pickup, in § 2
  • person, in § 2
  • PersonalData, in § 2 +
  • "pink", in § 2
  • pink, in § 2 +
  • "po-box", in § 2
  • po-box, in § 2
  • porridge, in § 2
  • protocol, in § 2 +
  • "psh", in § 2
  • psh, in § 2 +
  • "purple", in § 2
  • purple, in § 2 +
  • "r", in § 2
  • r, in § 2 +
  • "red", in § 2
  • red, in § 2 +
  • "rst", in § 2
  • rst, in § 2
  • rwx, in § 2
  • rwxbits, in § 2 +
  • "sample-point", in § 2
  • sample-point, in § 2 +
  • "samples", in § 2
  • samples, in § 2
  • size, in § 2
  • solid, in § 2
  • square-roots, in § 2 +
  • "street", in § 2
  • street, in § 2
  • "swimwear", in § 1 +
  • swimwear, in § 1 +
  • "syn", in § 2
  • syn, in § 2
  • tcpflagbytes, in § 2
  • terminal-color, in § 2
  • unlimited-people, in § 2 +
  • "urg", in § 2
  • urg, in § 2 +
  • "w", in § 2
  • w, in § 2
  • water, in § 2 +
  • "white", in § 1
  • white, in § 1 +
  • "x", in § 2
  • x
      @@ -842,7 +914,9 @@

      cddl-key for rwx, in § 2

  • y, in § 2 +
  • "yellow", in § 2
  • yellow, in § 2 +
  • "zip-code", in § 2
  • zip-code, in § 2

    CDDL Index

    diff --git a/tests/cddl002.html b/tests/cddl002.html index 3a14c50a84..496be696e0 100644 --- a/tests/cddl002.html +++ b/tests/cddl002.html @@ -619,14 +619,14 @@

    Table of Contents

    request-id = uint agent-capability = &( - receive-audio: 1 - receive-video: 2 - receive-presentation: 3 - control-presentation: 4 - receive-remote-playback: 5 - control-remote-playback: 6 - receive-streaming: 7 - send-streaming: 8 + receive-audio: 1 + receive-video: 2 + receive-presentation: 3 + control-presentation: 4 + receive-remote-playback: 5 + control-remote-playback: 6 + receive-streaming: 7 + send-streaming: 8 ) @@ -636,15 +636,23 @@

    agent-capability, in § Unnumbered section
  • agent-info, in § Unnumbered section
  • agent-info-response, in § Unnumbered section +
  • "control-presentation", in § Unnumbered section
  • control-presentation, in § Unnumbered section +
  • "control-remote-playback", in § Unnumbered section
  • control-remote-playback, in § Unnumbered section +
  • "receive-audio", in § Unnumbered section
  • receive-audio, in § Unnumbered section +
  • "receive-presentation", in § Unnumbered section
  • receive-presentation, in § Unnumbered section +
  • "receive-remote-playback", in § Unnumbered section
  • receive-remote-playback, in § Unnumbered section +
  • "receive-streaming", in § Unnumbered section
  • receive-streaming, in § Unnumbered section +
  • "receive-video", in § Unnumbered section
  • receive-video, in § Unnumbered section
  • request-id, in § Unnumbered section
  • response, in § Unnumbered section +
  • "send-streaming", in § Unnumbered section
  • send-streaming, in § Unnumbered section

    CDDL Index

    diff --git a/tests/cddl003.bs b/tests/cddl003.bs index e47e404409..ffbd78d1f9 100644 --- a/tests/cddl003.bs +++ b/tests/cddl003.bs @@ -56,6 +56,8 @@ nested = { key2: "nested" } } + +warn = [ (dupl: "val"), "dupl" ] Linking to the CDDL {#links} @@ -77,3 +79,5 @@ If there is no ambiguity, a key or value may be referenced directly, for example Otherwise, full path must be specified, as in `{^anon1/"v1"^}`, `{^anon2/key/"v2"^}`, `{^nested/key1/key2/"nested"^}`: {^anon1/"v1"^}, {^anon2/key/"v2"^}, {^nested/key1/key2/"nested"^}. In particular, partial paths such as `{^key/"unique"^}` or `{^key2/"nested"^}` do not work: {^key/"unique"^}, {^key2/"nested"^} (Bikeshed reports `No 'cddl' refs found` messages in such cases). + +The {^warn^} type defines both a key and a value "dupl". Bikeshed reports a warning to the console to suggest creating additional CDDL types, because the shorthand `{^warn/dupl^}` can no longer be used. It remains possible to reference either construct by expliciting the link type, i.e., `dupl` and `dupl`: dupl, dupl. diff --git a/tests/cddl003.console.txt b/tests/cddl003.console.txt index f12b56d7bb..e0cd0ef649 100644 --- a/tests/cddl003.console.txt +++ b/tests/cddl003.console.txt @@ -1,6 +1,9 @@ -LINE 79: No 'cddl' refs found for '"unique"'. +WARNING: CDDL value dupl defined in type "warn" creates a duplicate with another CDDL definition. +Link type needs to be specified to reference the term. +Consider creating additional CDDL types to disambiguate. +LINE 81: No 'cddl' refs found for '"unique"'. {^key/"unique"^} -LINE 79: No 'cddl' refs found for '"nested"'. +LINE 81: No 'cddl' refs found for '"nested"'. {^key2/"nested"^} LINE 17: Unexported dfn that's not referenced locally - did you mean to export it? local end diff --git a/tests/cddl003.html b/tests/cddl003.html index 097c614cf2..f02f89bf6b 100644 --- a/tests/cddl003.html +++ b/tests/cddl003.html @@ -609,38 +609,40 @@

    Table of Contents

    1. The CDDL

    CDDL specified for the local end:

    agent-capability = &(
    -  receive-audio: 1
    -  receive-video: 2
    -  receive-presentation: 3
    -  control-presentation: 4
    -  receive-remote-playback: 5
    -  control-remote-playback: 6
    -  receive-streaming: 7
    -  send-streaming: 8
    +  receive-audio: 1
    +  receive-video: 2
    +  receive-presentation: 3
    +  control-presentation: 4
    +  receive-remote-playback: 5
    +  control-remote-playback: 6
    +  receive-streaming: 7
    +  send-streaming: 8
     )
     

    CDDL specified for the remote end:

    webExtension.ExtensionBase64Encoded = {
    -  type: "base64",
    -  value: text,
    +  type: "base64",
    +  value: text,
     }
     

    CDDL specified for all modules:

    barewords = {
    -  bare: [+ float],
    -  "word": int,
    +  bare: [+ float],
    +  "word": int,
     }
     
    -anon1 = [("v1" / "v2")]
    +anon1 = [("v1" / "v2")]
     anon2 = {
    -  key: "v1" / "v2" / "unique"
    +  key: "v1" / "v2" / "unique"
     }
     
     nested = {
    -  key1: {
    -    key2: "nested"
    +  key1: {
    +    key2: "nested"
       }
     }
    +
    +warn = [ (dupl: "val"), "dupl" ]
     

    The agent-capability production gives information about agent capabilities.

    @@ -659,6 +661,7 @@