addressed consistency
This commit is contained in:
1 parent
3b971cda61
commit
ebb471524e
8 files changed
+99
-81
No files matched your search
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{.Lang}}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{.Title}}</title>
|
||||
<link rel="stylesheet" href="{{.CSSURL}}" integrity="{{.Integrity}}" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<main class="container-fluid py-4">
|
||||
<h1>{{.Title}}</h1>
|
||||
<div class="table-responsive">
|
||||
{{.TableFragment}}
|
||||
</div>
|
||||
<p class="text-muted small">Generated at {{.GeneratedAt}}</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,79 +1,73 @@
|
||||
// Package html renders an internal.AggregatorWriter as a complete,
|
||||
// standalone HTML document built around the same table fragment produced by
|
||||
// pretty.PrettyPrinter.RenderHTML. The document includes the Bootstrap 5.3
|
||||
// table.Printer.RenderHTML. The document includes the Bootstrap 5.3
|
||||
// stylesheet via CDN and embeds the raw table fragment without HTML-escaping
|
||||
// it so the result opens directly in a browser.
|
||||
package html
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/nmoniz/any2anexoj/internal"
|
||||
"github.com/nmoniz/any2anexoj/internal/writing/pretty"
|
||||
"github.com/nmoniz/any2anexoj/internal/writing/table"
|
||||
)
|
||||
|
||||
// translator is the contract the HTML writer relies on for localized
|
||||
// document text such as the page title. *writing.Localizer satisfies it.
|
||||
type translator interface {
|
||||
Translate(key string, count int, values map[string]any) string
|
||||
}
|
||||
|
||||
// bootstrapCSSURL is the jsDelivr CDN URL for Bootstrap 5.3.3.
|
||||
const bootstrapCSSURL = "https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
|
||||
|
||||
// bootstrapIntegrity is the official SRI hash for Bootstrap 5.3.3 css.
|
||||
const bootstrapIntegrity = "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
|
||||
|
||||
// HTMLWriter writes a complete HTML document for an AggregatorWriter.
|
||||
type HTMLWriter struct {
|
||||
output io.Writer
|
||||
printer *pretty.PrettyPrinter
|
||||
title string
|
||||
lang string
|
||||
// Writer writes a complete HTML document for an AggregatorWriter.
|
||||
type Writer struct {
|
||||
output io.Writer
|
||||
printer *table.Printer
|
||||
translator translator
|
||||
lang string
|
||||
}
|
||||
|
||||
// NewHTMLWriter builds an HTMLWriter that writes a complete HTML document to
|
||||
// NewWriter builds a Writer that writes a complete HTML document to
|
||||
// w. The printer is used purely as a source for the raw <table> fragment via
|
||||
// RenderHTML; this writer never invokes Render or any method that writes to
|
||||
// printer's own io.Writer.
|
||||
func NewHTMLWriter(w io.Writer, printer *pretty.PrettyPrinter, title, lang string) *HTMLWriter {
|
||||
return &HTMLWriter{
|
||||
output: w,
|
||||
printer: printer,
|
||||
title: title,
|
||||
lang: lang,
|
||||
// printer's own io.Writer. The document title is looked up from the
|
||||
// "report_title" translation via tr, so callers never pass a pre-baked title.
|
||||
func NewWriter(w io.Writer, printer *table.Printer, tr translator, lang string) *Writer {
|
||||
return &Writer{
|
||||
output: w,
|
||||
printer: printer,
|
||||
translator: tr,
|
||||
lang: lang,
|
||||
}
|
||||
}
|
||||
|
||||
// documentTemplate renders the full HTML page. The table fragment is typed as
|
||||
// template.HTML so the template engine emits it verbatim without escaping
|
||||
// the angle brackets in the <table> markup.
|
||||
var documentTemplate = template.Must(template.New("html").Parse(`<!DOCTYPE html>
|
||||
<html lang="{{.Lang}}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{.Title}}</title>
|
||||
<link rel="stylesheet" href="{{.CSSURL}}" integrity="{{.Integrity}}" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<main class="container-fluid py-4">
|
||||
<h1>{{.Title}}</h1>
|
||||
<div class="table-responsive">
|
||||
{{.TableFragment}}
|
||||
</div>
|
||||
<p class="text-muted small">Generated at {{.GeneratedAt}}</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
//go:embed document.tmpl
|
||||
var documentTemplateBody string
|
||||
|
||||
// documentTemplate renders the full HTML page from document.tmpl. The table
|
||||
// fragment is typed as template.HTML so the template engine emits it
|
||||
// verbatim without escaping the angle brackets in the <table> markup.
|
||||
var documentTemplate = template.Must(template.New("html").Parse(documentTemplateBody))
|
||||
|
||||
// Render writes a complete standalone HTML document for aw to the configured
|
||||
// output. The same HTMLWriter may be reused; each call rebuilds the table
|
||||
// output. The same Writer may be reused; each call rebuilds the table
|
||||
// fragment from aw so the rendered <tr> count reflects aw's current items and
|
||||
// no rows are duplicated across invocations.
|
||||
func (hw *HTMLWriter) Render(aw *internal.AggregatorWriter) error {
|
||||
func (hw *Writer) Render(aw *internal.AggregatorWriter) error {
|
||||
if hw.printer == nil {
|
||||
return fmt.Errorf("html: nil printer")
|
||||
}
|
||||
fragment := hw.printer.RenderHTML(aw)
|
||||
title := hw.translator.Translate("report_title", 1, nil)
|
||||
data := struct {
|
||||
Lang string
|
||||
Title string
|
||||
@@ -83,7 +77,7 @@ func (hw *HTMLWriter) Render(aw *internal.AggregatorWriter) error {
|
||||
GeneratedAt string
|
||||
}{
|
||||
Lang: hw.lang,
|
||||
Title: hw.title,
|
||||
Title: title,
|
||||
CSSURL: bootstrapCSSURL,
|
||||
Integrity: bootstrapIntegrity,
|
||||
TableFragment: template.HTML(fragment),
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
htmlwriter "github.com/nmoniz/any2anexoj/internal/writing/html"
|
||||
"github.com/nmoniz/any2anexoj/internal/writing/pretty"
|
||||
"github.com/nmoniz/any2anexoj/internal/writing/table"
|
||||
"github.com/nmoniz/any2anexoj/internal/writing"
|
||||
"github.com/nmoniz/any2anexoj/internal"
|
||||
"github.com/shopspring/decimal"
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
// buildTestAggregator returns an AggregatorWriter seeded with two
|
||||
// deterministic ReportItems so every test can assert on a stable document
|
||||
// shape. Mirrors the fixture pattern used in
|
||||
// internal/writing/pretty/printer_test.go.
|
||||
// internal/writing/table/printer_test.go.
|
||||
func buildTestAggregator(t *testing.T) *internal.AggregatorWriter {
|
||||
t.Helper()
|
||||
aw := internal.NewAggregatorWriter()
|
||||
@@ -57,7 +57,7 @@ func buildTestAggregator(t *testing.T) *internal.AggregatorWriter {
|
||||
return aw
|
||||
}
|
||||
|
||||
func TestHTMLWriter_Render(t *testing.T) {
|
||||
func TestWriter_Render(t *testing.T) {
|
||||
loc, err := writing.NewLocalizer("en")
|
||||
if err != nil {
|
||||
t.Fatalf("create localizer: %v", err)
|
||||
@@ -65,8 +65,8 @@ func TestHTMLWriter_Render(t *testing.T) {
|
||||
|
||||
var buf bytes.Buffer
|
||||
// RenderHTML never writes to the printer's output, so Discard is safe.
|
||||
pp := pretty.NewPrettyPrinter(&buf, loc)
|
||||
hw := htmlwriter.NewHTMLWriter(&buf, pp, "Tax report", "en")
|
||||
pp := table.NewPrinter(&buf, loc)
|
||||
hw := htmlwriter.NewWriter(&buf, pp, loc, "en")
|
||||
|
||||
if err := hw.Render(buildTestAggregator(t)); err != nil {
|
||||
t.Fatalf("Render returned an error: %v", err)
|
||||
@@ -123,18 +123,18 @@ func TestHTMLWriter_Render(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestHTMLWriter_NoRowDuplication verifies that calling Render twice on the
|
||||
// TestWriter_NoRowDuplication verifies that calling Render twice on the
|
||||
// same writer produces documents with the same number of <tr> rows, just
|
||||
// like pretty.PrettyPrinter's no-duplication guarantee.
|
||||
func TestHTMLWriter_NoRowDuplication(t *testing.T) {
|
||||
// like table.Printer's no-duplication guarantee.
|
||||
func TestWriter_NoRowDuplication(t *testing.T) {
|
||||
loc, err := writing.NewLocalizer("en")
|
||||
if err != nil {
|
||||
t.Fatalf("create localizer: %v", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
pp := pretty.NewPrettyPrinter(&buf, loc)
|
||||
hw := htmlwriter.NewHTMLWriter(&buf, pp, "Tax report", "en")
|
||||
pp := table.NewPrinter(&buf, loc)
|
||||
hw := htmlwriter.NewWriter(&buf, pp, loc, "en")
|
||||
|
||||
aw := buildTestAggregator(t)
|
||||
|
||||
@@ -160,15 +160,15 @@ func TestHTMLWriter_NoRowDuplication(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTMLWriter_LangAttribute(t *testing.T) {
|
||||
func TestWriter_LangAttribute(t *testing.T) {
|
||||
loc, err := writing.NewLocalizer("pt")
|
||||
if err != nil {
|
||||
t.Fatalf("create localizer: %v", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
pp := pretty.NewPrettyPrinter(&buf, loc)
|
||||
hw := htmlwriter.NewHTMLWriter(&buf, pp, "Relatório fiscal", "pt")
|
||||
pp := table.NewPrinter(&buf, loc)
|
||||
hw := htmlwriter.NewWriter(&buf, pp, loc, "pt")
|
||||
|
||||
if err := hw.Render(buildTestAggregator(t)); err != nil {
|
||||
t.Fatalf("Render returned an error: %v", err)
|
||||
|
||||
Reference in new issue
Block a user