The Crystal Programming Language Forum

Celestine: Code review? Performance? Proper way to "construct" html like tags and documents?

Hello everyone.

I was hoping to get some code review/suggestions on how to do things better for my SVG drawing library, I’ve been working on it for months now, and I’ve gotten it to a fairly complete point. That being said, I’m sure there are things I could do better, and since I’m at such a good point I thought I would work on some refactoring.

Celestine Github

Celestine Basics

Everything in Celestine is done via a Celestine.draw, this returns a string of SVG code created. During the Celestine.draw method, a Celestine::Meta::Context object is created to handle all the DSL calls, and hold all the objects created by the programmer.

puts \
Celestine.draw do |ctx|
  ctx.rectangle do |rect|
    # Modify the newly created rectangle
    rect.fill = "red"
    # Give it back since it's a struct.
    rect
  end
end

All of the objects created by Celestine via context methods such as rectangle, circle, ellipse need to be returned at the end of the method because they are structs. From my understanding this is supposed to give better performance over class. Is this true for this case?

Internally, the Celestine::Meta::Context stores all created objects in an array of Celestine::Drawable which Celestine::Rectangle, Celestine::Circle and etc all inherit from. At the end of the Celestine.draw the Celestine::Meta::Context#render method is called and SVG code is returned through draw.

Celestine::Meta::Context#render source code

Ok, so this is where things start to get a little muddy.

Naturally the SVG spec is a monster and there is a lot to implement, some of which can be very difficult to test because it has to be done by a human AND with multiple browsers. This has lead to each of my drawable objects containing a similar but fundamentally different draw method which renders the SVG code for the object. When looking at the spec for something like SVG’s <rect> tag versus something like <circle> there are similarities and differences, one notable example is that <rect> has an x attribute, but <circle> has a cx attribute instead, they both do the same thing, change the x position.

To solve this I created modules which hold constants related to the attribute names. This gets included into the Celestine::Drawable::Attrs module for each subclass of Celestine::Drawable as long as it uses the include_options macro. (Otherwise you would just have to include all the modules, attribute names yourself). This is done so programmer have access to all the attribute names in a Drawable when using the <animate> tag. For example, this makes using Celestine::Rectangle::Attrs::X instead of needing to use the module that was included, like Celestine::Modules::Position::Attrs::X.

This created a new problem in that each Celestine::Drawable must implement their own draw method, each of which have slight variations in them. For example, Celestine::Rectangle uses Celestine::Modules::Position while Celestine::Circle uses Celestine::Modules::CPosition, this makes a difference in the draw method.

Examples:
Celestine::Circle#draw source code
Celestine::Rectangle#draw source code

Is there a good way to simplify this or at the very least not need to reuse sooo much code? I tried concocting some way with macros, but nothing I came up with seemed like a sure winner.

I also can’t help but think there has to be a better way of programmatically putting together this HTML-like code, any suggestions on how to better handle this? An example of something I’d like to fix, <animate> tags are allowed to have an inner <animate> tag, allowing incredibly complicated animations to be made. Currently my system cannot handle this, as Celestine’s animate cannot have inner tags yet, as well as animate being immediately turned into a string when being handled through a context method.

Celestine::Modules::Animate source code

Questions:

  1. Is struct really that much faster that class here?
  2. Is there a better way to handle Celestine::Meta::Context#render?
  3. Better way to construct HTML like code?
  4. Advice on how to handle potentially recursive inner tags?
  5. Any other advice on DSLs?
3 Likes