Updates for publishing

This commit is contained in:
Matt Jadud
2025-09-18 07:16:19 -04:00
parent 7e64954bcb
commit b26471fce5
44 changed files with 1513 additions and 257 deletions

18
Makefile Normal file
View File

@@ -0,0 +1,18 @@
SHELL:=/bin/bash
build:
pushd jadudcom ; rm -rf public ; hugo
serve:
pushd jadudcom ; rm -rf public ; hugo serve -D ; popd
test:
pushd jadudcom ; rm -rf public ; hugo serve ; popd
upload:
cd jadudcom ; rm -rf public ; hugo
surfer put \
--server jadud.com \
--token ${JADUDCOM_SURFER_TOKEN} \
public/* /

View File

@@ -1,17 +1,19 @@
---
date: '2025-09-14T08:27:23-04:00'
draft: true
title: ''
#title: 'fractional cto in waiting'
cascade:
type: docs
params:
breadcrumbs: false
---
# jadud.com
# {{< sitetitle >}}
asdfasdf
As a leader or member of a team, I believe I am successful when the people I work with are successful. I strive to develop healthy teams and create opportunities for growth and learning for people around me.
## something
## articles
more
{{< listofposts "article" >}}
## and another
## blog
asdfasdf
{{< listofposts "blog" >}}

View File

@@ -0,0 +1,9 @@
---
title: 'articles'
weight: 20
cascade:
type: docs
# layout: section-summary
---
{{< listofposts "article" >}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

View File

@@ -0,0 +1,121 @@
---
title: 'evaluating todo systems'
weight: 10
tags:
- article
params:
blurb: a look at todo tools in terms of collaboration and reliability
---
## summary
The ideal todo list is the one you always have with you and, no matter what happens, you can't lose. If you're looking to manage projects (which todo lists are *kinda* projects), [Trello](https://trello.com) is great. If you just need a todo list that works everywhere, [Tick Tick](https://ticktick.com) is a sharp, simple todo list app that runs in the browser and on all mobile phone/tablet flavors.
## criteria
Solutions to managing todo and tasks items were considered along two dimensions:
* Reliability: is the tool readily available, and what does it take to lose data?
* Collaboration: Is it a tool you use by yourself, or can you collaborate with friends?
## recommendation: tick tick
For task tracking, [Tick Tick](https://ticktick.com/) is an excellent choice. It can be accessed from any computer via a web browser, and can be accessed on all flavors of mobile device. As icing on the cake, it supports shared task lists, so that you can coordinate work with friends, family, and colleagues.
```mermaid
quadrantChart
title choosing a todo system
x-axis solo use --> collaborative
y-axis unreliable --> reliable
%% quadrant-1 We should expand
%% quadrant-2 Need to promote
%% quadrant-3 Re-evaluate
%% quadrant-4 May be improved
Tick Tick: [0.68, 0.8] color: #f1c928, radius: 8
Trello: [0.9, 0.85]
Notebook: [0.1, 0.9]
Google Docs: [0.9, 0.95]
Outlook Tasks: [0.2, 0.25]
Whiteboard: [0.8, 0.2]
```
### simple interface
Tick Tick has a simple interface. It has an inbox where items can be added quickly, or they can be added to any number of project-specific lists. In the screenshot below, we see my "Work" list, where the task "pick up milk" has some additional notes attached to it. Recently completed tasks are also visible, but grayed out.
![Tick Tick](image-1.png)
However, Tick Tick is not *simplistic*. There are labels (allowing for categorization and color coding), features for adding notes by voice, notifications, multiple ways to visualize the tasks (lists, timeline, etc.), integration with calendar systems (Google, Office 365, and others), and more.
### browser and mobile
Tick Tick is available in the web browser on the desktop, and there are apps for both Android and iPhone/iPad.
The fact that Tick Tick is available in a web browser is important for one simple reason: *any* computer can be used to get to your to do list. Did your computer catch on fire? Use another computer. Did your hard drive fail, or were you hit by a virus? Use another computer.
The fact that lists can easily be accessed (in sync, automatically) between computer and phone also means that you never have to be away from the ability to check or update your tasks.
### collaboration
Are you trying to coordinate activity between multiple people? Are there a set of tasks that need to happen for a project, retreat, or perhaps every week/month that you want a group of people to help you finish?
Lists in Tick Tick can be shared amongst a group. At no point does anyone get out of sync on the work that remains, or the work that has been completed.
## alternatives
### trello
[Trello](https://trello.com) is in the category of "kanban"-style, or "project board" work management systems. Trello shines for work that goes through phases (from "idea" to being in a "to do" list to being "in progress"), and when you need to coordinate that work amongst a team or teams regularly.
![Trello](image.png)
When you outgrow simple todo lists, Trello is the next step.
### a notebook
![A physical notebook](image-2.png)
It is often the case that a physical notebook is the simplest and most reliable way to keep track of things that need to be done. There are many articles online to help you think about how to use the notebook effectively.
They're cheap, come in lots of colors, and short of leaving them somewhere, they're robust. Not good for collaboration, but the humble notebook cannot be all things to all people.
* [My GTD “Get Things Done” Moleskine Setup](https://www.hatfieldtaylor.com/blogs/gtd-get-things-done-moleskine-setup)
* [Bullet journal](https://bulletjournal.com/blogs/faq), the [bullet journal reddit](https://bulletjournal.com/blogs/faq)...
* [Using notebooks for todos](https://www.reddit.com/r/productivity/comments/vtfekx/how_do_i_keep_a_good_todo_list_on_paper/)
### google docs
Although lacking in many features, a simple Google Doc can serve as a powerful todo list manager. It works in the browser on all computers, is easy to use to collaborate with others, and you are unlikely to ever lose the data.
It is, arguably, the most robust and collaborative tool you could choose from this list, but it is not *really* a todo system/application. That said, you can find many templates and approaches to using both GDocs and Google Sheets for task tracking and todo management.
* [Google todo list template](https://docs.google.com/document/d/1umNngFZOj0nn5kfnXe1n2u0NJFdGM8VLa6Oxpvsh334/edit?tab=t.0)
* [Using GDocs/Sheets for productivity](https://www.reddit.com/r/productivity/comments/11q2ira/using_google_docs_sheets_drive_as_my_productivity/)
* [Two ways to create a todo list in GDocs](https://instructionaltechtalk.com/2-ways-to-create-a-to-do-list-in-google-docs/)
### outlook tasks
Outlook Tasks are rated as unreliable and non-collaborative for two reasons:
1. **They live on the computer**. If you have an Office 365 account, they should be synchronized elsewhere. But, if you are using them on your own, then they might only live on the machine you have Outlook installed on. This means it is hard to collaborate with others on the tasklist.
2. **They live on the computer**. This means that their reliability is as reliable as your backup practices.
A separate evaluation regarding *backup strategies* would be needed to explore how to improve the reliability of Outlook Tasks. At the least, you should be comfortable with backing up your computer; for example, if you have a Mac, the [Apple Support article on how to back up your Mac](https://support.apple.com/en-us/102307) is required reading. If you have an external drive, you would want to read about [Time Machine](https://support.apple.com/en-us/104984). You might have iCloud (which might back things up automatically), or you might choose to use a service like [Backblaze](https://www.backblaze.com/cloud-backup/personal) to back up your machine.
The point here is that any solution that lives *only on your computer* is fundamentally unreliable. It can't easily be shared to your phone, you can't collaborate with others on tasking, and you can easily lose everything in your task tracker.
## conclusion
If you're collaborating with a team on projects, I consider [Trello](https://trello.com) and other task boards to be a gold standard. It makes it easy to keep different projects seoparate, to work with others on those projects, and keep track of complex work at every stage, from idea to completion.
If you're looking to track tasks for yourself, and maybe occasionally with a few others, [Tick Tick](https://ticktick.com) is an excellent todo list manager. The free tier is incredibly feature-rich. It is available anywhere you have a web browser, and there are apps for iPhone and Android. (I'm personally a fan of the habit tracking feature.)
If you're looking to keep things simple, a plain old notebook can go a long way. If you want the resiliency of the cloud, using GDocs or similar to create todo lists in a document gets you reliability and the ability to share without strictly learning a new tool. If you want to use a tool that lives on your local computer, then the question of how robust it is comes down to your own data management, backup, and recovery practices.
## musical postlude
They Might Be Giants have been around for a long time. From their first album, *Put Your Hand Inside the Puppet Head*, with the (somewhat) relevant lyric: "Memo to myself: do the dumb things I gotta do; touch the puppet head."
<br>
<iframe width="560" height="315" src="https://www.youtube.com/embed/-GiCfqQEEwk?si=5MTcc0CbJjfRWJrp" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

View File

@@ -0,0 +1,7 @@
---
title: March 2020
# type: blog
description: All posts in March 2020
---
{{< listofposts "2020-03" >}}

View File

@@ -0,0 +1,321 @@
---
layout: post
title: "python: metaprogramming marshmallow"
author: mjadud
tags:
- cc-sa
- python
- metaprogramming
- blog
- "2020"
- 2020-03
publishDate: 2020-03-19
---
## tl;dr
I used Python's metaprogramming features to auto-generate Marshmallow schemas that correspond to `attrs`-derived data classes.
If you like the thought of thinking about metaprogramming as much as I do, you'll grove on this post.
## a theme of metaprogramming...
*Oddly, related as a piece to my explorations of `tbl` in Python, as well looking at GraphQL, but still it's own post...*
It is hard to extend Python's syntax, but that doesn't mean you can't engage in some dynamic metaprogramming in the language. While it isn't always the first tool you should reach for, it can be nice for **reducing boilerplate**.
For example, I am staring down a bunch of JSON-y things. They come-and-go from the front-end to the back-end:
```json
{ email: "vaderd@empire.com",
token: "89425abc-69f9-11ea-b973-a244a7b51496" }
```
Let's pretend that the front-end is [React](https://reactjs.org/), the storage layer is [MongoDB](https://www.mongodb.com/), and the middleware is [Flask](https://palletsprojects.com/p/flask/) (a Python web framework).
<img src="{{ site.base }}/images/posts/react-flask-mongo.png"
alt="react<->flask<->mongo"
>
At the Flask layer, there's a lot of work that needs to be done: the JSON comes in, and in the first instance, it comes in as a dictionary. This is not very nice. By "not very nice," I mean "dictionary convey no notion of types or the regularity of their contents, and therefore provide us with no notion of safety." What I'd like is for the data coming from the front-end to be strongly typed and well described, the middleware to be aware of those types, and the database to help enforce them as well. (I'm thinking GraphQL starts to do things like this... almost.)
BUT, we have a RESTful web application sharing data in webby, untyped ways. This inspired me to do some digging. First, I found Flask Resful, which is a nice library. It lets you define a class, set up `get`, `put`, `post`, and other methods on endpoints, and register them with the app. Leaving a bunch of bits out, this looks like:
```python
from flask_restful import Resource, Api
import db.models as M
import db.db as DB
class Tokens(Resource):
def post(self, email):
# Create a UUID string
tok = str(uuid.uuid1())
# Create a TimedToken object, with a current timestamp
t = M.TimedToken(email=email, token=tok, created_at=time())
# Grab the correct collection in Mongo for tokens
collection = DB.get_collection(M.TimedToken.collection)
# Save the token into Mongo by dumping the token through marshmallow
as_json = t.dump()
collection.insert(as_json)
# Return the token as JSON to the client
return as_json
mapping = [
[Tokens, "/token/<string:email>"]
]
def add_api(api):
for m in mapping:
api.add_resource(m[0], m[1])
```
which is in a module called "API", and at the top level of the app:
```python
from flask_restful import Api
from flask import Flask
import hydra
from api.api import add_api
app = Flask(__name__)
@hydra.main(config_path="config.yaml")
def init(cfg):
# Dynamically define classes from the YAML config.
M.create_classes(cfg)
# Set the Mongo params from the config.
DB.set_params(cfg.db.host, cfg.db.port, cfg.db.database)
# Add the REST API to the app.
A = Api(app)
add_api(A)
```
This is a lot to take in, but I'm actually trying to get to the good bit. The top level has an `init` function that reads in a configuration file (more on that later), and uses that to build a whole bunch of classes *dynamically at run time*. (This is the cool bit.) Those are instantiated in the `models` submodule of `db`, and they get used throughout the application.
Looking back at the first code block, it's possible to see some of those uses. For example, I'm creating a timed token (e.g. a random string associated with a user that will ultimately have a finite lifetime).
```python
t = M.TimedToken(email=email, token=tok, created_at=time())
```
This class takes three parameters: `email`, `token`, and `created_at`. The whole purpose of the class is that I want it to serve as a `struct` (in Racket or C) or `record` (in... Pascal?). In Python, `namedtuple`s, `dataclass`es, and classes decorated with `attrs` are all examples of what I'm aiming for.
But... **BUT**... I also want easy marshalling to-and-from JSON. The front-end speaks it, and Mongo speaks it... but, while I'm in the middle, I need to interact with it. I would like it to be *typed* (in as much as Python is typed) while I am working with it in the middleware. And, I'd rather not do the conversions myself. (Why would I write code if I wanted to do all the hard stuff by hand?)
To solve this, enter [marshmallow](https://marshmallow.readthedocs.io/en/stable/). This Python library lets you define schemas for classes, and in doing so, leverage machinery to marshal JSON structures to-and-from those classes. For example, my `TimedToken` class looks looks (er, used to look like):
```python
@attr.s
class TimedToken:
email = attr.ib(type=int)
token = attr.ib(type=str)
created_at = attr.ib(type=float)
```
To marshal this to-and-from JSON, I can use marshmallow. I need to create a schema first:
```python
from marshmallow import Schema, fields
class TimedTokenSchema(Schema):
email = fields.Str()
token = fields.Str()
created_at = fields.Number()
```
Once I have a schema, I can do things like this:
```python
a_token = TimedToken(...)
schema = TimedTokenSchema()
as_json = schema.dump(a_token)
```
The machinery inside of marshmallow will take an object of type `TimedToken`, a schema describing them (`TimedTokenSchema`), and use the schema to walk through a `TimedToken` object to convert it to JSON (and, back, if you want).
This is cool.
But, it's not automatic. And, for every data structure I want to create in my app, I need to write a schema. This is duplicating code. If I change a structure, I need to remember to change the corresponding schema. *That isn't going to happen*. What's actually going to happen is that I'll forget something, and everything will break.
## enter metaprogramming!
I wanted to be able to declare my data structures as YAML, and then have Python generate both the `attrs`-based class as well as the `marshmallow`-based schema. Is that so much to ask? No, I don't think it is.
Using Facebook's [Hydra](https://hydra.cc/), I created a config file. This important bit (for this discussion) looks like this:
```yaml
models:
- name: TimedToken
fields:
- email
- token
- created_at
types:
- String
- UUID
- Number
```
Then, the fun bit is the function `create_classes`. It takes a config that includes the `models` key, and does the following:
```python
def create_classes(cfg):
for c in cfg.models:
make_classes(c.name, c.fields, c.types)
```
OK... so, `make_classes` must do the interesting work.
```python
def make_classes(name, fs, ts):
# Dynamically generate the marshmallow schema
schema = make_schema(fs, ts)
# Generate a base class, and wrap it with the attr.s decorator.
base = attr.s(make_base(name, fs,ts, schema))
# Insert the class into the namespace.
globals()[name] = base
```
This is probably **really bad**. But, it's fun, so I'll keep going.
I pass in the name of the class as a string (`"TimedToken"`), and then I pass in the fields as a list of strings, and their types as a list of strings. (These are given in the YAML, above). The last line here is where the evil happens. The function `globals()` returns the dictionary representing the current namespace. I proceed to overwrite the namespace; specifically, I insert a new class of the name `TimedToken` (in this example). (I *hope* the use of `global()` is restricted to the *module*, and not the entire *application*... I have some more reading/experimenting to do in that regard. It *seems* like it is the module...)
Backing up, I'll start with `make_schema()`. It takes the fields and types, and does the following:
```python
def make_schema(fs, ts):
# Create an empty dictionary
d = {}
# Walk the fields and types together (using zip)
for f, t in zip(fs, ts):
# Convert each type into the appropriate fields.X from marshmallow
# and insert it into the dictionary
d[f] = get_field_type(t)
# Use marshmallow's functionality to create a schema from a dictionary
return Schema.from_dict(d)
```
`get_field_type()` is pretty simple:
```python
def get_field_type(t):
if t == "Integer":
return fields.Integer()
if t == "Float":
return fields.Float()
if t == "String":
return fields.String()
if t == "UUID":
return fields.UUID()
if t == "Number":
return fields.Number()
```
(No, there's no error handling yet. Not even a default case... *sigh*.)
The `make_schema` function literally returns a `class` that I can use to convert objects that match the layout of the dictionary that I built. That's great... but what good is a `TimedTokenSchema` if I don't have a `TimedToken` class in the first place? Hm...
```python
@attr.s
class Base ():
pass
def make_base(name, fs, ts, schema):
cls = type(name, tuple([Base]), {})
setattr(cls, "schema", schema)
setattr(cls, "dump", lambda self: self.schema().dump(self))
setattr(cls, "collection", "{}s".format(name.lower()))
for f, t in zip(fs, ts):
setattr(cls, f, attr.ib())
return cls
```
The function `make_base()` does some heavy lifting for me. First, it uses the `type()` function in Python to dynamically generate a class. In this case, it will create a class with the name `TimedToken`, it will use `Base` as a superclass, and it will attach no attributes at time of creation. (I actually do not want to overwrite anything, because `attrs` does a lot of invisible work.)
The function `setattr` is, used casually, probably a bad thing. It literally reaches into a class (not an *object*, but a *class*) and attaches attributes to the class. If you're not used to metaprogramming, this is like... writing the code for the class on-the-fly.
I attach three attributes:
* `schema` is a field that will hold a marshmallow `Schema` class. (Because, in Python, classes are objects too! Wait...) If you look back, you can see that I pass it in after creating it in `make_classes()`.
* `dump`, which is a function of zero arguments. It takes a reference to `self` (because this class will get instantiated as an object), and it instantiates the `schema` that I've stored, and then invokes `dump()` on... itself. This feels metacircular, but fortunately marshmallow knows to only look for fields that are in the schema. Therefore, we don't get an infinite traversal here.
* `collection`, which is so I can map directly into Mongo. I take the name of the class, lowercase it, and add an 's'. So, `TimedToken` becomes `timedtokens` as a collection name. I like the idea of the object knowing where it should be stored, so I don't have to think about it.
Once I have these things set up, I walk the fields, and add them to the class. For each, I add a (currently) untyped `attr.ib()` to the field. This way, the `TimedToken` class will act like a proper `attrs` class.
Finally, I return this class, which then gets attached (back in `make_classes()`) to the `global()` namespace.
## what?
If you like the thought of thinking about metaprogramming as much as I do, you're excited at this point. If you're wondering why I would do this... well, I'll go back to my REST handler for TimedTokens:
```python
from flask_restful import Resource, Api
import db.models as M
import db.db as DB
class Tokens(Resource):
def post(self, email):
# Create a UUID string
tok = str(uuid.uuid1())
# Create a TimedToken object, with a current timestamp
t = M.TimedToken(email=email, token=tok, created_at=time())
# Grab the correct collection in Mongo for tokens
collection = DB.get_collection(M.TimedToken.collection)
# Save the token into Mongo by dumping the token through marshmallow
as_json = t.dump()
collection.insert(as_json)
# Return the token as JSON to the client
return as_json
mapping = [
[Tokens, "/token/<string:email>"]
]
def add_api(api):
for m in mapping:
api.add_resource(m[0], m[1])
```
The function `create_classes(cfg)` is in the `db.models` module. I import that as `M`. Because I created classes in this module at the point that Flask was initialized, I now have a whole bunch of dynamically generated classes floating around in there. Those classes were generated *from a YAML file*, and can be used anywhere in the application.
```yaml
models:
- name: TimedToken
fields:
- email
- token
- created_at
types:
- String
- UUID
- Number
```
To add a new class to my application, I add it to the YAML file, and restart Flask. This will call `create_classes` as part of the init, and the new class will be generated in the `db.models` module. I can then use those classes just as if I had written them out, by hand, duplicating the effort of defining both the `attrs` class and the marshmallow `Schema` class.
In my REST handler, this is where this dynamic programming comes into play:
```python
# Create a TimedToken object, with a current timestamp
t = M.TimedToken(email=email, token=tok, created_at=time())
# Grab the correct collection in Mongo for tokens
collection = DB.get_collection(M.TimedToken.collection)
# Save the token into Mongo by dumping the token through marshmallow
as_json = t.dump()
collection.insert(as_json)
# Return the token as JSON to the client
return as_json
```
I create the object. Then, I use the `collection` attribute to ask for a database connection to the collection that holds objects of this type (this is like a table in relational databases). Next, I convert the object to JSON by invoking the `.dump()` method, which was added dynamically. In fact, it is using a Schema class that was created dynamically as well, and then embedded in the enclosing object for later use. Finally, I insert this JSON into the Mongo database, and return it to the client, because both Mongo and the client speak JSON natively.
The result is that I've metaprogrammed my way around `attrs` and `marshmallow` to create a dynamic middleware layer that can marshal to-and-from JSON. In doing this, I've saved myself a large amount of boilerplate, and I have a single point of control/failure for all of my class definitions, which is external to the code itself. (I think I still need to add the marshalling *from* JSON, but that won't be hard.)
## what will you do with this, matt?
Personally, I haven't found anything on the net that eliminates the boilerplate in marshmallow. In the world of open source, I'd say this is an "itch" that I scratched. It might be an itch other people have.
Perhaps my next post will be about packing code for `pip`?

View File

@@ -0,0 +1,29 @@
---
layout: post
title: "python: marshmallow fluff..."
author: mjadud
tags:
- cc-sa
- python
- metaprogramming
- blog
- "2020"
- 2020-03
publishDate: 2020-03-20
---
So, I still like my metaprogramming tricks. It was fun. I learned things.
But, I went to [PyPi](https://pypi.org/), and discovered they have a very nice search feature. [I searched for marshmallow](https://pypi.org/search/?q=marshmallow). I found... 263 projects referencing marshmallow. It's a unique enough word that I'm going to *guess* that they all interact with the `marshmallow` library in some way.
A lot of them do what I was exploring. For example, [marshmallow-objects](https://github.com/sv-tools/marshmallow-objects) does *exactly* what I was doing, but better.
(Well, mostly. Kinda.)
Actually, it is different. You still have to define Python classes... but, you can subclass a marshmallow model that gives you serialization/deserialization without having to write a separate schema. It wouldn't let me dynamically generate the classes from a YAML file (that's a neat trick, I think), but it might be fine to write the class as code. I mean, it's easier to test the class, whereas the dynamic trickery is just that...
So. Lesson learned. Or, if you prefer, a lesson I've always known, and taught my students many times: *do a search first*. Someone else has probably done it.
As the old joke goes:
![asdfasdf](https://imgs.xkcd.com/comics/python.png)

View File

@@ -0,0 +1,88 @@
---
layout: post
title: "tbl: abstractions and imports"
author: mjadud
commit: https://github.com/jadudm/pytbl/tree/527b16bdecbf73b874103922cf3038a1f2c1e1c7
tags:
- cc-sa
- tbl
- blog
- "2020"
- 2020-03
date: 2020-03-08
---
There is a debate in the data science community (and, in particular, in the R community) as to whether one should learn libraries or a core language when working with data. For R programmers, it is a question of learning the dyplr-family of libraries vs. working directly in the language without those tools. This is, from what I can gather, a sometimes divisive argument.
As an educator and a developer, I've come to appreciate the power of a good abstraction and tools that support that abstraction. I want tools that help me map the way I think about a problem directly into code. Or, I want tools that will shape the way I think about problems, so that I can more concisely express solutions using those tools. Here, "tools" means "libraries" or "programming languages."
My approach to working on `tbl` has therefore been to think about how to make it easy for beginners to work with interesting data. "Interesting" might mean it is personally meaningful, and possibly a small amount of data. "Interesting" might mean it is large and complex data... but, important to the developer. This means `tbl` needs to support data that is both small and big, and it needs to be easy for a developer to get started.
## Imports: The `tbl`
I want my beginner to be thinking about tabular data. So, I want a `tbl` to make it easy to turn a spreadsheet into something that they can do meaningful work with. In this way, the first abstraction that a programmer sees with `tbl` is the spreadsheet, and they can map that abstraction directly into the library. A `tbl` is, in the first instance, a spreadsheet.
<img src="/images/posts/20200308-blue-lobster.jpg" align="right" width="20%" alt="Blue lobster photo by David Clode on Unsplash.">
Here, for example, is the [spreadsheet](https://docs.google.com/spreadsheets/d/1aCjhAepc2Ms-eIr97hPb2FvzEKmqWK1w6mH8MnVUtRs/edit?usp=sharing) I use to keep track of my pet lobsters.
<div style='width: 100%;'>
<iframe src="https://docs.google.com/spreadsheets/d/e/2PACX-1vSK2rd47ogfI2CpQi2L6HDo9AOEhnhqBN4zR4kLPUO28vBzmlc8XQWrvTfBYCU0ePf478yxcNKdOy5m/pubhtml/sheet?headers=false&amp;gid=0&amp;range=A1:C5" style="display:block;margin: 0 auto;"></iframe>
</div>
In the case that I have small, but interesting data, it would be nice if I could have a GUI for manipulating/entering that data, and could quickly pull it into a program that I'm writing without having to go through lots of hoops. **If I want a good GUI for manipulating tabular data, I should use a spreadsheet!** As it happens, not only can I use Google Sheets for this, but Sheets will let me publish my data to the web for embedding, and Sheets also makes it easy for me to pull the CSV directly. But, I don't want a programmer to know that there's a CSV file waiting for them... I just want them to be able to import the data.
Something that might look like this:
```python
import tbl
pets_url = "https://docs.google.com/spreadsheets/d/e/2PACX-1vSK2rd47ogfI2CpQi2L6HDo9AOEhnhqBN4zR4kLPUO28vBzmlc8XQWrvTfBYCU0ePf478yxcNKdOy5m/pub?gid=0&single=true&output=csv"
pets_tbl = tbl_from_sheet(pets_url)
```
To test this out, I'll drop some code in `lobsters.py` and `tbl.py`.
<script src="https://gist.github.com/jadudm/c1e4f42ff3b1abb58f1875e13a646cf1.js"></script>
When I run
`python lobsters.py`
I get the following:
```
(venv) jadudm@lego:~/git/pytbl$ python lobsters.py
['Name', 'Weight', 'Color']
['Bart', '0.75', 'Muddy']
['Flick', '1', 'Muddy']
['Bubbles', '1.2', 'Blue']
['Crabby', '0.5', 'Muddy']
```
Now, this doesn't get us all the way, but it takes the first step: I've created a data table in Google Sheets, and I can pull it in via the Requests library as a CSV document that is parseable and iterable. So far, so good.
## Abstractions
The next step is to design the abstraction for a `tbl`.
So that no one post gets too long, this will be the subject of tomorrow's explorations. The goal here will be to avoid creating an [abstraction that is overly leaky](https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/), to use Joel Spolsky's terminology. I'm going to want a way to work with this data that:
* Can store the data locally or remotely
* Can work with centralized and decentralized data
* Can leverage multiple concrete representations, invisibly
* Can operate on the data closer conceptually rather than syntactically
* Can support programmers at multiple levels of experience and expertise
These are going to be a complex set of requirements, and I'll miss the first time. (This is actually the *second time* I've explored this idea; I've already done a deep dive in the programming language Racket, so in truth, I've got some ideas in my back pocket.)
For the exercise that this is, I'll probably do the following:
* Explore SQLite for local and CockroachDB for remote/distributed data
* Use an ORM (SQLalchemy, Peewee, or similar) to manage those relationships
* Use R, Python (pandas), Pyret, and other data languages/frameworks as inspiration
* Choose some authentic use-cases to drive the development (e.g. perhaps interface into some of my own research data to drive both the research and the development of `tbl` forward)
## Get the code!
It's early days, but you can get the code. This work will be open (as all of my work is, whenever possible), at Github. I'll call the project [pytbl](https://github.com/jadudm/pytbl).

View File

@@ -0,0 +1,70 @@
---
layout: post
title: "tbl: structuring the project"
author: mjadud
commit: https://github.com/jadudm/pytbl/tree/4433e25769f8ee70da0de363d6589f3c77a96a53
tags:
- cc-sa
- tbl
- blog
- "2020"
- 2020-03
date: 2020-03-09
---
It helps, early, to structure a project well.
Having written a version of `tbl` in another language once before, and now revisiting the design and implementation in Python, I know I should think about how the project is structured from the start.
I find [The Hitchhiker's Guide to Python](https://docs.python-guide.org/writing/structure/) to be a wholly remarkable book, like it's namesake (*The Hitchhiker's Guide to the Galaxy*). As a result, I'll restructure the code around their recommended format for a Python module at this point. `tbl` will become a module that I want to `pip install`, so it makes sense to clean it up now.
## the layout
First, I'm going to move some things around. The project directory looks like this:
```
drwxr-xr-x 9 jadudm jadudm 4096 Mar 8 20:10 .
drwxr-xr-x 7 jadudm jadudm 4096 Mar 8 20:07 ..
drwxr-xr-x 2 jadudm jadudm 4096 Mar 8 20:08 docs
drwxr-xr-x 8 jadudm jadudm 4096 Mar 9 08:47 .git
-rw-r--r-- 1 jadudm jadudm 25 Mar 8 15:03 .gitignore
-rw-r--r-- 1 jadudm jadudm 1093 Mar 8 14:42 LICENSE
-rw-r--r-- 1 jadudm jadudm 239 Mar 8 20:42 lobsters.py
-rw-r--r-- 1 jadudm jadudm 79 Mar 8 20:11 Makefile
drwxr-xr-x 2 jadudm jadudm 4096 Mar 8 15:01 __pycache__
-rw-r--r-- 1 jadudm jadudm 1762 Mar 9 08:47 README.md
-rw-r--r-- 1 jadudm jadudm 15 Mar 8 14:45 requirements.txt
drwxr-xr-x 3 jadudm jadudm 4096 Mar 9 08:07 tbl
drwxr-xr-x 2 jadudm jadudm 4096 Mar 8 20:08 tests
drwxr-xr-x 6 jadudm jadudm 4096 Mar 8 13:53 venv
drwxr-xr-x 3 jadudm jadudm 4096 Mar 9 08:12 .vscode
```
Because I want this to become a library that I can `pip install`, I've taken a few necessary steps in that direction. First, I've created a subdirectory called `tbl`, and in that directory, I moved the file previously called `main.py` and called it `__init__.py`. The secret here is that, in Python, any directory containing a file called `__init.py` is considered a *module*. Modules are the fundamental unit of organization for libraries of code, so this is a clear and necessary step.
Running `ls -al tbl`:
```
(venv) jadudm@lego:~/git/pytbl$ ls -al tbl/
total 20
drwxr-xr-x 3 jadudm jadudm 4096 Mar 9 08:07 .
drwxr-xr-x 9 jadudm jadudm 4096 Mar 8 20:10 ..
-rw-r--r-- 1 jadudm jadudm 2077 Mar 9 08:19 __init__.py
drwxr-xr-x 2 jadudm jadudm 4096 Mar 9 08:14 __pycache__
-rw-r--r-- 1 jadudm jadudm 406 Mar 9 08:14 util.py
```
I also, in the last commit, created a small utility library. I'll blog about that later.
At the top level, there are directories for `tests` and `docs`, which I'll begin filling in soon.
The `.gitignore` is an important file; it says which files and directories I never want to put under version control. For example, my [venv](http://bit.ly/2v6zyON) is something I never want to see in the repository... it's a local working environment for my Python interpreter, so that when I install libraries to support the use of `tbl`, I don't install them globally... instead, they get installed in the `venv` directory. (This, too, is probably a good subject for another post... or, at least, a few more links.)
The `requirements.txt` says which libraries are needed to support `tbl`. Right now, I have the [hydra](https://github.com/facebookresearch/hydra) library from Facebook (I think I'm going to need it later) and the [requests](https://requests.readthedocs.io/en/master/) library, which makes working with content over the 'net a lot easier.
It turns out (for those following along) *that the structure of code is often as important, if not moreso, than the code itself*. If I don't place files in the right places, with the right names, then my code is not, and cannot, become a Python library. Similarly, if I want to write an application in Java for Android... some files have to be named specific ways, and be placed in particular places in order for them to be assembed into an app. This is a critical, but sometimes invisible, part of writing code that is too often glossed over when students are getting started programming.
## structure, complete
This is a first step in shifting the structure of the project around. There will be more, but for now it brings `tbl` one step closer to being installable as a Python package via `pip`.

View File

@@ -0,0 +1,266 @@
---
layout: post
title: "tbl: testing round one"
author: mjadud
commit: https://github.com/jadudm/pytbl/tree/d6f45ba0c273e847243b9fd5348de5dc949bc8f4
tags:
- cc-sa
- tbl
- blog
- "2020"
- 2020-03
publishDate: 2020-03-09
---
In the previous post, I rearranged the structure of the code to align it more closely with what we might expect for a Python package that can be installed via `pip`. It is never too early to begin arranging the structure of a project appropriately, and it is never too early to begin **testing**.
I have a clear idea of what I expect this project to be, because I've written it before, and written tests, documentation, and a draft paper on it. However, coming soon, I'll need to get those ideas expressed here in something resembling a coherent design. For now, though, I'll continue exploring a bit. But, I'll do it properly.
My "driver" code right now looks like this:
```python
import tbl
pets_url = "https://docs.google.com/spreadsheets/d/e/2PACX-1vSK2rd47ogfI2CpQi2L6HDo9AOEhnhqBN4zR4kLPUO28vBzmlc8XQWrvTfBYCU0ePf478yxcNKdOy5m/pub?gid=0&single=true&output=csv"
a_tbl = tbl.tbl(url = pets_url)
a_tbl.show_columns()
```
And, when executed, it outputs this:
```
Name : None
Weight : None
Color : None
```
That's fine, because those are the contents of the header row of my spreadsheet. But, saying "it looks right" is no way to test software. Although I haven't articulated a complete design, one thing I know my library will need to be able to do is read a CSV file from a URL and convert it into a `tbl` (a structure that is yet to be fully described).
So, for my next trick, I'll put some light testing in place. Even though the structures might change, and this might require me to re-write some tests, there are two important things to committing to testing early: first, I can continue exploring with confidence that the code I've written so far is working the way I expect. Second, even if I change the structures over which I'm writing the tests, it should remain true that the tests themselves are "good." Or, put another way, I may have to rewrite the tests, but the tests will be a framework that will remain constant regardless of how the structures change. This will again provide confidence in the library in the face of refactoring, and give me velocity both in terms of development and the development of future tests.
(Some googling suggests that [there may be more than one way to do it](https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure), and that I may have some additional refactoring to do. But, I'll proceed with documentation from [pytest](https://pytest.readthedocs.io/en/2.7.3/index.html) for now, knowing that a first step that is reasonable is better than no step at all.)
## my friend pytest
There are many testing frameworks in many languages. I'm going to leverage pytest here because it is lightweight to leverage, and I'll take speed over complexity early in any programming endeavor. And, in truth, I may never need anything more complex than pytest, because it really is quite capable.
### first problem: not importable
Uh-oh.
```
(venv) jadudm@lego:~/git/pytbl$ pip install -e .
Directory '.' is not installable. File 'setup.py' not found.
```
It looks like I need a `setup.py`. My initial setup looks like this:
```python
from setuptools import setup
setup(name='tbl',
version='0.1',
description='A tabular way to think about data.',
url='http://github.com/jadudm/pytbl',
author='Matt Jadud',
author_email='matt@jadud.com',
license='MIT',
packages=['tbl'],
zip_safe=False)
```
And, now:
```
(venv) jadudm@lego:~/git/pytbl$ pip install -e .
Obtaining file:///home/jadudm/git/pytbl
Installing collected packages: tbl
Running setup.py develop for tbl
Successfully installed tbl
```
That 'pip install' command creates a symlink to my package directory in the venv. This way, I can keep developing the code and running tests, and I will always be testing against the "live" code.
I can now run `python3 -m pytest`, and get:
```
(venv) jadudm@lego:~/git/pytbl$ python3 -m pytest
======================================= test session starts ========================================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: /home/jadudm/git/pytbl
collected 0 items
====================================== no tests ran in 1.03s =======================================
```
This is good.
## testing one function
At this point, I want to test the import of the CSV file. There's a lot of tests I can run at this point, because (really) my first function is almost too complex.
What if the programmer using `tbl`...
* gives me a bad URL?
* says there is a header, but there isn't?
* says there isn't a header, but there is?
* gives me a spreadsheet with a header and no data?
* gives me a URL to more data than I can hold in memory?
* gives me a URL to more data than I can store on disk?
* gives me a URL to something that is not a CSV/spreadsheet?
* gives me a spreadsheet with data, and a header, and says it has a header?
The last one is actually the easy/ideal case. The others are failure cases, some of which might be difficult to catch early. But, anywhere you give a programmer the ability to pull data in---especially over the network---you have to begin thinking in a *really* paranoid way. And, when dealing with novice programmers, they might be taking random stabs at things, or (more likely) really trying hard to figure things out, but this will still be in the space of "desperate guessing" in some cases.
So, time to write some tests.
### a bad URL
What is a "bad" URL? In this case, we'll call it a URL that does not point to a CSV file, or (worse) is simply not a URL. This could look like the following:
```python
a_tbl = tbl.tbl(url = True)
a_tbl = tbl.tbl(url = 1)
a_tbl = tbl.tbl(url = "lobster")
a_tbl = tbl.tbl(url = [True])
a_tbl = tbl.tbl(url = [1])
a_tbl = tbl.tbl(url = ["lobster"])
a_tbl = tbl.tbl(url = ["https://lobster.org/northaven.csv"])
a_tbl = tbl.tbl(url = "http")
a_tbl = tbl.tbl(url = "https")
a_tbl = tbl.tbl(url = "https://lobster")
# Technically, this is a good URL, but we have no idea if
# it serves up a CSV file.
a_tbl = tbl.tbl(url = "https://lobster.org/")
a_tbl = tbl.tbl(url = "https://lobster.org/northhaven")
a_tbl = tbl.tbl(url = "https://lobster.org/northhaven.txt")
```
This has begun to suggest what we're going to consider a "good" URL. This may not be obvious, but I'm going to bet money that *validating URLs is hard*. There's whole specs on how to format a URL/URI, so... why would I want to try and write this myself? A bit of googling confirms that Python has what I want: a [validation](http://bit.ly/2vVxzx4) package for URLs (and other stuff). I found this from a Stack Overflow thread, which (had I followed the first recommendation), I would have ended up implementing my own. *Not a good idea*.
I'm going to have to do the checking inside of the call to the `tbl` constructor, but I'll farm it out to a helper function. I've created a module called `validation.py` that will contain all of my validation code, so that the class doesn't get too heavy. (Is this good OOP? Probably not.)
My first validation function looks like this:
```python
import validators as val
from collections import namedtuple as NT
import re
OK = NT("OK", [])
KO = NT("KO", ["code", "message"])
# Error Codes
BAD_URL = 0
DOES_NOT_END_IN_CSV = 1
URL_NOT_A_STRING = 2
def _check_from_sheet(url, has_header):
# These will "fail fast."
# Make sure it is a string.
if not isinstance(url, str):
return KO(URL_NOT_A_STRING, "The URL you passed does not look like a string: {}".format(url))
if not val.url(url):
return KO(BAD_URL, "The URL '{}' appears to be invalid.".format(url))
# Should the URL end in CSV? Am I guaranteed that a Google Sheets
# CSV URL will end this way? This might get tricky.
# If it is a sheets URL, and the letters "csv" appear in the URL, it will be OK.
if (re.search("docs.google.com", url)
and re.search("spreadsheets", url)
and re.search("csv", url)):
return OK()
# If it isn't a sheets URL, then perhaps it is a valid URL that
# just points to a CSV. Therefore, it should end in '.csv'.
if not (re.search(".csv$", url) or re.search(".CSV$", url)):
return KO(DOES_NOT_END_IN_CSV, "The file you linked to does not end in '.csv'.")
return OK()
```
I've created two unique types -- OK and KO -- and started defining some error codes. I don't know how I'll use them yet, but I do like the idea of being able to ask if something is `validation.OK()`. Now, I need to see if I can write test code for all of the above examples, and get back responses that I expect.
This has turned into the following test file:
```python
import tbl
from tbl import validation as V
pets_url = "https://docs.google.com/spreadsheets/d/e/2PACX-1vSK2rd47ogfI2CpQi2L6HDo9AOEhnhqBN4zR4kLPUO28vBzmlc8XQWrvTfBYCU0ePf478yxcNKdOy5m/pub?gid=0&single=true&output=csv"
def test_bool_url():
a_tbl = tbl.tbl(url = True)
assert type(a_tbl.fields.status) is V.KO
def test_int_url():
a_tbl = tbl.tbl(url = 1)
assert type(a_tbl.fields.status) is V.KO
def test_url_str_not_url():
a_tbl = tbl.tbl(url = "lobster")
assert type(a_tbl.fields.status) is V.KO
def test_url_list_bool():
a_tbl = tbl.tbl(url = [True])
assert type(a_tbl.fields.status) is V.KO
def test_url_list_int():
a_tbl = tbl.tbl(url = [1])
assert type(a_tbl.fields.status) is V.KO
def test_url_list_str():
a_tbl = tbl.tbl(url = ["lobster"])
assert type(a_tbl.fields.status) is V.KO
def test_list_good_url():
a_tbl = tbl.tbl(url = ["https://lobster.org/northaven.csv"])
assert type(a_tbl.fields.status) is V.KO
def test_protocol():
a_tbl = tbl.tbl(url = "http")
assert type(a_tbl.fields.status) is V.KO
def test_protocol_s():
a_tbl = tbl.tbl(url = "https")
assert type(a_tbl.fields.status) is V.KO
def test_partial_url():
a_tbl = tbl.tbl(url = "https://lobster")
assert type(a_tbl.fields.status) is V.KO
# Technically, this is a good URL, but we have no idea if
# it serves up a CSV file.
def test_good_url_not_csv():
a_tbl = tbl.tbl(url = "https://lobster.org/")
assert type(a_tbl.fields.status) is V.KO
def test_good_url_not_csv2():
a_tbl = tbl.tbl(url = "https://lobster.org/northhaven")
assert type(a_tbl.fields.status) is V.KO
def test_good_url_txt():
a_tbl = tbl.tbl(url = "https://lobster.org/northhaven.txt")
assert type(a_tbl.fields.status) is V.KO
def test_goog_url_incomplete():
a_tbl = tbl.tbl(url = "https://docs.google.com/spreadsheets/d/e/2PACX-1vSK2rd47ogfI2CpQi2L6HDo9AOEhnhqBN4zR4kLPUO28vBzmlc8XQWrvTfBYCU0ePf478yxcNKdOy5m/pub?gid=0&single=true")
assert type(a_tbl.fields.status) is V.KO
def test_complete_goog_url():
a_tbl = tbl.tbl(url = "https://docs.google.com/spreadsheets/d/e/2PACX-1vSK2rd47ogfI2CpQi2L6HDo9AOEhnhqBN4zR4kLPUO28vBzmlc8XQWrvTfBYCU0ePf478yxcNKdOy5m/pub?gid=0&single=true&output=csv")
assert type(a_tbl.fields.status) is V.OK
```
It feels repetitious. In fact, I now realize that I could create some tables/lists of input data, and do all of this testing in a loop. However, I'll leave this for the moment: I now have good tests over the possible inputs a user might throw my way, and that makes me happy.
### error codes, or exceptions?
What should this library do if a user provides a bad URL? Should a_tbl be an object that is in a bad state, but the object knows it, and therefore won't do bad things? Or, should the object throw exceptions, causing the user's code to crash out?
This question has answers that are less obvious than I would like. There are different schools of thought on language/class design around the topic of exceptions. Python likes exceptions, golang prefers error codes. This will require some thought and a bit more reading, as I prefer the latter, but wonder if it is more important to be Pythonic.
And, regardless of whether it is "Pythonic," the question really is "what would be most usable to a novice programmer working with data?"
## still not done testing...
And, if you're still reading, you'll realize that I'm not done testing. That is, I had imagined more ways the user might try and abuse my library than I actually tested for. So far, I've only handled the "bad URL" condition. What if the CSV they hand me is malformed? That's another whole round of validation that has to come after I check if the CSV URL is even valid. Then, I have to check if I can fetch the URL, and if it is a reasonable size, and ...
For tomorrow. For tonight, I've made progress.

View File

@@ -0,0 +1,35 @@
---
layout: post
title: "tbl: thinking about data"
author: mjadud
commit: https://github.com/jadudm/pytbl/tree/527b16bdecbf73b874103922cf3038a1f2c1e1c7
tags:
- cc-sa
- tbl
- blog
- "2020"
- "2020-03"
date: 2020-03-07
---
For the past six months, I have been working in a space where all of my intellectual output was owned by the company I worked for. As a result, there were projects that simply had to sit. That time has passed, and I have two that I want to revisit: the teaching and learning of data science in the broader context of computing, and my own explorations regarding the principles and practices that tooling can embody when it comes to working with data. I might even sneak some IoT/embedded systems in, but there are only so many hours in the day.
I'll probably sneak some articles about hardware and firmware design in here as well, because that's part of the data chain, so-to-speak.
## teaching and learning of data
Last spring and summer, I was thinking hard about the teaching and learning of <em>data</em>.
<a href='https://photos.google.com/share/AF1QipM4nF5IbEJk0q4EiMI6V1XxYRkkyKpoLCOqjEEnpjwtkOJL7kb4ahZOEUF65Xq5Ow?key=RlNDSUNSZjUxc1dqQ0lfWjFiT2hsYkI0RURodWpn&source=ctrlq.org'><img src='https://lh3.googleusercontent.com/7_4aQQPN_0_ppLSk-nH8JxGJIX8wjsRk4MAP84SBg--IJ0HXZwXA0BWHawnrHf1JgzHhmfeGYsD31wD_rDTrVWSe0ghN6lnzin9WlNo6TizymBqPjmIIVhtlkQtFvTqOq9ICXMdvQik=w2400' /></a>
Along with my colleague at Fulbright University Vietnam, <a href="https://fulbright.edu.vn/our-team/sebastian-dziallas/">Sebastian Dziallas</a>, we began laying out a two-course sequence that would introduce students to human-centered principles of collectiong, working with, and questioning data in deep and meaningful ways. Who asks the questions? Who collects the data? How is it collected? What biases do we bring to the analysis? How do we report our findings, and to whom? What hardware and software is needed to support this learning in active and meaningful ways?
This is one space that I will begin documenting and unpacking here. Sebastian and I spent a year discussing related topics prior, and put in weeks of intense work on this during the summer. It has been unpacked (in part) in notes and documents, but should be unpacked more fully before the memory fades completely.
## embodied ideas in tooling
One red thread to my time at Bates was thinking hard about how you introduce programming and the analysis of data to students from across the full breadth of the liberal arts. Computation has a place in every discipline, but <em>how</em> and <em>why</em> it is employed varies greatly. Artists might work with real-time data as part of performance, while social scientists generate their data through survey and interview, while natural scientists might use experiment or simulation to develop the data that informs their analysis. The context to each of these matters, the computational tools are not strictly the same, and the metalearning is drastically different in each case. What, then, become the driving <em>principles</em> that might unify these kinds of inquiry, and how can those principles be exemplified in the teaching and tooling that we bring to our students?
To explore this, I began work on <code>tbl</code>, a library of code in Racket that explores these concepts.
Now that I am once again free to author open code and write about my ideas without them explicitly being owned by others, I will be revisiting this work here over the coming weeks.

View File

@@ -0,0 +1,7 @@
---
title: 2020
weight: 80
description: All posts in 2020
---
{{< listofposts "2020" >}}

View File

@@ -0,0 +1,40 @@
---
layout: post
title: "wbc: generating xlsx"
author: jadudm
draft: true
# commit: https://github.com/jadudm/pytbl/tree/527b16bdecbf73b874103922cf3038a1f2c1e1c7
tags:
- cc-sa
- wbc
- blog
- "2025"
- 2025-09
date: 2025-09-16
---
Generating Excel workbooks in code is hard. By this, I mean that writing code that outputs an XLSX document involves a lot of details (contents, formatting, data validations), and you have to learn a lot about XLSX in order to do it right.
**But Matt: who would ever want to generate Excel documents from code?**
It turns out, most everyone who works with data. Also, *every government everywhere*. Spreadsheets are the lifeblood of governments around the world. Also, financial institutions. And researchers. And... and...
## a workbook "compiler"
For a number of months (years?), I've been thinking about writing a workbook "compiler." In technical terms, a compiler transforms one language or representation of content to another while preserving the intention of the programmer. In this case, I want a way to express a workbook in one language (say, a textual representation like JSON) and I want to transform it to another representation (an XLSX document, or spreadsheet).
For example, I'd like to be able to say write something like this:
```json
{
"workbook": "my first spreadsheet",
"sheets": [
{
"name": "empty sheet"
}
]
}
```
and run a program that consumes that textual file, producing as output the XLSX document.

View File

@@ -0,0 +1,8 @@
---
title: Sept 2025
# type: blog
weight: 10
description: All posts in September 2025
---
{{< listofposts "2025-09" >}}

View File

@@ -0,0 +1,8 @@
---
title: "2025"
# type: blog
weight: 75 # 2025 - 2000
description: All posts in 2025
---
{{< listofposts "2025" >}}

View File

@@ -0,0 +1,7 @@
---
title: das blog
description: All posts in th blog
weight: 50
---
{{< listofposts "blog" >}}

View File

@@ -1,9 +0,0 @@
---
date: '2025-09-14T08:27:23-04:00'
draft: true
title: 'Docs'
---
# wee
wee this is lobster content

View File

@@ -1,240 +1,20 @@
---
date: '2025-09-14T08:27:23-04:00'
draft: true
title: ''
title: 'research'
weight: 30
cascade:
type: docs
---
# research
My research is fundamentally human-centered, and my publications are best broken down into work regarding the behavior of novice programmers and the design and development of tools to support parallel programming in small, embedded spaces.
It was an honor and privilege to be awarded the [**2025 SIGCSE Test of Time Award**](https://sigcse.org/news/2025-01-SIGCSE-Awards-2025-Announced.html). ACM SIGCSE describes the award this way:
It was an honor and privilege to be awarded the [**2025 SIGCSE Test of Time Award**](https://sigcse.org/news/2025-01-SIGCSE-Awards-2025-Announced.html). ACM SIGCSE describes the award this way:
> The ACM SIGCSE Test of Time Award will recognize an outstanding paper published in the SIGCSE community that has had a meaningful impact on computing education practice and research. Significant impact can be demonstrated through citations, adoptions and/or adaptations of techniques and practices described in the paper by others, techniques described in the paper that have become widely recognized as best practices, or other evidence the paper is a seminal work in the domain of computing education. The paper must have been published in a conference sponsored or co-sponsored by SIGCSE or in an ACM journal at least 10 years prior.
* [Google Scholar](https://scholar.google.com/citations?user=lmqXht8AAAAJ&hl=en)
* [ACM author page](https://dl.acm.org/author_page.cfm?id=81100470027).
* Publications regarding [novice programmers](#novice-programmer-behavior)
* Publications regarding [parallel languages, robotics, and sensing](#parallel-languages-for-embedded-control)
* Publications regarding [teaching and learning](#education-related).
* [Google Scholar](https://scholar.google.com/citations?user=lmqXht8AAAAJ&hl=en)
* [ACM author page](https://dl.acm.org/author_page.cfm?id=81100470027).
* Publications regarding [novice programmers](novice-programmer-behavior)
* Publications regarding [parallel languages, robotics, and sensing](parallel-languages-for-embedded-control)
* Publications regarding [teaching and learning](teaching-and-learning).
## Novice Programmer Behavior
<img src="{{site.base}}/images/edit-compile-no-run-2.png" align="right" />
I am interested in how novices use programming tools. Along with colleagues at the Ateneo de Manila and Worcester Polytechnic, we have explored the behavior of novice programmers as they wrestle with the challenging task of writing syntactically correct programs. This work links behavior to affect, and hope to develop tools to better support teachers and students learning to program.
**ICER 2015**
<div class="acmdlitem" id="item2787718"><a href="http://dl.acm.org/authorize?N45525" title="Aggregate Compilation Behavior: Findings and Implications from 27,698 Users">Aggregate Compilation Behavior: Findings and Implications from 27,698 Users</a></div>
Jadud, M. C., Dorn, B.
**ICER 2011**
[Predicting At-Risk Novice Java Programmers Through
the Analysis of Online Protocols](http://dx.doi.org/10.1145/2016911.2016930)
Tabanao, E., Rodrigo, M. M. T., Jadud, M. C.
**Computer Science Education Vol 20, No. 3, 2010**
[String Formatting Considered Harmful for Novice Programmers](http://www.tandfonline.com/doi/abs/10.1080/08993408.2010.507335) ([PDF]({{site.base}}/dl/pdf/hughes-jadud-rodrigo-cse-2010.pdf))
Hughes, M. C., Jadud, M. C., Rodrigo, M. M. T.
**Philippine Journal of Science 2009**
[Analyzing online protocols to characterize novice Java programmers](http://philjournalsci.dost.gov.ph/vol138no2/analyzing%20online%20protocols%20to%20characterize%20novice%20java%20programmers.html) ([PDF]({{site.base}}/dl/pdf/tabanao-mercedes-jadud-pjs2009.pdf/))
Rodrigo, M. M. T., Tabanao, E., Lahoz, M. B. E., Jadud, M. C.
**ICER 2009**
[Flexible, reusable tools for studying novice programmers]({{site.base}}/dl/pdf/2009-icer-jadud-henriksen.pdf) ([PDF]({{site.base}}/dl/pdf/2009-icer-jadud-henriksen.pdf))
Jadud, M.C., Henriksen, P.
**ITICSE 2009**
[Affective and Behavioral Predictors of Novice Programmer Achievement](http://dl.acm.org/citation.cfm?doid=1595496.1562929) ([PDF]({{site.base}}/dl/pdf/2009-iticse-rodrigo.pdf))
Authors: Rodrigo, M.M.T., Baker, R.S., Jadud, M.C., Amarra, A.C.M., Dy, T., Espejo-Lahoz, M.B.V., Lim, S.A.L., Pascua, S.A.M.S., Sugay, J.O., Tabanao, E.S.
**PCSC 2008**
[Identifying At-Risk Novice Java Programmers Through the Analysis of Online Protocols]({{site.base}}/dl/pdf/2008-pcsc-tabanao-rodrigo-jadud.pdf)
Tabanao, E.S, Rodrigo, M.M.T., Jadud, M.C.
**DISSERTATION (UKC 2006)**
[An Exploration of Novice Compilation Behaviour in BlueJ]({{site.base}}/dl/pdf/jadud-dissertation.pdf)
M. Jadud
**BOOK**
[Studying Programming](http://www.palgrave.com/products/title.aspx?is=1403946876)
Fincher, S.A. and the Computer Science Education Research Group
**ICER 2006**
[Methods and tools for exploring novice compilation behaviour]({{site.base}}/dl/pdf/2006-icer-jadud.pdf)
M. Jadud
**Computer Science Education Vol 15, No 1, 2005**
[A first look at novice compilation behavior using BlueJ]({{site.base}}/dl/pdf/jadud-cse-2005.pdf)
M. Jadud
## Parallel Languages for Embedded Control
We need usable, expressive languages to support programmers in safely handling the multitude of inputs and outputs of embedded systems. The tools we build in this space are freely available online at concurrency.cc.
My work on novice programmers informs our work on the design and implementation of usable tools for beginners. Fortunately for us, in the realm of parallel languages, the vast majority of programmers are novices.
**ACM JCSC**
*Volume 29 Issue 5, May 2014*
[The siren song of open hardware/software in wireless sensor design
](https://dl.acm.org/citation.cfm?id=2600625)
Matthew Jadud, Namukaba Hichilo, Hatinawedu Mupiwa, Logan Ray, Mark P. Mahoney
**GECCO 2012**
GECCO 2012 (Genetic and Evolutionary Computation Conference), Full Paper, July 7-11 2012, Philadelphia, PA.
[Exploring and Evolving Process-oriented Control for Real and Virtual Fire Fighting Robots]({{site.base}}/dl/pdf/khardey-gecco-2012.pdf)
Kathryn Hardey, Eren Corapcioglu, Molly Mattis, Mark Goadrich and Matthew Jadud
**CPA 2011**
[The Flying Gator: Towards Aerial Robotics in occam-pi]({{site.base}}/dl/pdf/armstrong-brusse-smith-jadud-cpa2011.pdf)
Armstrong, I., Pirrone-Brusse, M. A., Jadud, M. C., Smith, A.
**CPA 2011**
[Concurrent Event-driven Programming in occam-pi for the Arduino]({{site.base}}/dl/pdf/jacobsen-jadud-kilic-sampson-cpa2011.pdf)
Jacobsen, C. L., Jadud, M. C., Kilic, O., and Sampson, A. T.
**IEEE TePRA 2008**
[Safe Parallelism for Robotic Control]({{site.base}}/dl/pdf/jadud-tepra-2008.pdf)
M. Jadud, C. Jacobsen, C. Ritson, J. Simpson
**SIGCSE 2008**
[Patterns for programming in parallel, pedagogically]({{site.base}}/dl/pdf/jadud-simpson-jacobsen-sigcse2008.pdf)
M. Jadud, J. Simpson, C. Jacobsen
**AAAI 2007**
[Concurrency, Robotics, and RoboDeb]({{site.base}}/dl/pdf/2007-aaai-jacobsen-jadud.pdf)
C. Jacobsen and M. Jadud
**CPA 2006**
[Mobile Robot Control: Subsumption Architecture and occam-pi](http://www.transterpreter.org/papers/simpson-jacobsen-jadud-cpa-2006.pdf)
J. Simpson, C. Jacobsen, M. Jadud
**CPA 2006**
[A Cell Transterpreter](http://www.transterpreter.org/papers/dimmich-jacobsen-jadud-cpa-2006.pdf)
D. Dimmich, C. Jacobsen, M. Jadud
**CPA 2006**
[Native Code Generation Using the Transterpreter](http://www.jadud.com/Research.htmlhttp://www.transterpreter.org/papers/jacobsen-dimmich-jadud-cpa-2006.pdf)
C. Jacobsen, D. Dimmich, M. Jadud
**SIGCSE 2005**
[Towards Concrete Concurrency: occam-pi on the LEGO Mindstorms](http://www.transterpreter.org/papers/jacobsen-jadud-sigcse-2005.pdf)
C. Jacobsen, M. Jadud
**CPA 2004**
[The Transterpreter: a Transputer interpreter]({{site.base}}/dl/pdf/2004-cpa-jacobsen-jadud.pdf)
C. Jacobsen, M. Jadud
**PPIG 2003**
[Little Languages for Little Robots]({{site.base}}/dl/pdf/2003-PPIG-lllr.pdf)
M. Jadud, B. Chenoweth, J. Schleter
**MINDFEST 2001**
All Things LEGO at Indiana University Bloomington ([left]({{site.base}}/dl/pdf/mindfest-left.pdf), [right]({{site.base}}/dl/pdf/mindfest-right.pdf))
M. Jadud
**IEEE SMC 2000**
[Teamstorms as a theory of instruction]({{site.base}}/dl/pdf/2000-SMC-teamstorms.pdf)
M. Jadud
**IEEE SMC 2000**
[Webworms: Modeling emergent behaviors using LEGO robotics]({{site.base}}/dl/pdf/2000-SMC-webworms.pdf)
M. Riddle, J.T. Lawson, Matthew Jadud
## Education Related
**ACM SIGCSE Bulletin**
<div class="acmdlitem" id="item2993229"><a href="https://dl.acm.org/authorize?N45526" title="IRB reviews required">IRB reviews required</a><div style="margin-left:25px"></div></div>
Jadud, M. C.
**SMACK 2011**
[Exploring the Use of Android OS in CS2](http://cs.ua.edu/SMACK/#schedule) ([PDF]({{site.base}}/dl/pdf/goadrich-jadud-jacobs-smack2011.pdf))
Goadrich, M. H., Jadud, M. C., Jacobs, J.
**SIGCSE 2010**
[(Special Session) If ____________, you might be a computational thinker!](http://dl.acm.org/citation.cfm?doid=1734263.1734355) ([PDF]({{site.base}}/dl/pdf/garcia-lewis-dougherty-jadud-sigcse2010.pdf))
Garcia, D. D., Lewis, C. M., Dougherty, J. P., Jadud, M. C.

View File

@@ -0,0 +1,83 @@
---
title: 'novice programmer behavior'
weight: 10
---
<img src="{{site.base}}/images/edit-compile-no-run-2.png" align="right" />
I am interested in how novices use programming tools. Along with colleagues at the Ateneo de Manila and Worcester Polytechnic, we have explored the behavior of novice programmers as they wrestle with the challenging task of writing syntactically correct programs. This work links behavior to affect, and hope to develop tools to better support teachers and students learning to program.
## ICER 2015
[Aggregate Compilation Behavior: Findings and Implications from 27,698 Users](http://dl.acm.org/authorize?N45525)
Jadud, M. C., Dorn, B.
## ICER 2011
[Predicting At-Risk Novice Java Programmers Through
the Analysis of Online Protocols](http://dx.doi.org/10.1145/2016911.2016930)
Tabanao, E., Rodrigo, M. M. T., Jadud, M. C.
## Computer Science Education Vol 20, No. 3, 2010
[String Formatting Considered Harmful for Novice Programmers](http://www.tandfonline.com/doi/abs/10.1080/08993408.2010.507335) ([PDF]({{site.base}}/dl/pdf/hughes-jadud-rodrigo-cse-2010.pdf))
Hughes, M. C., Jadud, M. C., Rodrigo, M. M. T.
## Philippine Journal of Science 2009
[Analyzing online protocols to characterize novice Java programmers](http://philjournalsci.dost.gov.ph/vol138no2/analyzing%20online%20protocols%20to%20characterize%20novice%20java%20programmers.html) ([PDF]({{site.base}}/dl/pdf/tabanao-mercedes-jadud-pjs2009.pdf/))
Rodrigo, M. M. T., Tabanao, E., Lahoz, M. B. E., Jadud, M. C.
## ICER 2009
[Flexible, reusable tools for studying novice programmers]({{site.base}}/dl/pdf/2009-icer-jadud-henriksen.pdf) ([PDF]({{site.base}}/dl/pdf/2009-icer-jadud-henriksen.pdf))
Jadud, M.C., Henriksen, P.
## ITICSE 2009
[Affective and Behavioral Predictors of Novice Programmer Achievement](http://dl.acm.org/citation.cfm?doid=1595496.1562929) ([PDF]({{site.base}}/dl/pdf/2009-iticse-rodrigo.pdf))
Authors: Rodrigo, M.M.T., Baker, R.S., Jadud, M.C., Amarra, A.C.M., Dy, T., Espejo-Lahoz, M.B.V., Lim, S.A.L., Pascua, S.A.M.S., Sugay, J.O., Tabanao, E.S.
## PCSC 2008
[Identifying At-Risk Novice Java Programmers Through the Analysis of Online Protocols]({{site.base}}/dl/pdf/2008-pcsc-tabanao-rodrigo-jadud.pdf)
Tabanao, E.S, Rodrigo, M.M.T., Jadud, M.C.
## DISSERTATION (UKC 2007)
[An Exploration of Novice Compilation Behaviour in BlueJ]({{site.base}}/dl/pdf/jadud-dissertation.pdf)
M. Jadud
## BOOK
[Studying Programming](http://www.palgrave.com/products/title.aspx?is=1403946876)
Fincher, S.A. and the Computer Science Education Research Group
## ICER 2006
[Methods and tools for exploring novice compilation behaviour]({{site.base}}/dl/pdf/2006-icer-jadud.pdf)
M. Jadud
## Computer Science Education Vol 15, No 1, 2005
[A first look at novice compilation behavior using BlueJ]({{site.base}}/dl/pdf/jadud-cse-2005.pdf)
M. Jadud

View File

@@ -0,0 +1,104 @@
---
title: 'parallel languages for embedded control'
weight: 20
---
We need usable, expressive languages to support programmers in safely handling the multitude of inputs and outputs of embedded systems. The tools we build in this space are freely available online at concurrency.cc.
My work on novice programmers informs our work on the design and implementation of usable tools for beginners. Fortunately for us, in the realm of parallel languages, the vast majority of programmers are novices.
## ACM JCSC
*Volume 29 Issue 5, May 2014*
[The siren song of open hardware/software in wireless sensor design
](https://dl.acm.org/citation.cfm?id=2600625)
Matthew Jadud, Namukaba Hichilo, Hatinawedu Mupiwa, Logan Ray, Mark P. Mahoney
## GECCO 2012
GECCO 2012 (Genetic and Evolutionary Computation Conference), Full Paper, July 7-11 2012, Philadelphia, PA.
[Exploring and Evolving Process-oriented Control for Real and Virtual Fire Fighting Robots]({{site.base}}/dl/pdf/khardey-gecco-2012.pdf)
Kathryn Hardey, Eren Corapcioglu, Molly Mattis, Mark Goadrich and Matthew Jadud
## CPA 2011
[The Flying Gator: Towards Aerial Robotics in occam-pi]({{site.base}}/dl/pdf/armstrong-brusse-smith-jadud-cpa2011.pdf)
Armstrong, I., Pirrone-Brusse, M. A., Jadud, M. C., Smith, A.
[Concurrent Event-driven Programming in occam-pi for the Arduino]({{site.base}}/dl/pdf/jacobsen-jadud-kilic-sampson-cpa2011.pdf)
Jacobsen, C. L., Jadud, M. C., Kilic, O., and Sampson, A. T.
## IEEE TePRA 2008
[Safe Parallelism for Robotic Control]({{site.base}}/dl/pdf/jadud-tepra-2008.pdf)
M. Jadud, C. Jacobsen, C. Ritson, J. Simpson
## SIGCSE 2008
[Patterns for programming in parallel, pedagogically]({{site.base}}/dl/pdf/jadud-simpson-jacobsen-sigcse2008.pdf)
M. Jadud, J. Simpson, C. Jacobsen
## AAAI 2007
[Concurrency, Robotics, and RoboDeb]({{site.base}}/dl/pdf/2007-aaai-jacobsen-jadud.pdf)
C. Jacobsen and M. Jadud
## CPA 2006
[Mobile Robot Control: Subsumption Architecture and occam-pi](http://www.transterpreter.org/papers/simpson-jacobsen-jadud-cpa-2006.pdf)
J. Simpson, C. Jacobsen, M. Jadud
[A Cell Transterpreter](http://www.transterpreter.org/papers/dimmich-jacobsen-jadud-cpa-2006.pdf)
D. Dimmich, C. Jacobsen, M. Jadud
[Native Code Generation Using the Transterpreter](http://www.jadud.com/Research.htmlhttp://www.transterpreter.org/papers/jacobsen-dimmich-jadud-cpa-2006.pdf)
C. Jacobsen, D. Dimmich, M. Jadud
## SIGCSE 2005
[Towards Concrete Concurrency: occam-pi on the LEGO Mindstorms](http://www.transterpreter.org/papers/jacobsen-jadud-sigcse-2005.pdf)
C. Jacobsen, M. Jadud
## CPA 2004
[The Transterpreter: a Transputer interpreter]({{site.base}}/dl/pdf/2004-cpa-jacobsen-jadud.pdf)
C. Jacobsen, M. Jadud
## PPIG 2003
[Little Languages for Little Robots]({{site.base}}/dl/pdf/2003-PPIG-lllr.pdf)
M. Jadud, B. Chenoweth, J. Schleter
## MINDFEST 2001
All Things LEGO at Indiana University Bloomington ([left]({{site.base}}/dl/pdf/mindfest-left.pdf), [right]({{site.base}}/dl/pdf/mindfest-right.pdf))
M. Jadud
## IEEE SMC 2000
[Teamstorms as a theory of instruction]({{site.base}}/dl/pdf/2000-SMC-teamstorms.pdf)
M. Jadud
## IEEE SMC 2000
[Webworms: Modeling emergent behaviors using LEGO robotics]({{site.base}}/dl/pdf/2000-SMC-webworms.pdf)
M. Riddle, J.T. Lawson, Matthew Jadud

View File

@@ -0,0 +1,24 @@
---
title: 'teaching and learning'
weight: 30
---
## ACM SIGCSE Bulletin
[IRB reviews required](https://dl.acm.org/authorize?N45526)
Jadud, M. C.
## SMACK 2011
[Exploring the Use of Android OS in CS2](http://cs.ua.edu/SMACK/#schedule) ([PDF]({{site.base}}/dl/pdf/goadrich-jadud-jacobs-smack2011.pdf))
Goadrich, M. H., Jadud, M. C., Jacobs, J.
## SIGCSE 2010
[(Special Session) If ____________, you might be a computational thinker!](http://dl.acm.org/citation.cfm?doid=1734263.1734355) ([PDF]({{site.base}}/dl/pdf/garcia-lewis-dougherty-jadud-sigcse2010.pdf))
Garcia, D. D., Lewis, C. M., Dougherty, J. P., Jadud, M. C.

View File

@@ -0,0 +1,15 @@
---
title: 'sammy'
weight: 100
type: default
sidebar:
exclude: true
---
This is Sammy. If you've found this page, perhaps you've found Sammy. It is linked from his dog tag.
If you email **sammy@jadud.com**, it will send a message to everyone in his family. Or, you could text or call us; our phone numbers are on his tags.
![sammy](/images/sammy-in-a-sunbeam.jpg)
Sammy is chipped and vaccinated, and is very much loved. You have probably already discovered that he loves belly rubs, and if you could give him a few before we can come pick him up from you, that would be wonderful.

View File

@@ -1,22 +1,36 @@
baseURL: https://example.org/
languageCode: en-us
title: My New Hugo Site
title: people & process & tech (oh my)
enableInlineShortcodes: true
module:
imports:
- path: github.com/imfing/hextra
markup:
goldmark:
renderer:
unsafe: true
frontmatter:
date:
- date
- publishdate
permalinks:
# post: "/:year/:month/:slug/"
post: "/:slug/"
menu:
main:
- name: Blog
- name: blog
pageRef: /blog
weight: 10
- name: Documentation
pageRef: /docs
- name: articles
pageRef: /articles
weight: 20
- name: About
pageRef: /about
weight: 30
- name: Search
- name: research
pageRef: /research
weight: 20
- name: search
weight: 40
params:
type: search
@@ -24,4 +38,18 @@ menu:
weight: 50
url: "https://github.com/jadudm"
params:
icon: github
icon: github
- name: Gitea
weight: 50
url: "https://git.jadud.com/jadudm"
params:
icon: github
sidebar:
- name: other stuff
params:
type: separator
weight: 1
- name: "sammy"
pageRef: "/sammy"
weight: 2

View File

@@ -0,0 +1,8 @@
{{ define "main" }}
{{.Content}}
<ul class="all-posts">
{{range .Site.RegularPages}}
<li><a href="{{.Permalink}}">{{.Title}}</a></li>
{{end}}
</ul>
{{ end }}

View File

@@ -0,0 +1,39 @@
{{ define "main" }}
{{- $readMore := (T "readMore") | default "Read more →" -}}
<div class="hx:mx-auto hx:flex hextra-max-page-width">
{{ partial "sidebar.html" (dict "context" . "disableSidebar" false "displayPlaceholder" true) }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
{{ partial "breadcrumb.html" (dict "page" . "enable" false) }}
<br class="hx:mt-1.5 hx:text-sm" />
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
<div class="content">aqwef {{ .Content }}</div>
{{- $pages := partial "utils/sort-pages" (dict "page" . "by" site.Params.blog.list.sortBy "order" site.Params.blog.list.sortOrder) -}}
{{- $pagerSize := site.Params.blog.list.pagerSize | default 10 -}}
{{- $paginator := .Paginate $pages $pagerSize -}}
{{- range $paginator.Pages }}
<div class="hx:mb-10">
<h3><a style="color: inherit; text-decoration: none;" class="hx:block hx:font-semibold hx:mt-8 hx:text-2xl " href="{{ .RelPermalink }}">{{ .Title }}</a></h3>
{{ if site.Params.blog.list.displayTags }}
<div class="hx:text-sm hx:leading-7">
{{ partial "tags.html" (dict "context" .) }}
</div>
{{ end }}
<p class="hx:opacity-80 hx:mt-4 hx:leading-7">{{- partial "utils/page-description" . -}}</p>
<p class="hx:opacity-80 hx:mt-1 hx:leading-7">
<a class="hx:text-[color:hsl(var(--primary-hue),100%,50%)] hx:underline hx:underline-offset-2 hx:decoration-from-font" href="{{ .RelPermalink }}">
{{- $readMore -}}
</a>
</p>
<p class="hx:opacity-50 hx:text-sm hx:mt-4 hx:leading-7">{{ partial "utils/format-date" .Date }}</p>
</div>
{{ end -}}
{{- if gt $paginator.TotalPages 1 -}}
{{ partial "components/blog-pager.html" $paginator }}
{{- end -}}
</main>
</article>
<div class="hx:max-xl:hidden hx:h-0 hx:w-64 hx:shrink-0"></div>
</div>
{{- end -}}

View File

@@ -0,0 +1,50 @@
{{ define "main" }}
<div class="hx:mx-auto hx:flex hextra-max-page-width">
{{ partial "sidebar.html" (dict "context" . "disableSidebar" false "displayPlaceholder" true) }}
{{ partial "toc.html" . }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
{{ partial "breadcrumb.html" (dict "page" . "enable" true) }}
{{ if .Title }}<h1 class="hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
<div class="hx:mt-4 hx:mb-16 hx:text-gray-500 hx:dark:text-gray-400 hx:text-sm hx:flex hx:items-center hx:flex-wrap hx:gap-y-2">
{{- with $date := .Date }}<span class="hx:mr-1">{{ partial "utils/format-date" $date }}</span>{{ end -}}
{{- $lazyLoading := site.Params.enableImageLazyLoading | default true -}}
{{ if and .Date .Params.authors }}<span class="hx:mx-1">·</span>{{ end -}}
{{- with $.Params.authors -}}
{{- range $i, $author := . -}}
{{- if reflect.IsMap $author -}}
{{- if and $i (not $author.image) }}<span class="hx:mr-1">,</span>{{ end -}}
<a
{{ with $author.link }}href="{{ . }}" target="_blank"{{ end }}
class="hx:group hx:inline-flex hx:items-center hx:text-current hx:gap-x-1.5 hx:mx-1"
{{ with $author.name }}title="{{ . }}"{{ end }}
>
{{- with $image := $author.image }}
{{- $isLocal := not (urls.Parse $image).Scheme -}}
{{- $startsWithSlash := hasPrefix $image "/" -}}
{{- if and $isLocal $startsWithSlash }}
{{- $image = (relURL (strings.TrimPrefix "/" $image)) -}}
{{ end -}}
<img src="{{ $image | safeURL }}" alt="{{ $author.name }}" class="hx:inline-block hx:h-4 hx:w-4 hx:rounded-full" {{ if $lazyLoading }}loading="lazy"{{ end }} />
{{ end -}}
<div class="hx:group-hover:underline">{{ $author.name }}</div>
</a>
{{- else -}}
{{- if $i }}<span class="hx:mr-1">,</span>{{ end -}}<span class="hx:mx-1">{{ $author }}</span>
{{- end -}}
{{- end -}}
{{- end -}}
</div>
<div class="content">
X {{ .Content }}
</div>
{{- partial "components/last-updated.html" . -}}
{{- if (site.Params.blog.article.displayPagination | default true) -}}
{{- .Store.Set "reversePagination" (.Params.reversePagination | default true) -}}
{{- partial "components/pager.html" . -}}
{{ end }}
{{- partial "components/comments.html" . -}}
</main>
</article>
</div>
{{ end }}

View File

@@ -0,0 +1,19 @@
{{ define "main" }}
<!-- this is a list/_index article / docs -->
<div class='hx:mx-auto hx:flex hextra-max-page-width'>
{{ partial "sidebar.html" (dict "context" .) }}
{{ partial "toc.html" . }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
{{ partial "breadcrumb.html" (dict "page" . "enable" true) }}
<div class="content">
{{ if .Title }}<h1>{{ .Title }}</h1>{{ end }}
{{ .Content }}
</div>
{{ partial "components/last-updated.html" . }}
{{ partial "components/pager.html" . }}
{{ partial "components/comments.html" . }}
</main>
</article>
</div>
{{ end }}

View File

@@ -0,0 +1,24 @@
{{ define "main" }}
<!-- this is a single article / docs -->
{{/* https://discourse.gohugo.io/t/how-can-i-filter-pages-by-tag/12245/6 */}}
{{$articles := .Site.RegularPages.RelatedTo (keyVals "tags" "article")}}
<div class='hx:mx-auto hx:flex hextra-max-page-width'>
{{ partial "sidebar.html" (dict "context" .) }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
<div class="content">
section-summary {{.Content}}
<ul class="all-posts">
{{range $articles }}
<li>d <a href="{{.Permalink}}">{{.Title}}</a><br>{{.Param "blurb"}}</li>
{{end}}
asdfasdf
</ul>
</div>
{{ partial "components/last-updated.html" . }}
{{ partial "components/pager.html" . }}
{{ partial "components/comments.html" . }}
</main>
</article>
</div>
{{ end }}

View File

@@ -0,0 +1,19 @@
{{ define "main" }}
<!-- this is a single blog post page-->
<div class='hx:mx-auto hx:flex hextra-max-page-width'>
{{ partial "sidebar.html" (dict "context" .) }}
{{ partial "toc.html" . }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
{{ partial "breadcrumb.html" (dict "page" . "enable" true) }}
<div class="content">
{{ if .Title }}<h1>{{ .Title }}</h1>{{ end }}
{{ .Content }}
</div>
{{ partial "components/last-updated.html" . }}
{{ partial "components/pager.html" . }}
{{ partial "components/comments.html" . }}
</main>
</article>
</div>
{{ end }}

View File

@@ -0,0 +1,8 @@
{{ $tag := .Get 0 }}
{{ $articles := (.Site.RegularPages.RelatedTo (keyVals "tags" $tag)).ByDate.Reverse }}
<ul>
{{range $articles }}
<li><a href="{{.Permalink}}">{{.Title}}</a><br>{{.Param "blurb"}}</li>
{{end}}
</ul>

View File

@@ -0,0 +1 @@
{{ .Site.Title }}

View File

@@ -0,0 +1,21 @@
{{ define "main" }}
<div class='hx:mx-auto hx:flex hextra-max-page-width'>
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }}
{{ partial "toc.html" . }}
<article class="hx:w-full hx:break-words hx:flex hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:justify-center hx:pb-8 hx:pr-[calc(env(safe-area-inset-right)-1.5rem)]">
<main class="hx:w-full hx:min-w-0 hx:max-w-6xl hx:px-6 hx:pt-4 hx:md:px-12">
{{ partial "breadcrumb.html" (dict "page" . "enable" false) }}
<br class="hx:mt-1.5 hx:text-sm" />
{{ if .Title }}<h1 class="hx:text-center hx:mt-2 hx:text-4xl hx:font-bold hx:tracking-tight hx:text-slate-900 hx:dark:text-slate-100">{{ .Title }}</h1>{{ end }}
<div class="hx:mb-16"></div>
<div class="content">
POOPING top-level single
{{ .Content }}
</div>
<div class="hx:mt-16"></div>
{{ partial "components/comments.html" . }}
</main>
</article>
</div>
{{ end }}

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 983 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
jadudcom/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

6
scratchpad/misc.md Normal file
View File

@@ -0,0 +1,6 @@
<!--
Currently, I am a Sr. Software Engineer at [Nava PBC](https://navapbc.com); I was previously a software engineer at [GSA](https://gsa.gov).
I began my time at GSA in [18F](https://18f.org), and I served as the acting director for the <a href="https://fac.gov/">Federal Audit Clearinghouse</a> for 2 years, the modernization lead for <a href="https://search.gov">search.gov</a>, and as a consulting engineer for multiple projects with <a href="https://10x.gsa.gov/">10x</a> and <a href="https://18f.gsa.gov">18F</a>. Before my time in Federal service, I was the founding chair for the Digital and Computational Studies program at [Bates College](https://bates.edu), and tenured Computer Science faculty at [Berea College](https://berea.edu).
-->