Files
gh-action-readme/internal/json_writer.go
2025-07-30 19:17:36 +03:00

262 lines
7.0 KiB
Go

package internal
import (
"encoding/json"
"fmt"
"os"
"time"
)
// getVersion returns the current version - can be overridden at build time.
var getVersion = func() string {
return "0.1.0" // Default version, should be overridden at build time
}
// JSONOutput represents the structured JSON documentation output.
type JSONOutput struct {
Meta MetaInfo `json:"meta"`
Action ActionYMLForJSON `json:"action"`
Documentation DocumentationInfo `json:"documentation"`
Examples []ExampleInfo `json:"examples"`
Generated GeneratedInfo `json:"generated"`
}
// MetaInfo contains metadata about the documentation generation.
type MetaInfo struct {
Version string `json:"version"`
Format string `json:"format"`
Schema string `json:"schema"`
Generator string `json:"generator"`
}
// ActionYMLForJSON represents the action.yml data in JSON format.
type ActionYMLForJSON struct {
Name string `json:"name"`
Description string `json:"description"`
Inputs map[string]ActionInputForJSON `json:"inputs,omitempty"`
Outputs map[string]ActionOutputForJSON `json:"outputs,omitempty"`
Runs map[string]any `json:"runs"`
Branding *BrandingForJSON `json:"branding,omitempty"`
}
// ActionInputForJSON represents an input parameter in JSON format.
type ActionInputForJSON struct {
Description string `json:"description"`
Required bool `json:"required"`
Default any `json:"default,omitempty"`
}
// ActionOutputForJSON represents an output parameter in JSON format.
type ActionOutputForJSON struct {
Description string `json:"description"`
}
// BrandingForJSON represents branding information in JSON format.
type BrandingForJSON struct {
Icon string `json:"icon"`
Color string `json:"color"`
}
// DocumentationInfo contains information about the generated documentation.
type DocumentationInfo struct {
Title string `json:"title"`
Description string `json:"description"`
Usage string `json:"usage"`
Badges []BadgeInfo `json:"badges,omitempty"`
Sections []SectionInfo `json:"sections"`
Links map[string]string `json:"links"`
}
// BadgeInfo represents a documentation badge.
type BadgeInfo struct {
Name string `json:"name"`
URL string `json:"url"`
Alt string `json:"alt"`
}
// SectionInfo represents a documentation section.
type SectionInfo struct {
Title string `json:"title"`
Content string `json:"content"`
Type string `json:"type"` // "inputs", "outputs", "examples", "text"
}
// ExampleInfo represents a usage example.
type ExampleInfo struct {
Title string `json:"title"`
Description string `json:"description"`
Code string `json:"code"`
Language string `json:"language"`
}
// GeneratedInfo contains metadata about when and how the documentation was generated.
type GeneratedInfo struct {
Timestamp string `json:"timestamp"`
Tool string `json:"tool"`
Version string `json:"version"`
Theme string `json:"theme,omitempty"`
}
// JSONWriter handles JSON output generation.
type JSONWriter struct {
Config *AppConfig
}
// NewJSONWriter creates a new JSON writer.
func NewJSONWriter(config *AppConfig) *JSONWriter {
return &JSONWriter{Config: config}
}
// Write generates JSON documentation from the action data.
func (jw *JSONWriter) Write(action *ActionYML, outputPath string) error {
jsonOutput := jw.convertToJSONOutput(action)
// Marshal to JSON with indentation
data, err := json.MarshalIndent(jsonOutput, "", " ")
if err != nil {
return err
}
// Write to file
return os.WriteFile(outputPath, data, 0644)
}
// convertToJSONOutput converts ActionYML to structured JSON output.
func (jw *JSONWriter) convertToJSONOutput(action *ActionYML) *JSONOutput {
// Convert inputs
inputs := make(map[string]ActionInputForJSON)
for key, input := range action.Inputs {
inputs[key] = ActionInputForJSON(input)
}
// Convert outputs
outputs := make(map[string]ActionOutputForJSON)
for key, output := range action.Outputs {
outputs[key] = ActionOutputForJSON(output)
}
// Convert branding
var branding *BrandingForJSON
if action.Branding != nil {
branding = &BrandingForJSON{
Icon: action.Branding.Icon,
Color: action.Branding.Color,
}
}
// Generate badges
var badges []BadgeInfo
if branding != nil {
badges = append(badges, BadgeInfo{
Name: "Icon",
URL: "https://img.shields.io/badge/icon-" + branding.Icon + "-" + branding.Color,
Alt: branding.Icon,
})
}
badges = append(badges,
BadgeInfo{
Name: "GitHub Action",
URL: "https://img.shields.io/badge/GitHub%20Action-" + action.Name + "-blue",
Alt: "GitHub Action",
},
BadgeInfo{
Name: "License",
URL: "https://img.shields.io/badge/license-MIT-green",
Alt: "MIT License",
},
)
// Generate examples
examples := []ExampleInfo{
{
Title: "Basic Usage",
Description: "Basic example of using " + action.Name,
Code: jw.generateBasicExample(action),
Language: "yaml",
},
}
// Build sections
sections := []SectionInfo{
{
Title: "Overview",
Content: action.Description,
Type: "text",
},
}
if len(action.Inputs) > 0 {
sections = append(sections, SectionInfo{
Title: "Inputs",
Content: "Input parameters for this action",
Type: "inputs",
})
}
if len(action.Outputs) > 0 {
sections = append(sections, SectionInfo{
Title: "Outputs",
Content: "Output parameters from this action",
Type: "outputs",
})
}
return &JSONOutput{
Meta: MetaInfo{
Version: "1.0.0",
Format: "gh-action-readme-json",
Schema: "https://github.com/ivuorinen/gh-action-readme/schema/v1",
Generator: "gh-action-readme",
},
Action: ActionYMLForJSON{
Name: action.Name,
Description: action.Description,
Inputs: inputs,
Outputs: outputs,
Runs: action.Runs,
Branding: branding,
},
Documentation: DocumentationInfo{
Title: action.Name,
Description: action.Description,
Usage: jw.generateBasicExample(action),
Badges: badges,
Sections: sections,
Links: map[string]string{
"action.yml": "./action.yml",
"repository": "https://github.com/your-org/" + action.Name,
},
},
Examples: examples,
Generated: GeneratedInfo{
Timestamp: time.Now().UTC().Format(time.RFC3339),
Tool: "gh-action-readme",
Version: getVersion(),
Theme: jw.Config.Theme,
},
}
}
// generateBasicExample creates a basic usage example.
func (jw *JSONWriter) generateBasicExample(action *ActionYML) string {
example := "- name: " + action.Name + "\n"
example += " uses: your-org/" + action.Name + "@v1"
if len(action.Inputs) > 0 {
example += "\n with:"
for key, input := range action.Inputs {
value := "value"
if input.Default != nil {
if str, ok := input.Default.(string); ok {
value = str
} else {
value = fmt.Sprintf("%v", input.Default)
}
}
example += "\n " + key + ": \"" + value + "\""
}
}
return example
}