Skip to content

Commit

Permalink
Rename properties to attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
MaddyGuthridge committed Sep 29, 2023
1 parent 4d6222a commit 00fefaa
Show file tree
Hide file tree
Showing 12 changed files with 837 additions and 843 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ instantiated element.
```

Calling an instance of a `Tag` will return a new tag containing all elements of
the original tag combined with the new properties, but will not modify the
original instance, as I found the old behaviour confusing and bug-prone.
the original tag combined with the new attributes and children, but will not
modify the original instance, as I found the old behaviour confusing and
bug-prone.

```py
>>> para = p("Base paragraph")
Expand All @@ -93,11 +94,11 @@ original instance, as I found the old behaviour confusing and bug-prone.
</p>
```

## TODOs
## Known issues

There are a couple of things I haven't done yet
There are a couple of things I haven't gotten round to sorting out yet

* [ ] Add default properties to more tags
* [ ] Add default attributes to more tags
* [ ] Some tags (eg `<pre>`, `<script>`) currently aren't properly implemented
and escape their contents.

Expand All @@ -114,7 +115,7 @@ problem with the following steps:
garbage data and obsolete tags.

3. Use data from a YAML configuration file ([`meta/tags.yml`](meta/tags.yml))
to gather information on suggested properties and base classes to use for
to gather information on suggested attributes and base classes to use for
each tag.

4. Generate Python code to represent all of these tags, including their
Expand Down
51 changes: 22 additions & 29 deletions meta/generate_tag_defs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,9 @@
TEMPLATES_FOLDER = Path('./meta/templates')


def get_template_class_no_props():
"""
Returns the template for when there are no properties
"""
return open(TEMPLATES_FOLDER.joinpath('class_no_props.py')).read()


def get_template_class(name: str):
try:
return open(TEMPLATES_FOLDER.joinpath(f"class_props_{name}.py")).read()
return open(TEMPLATES_FOLDER.joinpath(f"class_attrs_{name}.py")).read()
except FileNotFoundError:
print(
f"Failed to find template file using base class {name}!",
Expand All @@ -37,46 +30,46 @@ def generate_tag_class(output: TextIO, tag: TagInfo):
"""
text = get_template_class(tag.base)

# Generate property arguments, unions and documentation
# Generate attribute arguments, unions and documentation
# To get a better idea of these, look inside the template files to see
# what would be replaced
prop_args_gen = []
prop_unions_gen = []
prop_docs_gen = []
for prop in tag.properties:
prop_args_gen.append(
attr_args_gen = []
attr_unions_gen = []
attr_docs_gen = []
for attr in tag.attributes:
attr_args_gen.append(
# Yucky hard-coded spaces, I can't be bothered to fix this
# Also making everything optional for the sake of users always
# being able to remove a property
f" {prop.name}: Optional[{prop.type}] = {prop.default!r},"
# being able to remove an attribute
f" {attr.name}: Optional[{attr.type}] = {attr.default!r},"
)
prop_unions_gen.append(f" '{prop.name}': {prop.name},")
prop_docs_gen.append(f"* {prop.name}: {prop.doc}")
attr_unions_gen.append(f" '{attr.name}': {attr.name},")
attr_docs_gen.append(f"* {attr.name}: {attr.doc}")

prop_args = '\n'.join(prop_args_gen).strip()
prop_unions = '\n'.join(prop_unions_gen).strip()
prop_docs_outer = '\n'.join(increase_indent(prop_docs_gen, 4)).strip()
prop_docs_inner = '\n'.join(increase_indent(prop_docs_gen, 8)).strip()
attr_args = '\n'.join(attr_args_gen).strip()
attr_unions = '\n'.join(attr_unions_gen).strip()
attr_docs_outer = '\n'.join(increase_indent(attr_docs_gen, 4)).strip()
attr_docs_inner = '\n'.join(increase_indent(attr_docs_gen, 8)).strip()

# Determine whether the class should mandate keyword-only args
# If there are no named properties, we set it to '' to avoid a syntax error
# If there are no named attributes, we set it to '' to avoid a syntax error
# Otherwise, we add a `*,` to prevent kwargs from being used as positional
# args in self-closing tags
if len(tag.properties):
if len(tag.attributes):
kw_only = '*,'
else:
kw_only = ''

# Now we just need to replace in all of the templated properties
# Now we just need to replace in all of the templated attributes
text = text\
.replace("{name}", tag.name)\
.replace("{base}", tag.base)\
.replace("{description}", tag.description)\
.replace("{link}", tag.mdn_link)\
.replace("{prop_args}", prop_args)\
.replace("{prop_unions}", prop_unions)\
.replace("{prop_docs_outer}", prop_docs_outer)\
.replace("{prop_docs_inner}", prop_docs_inner)\
.replace("{attr_args}", attr_args)\
.replace("{attr_unions}", attr_unions)\
.replace("{attr_docs_outer}", attr_docs_outer)\
.replace("{attr_docs_inner}", attr_docs_inner)\
.replace("{kw_only}", kw_only)

print(text, file=output)
Expand Down
66 changes: 33 additions & 33 deletions meta/scrape_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,30 +98,30 @@ def domXrefReplace(lookup: str, presentation: Optional[str] = None) -> str:
"""Type definition for info grabbed from MDN docs"""


class PropYmlItem(TypedDict):
class AttrYmlItem(TypedDict):
"""
Properties of a tag, defined in tags.yml
Attributes of a tag, defined in tags.yml
"""

doc: str
"""Documentation for the property"""
"""Documentation for the attribute"""

default: NotRequired[str]
"""
Default value of the property - this is passed to eval to convert to Python
code.
Default value of the attribute - this is passed to eval to convert to
Python code.
"""

type: NotRequired[str]
"""Python type to accept for the property"""
"""Python type to accept for the attribute"""


class TagsYmlItem(TypedDict):
"""
A tag which has suggested keys
"""
properties: NotRequired[dict[str, str | PropYmlItem]]
"""Mapping of properties used by the tag (name: description)"""
attributes: NotRequired[dict[str, str | AttrYmlItem]]
"""Mapping of attributes used by the tag (name: description)"""

base: NotRequired[str]
"""Name of the base class to derive from (eg SelfClosingTag)"""
Expand All @@ -135,29 +135,29 @@ class TagsYmlItem(TypedDict):


@dataclass
class Prop:
class Attr:
"""
Information about a property
Information about a attribute
"""

name: str
"""
Name of the property
Name of the attribute
"""

doc: Optional[str]
"""
Documentation of the property if applicable
Documentation of the attribute if applicable
"""

type: str
"""
Type to accept for the property
Type to accept for the attribute
"""

default: Optional[Any]
"""
Default value for the property
Default value for the attribute
"""


Expand Down Expand Up @@ -187,9 +187,9 @@ class TagInfo:
Link to full documentation on MDN
"""

properties: list[Prop]
attributes: list[Attr]
"""
List of properties and their documentation.
List of attributes and their documentation.
"""


Expand Down Expand Up @@ -348,34 +348,34 @@ def scrape_html_elements() -> list[TagMdnInfo]:
return parsed


def load_tag_props_yaml() -> TagsYaml:
def load_tag_attrs_yaml() -> TagsYaml:
"""
Load tags configuration
"""
with open(TAGS_YAML) as f:
return yaml.load(f, yaml.Loader)


def prop_entries_to_object(
def attr_entries_to_object(
tags: TagsYaml,
tag_name: str,
) -> list[Prop]:
) -> list[Attr]:
"""
Convert a tags yaml entry into a Prop object for use elsewhere, given its
Convert a tags yaml entry into a Attr object for use elsewhere, given its
name.
For items with no entries, give no properties
For items with no entries, give no attributes
"""
if tag_name not in tags:
return []

tag_data = tags[tag_name]

if 'properties' not in tag_data:
if 'attributes' not in tag_data:
return []

props = []
for name, value in tag_data['properties'].items():
attrs = []
for name, value in tag_data['attributes'].items():
if isinstance(value, str):
doc: Optional[str] = value
default: Optional[str] = None
Expand All @@ -389,8 +389,8 @@ def prop_entries_to_object(
else:
default = None
type = value.get("type", "Any")
props.append(Prop(name, doc, type, default))
return props
attrs.append(Attr(name, doc, type, default))
return attrs


def get_tag_rename(tags: TagsYaml, tag_name: str) -> str:
Expand Down Expand Up @@ -427,7 +427,7 @@ def make_mdn_link(tag: str) -> str:

def elements_to_element_structs(
mdn_data: list[TagMdnInfo],
tag_props: TagsYaml,
tag_attrs: TagsYaml,
) -> list[TagInfo]:
"""
Combine all MDN tag info with our own info (where applicable)
Expand All @@ -436,11 +436,11 @@ def elements_to_element_structs(

for name, description in mdn_data:
output.append(TagInfo(
name=get_tag_rename(tag_props, name),
name=get_tag_rename(tag_attrs, name),
description=description,
base=get_tag_base_class(tag_props, name),
base=get_tag_base_class(tag_attrs, name),
mdn_link=make_mdn_link(name),
properties=prop_entries_to_object(tag_props, name),
attributes=attr_entries_to_object(tag_attrs, name),
))

return output
Expand All @@ -451,9 +451,9 @@ def main():
Main logic of the scraper
"""
mdn_elements = scrape_html_elements()
tag_props = load_tag_props_yaml()
tag_attrs = load_tag_attrs_yaml()

return elements_to_element_structs(mdn_elements, tag_props)
return elements_to_element_structs(mdn_elements, tag_attrs)


def print_elements(parsed: list[TagInfo]):
Expand All @@ -466,7 +466,7 @@ def print_elements(parsed: list[TagInfo]):
print(ele.description)
print(ele.mdn_link)
print()
print(ele.properties)
print(ele.attributes)
print()
print('------------------')
print()
Expand Down
18 changes: 9 additions & 9 deletions meta/tags.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,43 @@ del:

base:
base: SelfClosingTag
properties:
attributes:
href: Base URL to use in the document
target: Default target to use in the document

link:
base: SelfClosingTag
properties:
attributes:
href: Location of the file being linked to
rel: Kind of file being loaded (eg stylesheet)

a:
base: StylableTag
properties:
attributes:
href: URL of page to link to
target: Use "_blank" to open in a new tab

script:
properties:
attributes:
type:
doc: Type of script to use
default: "'text/javascript'"

style:
properties:
attributes:
type:
doc: Type of style to use
default: "'text/css'"

form:
properties:
attributes:
method:
doc: HTTP request method to use
default: "'POST'"

input:
base: SelfClosingTag
properties:
attributes:
type: Kind of input control to use (checkbox, radio, date, password, text, etc)
name: Name of the field. Submitted with the form as part of a name/value pair
value: Initial value of the control
Expand All @@ -57,7 +57,7 @@ input:
default: "False"

label:
properties:
attributes:
for_: ID of input field to associate this label with

p:
Expand Down Expand Up @@ -97,6 +97,6 @@ h6:
base: StylableTag

img:
properties:
attributes:
src: Source of the image
alt: Alt text of the image
Loading

0 comments on commit 00fefaa

Please sign in to comment.