All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.rahulsom.grooves.asciidoctor.SvgBuilder.groovy Maven / Gradle / Ivy

package com.github.rahulsom.grooves.asciidoctor

import groovy.xml.StreamingMarkupBuilder
import groovy.xml.XmlUtil
import com.github.sommeri.less4j.core.ThreadUnsafeLessCompiler

/**
 * Builds an SVG from a text representation of an event sourced aggregate.
 *
 * @author Rahul Somasunderam
 */
class SvgBuilder {

    private List aggregates = []
    List allEvents = []
    Map dates
    private long minInterval = 0

    SvgBuilder(String input) {
        Aggregate lastAggregate = null

        input.split('\n').each {
            if (it.startsWith('|')) {
                def parts = it.replaceFirst('\\|', '').split(',')
                lastAggregate = new Aggregate(*parts)
                lastAggregate.index = aggregates.size()
                aggregates << lastAggregate
            }
            if (it.startsWith('  - ') || it.startsWith('  + ')) {
                def m = it =~ / +([-+]) ([^ ]+) ([^ ]+) (.+)/
                def parts = m[0] as List
                def (_, sign, id, date, description) = m[0]
                EventType type = computeEventType(description)
                def event = new Event(id, Date.parse('yyyy-MM-dd', date), description, type)
                if (sign == '-') {
                    event.reverted = true
                }
                lastAggregate.events << event
                allEvents << event
            }
        }

        def justDates = aggregates*.events*.date.flatten().sort().unique() as List
        minInterval = justDates.collate(2, 1, false).collect { a -> a[1].time - a[0].time }.min()
        dates = justDates.collectEntries { [it, (it.time - justDates[0].time) * 1.0 / minInterval] }

        double diff = 0.0
        double lastV = 0.0

        dates.each { k, v ->
            if (v - lastV - diff > 3.0) {
                diff += v - (lastV + 3.0)
            }
            v -= diff
            dates[k] = v
            lastV = v
        }
    }

    private EventType computeEventType(String description) {
        EventType type = EventType.Normal
        switch (description) {
            case ~/.*revert.*/: type = EventType.Revert; break
            case ~/.*deprecates.*/: type = EventType.Deprecates; break
            case ~/.*deprecated.*/: type = EventType.DeprecatedBy; break
            case ~/.*disjoin.*/: type = EventType.Disjoin; break
            case ~/.*join.*/: type = EventType.Join; break
        }
        type
    }

    void write(File file) {
        def m = new StreamingMarkupBuilder().bind { builder ->
            builder.svg(xmlns: "http://www.w3.org/2000/svg",
                    height: aggregates.size() * Constants.eventLineHeight,
                    width: dates.values().max() * Constants.eventSpace + 4 * Constants.aggregateWidth) {
                mkp.comment "Generated on ${new Date()} from\n${aggregates.join('\n')}\n"

                buildStyle(builder, new ThreadUnsafeLessCompiler().compile(Constants.LESS).css)

                builder.rect x: 0, y: 0,
                        width: dates.values().max() * Constants.eventSpace + 4 * Constants.aggregateWidth ,
                        height: aggregates.size() * Constants.eventLineHeight ,
                        class: 'background'
                defs {
                    marker(id: 'triangle', viewBox: '0 0 10 10', refX: 0, refY: 5,
                            markerWidth: 10, markerHeight: 10, orient: 'auto',
                            markerUnits: "userSpaceOnUse") {
                        path d: 'M 0 0 L 10 5 L 0 10 z'
                    }
                }
                aggregates.each { Aggregate aggregate ->
                    mkp.comment ' '
                    mkp.comment "|${aggregate.type},${aggregate.id},${aggregate.description}"
                    mkp.comment ' '
                    aggregate.buildSvg(builder, dates)
                    aggregate.events.each { Event event ->
                        event.buildSvg(builder, aggregate.index, this)
                    }
                }
            }
        }

        XmlUtil.serialize(m, file.newPrintWriter())
    }

    private void buildStyle(builder, String css) {
        builder.style { mkp.yieldUnescaped '/*  */' }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy