Skip to content

Commit 79c1f7b

Browse files
committed
Merge remote-tracking branch 'origin/main' into land/pr-801
2 parents ecfdaeb + c57fb9d commit 79c1f7b

17 files changed

Lines changed: 448 additions & 233 deletions

internal/cmd/slides.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"google.golang.org/api/drive/v3"
1111

1212
"github.com/steipete/gogcli/internal/outfmt"
13+
"github.com/steipete/gogcli/internal/slidesmarkdown"
1314
"github.com/steipete/gogcli/internal/ui"
1415
)
1516

@@ -222,7 +223,7 @@ func (c *SlidesCreateFromMarkdownCmd) Run(ctx context.Context, flags *RootFlags)
222223
return usage("either --content or --content-file is required")
223224
}
224225

225-
parsed, err := ParseMarkdownToSlides(markdown, ParseOptions{DefaultFAStyle: c.FAStyle})
226+
parsed, err := slidesmarkdown.Parse(markdown, slidesmarkdown.ParseOptions{DefaultFAStyle: c.FAStyle})
226227
if err != nil {
227228
return fmt.Errorf("parse markdown: %w", err)
228229
}

internal/cmd/slides_assets.go

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,28 @@ import (
1313
"time"
1414

1515
"google.golang.org/api/drive/v3"
16+
17+
"github.com/steipete/gogcli/internal/slidesmarkdown"
1618
)
1719

1820
// AssetMap pairs parsed AST references with uploaded Drive ImageRefs.
19-
// Icons is keyed by IconRef value (Style+Name); Diagrams is keyed by
20-
// DiagramBlock.ID.
21+
// Icons is keyed by slidesmarkdown.IconRef value (Style+Name); Diagrams is
22+
// keyed by slidesmarkdown.DiagramBlock.ID.
2123
type AssetMap struct {
22-
Icons map[IconRef]ImageRef
24+
Icons map[slidesmarkdown.IconRef]ImageRef
2325
Diagrams map[string]ImageRef
2426
}
2527

28+
// ImageRef is the result of uploading an asset to Drive.
29+
type ImageRef struct {
30+
DriveFileID string
31+
PublicURL string
32+
}
33+
2634
// NewAssetMap returns an empty initialized AssetMap.
2735
func NewAssetMap() AssetMap {
2836
return AssetMap{
29-
Icons: map[IconRef]ImageRef{},
37+
Icons: map[slidesmarkdown.IconRef]ImageRef{},
3038
Diagrams: map[string]ImageRef{},
3139
}
3240
}
@@ -192,7 +200,7 @@ type AssetPipeline struct {
192200
// fetches/renders/uploads each, and returns the resulting AssetMap.
193201
//
194202
// Per-asset failures are logged (warn-and-skip) unless Config.Strict.
195-
func (p *AssetPipeline) Resolve(ctx context.Context, slides []Slide) (AssetMap, error) {
203+
func (p *AssetPipeline) Resolve(ctx context.Context, slides []slidesmarkdown.Slide) (AssetMap, error) {
196204
am := NewAssetMap()
197205

198206
icons := collectIconRefs(slides)
@@ -274,31 +282,31 @@ func (p *AssetPipeline) Cleanup(ctx context.Context) error {
274282
}
275283

276284
// collectIconRefs walks all slides, deduping IconRef values.
277-
func collectIconRefs(slides []Slide) map[IconRef]struct{} {
278-
out := map[IconRef]struct{}{}
279-
var walkBlocks func([]Block)
280-
walkBlocks = func(blocks []Block) {
285+
func collectIconRefs(slides []slidesmarkdown.Slide) map[slidesmarkdown.IconRef]struct{} {
286+
out := map[slidesmarkdown.IconRef]struct{}{}
287+
var walkBlocks func([]slidesmarkdown.Block)
288+
walkBlocks = func(blocks []slidesmarkdown.Block) {
281289
for _, b := range blocks {
282290
switch v := b.(type) {
283-
case ParagraphBlock:
291+
case slidesmarkdown.ParagraphBlock:
284292
if r, ok := leadingIcon(v.Inlines); ok {
285293
out[r] = struct{}{}
286294
}
287-
case BulletsBlock:
295+
case slidesmarkdown.BulletsBlock:
288296
for _, item := range v.Items {
289297
if r, ok := leadingIcon(item.Inlines); ok {
290298
out[r] = struct{}{}
291299
}
292300
}
293-
case HeadingBlock:
301+
case slidesmarkdown.HeadingBlock:
294302
if r, ok := leadingIcon(v.Inlines); ok {
295303
out[r] = struct{}{}
296304
}
297-
case ColumnsBlock:
305+
case slidesmarkdown.ColumnsBlock:
298306
for _, col := range v.Columns {
299307
walkBlocks(col)
300308
}
301-
case IconRowsBlock:
309+
case slidesmarkdown.IconRowsBlock:
302310
for _, row := range v.Rows {
303311
if row.Icon != nil {
304312
out[*row.Icon] = struct{}{}
@@ -313,24 +321,24 @@ func collectIconRefs(slides []Slide) map[IconRef]struct{} {
313321
return out
314322
}
315323

316-
func leadingIcon(inlines []Inline) (IconRef, bool) {
324+
func leadingIcon(inlines []slidesmarkdown.Inline) (slidesmarkdown.IconRef, bool) {
317325
if len(inlines) == 0 {
318-
return IconRef{}, false
326+
return slidesmarkdown.IconRef{}, false
319327
}
320-
ref, ok := inlines[0].(IconRef)
328+
ref, ok := inlines[0].(slidesmarkdown.IconRef)
321329
return ref, ok
322330
}
323331

324332
// collectDiagrams walks all slides for DiagramBlocks, returning {ID: source}.
325-
func collectDiagrams(slides []Slide) map[string]string {
333+
func collectDiagrams(slides []slidesmarkdown.Slide) map[string]string {
326334
out := map[string]string{}
327-
var walkBlocks func([]Block)
328-
walkBlocks = func(blocks []Block) {
335+
var walkBlocks func([]slidesmarkdown.Block)
336+
walkBlocks = func(blocks []slidesmarkdown.Block) {
329337
for _, b := range blocks {
330338
switch v := b.(type) {
331-
case DiagramBlock:
339+
case slidesmarkdown.DiagramBlock:
332340
out[v.ID] = v.Source
333-
case ColumnsBlock:
341+
case slidesmarkdown.ColumnsBlock:
334342
for _, col := range v.Columns {
335343
walkBlocks(col)
336344
}
@@ -348,7 +356,7 @@ func collectDiagrams(slides []Slide) map[string]string {
348356
// only published under brands/), it tries the other free-tier styles in a
349357
// fixed order: brands, regular, solid. Returns the body, the style that
350358
// actually served, and the final error.
351-
func fetchFAIconWithStyleFallback(ctx context.Context, client *http.Client, ref IconRef) ([]byte, string, error) {
359+
func fetchFAIconWithStyleFallback(ctx context.Context, client *http.Client, ref slidesmarkdown.IconRef) ([]byte, string, error) {
352360
tried := map[string]bool{}
353361
order := []string{ref.Style, "brands", "regular", "solid"}
354362
var lastErr error

internal/cmd/slides_assets_test.go

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616

1717
"github.com/stretchr/testify/assert"
1818
"github.com/stretchr/testify/require"
19+
20+
"github.com/steipete/gogcli/internal/slidesmarkdown"
1921
)
2022

2123
func TestFetchFAIcon_OK(t *testing.T) {
@@ -151,14 +153,14 @@ func TestAssetPipeline_CollectsUniqueIcons(t *testing.T) {
151153
uploader := &fakeDriveUploader{}
152154
p := &AssetPipeline{Config: cfg, Uploader: uploader}
153155

154-
slides := []Slide{
155-
{Body: []Block{ParagraphBlock{Inlines: []Inline{
156-
IconRef{Style: "solid", Name: "truck-fast"},
157-
TextRun{Text: " hello "},
158-
IconRef{Style: "solid", Name: "truck-fast"}, // duplicate, should not re-upload
156+
slides := []slidesmarkdown.Slide{
157+
{Body: []slidesmarkdown.Block{slidesmarkdown.ParagraphBlock{Inlines: []slidesmarkdown.Inline{
158+
slidesmarkdown.IconRef{Style: "solid", Name: "truck-fast"},
159+
slidesmarkdown.TextRun{Text: " hello "},
160+
slidesmarkdown.IconRef{Style: "solid", Name: "truck-fast"}, // duplicate, should not re-upload
159161
}}}},
160-
{Body: []Block{IconRowsBlock{Kind: "boxes", Rows: []IconRow{
161-
{Icon: &IconRef{Style: "brands", Name: "github"}, Text: "GitHub"},
162+
{Body: []slidesmarkdown.Block{slidesmarkdown.IconRowsBlock{Kind: "boxes", Rows: []slidesmarkdown.IconRow{
163+
{Icon: &slidesmarkdown.IconRef{Style: "brands", Name: "github"}, Text: "GitHub"},
162164
}}}},
163165
}
164166

@@ -174,8 +176,8 @@ func TestAssetPipeline_StrictFailsWhenMMDCDisabled(t *testing.T) {
174176
cfg.Strict = true
175177

176178
p := &AssetPipeline{Config: cfg, Uploader: &fakeDriveUploader{}}
177-
slides := []Slide{{Body: []Block{
178-
DiagramBlock{Kind: "mermaid", Source: "graph TD\nA-->B", ID: "block-1"},
179+
slides := []slidesmarkdown.Slide{{Body: []slidesmarkdown.Block{
180+
slidesmarkdown.DiagramBlock{Kind: "mermaid", Source: "graph TD\nA-->B", ID: "block-1"},
179181
}}}
180182

181183
_, err := p.Resolve(context.Background(), slides)
@@ -190,8 +192,8 @@ func TestAssetPipelineWarningUsesRuntimeStderr(t *testing.T) {
190192
var stderr bytes.Buffer
191193
ctx := newCmdRuntimeOutputContext(t, io.Discard, &stderr)
192194
p := &AssetPipeline{Config: cfg, Uploader: &fakeDriveUploader{}}
193-
slides := []Slide{{Body: []Block{
194-
DiagramBlock{Kind: "mermaid", Source: "graph TD\nA-->B", ID: "block-1"},
195+
slides := []slidesmarkdown.Slide{{Body: []slidesmarkdown.Block{
196+
slidesmarkdown.DiagramBlock{Kind: "mermaid", Source: "graph TD\nA-->B", ID: "block-1"},
195197
}}}
196198

197199
_, err := p.Resolve(ctx, slides)
@@ -200,13 +202,13 @@ func TestAssetPipelineWarningUsesRuntimeStderr(t *testing.T) {
200202
}
201203

202204
func TestCollectIconRefs_OnlyLeadingParagraphAndHeadingIcons(t *testing.T) {
203-
leading := IconRef{Style: "solid", Name: "file"}
204-
mid := IconRef{Style: "solid", Name: "truck-fast"}
205+
leading := slidesmarkdown.IconRef{Style: "solid", Name: "file"}
206+
mid := slidesmarkdown.IconRef{Style: "solid", Name: "truck-fast"}
205207

206-
got := collectIconRefs([]Slide{{
207-
Body: []Block{
208-
HeadingBlock{Inlines: []Inline{leading, TextRun{Text: " Rethink"}}},
209-
ParagraphBlock{Inlines: []Inline{TextRun{Text: "middle "}, mid}},
208+
got := collectIconRefs([]slidesmarkdown.Slide{{
209+
Body: []slidesmarkdown.Block{
210+
slidesmarkdown.HeadingBlock{Inlines: []slidesmarkdown.Inline{leading, slidesmarkdown.TextRun{Text: " Rethink"}}},
211+
slidesmarkdown.ParagraphBlock{Inlines: []slidesmarkdown.Inline{slidesmarkdown.TextRun{Text: "middle "}, mid}},
210212
},
211213
}})
212214

internal/cmd/slides_e2e_test.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ import (
88

99
"github.com/stretchr/testify/assert"
1010
"github.com/stretchr/testify/require"
11+
12+
"github.com/steipete/gogcli/internal/slidesmarkdown"
1113
)
1214

1315
func TestSlideyFixture_ParsesAndRenders(t *testing.T) {
1416
path := filepath.Join("..", "..", "testdata", "slidey", "index.md")
1517
data, err := os.ReadFile(path)
1618
require.NoError(t, err)
1719

18-
parsed, err := ParseMarkdownToSlides(string(data), ParseOptions{})
20+
parsed, err := slidesmarkdown.Parse(string(data), slidesmarkdown.ParseOptions{})
1921
require.NoError(t, err)
2022
assert.GreaterOrEqual(t, len(parsed), 30, "fixture should produce ~30+ slides")
2123

@@ -33,35 +35,35 @@ func TestSlideyFixture_ParsesAndRenders(t *testing.T) {
3335
if s.Notes != "" {
3436
sawNotes = true
3537
}
36-
var walk func([]Block)
37-
walk = func(blocks []Block) {
38+
var walk func([]slidesmarkdown.Block)
39+
walk = func(blocks []slidesmarkdown.Block) {
3840
for _, b := range blocks {
3941
switch v := b.(type) {
40-
case ParagraphBlock:
42+
case slidesmarkdown.ParagraphBlock:
4143
for _, in := range v.Inlines {
42-
if _, ok := in.(IconRef); ok {
44+
if _, ok := in.(slidesmarkdown.IconRef); ok {
4345
sawIcon = true
4446
}
4547
}
46-
case BulletsBlock:
48+
case slidesmarkdown.BulletsBlock:
4749
for _, item := range v.Items {
4850
for _, in := range item.Inlines {
49-
if _, ok := in.(IconRef); ok {
51+
if _, ok := in.(slidesmarkdown.IconRef); ok {
5052
sawIcon = true
5153
}
5254
}
5355
}
54-
case IconRowsBlock:
56+
case slidesmarkdown.IconRowsBlock:
5557
for _, row := range v.Rows {
5658
if row.Icon != nil {
5759
sawIcon = true
5860
}
5961
}
60-
case ColumnsBlock:
62+
case slidesmarkdown.ColumnsBlock:
6163
for _, col := range v.Columns {
6264
walk(col)
6365
}
64-
case DiagramBlock:
66+
case slidesmarkdown.DiagramBlock:
6567
sawDiagram = true
6668
}
6769
}

0 commit comments

Comments
 (0)