New D update "dwidder" website launched, making-of post here

Posted 2020-09-07

People on the main D forum asked for more little project updates. While one option is adding a mailing list to the forum among other things, I spent part of the weekend to put a little website together people can use here: https://dwidder.arsdnet.net/

Core D Development Statistics

In the community

Community announcements

See more at the announce forum.

My D updates website

My new website is currently live here https://dwidder.arsdnet.net/ and you can post right now! I might move it later, but the idea right now is to be like Twitter, but just for D. Write little updates to let interested people in the community know what you are doing, without the effort of writing a proper blog post.

Bonus fact: you can add ?format=json to any url on the site if you prefer to parse it that way. My HTML is reasonably easy to scrape too so feel free, but try not to hit my little server too much.

I might be adding email notifications or something to it later, but right now I'm just not set up for it on the server; if I sent email it would surely all go to spam anyway. So just check back on the site periodically for new posts.

Making of the site

You can see its source code here: https://github.com/adamdruppe/dupdates

There's not a whole lot to it; it is a simple website. Most the length is because I copy/pasted my queries then did slight tweaks in each function. But it should be somewhat readable if you want to browse around.

This is only the second program I've written using the arsd.webtemplate library and just the fourth using the new advanced features in arsd.cgi. So I'm still figuring out just how I want those libraries to work (I tend to refine libraries *as* I use them, to help me ensure their design is actually practical).

Well, let's take a look inside. There's two places I want to direct your attention:

1 class DUpdate : WebObject {
2         this() { }
3 
4         @UrlName("")
5         @Template("home.html")
6         Paginated!Post recentPosts(string cursor = null) {
7 		// snip
8 		next = "/?cursor=" ~ Uri.encode(ret[$-1].date ~ "!" ~ ret[$-1].id.to!string);
9 
10 		return typeof(return)(ret, next);
11 	}
12 	
13         @(Cgi.RequestMethod.POST)
14         @UrlName("posts")
15         Redirection post(Cgi cgi, string content) {
16 		// snip
17 		return Redirection("/posts/" ~ row[0]);
18 	}
19 }

And further down the file:

1 void handler(Cgi cgi) {
2         if(presenter is null)
3                 presenter = new MyPresenter();
4         if(db is null)
5                 db = new PostgreSql("dbname=dupdate");
6 
7         if(cgi.dispatcher!(
8                 "/assets/".serveStaticFileDirectory,
9                 "/posts/".serveApi!PostController,
10                 "/".serveApi!DUpdate
11         )(presenter)) return;
12 
13         presenter.renderBasicError(cgi, 404);
14 }
15 
16 class MyPresenter : WebPresenterWithTemplateSupport!MyPresenter {
17         override void addContext(Cgi cgi, var ctx) {
18                 auto ld = cgi.getSessionObject!LoginData;
19                 ctx.user = ld.userId;
20         }
21 }

The first section illustrates the page implementation code and the second section shows some of the plumbing code that customizes the library for this particular project.

In the first section, I return a Paginated!Post. Post is defined for this project, just a simple struct to gather some data. Paginated is defined inside arsd.cgi (as of a few days ago) and it contains an array and a string for the next page's URL. Here, I made the url by hand, but I want to think of a better way. Just the url string gives the author flexibility in how exactly it should be done. It is passed as the cursor argument here.

The function is called based on its UrlName, which defaults to a "url-ified" version of the method name or you can override it with a UDA. This is relative to the dispatcher in the second snippet, which gives URL bases and handlers.

Parameters are pulled out of the request based on types. For a GET request, they are pulled out of query params. For POST and others, they come from the request body. The exact layout is defined to be both easy for people to use and easy to generate from scripts or from HTML forms with minimal javascript - indeed, forms can be auto-generated from the params.

But, there's exceptions. Some types, like Cgi or Session, are obviously never pulled from a request. Others can be passed by code, or filled automatically depending on the user. You can define your own types with static T getAutomaticallyForCgi(Cgi cgi) {} and then can work with what you need. (This will probably change slightly, I expect to also pass the parameter name to that function in the future.)

These are passed on the request automatically. This allows you to avoid using state in the objects while maintaining flexibility to call the functions from code as well as from the web; they are easier to unit test and to reuse.

Then, the return value of the function is passed to the "Presenter". The default presenter displays data in a generic way: lists, tables, redirects, etc. Or this can be overridden by the user providing their own presenter. I've written about this before in bits and pieces if it sounds familiar.

But here, my custom presenter pulls from arsd.webtemplate's new WebPresenterWithTemplateSupport. This gives support for a new type RenderTemplate you can return, but here I actually used the @Template UDA. The reason I separate all this out is to provide maximum flexibility to users - you aren't forced to use any of my libraries, but they are easily available if you want them. And if you don't like how it does something, you can customize parts piece by piece.

This gives a filename that is loaded as a HTML template and provides the return value as a variable called data in there.

<main>
	<!-- snipped -->

        <h2 id="recent-updates">Recent updates</h2>

        <for-each over="data.items" as="post">
                <render-template file="post-snippet.html" />
        </for-each>
        <if-true cond="data.nextPageUrl">
                <a href="<%= data.nextPageUrl %>">Next Page</a>
        </if-true>
</main>

Notice that there is no explicit renderTemplate call in my code. This is part of why the ?format=json trick works: the base presenter actually checks the requested format and passes the return value to a function to handle it. So json gets written out as returnValue.toJson and html gets renderTemplate(returnValue) (basically).

But the functions themselves don't worry about that. Which also means they can be more easily unit tested or otherwise reused in code, since the original return value object is still available as well.

The template is just a HTML file with a few special things added. You can probably tell what it does just looking at it here. Certain parts use arsd.script for customization, but very little script work is needed. Otherwise, there's two files for any template: a named file and skeleton.html. skeleton.html is a complete HTML file with placeholders that the named file replaces, like <main>.

Not all return values actually go to the template. Redirection is a type cgi.d defines that the default presenter recognizes as a HTTP redirect instead of a web page. You can define your own types in your own presenters to do this too by using template specialization. I'll probably show this in another week.

On the second part of the snippet, there's handler and the presenter class. handler is called (in another part of the code not quoted here) as the handler for each incoming request.

This initializes some data, then tries to pass to dispatcher for url routing. I almost regret this because it means functions do not know their own url... but on the other hand, it again gives me a huge amount of flexibility. If I don't need fancy routing, I don't have to use it. And if I do, I can delegate sections as much or as little as needed to other functions too. The free-form function also gives me explicit flow control at any time.

The slight extra boilerplate - like the explicit call to renderBasicError(404) - is slightly annoying (though not as much as not having a static url table... but I'll see if I can fix that without sacrificing later), but it also means I can customize things nice and easy. It is just a function.

Finally, my custom presenter mostly just delegates to the library, but there is one override function to pull login data from the session object into the template context. Thanks to the curiously-recurring template pattern, you can even "override" templates. In fact, let's peer inside webtemplate.d as an example:

1 template WebPresenterWithTemplateSupport(CTRP) {
2 	import arsd.cgi;
3 	class WebPresenterWithTemplateSupport : WebPresenter!(CTRP) {
4 
5 		// snip
6 
7 		static struct Meta {
8 			typeof(null) at;
9 			string templateName;
10 			string skeletonName;
11 			alias at this;
12 		}
13 		template methodMeta(alias method) {
14 			static Meta helper() {
15 				Meta ret;
16 
17 				// ret.at = typeof(super).methodMeta!method;
18 
19 				foreach(attr; __traits(getAttributes, method))
20 					static if(is(typeof(attr) == Template))
21 						ret.templateName = attr.name;
22 					else static if(is(typeof(attr) == Skeleton))
23 						ret.skeletonName = attr.name;
24 
25 				return ret;
26 			}
27 			enum methodMeta = helper();
28 		}
29 
30 		/// You can override this
31 		void addContext(Cgi cgi, var ctx) {}
32 
33 		void presentSuccessfulReturnAsHtml(T : RenderTemplate)(Cgi cgi, T ret, Meta meta) {
34 			addContext(cgi, ret.context);
35 			auto skeleton = renderTemplate(ret.name, ret.context, ret.skeletonContext);
36 			cgi.setResponseContentType("text/html; charset=utf8");
37 			cgi.gzipResponse = true;
38 			cgi.write(skeleton.toString(), true);
39 		}
40 
41 		void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, Meta meta) {
42 			if(meta.templateName.length) {
43 				var obj = var.emptyObject;
44 				obj.data = ret;
45 				presentSuccessfulReturnAsHtml(cgi, RenderTemplate(meta.templateName, obj), meta);
46 			} else
47 				super.presentSuccessfulReturnAsHtml(cgi, ret, meta);
48 		}
49 	}
50 }

This is the implementation of the class I inherited from. First, I wrote the template long-form to decouple webtemplate.d from cgi.d; you can use this module independently if you don't use this class, but the interop is easily available if you do want it. The lazy import just works.

Then it defines the template and forwards the CRTP to the base. The ultimate base class must know the most derived type to close the circle between virtuals and templates. I know that looks weird, but I wrote about it before: http://dpldocs.info/this-week-in-d/Blog.Posted_2019_06_10.html#tip-of-the-week and it really does enable some cool stuff.

I immediately use it for the methodMeta template. The base class calls the ultimately-derived methodMeta which can get custom compile-time reflection on the methods it uses. I pull the Template UDA out here for use later, then call super.methodMeta to ensure the base class has everything it needs too.

Then addContext is a traditional virtual function. It just lets the user add custom context to their template handler before they are rendered.

Then, the presentSuccessfulReturnAsHtml is overriding a template in the base class, almost as if it was virtual. This is where the CRTP comes in again: the base class actually calls the most-derived template, which, in turn, can call the base template again. It is all statically dispatched in a full circle for maximum flexibility.

First, I define one specialized on the custom type RenderTemplate. It actually calls the render function and writes out the resultant document as the response to the http request.

Then, the overload set needs to be completed, so I define a generic catch-all which forwards either back to the base class or to the new custom type as needed.

It ends up working very much like a regular virtual function... but all the compile time info is still available! Biggest downside is the error messages are not as good as a regular virtual class, but it is usable.

If you wanted to provide your own additional customizations, you can do the same thing: define some specializations, then define a generic catch-all that forwards to the super class.

Well, I still have a lot of work to do and have another example coming eventually, but for now, the D update aka dwidder site is the best public example I have of actually using it. And if you want to use it, grab the pieces you want and have fun! Just be warned I might still change a few things but it shouldn't be too hard to keep up so you probably can enjoy using it already. Just let me know if you do - real use is how I design practical libraries.