elliotchance/gedcom

View on GitHub
html/publish.go

Summary

Maintainability
A
2 hrs
Test Coverage
package html

import (
    "sort"
    "strings"

    "github.com/elliotchance/gedcom/v39"
    "github.com/elliotchance/gedcom/v39/html/core"
    "github.com/elliotchance/gedcom/v39/util"
)

type PublishShowOptions struct {
    ShowIndividuals  bool
    ShowPlaces       bool
    ShowFamilies     bool
    ShowSurnames     bool
    ShowSources      bool
    ShowStatistics   bool
    LivingVisibility LivingVisibility
}

type Publisher struct {
    doc               *gedcom.Document
    options           *PublishShowOptions
    fileWriter        core.FileWriter
    GoogleAnalyticsID string
    indexLetters      []rune
    individuals       map[string]*gedcom.IndividualNode
    placesMap         map[string]*place
}

// NewPublisher generates the pages to be rendered for a published website.
//
// The fileWriter will be responsible for rendering the pages to their
// destination. Which may be a file system, or somewhere else of your choosing.
// If you only wish to generate files you should use a DirectoryFileWriter.
func NewPublisher(doc *gedcom.Document, options *PublishShowOptions) *Publisher {
    return &Publisher{
        doc:          doc,
        options:      options,
        indexLetters: GetIndexLetters(doc, options.LivingVisibility),

        // placesMap can be nil because we handle found the places yet.
        individuals: GetIndividuals(doc, nil),
    }
}

func (publisher *Publisher) Publish(fileWriter core.FileWriter, parallel int) (err error) {
    files := publisher.Files(parallel)
    util.WorkerPool(parallel, func(_ int) {
        for file := range files {
            fileErr := fileWriter.WriteFile(file)
            if fileErr != nil {
                err = fileErr
                break
            }
        }
    })

    return
}

func (publisher *Publisher) Files(channelSize int) chan *core.File {
    files := make(chan *core.File, channelSize)

    go func() {
        publisher.sendFiles(files)
        close(files)
    }()

    return files
}

func (publisher *Publisher) sendIndividualFiles(files chan *core.File) {
    if publisher.options.ShowIndividuals {
        for _, letter := range publisher.indexLetters {
            files <- core.NewFile(
                PageIndividuals(letter),
                NewIndividualListPage(publisher.doc, letter,
                    publisher.GoogleAnalyticsID, publisher.options,
                    publisher.indexLetters, publisher.placesMap),
            )
        }

        for _, individual := range publisher.individuals {
            if individual.IsLiving() {
                switch publisher.options.LivingVisibility {
                case LivingVisibilityHide,
                    LivingVisibilityPlaceholder:
                    continue

                case LivingVisibilityShow:
                    // Proceed.
                }
            }

            page := NewIndividualPage(publisher.doc, individual,
                publisher.GoogleAnalyticsID, publisher.options,
                publisher.indexLetters, publisher.placesMap)
            pageName := PageIndividual(publisher.doc, individual,
                publisher.options.LivingVisibility, publisher.placesMap)
            files <- core.NewFile(pageName, page)
        }
    }
}

func (publisher *Publisher) sendPlaceFiles(files chan *core.File) {
    if publisher.options.ShowPlaces {
        // Sort the places so that the generated page names will be more
        // deterministic.
        places := publisher.Places()

        page := NewPlaceListPage(publisher.doc, publisher.GoogleAnalyticsID,
            publisher.options, publisher.indexLetters, places)
        files <- core.NewFile(PagePlaces(), page)

        var placeKeys []string

        for key := range places {
            placeKeys = append(placeKeys, key)
        }

        sort.Strings(placeKeys)

        for _, key := range placeKeys {
            place := places[key]
            page := NewPlacePage(publisher.doc, key,
                publisher.GoogleAnalyticsID, publisher.options,
                publisher.indexLetters, publisher.placesMap)
            files <- core.NewFile(
                PagePlace(place.PrettyName, publisher.placesMap), page)
        }
    }
}

func (publisher *Publisher) sendFamilyFiles(files chan *core.File) {
    if publisher.options.ShowFamilies {
        files <- core.NewFile(
            PageFamilies(),
            NewFamilyListPage(publisher.doc, publisher.GoogleAnalyticsID,
                publisher.options, publisher.indexLetters, publisher.placesMap),
        )
    }
}

func (publisher *Publisher) sendSurnameFiles(files chan *core.File) {
    if publisher.options.ShowSurnames {
        files <- core.NewFile(
            PageSurnames(),
            NewSurnameListPage(publisher.doc, publisher.GoogleAnalyticsID,
                publisher.options, publisher.indexLetters, publisher.placesMap))
    }
}

func (publisher *Publisher) sendSourceFiles(files chan *core.File) {
    if publisher.options.ShowSources {
        files <- core.NewFile(PageSources(),
            NewSourceListPage(publisher.doc, publisher.GoogleAnalyticsID,
                publisher.options, publisher.indexLetters, publisher.placesMap))

        for _, source := range publisher.doc.Sources() {
            page := NewSourcePage(publisher.doc, source,
                publisher.GoogleAnalyticsID, publisher.options,
                publisher.indexLetters, publisher.placesMap)
            files <- core.NewFile(PageSource(source), page)
        }
    }
}

func (publisher *Publisher) sendStatisticsFiles(files chan *core.File) {
    if publisher.options.ShowStatistics {
        files <- core.NewFile(PageStatistics(),
            NewStatisticsPage(publisher.doc, publisher.GoogleAnalyticsID,
                publisher.options, publisher.indexLetters, publisher.placesMap))
    }
}

func (publisher *Publisher) sendFiles(files chan *core.File) {
    publisher.sendIndividualFiles(files)
    publisher.sendPlaceFiles(files)
    publisher.sendFamilyFiles(files)
    publisher.sendSurnameFiles(files)
    publisher.sendSourceFiles(files)
    publisher.sendStatisticsFiles(files)
}

func (publisher *Publisher) Places() map[string]*place {
    if publisher.placesMap == nil {
        publisher.placesMap = map[string]*place{}

        // Get all of the unique place names.
        for placeTag, node := range publisher.doc.Places() {
            prettyName := prettyPlaceName(placeTag.Value())

            if prettyName == "" {
                prettyName = "(none)"
            }

            key := alnumOrDashRegexp.
                ReplaceAllString(strings.ToLower(prettyName), "-")

            if _, ok := publisher.placesMap[key]; !ok {
                country := placeTag.Country()
                if country == "" {
                    country = "(unknown)"
                }

                publisher.placesMap[key] = &place{
                    PrettyName: prettyName,
                    country:    country,
                    nodes:      gedcom.Nodes{},
                }
            }

            publisher.placesMap[key].nodes = append(publisher.placesMap[key].nodes, node)
        }

        for key := range publisher.placesMap {
            // Make sure the events are sorted otherwise the pages will be
            // different.
            sort.Slice(publisher.placesMap[key].nodes, func(i, j int) bool {
                left := publisher.placesMap[key].nodes[i]
                right := publisher.placesMap[key].nodes[j]

                // Years.
                leftYears := gedcom.Years(left)
                rightYears := gedcom.Years(right)

                if leftYears != rightYears {
                    return leftYears < rightYears
                }

                // Tag.
                leftTag := left.Tag().String()
                rightTag := right.Tag().String()

                if leftTag != rightTag {
                    return leftTag < rightTag
                }

                // Individual name.
                leftIndividual := individualForNode(publisher.doc, left)
                rightIndividual := individualForNode(publisher.doc, right)

                if leftIndividual != nil && rightIndividual != nil {
                    leftName := gedcom.String(leftIndividual.Name())
                    rightName := gedcom.String(rightIndividual.Name())

                    return leftName < rightName
                }

                // Value.
                valueLeft := gedcom.Value(left)
                valueRight := gedcom.Value(right)

                return valueLeft < valueRight
            })
        }
    }

    return publisher.placesMap
}