diff --git a/cmd/mark2note/main.go b/cmd/mark2note/main.go index f60aaff..eef0217 100644 --- a/cmd/mark2note/main.go +++ b/cmd/mark2note/main.go @@ -835,18 +835,29 @@ func absolutePath(path string) string { func buildAutoPublishXHSOptions(renderOpts Options, renderResult app.Result) (app.PublishOptions, error) { imagePaths := nonEmptyStrings(renderResult.ImagePaths) + validateImagesDone := timing.Stage("cmd.buildAutoPublishXHSOptions.validate_images", timing.Field("images", len(imagePaths))) if len(imagePaths) == 0 { - return app.PublishOptions{}, fmt.Errorf("no generated PNG files found") + err := fmt.Errorf("no generated PNG files found") + validateImagesDone(err) + return app.PublishOptions{}, err } for _, imagePath := range imagePaths { - if _, err := os.Stat(imagePath); err != nil { - if errors.Is(err, os.ErrNotExist) { - return app.PublishOptions{}, fmt.Errorf("generated PNG file not found: %s", imagePath) + if _, statErr := os.Stat(imagePath); statErr != nil { + if errors.Is(statErr, os.ErrNotExist) { + err := fmt.Errorf("generated PNG file not found: %s", imagePath) + validateImagesDone(err) + return app.PublishOptions{}, err } - return app.PublishOptions{}, fmt.Errorf("generated PNG file unavailable: %s: %v", imagePath, err) + err := fmt.Errorf("generated PNG file unavailable: %s: %v", imagePath, statErr) + validateImagesDone(err) + return app.PublishOptions{}, err } } + validateImagesDone(nil) + + readMarkdownDone := timing.Stage("cmd.buildAutoPublishXHSOptions.read_markdown") markdownBytes, err := readFile(renderOpts.InputPath) + readMarkdownDone(err) if err != nil { return app.PublishOptions{}, fmt.Errorf("%w: %v", app.ErrReadMarkdown, err) } @@ -865,25 +876,43 @@ func buildAutoPublishXHSOptions(renderOpts Options, renderResult app.Result) (ap ConfigPath: renderOpts.ConfigPath, ChromePathChanged: renderOpts.ChromePathChanged, } + + loadConfigDone := timing.Stage("cmd.buildAutoPublishXHSOptions.load_config") cfg, err := loadPublishXHSConfig(cliOpts.ConfigPath, true) + loadConfigDone(err) if err != nil { return app.PublishOptions{}, err } + + generateTitleDone := timing.Stage("cmd.buildAutoPublishXHSOptions.generate_title") title, err = buildAutoPublishXHSTitle(*cfg, markdown, title) + generateTitleDone(err) if err != nil { return app.PublishOptions{}, err } cliOpts.Title = title + + topicSource := "ai" + if len(renderOpts.XHSTags) > 0 { + topicSource = "manual" + } + generateTopicsDone := timing.Stage("cmd.buildAutoPublishXHSOptions.generate_topics", timing.Field("source", topicSource)) topics, err := buildAutoPublishXHSTopics(*cfg, markdown, title, renderOpts.XHSTags) + generateTopicsDone(err) if err != nil { return app.PublishOptions{}, err } cliOpts.Tags = topics + + buildContentDone := timing.Stage("cmd.buildAutoPublishXHSOptions.build_content", timing.Field("content_mode", renderOpts.XHSContentMode)) content, err := buildAutoPublishXHSContent(renderOpts, renderResult) + buildContentDone(err) if err != nil { return app.PublishOptions{}, fmt.Errorf("build xhs publish content: %w", err) } cliOpts.Content = content + + mergeDefaultsDone := timing.Stage("cmd.buildAutoPublishXHSOptions.merge_defaults") if renderOpts.XHSModeChanged { cliOpts.Mode = strings.TrimSpace(renderOpts.XHSMode) cliOpts.ModeChanged = true @@ -911,9 +940,14 @@ func buildAutoPublishXHSOptions(renderOpts Options, renderResult app.Result) (ap cliOpts.AllowContentCopyChanged = true } publishOpts := mergePublishXHSDefaults(cliOpts, cfg) + mergeDefaultsDone(nil) + + validateOptionsDone := timing.Stage("cmd.buildAutoPublishXHSOptions.validate_options") if err := validatePublishXHSOptions(publishOpts); err != nil { + validateOptionsDone(err) return app.PublishOptions{}, err } + validateOptionsDone(nil) return publishOpts, nil } diff --git a/cmd/mark2note/main_publish_test.go b/cmd/mark2note/main_publish_test.go index 960de74..784b1e6 100644 --- a/cmd/mark2note/main_publish_test.go +++ b/cmd/mark2note/main_publish_test.go @@ -13,6 +13,7 @@ import ( "github.com/walker1211/mark2note/internal/app" "github.com/walker1211/mark2note/internal/config" + "github.com/walker1211/mark2note/internal/timing" "github.com/walker1211/mark2note/internal/xhs" ) @@ -710,6 +711,47 @@ func TestRunAutoPublishXHSUsesManualTagsWithoutTopicGeneration(t *testing.T) { } } +func TestBuildAutoPublishXHSOptionsReportsTopicGenerationTiming(t *testing.T) { + originalReadFile := readFile + originalLoadConfig := loadConfig + originalBuildPublishTopics := buildPublishTopics + defer func() { + readFile = originalReadFile + loadConfig = originalLoadConfig + buildPublishTopics = originalBuildPublishTopics + }() + + var timingOutput bytes.Buffer + oldTimingOutput := timing.SetOutput(&timingOutput) + defer timing.SetOutput(oldTimingOutput) + + imagePath := filepath.Join(t.TempDir(), "p01-cover.png") + if err := os.WriteFile(imagePath, []byte("png"), 0o644); err != nil { + t.Fatalf("WriteFile() error = %v", err) + } + readFile = func(path string) ([]byte, error) { + return []byte("# 标题\n\n正文"), nil + } + loadConfig = func(path string) (*config.Config, error) { + enabled := true + return &config.Config{XHS: config.XHSCfg{Publish: config.XHSPublishCfg{Account: "walker", TopicGeneration: config.XHSTopicGenerationCfg{Enabled: &enabled}}}}, nil + } + buildPublishTopics = func(cfg *config.Config, markdown string, title string) ([]string, error) { + return []string{"AI新闻", "科技日报"}, nil + } + + _, err := buildAutoPublishXHSOptions(Options{InputPath: "article.md", ConfigPath: "configs/config.yaml"}, app.Result{ImagePaths: []string{imagePath}}) + if err != nil { + t.Fatalf("buildAutoPublishXHSOptions() error = %v", err) + } + output := timingOutput.String() + for _, want := range []string{"stage=cmd.buildAutoPublishXHSOptions.generate_topics", "source=ai", "status=ok"} { + if !strings.Contains(output, want) { + t.Fatalf("timing output missing %q:\n%s", want, output) + } + } +} + func TestRunAutoPublishXHSRejectsDisabledTopicGenerationWithoutManualTags(t *testing.T) { originalReadFile := readFile originalLoadConfig := loadConfig