refactor #1
14
config.yaml
14
config.yaml
@ -1,12 +1,8 @@
|
||||
author: Jim Shepich III
|
||||
templates_folder: ./templates
|
||||
site_defaults:
|
||||
templates:
|
||||
partials: ./templates/partials
|
||||
components: ./templates/components
|
||||
pages: ./templates/pages
|
||||
templates_folder: ./site/templates
|
||||
sites:
|
||||
main:
|
||||
title: Jimlab
|
||||
base_url: http://localhost:8000
|
||||
web_root: ./dist
|
||||
build_cache: ./site
|
||||
@ -14,14 +10,20 @@ sites:
|
||||
- /assets
|
||||
articles:
|
||||
- ./pages/*.md
|
||||
template_selections:
|
||||
article: templates.components.simple_article
|
||||
|
||||
resume:
|
||||
title: Resume
|
||||
base_url: http://localhost:8000
|
||||
web_root: ./dist
|
||||
git_repo: ssh://gitea/jim/resume.git
|
||||
build_cache: ./build/resume
|
||||
assets:
|
||||
- 'shepich_resume.pdf'
|
||||
|
||||
dogma_jimfinium:
|
||||
title: Dogma Jimfinium
|
||||
base_url: http://localhost:8080/dogma-jimfinium
|
||||
git_repo: ssh://gitea/jim/dogma-jimfinium.git
|
||||
build_cache: ./build/dogma-jimfinium
|
||||
|
||||
@ -10,11 +10,11 @@ from typing import Optional
|
||||
from datetime import datetime, date
|
||||
from dotmap import DotMap
|
||||
|
||||
from .common import filepath_or_string, GlobalVars, SiteConfig
|
||||
from .templating import format_html_template, map_templates
|
||||
from .common import filepath_or_string, GlobalVars, SiteConfig, dotmap_access
|
||||
from .templating import format_html_template, map_templates, TemplateSelections
|
||||
from .assets import pull_git_repo, copy_assets
|
||||
from .articles import ArticleMetadata, load_markdown, build_articles, build_index
|
||||
|
||||
from .blog import build_blog_archive, build_rss_feed
|
||||
|
||||
|
||||
|
||||
@ -25,6 +25,10 @@ from .articles import ArticleMetadata, load_markdown, build_articles, build_inde
|
||||
|
||||
def build_site(site: SiteConfig, templates: DotMap):
|
||||
|
||||
# Do not build a site marked as unpublished.
|
||||
if not site.published:
|
||||
return None
|
||||
|
||||
# Initialize the build cache and web root, in case they do not exist.
|
||||
os.makedirs(site.build_cache, exist_ok = True)
|
||||
os.makedirs(site.web_root, exist_ok = True)
|
||||
@ -39,8 +43,15 @@ def build_site(site: SiteConfig, templates: DotMap):
|
||||
# Load the site's articles into an index.
|
||||
index = build_index(site)
|
||||
|
||||
# Determine which templates are to be used for explicit applications, e.g.
|
||||
# the tag component.
|
||||
template_selections = TemplateSelections(site, templates)
|
||||
|
||||
# Generate HTML pages for the articles.
|
||||
build_articles(site, index, templates)
|
||||
build_articles(site, index, templates, template_selections)
|
||||
|
||||
if len(site.articles or []):
|
||||
build_blog_archive(site, index, template_selections, templates = templates)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@ -8,7 +8,7 @@ from dotmap import DotMap
|
||||
from datetime import date
|
||||
|
||||
from .common import filepath_or_string, SiteConfig
|
||||
from .templating import format_html_template
|
||||
from .templating import format_html_template, TemplateSelections
|
||||
|
||||
class ArticleMetadata(pydantic.BaseModel):
|
||||
title: str
|
||||
@ -70,30 +70,39 @@ def build_index(site: SiteConfig) -> dict:
|
||||
return index
|
||||
|
||||
|
||||
def format_article_tags(tags: list[str], template = 'templates/components/blog_tag.html') -> list[str]:
|
||||
def format_article_tags(tags: list[str], tag_template, **kwargs) -> list[str]:
|
||||
'''Generates HTML article tag components from a list of tag names.'''
|
||||
return [
|
||||
format_html_template(template, tag_name = t) for t in tags
|
||||
format_html_template(tag_template, tag_name = t, **kwargs) for t in tags
|
||||
]
|
||||
|
||||
|
||||
def build_articles(site: SiteConfig, index: dict[str, tuple[ArticleMetadata, str]], templates: DotMap):
|
||||
def build_articles(
|
||||
site: SiteConfig,
|
||||
index: dict[str, tuple[ArticleMetadata, str]],
|
||||
templates: DotMap,
|
||||
template_selections: TemplateSelections
|
||||
):
|
||||
'''Generates HTML files for all of a given site's Markdown articles
|
||||
by interpolating the contents and metadata into the HTML templates.'''
|
||||
|
||||
for filestem, (metadata, content) in index.items():
|
||||
article = format_html_template(
|
||||
templates.components.blog_article,
|
||||
template_selections['article'],
|
||||
content = content,
|
||||
blog_tags = ' '.join(format_article_tags(metadata.tags)),
|
||||
blog_tags = ' '.join(format_article_tags(
|
||||
metadata.tags, template_selections['tag'], site = site
|
||||
)),
|
||||
metadata = metadata,
|
||||
templates = templates
|
||||
templates = templates,
|
||||
site = site
|
||||
)
|
||||
|
||||
page = format_html_template(
|
||||
templates.pages.default,
|
||||
template_selections['page'],
|
||||
content = article,
|
||||
templates = templates
|
||||
templates = templates,
|
||||
site = site
|
||||
|
||||
)
|
||||
|
||||
|
||||
@ -4,14 +4,13 @@ import datetime
|
||||
|
||||
from .common import SiteConfig
|
||||
from .articles import ArticleMetadata, format_article_tags
|
||||
from .templating import format_html_template
|
||||
|
||||
from .templating import format_html_template, TemplateSelections
|
||||
|
||||
|
||||
def build_blog_archive(
|
||||
site: SiteConfig,
|
||||
index: dict[str, tuple[str, str]],
|
||||
page_template = 'templates/pages/default.html',
|
||||
li_template = 'templates/components/blog_archive_li.html',
|
||||
template_selections: TemplateSelections,
|
||||
**kwargs
|
||||
) -> str:
|
||||
'''Converts an index, formatted as filestem: (metadata, contents) dict,
|
||||
@ -22,27 +21,50 @@ def build_blog_archive(
|
||||
'''
|
||||
|
||||
# Add each article as a list item to an unordered list.
|
||||
archive_html_content = '<ul>'
|
||||
archive_html_list = '<ul>'
|
||||
for article, (metadata, contents) in sorted(index.items(), key = lambda item: item[1][0].date)[::-1]:
|
||||
|
||||
# Generate HTML for the article (including metadata tags).
|
||||
archive_html_content += format_html_template(
|
||||
li_template,
|
||||
archive_html_list += format_html_template(
|
||||
template_selections['archive_li'],
|
||||
article_filestem = article,
|
||||
blog_tags = ' '.join(format_article_tags(metadata.tags)),
|
||||
metadata = metadata
|
||||
|
||||
blog_tags = ' '.join(format_article_tags(metadata.tags, template_selections['tag'], site = site)),
|
||||
metadata = metadata,
|
||||
site = site,
|
||||
**kwargs
|
||||
)
|
||||
archive_html_content +='</ul>'
|
||||
archive_html_list +='</ul>'
|
||||
|
||||
# Interpolate the article into the overall page template.
|
||||
archive_html_page = format_html_template(
|
||||
page_template,
|
||||
content = archive_html_content,
|
||||
# Generate the archive article.
|
||||
archive_html_article = format_html_template(
|
||||
template_selections['archive_article'],
|
||||
content = archive_html_list,
|
||||
site = site,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
return archive_html_page
|
||||
# Interpolate the article into the overall page template.
|
||||
archive_html_page = format_html_template(
|
||||
template_selections['page'],
|
||||
content = archive_html_article,
|
||||
site = site,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
with open(f'{site.web_root.rstrip('/')}/archive.html', 'w') as f:
|
||||
f.write(archive_html_page)
|
||||
|
||||
|
||||
def build_tag_reference(index: dict[str, tuple[str, str]]):
|
||||
tag_index = {}
|
||||
for article, (metadata, content) in index.items():
|
||||
for tag in metadata.tags:
|
||||
tag_index[tag] = (tag_index.get(tag,[])) + [article]
|
||||
|
||||
for tag, articles in tag_index.items():
|
||||
pass
|
||||
|
||||
|
||||
|
||||
# TODO: Finish
|
||||
def build_rss_feed(site: SiteConfig, index: dict[str, tuple[ArticleMetadata, str]]):
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import os
|
||||
import inspect
|
||||
import subprocess
|
||||
import pydantic
|
||||
from dotmap import DotMap
|
||||
from typing import Optional
|
||||
from datetime import date, datetime
|
||||
|
||||
@ -28,6 +30,42 @@ class SiteConfig(pydantic.BaseModel):
|
||||
base_url: str
|
||||
web_root: str
|
||||
build_cache: str
|
||||
title: str
|
||||
published: Optional[bool] = True
|
||||
git_repo: Optional[str] = None
|
||||
assets: Optional[list] = None
|
||||
articles: Optional[list] = None
|
||||
template_selections: Optional[dict] = {}
|
||||
|
||||
|
||||
def get_var_names(var):
|
||||
'''Gets the name(s) of an input variable.
|
||||
From https://stackoverflow.com/questions/18425225/getting-the-name-of-a-variable-as-a-string.'''
|
||||
callers_local_vars = inspect.currentframe().f_back.f_back.f_locals.items()
|
||||
return [var_name for var_name, var_val in callers_local_vars if var_val is var]
|
||||
|
||||
|
||||
def dotmap_access(d: DotMap, s: str):
|
||||
'''Facilitates nested subscripting into a DotMap using a
|
||||
string containing period-delimited keys.
|
||||
|
||||
# Example
|
||||
```python
|
||||
dd = DotMap({'a':{'b':{'c':42}}})
|
||||
dotmap_access(dd, 'dd.a.b.c')
|
||||
>>> 42
|
||||
```
|
||||
'''
|
||||
|
||||
keys = s.split('.')
|
||||
|
||||
# If the string starts with the dotmap's name, ignore it.
|
||||
if keys[0] in get_var_names(d):
|
||||
keys.pop(0)
|
||||
|
||||
# Iteratively subscript into the nested dotmap until it's done.
|
||||
result = d
|
||||
for k in keys:
|
||||
result = result[k]
|
||||
|
||||
return result
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
from dotmap import DotMap
|
||||
from .common import filepath_or_string, GlobalVars
|
||||
from .common import filepath_or_string, GlobalVars, SiteConfig, dotmap_access
|
||||
|
||||
|
||||
def extract_placeholders(s: str) -> set:
|
||||
@ -121,3 +121,32 @@ def map_templates(dir: str, parent = '') -> DotMap:
|
||||
output[filestem] = html
|
||||
|
||||
return DotMap(output)
|
||||
|
||||
|
||||
# TODO: Come up with a more consistent naming scheme for defaults.
|
||||
|
||||
class TemplateSelections:
|
||||
'''An interface for retrieving templates that are explicitly used in the
|
||||
compiler, such as the article and tag components, as well as the default page.
|
||||
|
||||
Relies on `SiteConfig.template_selection` for custom overrides, falling back on
|
||||
`config[template_default_selections]`, and finally on defaults hard-coded into
|
||||
the definition of this class.
|
||||
'''
|
||||
def __init__(self, site: SiteConfig, templates: DotMap, config: dict = None):
|
||||
self.site = site
|
||||
self.templates = templates
|
||||
self.defaults = dict(
|
||||
tag = 'templates.components.blog_tag',
|
||||
article = 'templates.components.blog_article',
|
||||
page = 'templates.pages.default',
|
||||
archive_li = 'templates.components.blog_archive_li',
|
||||
archive_article = 'templates.components.blog_archive'
|
||||
) | (config or {}).get('template_default_selections', {})
|
||||
|
||||
def __getitem__(self, key: str) -> str:
|
||||
|
||||
# Templates variable must be named `templates` for dotmap_access to work correctly.
|
||||
templates = self.templates
|
||||
return dotmap_access(templates, self.site.template_selections.get(key, self.defaults[key]))
|
||||
|
||||
|
||||
129
site/assets/img/favicon.svg
Normal file
129
site/assets/img/favicon.svg
Normal file
@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="190.6763mm"
|
||||
height="190.67599mm"
|
||||
viewBox="0 0 190.67628 190.676"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="jimlab.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.46774"
|
||||
inkscape:cx="153.93167"
|
||||
inkscape:cy="366.65669"
|
||||
inkscape:window-width="1280"
|
||||
inkscape:window-height="727"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs1">
|
||||
<linearGradient
|
||||
id="linearGradient18"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
style="stop-color:#666666;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop19" />
|
||||
<stop
|
||||
style="stop-color:#d9d9d9;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop20" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient18"
|
||||
id="linearGradient20"
|
||||
x1="109.12461"
|
||||
y1="131.25221"
|
||||
x2="92.506081"
|
||||
y2="321.20288"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-5.4771806,-130.88957)">
|
||||
<path
|
||||
id="rect1-6-0-6"
|
||||
style="display:inline;fill:url(#linearGradient20);fill-opacity:1;stroke:none;stroke-width:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
inkscape:label="hourglass"
|
||||
d="m 10.305609,180.9727 45.2546,45.25461 -45.254596,45.25512 h 90.509717 90.50972 l -45.2546,-45.25512 45.2546,-45.2546 -90.50921,-1e-5 h -5.1e-4 -5.2e-4 z" />
|
||||
<g
|
||||
id="g14"
|
||||
inkscape:label="blue-diamond"
|
||||
transform="rotate(45,-14.34779,124.80922)"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="display:inline;fill:#39b8ab;fill-opacity:1;stroke:none;stroke-width:1.54489;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1-6-0"
|
||||
width="64"
|
||||
height="64"
|
||||
x="106.79844"
|
||||
y="83.090195"
|
||||
inkscape:label="big-trim" />
|
||||
<rect
|
||||
style="display:inline;fill:#47edda;fill-opacity:1;stroke:none;stroke-width:1.44834;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1-6"
|
||||
width="60"
|
||||
height="60"
|
||||
x="108.79844"
|
||||
y="85.090195"
|
||||
inkscape:label="big-fill" />
|
||||
<rect
|
||||
style="display:inline;fill:#39b8ab;fill-opacity:1;stroke:none;stroke-width:1.5831;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1-6-7-0"
|
||||
width="32"
|
||||
height="32.000004"
|
||||
x="122.79843"
|
||||
y="99.090187"
|
||||
inkscape:label="small-trim" />
|
||||
<rect
|
||||
style="display:inline;fill:#47edda;fill-opacity:1;stroke:none;stroke-width:1.3852;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1-6-7"
|
||||
width="28"
|
||||
height="28.000002"
|
||||
x="124.79843"
|
||||
y="101.09019"
|
||||
inkscape:label="small-fill" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#39b8ab;stroke-width:2.2595;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 108.89019,85.181941 15.81649,15.816499"
|
||||
id="path11"
|
||||
inkscape:label="top-trim" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#39b8ab;stroke-width:2.16775;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 153.56485,100.32377 168.73912,85.149504"
|
||||
id="path12"
|
||||
inkscape:label="right-trim" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#39b8ab;stroke-width:1.77778;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 152.79843,129.09019 16,16"
|
||||
id="path13"
|
||||
inkscape:label="bottom-trim" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#39b8ab;stroke-width:2;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 124.79843,129.09019 -15.99999,16"
|
||||
id="path14"
|
||||
inkscape:label="left-trim" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@ -1,22 +0,0 @@
|
||||
<html>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<head>
|
||||
{templates.partials.default_css}
|
||||
{templates.partials.header}
|
||||
{templates.partials.nav}
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<article>
|
||||
<h1 class="headline">{metadata.title}</h1>
|
||||
<p class="byline">
|
||||
<address class="author">By <a rel="author" href="mailto:admin@jimlab.io">Jim Shepich III</a></address>
|
||||
<br/>First published: <time pubdate datetime="{metadata.date}">{metadata.date}</time>
|
||||
<br/>Last modified: <time pubdate datetime="{metadata.lastmod}">{metadata.lastmod}</time>
|
||||
</p>
|
||||
{content}
|
||||
</article>
|
||||
</main>
|
||||
{templates.partials.footer}
|
||||
</body>
|
||||
5
site/templates/components/blog_archive.html
Normal file
5
site/templates/components/blog_archive.html
Normal file
@ -0,0 +1,5 @@
|
||||
<article>
|
||||
<h1 class="headline">{site.title} Archive</h1>
|
||||
<hr />
|
||||
{content}
|
||||
</article>
|
||||
@ -1 +1 @@
|
||||
<a href='tags/{tag_name}.html'><span class='blog-tag'>{tag_name}</span></a>
|
||||
<a href='{site.base_url}/tags/{tag_name}.html'><span class='blog-tag'>{tag_name}</span></a>
|
||||
5
site/templates/components/simple_article.html
Normal file
5
site/templates/components/simple_article.html
Normal file
@ -0,0 +1,5 @@
|
||||
<article>
|
||||
<h1 class="headline">{metadata.title}</h1>
|
||||
<hr />
|
||||
{content}
|
||||
</article>
|
||||
@ -5,6 +5,8 @@
|
||||
{templates.partials.default_css}
|
||||
{templates.partials.header}
|
||||
{templates.partials.nav}
|
||||
<title>{site.title}</title>
|
||||
<link rel="icon" type="image/svg" href="/assets/img/favicon.svg">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<nav id="main-navbar" class="no-highlight">
|
||||
<a href='/home.html' target='_self'><div class='nav-tab'><span class='nav-text'>Home</span></div></a>
|
||||
<a href='/shepich_resume.pdf' target='_blank'><div class='nav-tab'><span class='nav-text'>Resume</span></div></a>
|
||||
<a href='/dogma-jimfinium/index.html' target='_self'><div class='nav-tab'><span class='nav-text'>Dogma Jimfinium</span></div></a>
|
||||
<a href='/dogma-jimfinium/archive.html' target='_self'><div class='nav-tab'><span class='nav-text'>Dogma Jimfinium</span></div></a>
|
||||
</nav>
|
||||
@ -1,14 +0,0 @@
|
||||
<html>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<head>
|
||||
{templates.partials.default_css}
|
||||
{templates.partials.header}
|
||||
{templates.partials.nav}
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
{content}
|
||||
</main>
|
||||
{templates.partials.footer}
|
||||
</body>
|
||||
@ -25,6 +25,14 @@
|
||||
"from jimsite import *"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "68b107f1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
@ -120,13 +128,74 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"execution_count": 4,
|
||||
"id": "a28b95a6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"ename": "NameError",
|
||||
"evalue": "name 'build_site' is not defined",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
|
||||
"\u001b[31mNameError\u001b[39m Traceback (most recent call last)",
|
||||
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[4]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mbuild_site\u001b[49m(sites[\u001b[33m'\u001b[39m\u001b[33mresume\u001b[39m\u001b[33m'\u001b[39m])\n",
|
||||
"\u001b[31mNameError\u001b[39m: name 'build_site' is not defined"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"build_site(sites['resume'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "60378277",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import inspect"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cd7fb9f3",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"DotMap(a=DotMap(b=DotMap(c=42)))\n",
|
||||
"a\n",
|
||||
"DotMap(b=DotMap(c=42))\n",
|
||||
"b\n",
|
||||
"DotMap(c=42)\n",
|
||||
"c\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"42"
|
||||
]
|
||||
},
|
||||
"execution_count": 29,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "fcd2faf5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user