Skip to content

Commit b6eb8af

Browse files
committed
refactor #39: distribute application layer into vertical slices with DI
- Create application service in each vertical slice: parser/application/ParseService, resolver/application/ResolveService, asset/application/FontService, layout/application/LayoutService, renderer/application/RenderService - Each service receives domain port interfaces via constructor injection - Move filterPages to shared/domain/FilterPagesByName - Add shared/domain/CollectFontFamilies (replaces duplicate collectFonts) - cmd/ acts as composition root: creates infrastructure, injects into services - Delete cross-cutting internal/application/ package - All application services tested with stubs (100% coverage each) Closes #39
1 parent ab9500a commit b6eb8af

File tree

22 files changed

+571
-604
lines changed

22 files changed

+571
-604
lines changed

cmd/info.go

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import (
66
"sort"
77

88
"github.com/spf13/cobra"
9-
"github.com/vpedrosa/pen2pdf/internal/application"
9+
parserApp "github.com/vpedrosa/pen2pdf/internal/parser/application"
1010
parserInfra "github.com/vpedrosa/pen2pdf/internal/parser/infrastructure"
11+
shared "github.com/vpedrosa/pen2pdf/internal/shared/domain"
1112
)
1213

1314
var infoCmd = &cobra.Command{
@@ -31,40 +32,43 @@ func runInfo(cmd *cobra.Command, args []string) error {
3132
}
3233
defer inputFile.Close() //nolint:errcheck
3334

34-
svc := application.NewInfoService(parserInfra.NewJSONParser())
35+
parseSvc := parserApp.NewParseService(parserInfra.NewJSONParser())
3536

36-
info, err := svc.GetInfo(inputFile)
37+
doc, err := parseSvc.Parse(inputFile)
3738
if err != nil {
38-
return err
39+
return fmt.Errorf("parse error: %w", err)
3940
}
4041

4142
cmd.Printf("File: %s\n", inputPath)
42-
cmd.Printf("Version: %s\n", info.Version)
43+
cmd.Printf("Version: %s\n", doc.Version)
4344
cmd.Println()
4445

45-
cmd.Printf("Pages (%d):\n", len(info.Pages))
46-
for _, page := range info.Pages {
47-
cmd.Printf(" - %s (%.0fx%.0f)\n", page.Name, page.Width, page.Height)
46+
cmd.Printf("Pages (%d):\n", len(doc.Children))
47+
for _, child := range doc.Children {
48+
if frame, ok := child.(*shared.Frame); ok {
49+
cmd.Printf(" - %s (%.0fx%.0f)\n", frame.Name, frame.Width.Value, frame.Height.Value)
50+
}
4851
}
4952
cmd.Println()
5053

51-
if len(info.Variables) > 0 {
52-
cmd.Printf("Variables (%d):\n", len(info.Variables))
53-
names := make([]string, 0, len(info.Variables))
54-
for name := range info.Variables {
54+
if len(doc.Variables) > 0 {
55+
cmd.Printf("Variables (%d):\n", len(doc.Variables))
56+
names := make([]string, 0, len(doc.Variables))
57+
for name := range doc.Variables {
5558
names = append(names, name)
5659
}
5760
sort.Strings(names)
5861
for _, name := range names {
59-
v := info.Variables[name]
62+
v := doc.Variables[name]
6063
cmd.Printf(" %-20s %s = %v\n", name, v.Type, v.Value)
6164
}
6265
cmd.Println()
6366
}
6467

65-
if len(info.Fonts) > 0 {
66-
cmd.Printf("Fonts (%d):\n", len(info.Fonts))
67-
for _, f := range info.Fonts {
68+
fonts := shared.CollectFontFamilies(doc)
69+
if len(fonts) > 0 {
70+
cmd.Printf("Fonts (%d):\n", len(fonts))
71+
for _, f := range fonts {
6872
cmd.Printf(" - %s\n", f)
6973
}
7074
}

cmd/render.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ import (
88
"strings"
99

1010
"github.com/spf13/cobra"
11-
"github.com/vpedrosa/pen2pdf/internal/application"
11+
assetApp "github.com/vpedrosa/pen2pdf/internal/asset/application"
1212
assetInfra "github.com/vpedrosa/pen2pdf/internal/asset/infrastructure"
13+
layoutApp "github.com/vpedrosa/pen2pdf/internal/layout/application"
1314
layoutInfra "github.com/vpedrosa/pen2pdf/internal/layout/infrastructure"
15+
parserApp "github.com/vpedrosa/pen2pdf/internal/parser/application"
1416
parserInfra "github.com/vpedrosa/pen2pdf/internal/parser/infrastructure"
17+
rendererApp "github.com/vpedrosa/pen2pdf/internal/renderer/application"
1518
rendererInfra "github.com/vpedrosa/pen2pdf/internal/renderer/infrastructure"
19+
resolverApp "github.com/vpedrosa/pen2pdf/internal/resolver/application"
1620
resolverInfra "github.com/vpedrosa/pen2pdf/internal/resolver/infrastructure"
1721
shared "github.com/vpedrosa/pen2pdf/internal/shared/domain"
1822
)
@@ -57,44 +61,66 @@ func runRender(cmd *cobra.Command, args []string) error {
5761
fontDirs = append(fontDirs, filepath.Join(home, ".local", "share", "fonts"))
5862
}
5963

60-
parser := parserInfra.NewJSONParser()
61-
resolver := resolverInfra.NewVariableResolver()
6264
fontLoader := assetInfra.NewFSFontLoader(fontDirs...)
6365
imageLoader := assetInfra.NewFSImageLoader(baseDir)
6466
measurer := layoutInfra.NewGopdfTextMeasurer(fontLoader)
65-
layoutEngine := layoutInfra.NewFlexboxEngine()
66-
renderer := rendererInfra.NewPDFRenderer(imageLoader, fontLoader)
67+
pdfRenderer := rendererInfra.NewPDFRenderer(imageLoader, fontLoader)
6768

68-
svc := application.NewRenderService(parser, resolver, fontLoader, imageLoader, layoutEngine, measurer, renderer)
69+
// Build application services (inject ports via DI)
70+
parseSvc := parserApp.NewParseService(parserInfra.NewJSONParser())
71+
resolveSvc := resolverApp.NewResolveService(resolverInfra.NewVariableResolver())
72+
fontSvc := assetApp.NewFontService(fontLoader)
73+
layoutSvc := layoutApp.NewLayoutService(layoutInfra.NewFlexboxEngine(), measurer)
74+
renderSvc := rendererApp.NewRenderService(pdfRenderer)
6975

70-
// Check for missing fonts (interactive CLI concern)
76+
// 1. Parse
7177
inputFile, err := os.Open(inputPath)
7278
if err != nil {
7379
return fmt.Errorf("open input: %w", err)
7480
}
75-
76-
missing, doc, err := svc.DetectMissingFonts(inputFile)
81+
doc, err := parseSvc.Parse(inputFile)
7782
inputFile.Close() //nolint:errcheck
7883
if err != nil {
79-
return err
84+
return fmt.Errorf("parse: %w", err)
85+
}
86+
87+
// 2. Resolve variables
88+
if err := resolveSvc.Resolve(doc); err != nil {
89+
return fmt.Errorf("resolve: %w", err)
8090
}
8191

92+
// 3. Detect and download missing fonts (interactive CLI concern)
93+
missing := fontSvc.DetectMissingFonts(doc)
8294
if len(missing) > 0 {
8395
if err := promptAndDownloadFonts(cmd, missing, fontsDir); err != nil {
8496
return err
8597
}
8698
}
8799

88-
// Render using the already-parsed document
100+
// 4. Filter pages
101+
if pagesFlag != "" {
102+
doc.Children, err = shared.FilterPagesByName(doc.Children, pagesFlag)
103+
if err != nil {
104+
return err
105+
}
106+
}
107+
108+
// 5. Layout
109+
pages, err := layoutSvc.Layout(doc)
110+
if err != nil {
111+
return fmt.Errorf("layout: %w", err)
112+
}
113+
114+
// 6. Render
89115
outputFile, err := os.Create(output)
90116
if err != nil {
91117
return fmt.Errorf("create output: %w", err)
92118
}
93119
defer outputFile.Close() //nolint:errcheck
94120

95-
result, err := svc.RenderDocument(doc, outputFile, pagesFlag)
121+
result, err := renderSvc.Render(pages, outputFile)
96122
if err != nil {
97-
return err
123+
return fmt.Errorf("render: %w", err)
98124
}
99125

100126
cmd.Printf("PDF written to %s (%d pages)\n", output, result.PageCount)

cmd/validate.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import (
55
"os"
66

77
"github.com/spf13/cobra"
8-
"github.com/vpedrosa/pen2pdf/internal/application"
8+
parserApp "github.com/vpedrosa/pen2pdf/internal/parser/application"
99
parserInfra "github.com/vpedrosa/pen2pdf/internal/parser/infrastructure"
10+
resolverApp "github.com/vpedrosa/pen2pdf/internal/resolver/application"
1011
resolverInfra "github.com/vpedrosa/pen2pdf/internal/resolver/infrastructure"
1112
)
1213

@@ -31,16 +32,18 @@ func runValidate(cmd *cobra.Command, args []string) error {
3132
}
3233
defer inputFile.Close() //nolint:errcheck
3334

34-
svc := application.NewValidateService(
35-
parserInfra.NewJSONParser(),
36-
resolverInfra.NewVariableResolver(),
37-
)
35+
parseSvc := parserApp.NewParseService(parserInfra.NewJSONParser())
36+
resolveSvc := resolverApp.NewResolveService(resolverInfra.NewVariableResolver())
3837

39-
result, err := svc.Validate(inputFile)
38+
doc, err := parseSvc.Parse(inputFile)
4039
if err != nil {
41-
return err
40+
return fmt.Errorf("parse error: %w", err)
4241
}
4342

44-
cmd.Printf("Valid: %s (%d pages, %d variables)\n", inputPath, result.PageCount, result.VariableCount)
43+
if err := resolveSvc.Resolve(doc); err != nil {
44+
return fmt.Errorf("resolve error: %w", err)
45+
}
46+
47+
cmd.Printf("Valid: %s (%d pages, %d variables)\n", inputPath, len(doc.Children), len(doc.Variables))
4548
return nil
4649
}

internal/application/helpers_test.go

Lines changed: 0 additions & 39 deletions
This file was deleted.

internal/application/info.go

Lines changed: 0 additions & 85 deletions
This file was deleted.

0 commit comments

Comments
 (0)