add renderers

This commit is contained in:
eyedeekay
2024-11-01 22:20:54 -04:00
parent d92b0aa71a
commit b8e4ac0123
13 changed files with 1434 additions and 16 deletions

97
CHECKLIST.md Normal file
View File

@ -0,0 +1,97 @@
# RST Feature Implementation Checklist
## ✅ Basic Document Structure
- [x] Headings/Sections
- [x] Paragraphs
- [x] Nesting/Hierarchy support
- [x] Basic content management
- [ ] Document title/subtitle
- [ ] Transitions (horizontal lines)
## ✅ Text Styling
- [x] Strong (bold) text
- [x] Emphasis (italic) text
- [ ] Interpreted text
- [ ] Inline literals
## ✅ Lists
- [x] Ordered lists
- [x] Unordered lists
- [x] List items
- [ ] Definition lists
- [ ] Field lists
- [ ] Option lists
## ✅ Links and References
- [x] Hyperlinks (explicit)
- [ ] Anonymous hyperlinks
- [ ] Internal references
- [ ] Footnotes
- [ ] Citations
## ✅ Code Blocks
- [x] Basic code blocks
- [x] Language specification
- [x] Line number support
- [ ] Code block options
- [ ] Line highlighting
## ✅ Tables
- [x] Basic table structure
- [x] Headers
- [x] Rows
- [ ] Grid tables
- [ ] Simple tables
- [ ] CSV tables
## ✅ Directives
- [x] Basic directive support
- [x] Directive arguments
- [x] Raw content handling
- [ ] Image directives
- [ ] Figure directives
- [ ] Include directives
- [ ] Admonitions
- [ ] Topic directives
- [ ] Sidebar directives
## ✅ Metadata
- [x] Basic metadata support
- [x] Key-value pairs
- [ ] Document metadata
- [ ] Role definitions
## Missing Features
### Document Components
- [ ] Block quotes
- [ ] Doctest blocks
- [ ] Line blocks
- [ ] Comments
### Advanced Features
- [ ] Substitutions
- [ ] Roles
- [ ] Math support
- [ ] Custom roles
- [ ] Raw input
- [ ] Container directives
### Specialized Elements
- [ ] Tables of contents
- [ ] Index entries
- [ ] Bibliography
- [ ] Glossary
## Implementation Notes
The current implementation provides a solid foundation with:
- A flexible node-based architecture
- Strong type hierarchy
- Clean interface definitions
- Good support for basic RST features
- Extensible structure for future additions
## Development Status
- Core Features: ~40% complete
- Basic Text Processing: ~70% complete
- Advanced Features: ~20% complete
- Overall Completion: ~45%

7
Makefile Normal file
View File

@ -0,0 +1,7 @@
docs: format
find pkg -type d -exec bash -c "ls {}/*.go && godocdown -o ./{}/doc.md ./{}" \;
format:
find . -name '*.go' -exec gofumpt -w -s {} \;

View File

@ -1,6 +1,7 @@
# go-rst
A Go library for parsing and rendering reStructuredText (RST) documents with translation support.
Supports only a subset of restructuredText for now, but relatively easy to expand compared to other attempts.
## Features

1
go.mod
View File

@ -3,6 +3,7 @@ module i2pgit.org/idk/go-rst
go 1.23.1
require (
github.com/jung-kurt/gofpdf v1.16.2
github.com/leonelquinteros/gotext v1.7.0
github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4
)

11
go.sum
View File

@ -1,10 +1,21 @@
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc=
github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0=
github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8=
github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw=
github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 h1:0sw0nJM544SpsihWx1bkXdYLQDlzRflMgFJQ4Yih9ts=
github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4/go.mod h1:+ccdNT0xMY1dtc5XBxumbYfOUhmduiGudqaDgD2rVRE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=

32
main.go
View File

@ -15,7 +15,8 @@ func main() {
// CLI flags
rstFile := flag.String("rst", "", "Input RST file path")
poFile := flag.String("po", "", "Input PO file path for translations")
outFile := flag.String("out", "", "Output HTML file path")
outFileFormat := flag.String("out-format", "html", "Output file format (html, pdf, markdown)")
outFile := flag.String("out", "", "Output file path")
debug := flag.Bool("debug", false, "Enable debug logging")
flag.Parse()
@ -65,17 +66,40 @@ func main() {
log.Printf("Parsed %d nodes", len(nodes))
}
switch *outFileFormat {
case "html":
// Initialize HTML renderer
r := renderer.NewHTMLRenderer()
// Render HTML
html := r.RenderPretty(nodes)
WriteRendered(*outFile, []byte(html))
case "pdf":
// Initialize PDF renderer
r := renderer.NewPDFRenderer()
// Render PDF
err := r.Render(nodes)
if err != nil {
log.Fatalf("Failed to render PDF: %v", err)
}
r.SaveToFile(*outFile)
case "markdown":
// Initialize Markdown renderer
r := renderer.NewMarkdownRenderer()
// Render Markdown
err := r.Render(nodes)
if err != nil {
log.Fatalf("Failed to render Markdown: %v", err)
}
WriteRendered(*outFile, []byte(r.String()))
}
fmt.Printf("Successfully converted %s to %s\n", *rstFile, *outFile)
}
func WriteRendered(outFile string, doc []byte) {
// Write output
err = ioutil.WriteFile(*outFile, []byte(html), 0o644)
err := ioutil.WriteFile(outFile, []byte(doc), 0o644)
if err != nil {
log.Fatalf("Failed to write HTML file: %v", err)
}
fmt.Printf("Successfully converted %s to %s\n", *rstFile, *outFile)
}

469
pkg/nodes/doc.md Normal file
View File

@ -0,0 +1,469 @@
# nodes
--
import "i2pgit.org/idk/go-rst/pkg/nodes"
## Usage
#### func GetIndentedContent
```go
func GetIndentedContent(node Node) string
```
GetIndentedContent Utility function to get node content with proper indentation
#### type BaseNode
```go
type BaseNode struct {
}
```
BaseNode provides the basic implementation of the Node interface that other node
types can embed
#### func NewBaseNode
```go
func NewBaseNode(nodeType NodeType) *BaseNode
```
NewBaseNode creates a new BaseNode with the specified node type
Parameters:
- nodeType: The type of node to create
Returns:
- *BaseNode: A new base node instance
#### func (*BaseNode) AddChild
```go
func (n *BaseNode) AddChild(child Node)
```
AddChild adds a child node to this node
#### func (*BaseNode) Children
```go
func (n *BaseNode) Children() []Node
```
Children returns the node's child nodes
#### func (*BaseNode) Content
```go
func (n *BaseNode) Content() string
```
Content returns the textual content of the node
#### func (*BaseNode) Level
```go
func (n *BaseNode) Level() int
```
Level returns the nesting level of the node
#### func (*BaseNode) SetContent
```go
func (n *BaseNode) SetContent(content string)
```
SetContent sets the node's textual content
#### func (*BaseNode) SetLevel
```go
func (n *BaseNode) SetLevel(level int)
```
SetLevel sets the nesting level of the node
#### func (*BaseNode) Type
```go
func (n *BaseNode) Type() NodeType
```
Type returns the NodeType
#### type CodeNode
```go
type CodeNode struct {
*BaseNode
}
```
CodeNode represents a code block
#### func NewCodeNode
```go
func NewCodeNode(language string, content string, lineNumbers bool) *CodeNode
```
NewCodeNode creates a new CodeNode with the given language and content
#### func (*CodeNode) Language
```go
func (n *CodeNode) Language() string
```
Language returns the language of the code block
#### func (*CodeNode) LineNumbers
```go
func (n *CodeNode) LineNumbers() bool
```
LineNumbers returns the line numbers flag of the code block
#### func (*CodeNode) String
```go
func (n *CodeNode) String() string
```
String representation for debugging
#### type DirectiveNode
```go
type DirectiveNode struct {
*BaseNode
}
```
DirectiveNode represents an RST directive
#### func NewDirectiveNode
```go
func NewDirectiveNode(name string, args []string) *DirectiveNode
```
NewDirectiveNode creates a new DirectiveNode with the given name and arguments
#### func (*DirectiveNode) Arguments
```go
func (n *DirectiveNode) Arguments() []string
```
Arguments returns the arguments of the directive
#### func (*DirectiveNode) Name
```go
func (n *DirectiveNode) Name() string
```
Name returns the name of the directive
#### func (*DirectiveNode) RawContent
```go
func (n *DirectiveNode) RawContent() string
```
RawContent returns the raw content of the directive
#### func (*DirectiveNode) SetRawContent
```go
func (n *DirectiveNode) SetRawContent(content string)
```
SetRawContent sets the raw content of the directive
#### func (*DirectiveNode) String
```go
func (n *DirectiveNode) String() string
```
String representation for debugging
#### type EmphasisNode
```go
type EmphasisNode struct {
*BaseNode
}
```
EmphasisNode represents emphasized text (italic)
#### func NewEmphasisNode
```go
func NewEmphasisNode(content string) *EmphasisNode
```
NewEmphasisNode creates a new EmphasisNode with the given content
#### type HeadingNode
```go
type HeadingNode struct {
*BaseNode
}
```
HeadingNode represents a section heading in RST
#### func NewHeadingNode
```go
func NewHeadingNode(content string, level int) *HeadingNode
```
NewHeadingNode creates a new HeadingNode with the given content and level
#### func (*HeadingNode) String
```go
func (n *HeadingNode) String() string
```
String representations for debugging
#### type LinkNode
```go
type LinkNode struct {
*BaseNode
}
```
LinkNode represents a hyperlink
#### func NewLinkNode
```go
func NewLinkNode(text, url, title string) *LinkNode
```
NewLinkNode creates a new LinkNode with the given text, URL, and title
#### func (*LinkNode) String
```go
func (n *LinkNode) String() string
```
String representation for debugging
#### func (*LinkNode) Title
```go
func (n *LinkNode) Title() string
```
Title returns the URL of the link
#### func (*LinkNode) URL
```go
func (n *LinkNode) URL() string
```
URL returns the URL of the link
#### type ListItemNode
```go
type ListItemNode struct {
*BaseNode
}
```
ListItemNode represents an individual list item
#### func NewListItemNode
```go
func NewListItemNode(content string) *ListItemNode
```
NewListItemNode creates a new ListItemNode with the given content
#### type ListNode
```go
type ListNode struct {
*BaseNode
}
```
ListNode represents an ordered or unordered list
#### func NewListNode
```go
func NewListNode(ordered bool) *ListNode
```
NewListNode creates a new ListNode with the given ordered flag
#### func (*ListNode) IsOrdered
```go
func (n *ListNode) IsOrdered() bool
```
IsOrdered returns true if the list is ordered, false otherwise
#### func (*ListNode) String
```go
func (n *ListNode) String() string
```
String representation for debugging
#### type MetaNode
```go
type MetaNode struct {
*BaseNode
}
```
MetaNode represents metadata information
#### func NewMetaNode
```go
func NewMetaNode(key, value string) *MetaNode
```
NewMetaNode creates a new MetaNode with the given key and value
#### func (*MetaNode) Key
```go
func (n *MetaNode) Key() string
```
Key returns the key of the metadata
#### type Node
```go
type Node interface {
// Type returns the NodeType of this node
Type() NodeType
// Content returns the textual content of the node
Content() string
// SetContent sets the node's textual content
SetContent(string)
// Level returns the nesting level of the node
Level() int
// SetLevel sets the nesting level of the node
SetLevel(int)
// Children returns the node's child nodes
Children() []Node
// AddChild adds a child node to this node
AddChild(Node)
}
```
Node interface defines the common behavior for all RST document nodes
#### type NodeType
```go
type NodeType int
```
NodeType represents the type of a node in the RST document structure
```go
const (
NodeHeading NodeType = iota // Represents a section heading
NodeParagraph // Represents a text paragraph
NodeList // Represents an ordered or unordered list
NodeListItem // Represents an item within a list
NodeLink // Represents a hyperlink
NodeEmphasis // Represents emphasized (italic) text
NodeStrong // Represents strong (bold) text
NodeMeta // Represents metadata information
NodeDirective // Represents an RST directive
NodeCode // Represents a code block
NodeTable // Represents a table structure
)
```
Node type constants define the possible types of nodes in the RST document tree
#### type ParagraphNode
```go
type ParagraphNode struct {
*BaseNode
}
```
ParagraphNode represents a text paragraph
#### func NewParagraphNode
```go
func NewParagraphNode(content string) *ParagraphNode
```
NewParagraphNode creates a new ParagraphNode with the given content
#### func (*ParagraphNode) String
```go
func (n *ParagraphNode) String() string
```
String representation for debugging
#### type StrongNode
```go
type StrongNode struct {
*BaseNode
}
```
StrongNode represents strong text (bold)
#### func NewStrongNode
```go
func NewStrongNode(content string) *StrongNode
```
NewStrongNode creates a new StrongNode with the given content
#### type TableNode
```go
type TableNode struct {
*BaseNode
}
```
TableNode represents a table structure
#### func NewTableNode
```go
func NewTableNode() *TableNode
```
NewTableNode creates a new TableNode
#### func (*TableNode) AddRow
```go
func (n *TableNode) AddRow(row []string)
```
AddRow adds a row to the table
#### func (*TableNode) Headers
```go
func (n *TableNode) Headers() []string
```
Headers returns the headers of the table
#### func (*TableNode) Rows
```go
func (n *TableNode) Rows() [][]string
```
Rows returns the rows of the table
#### func (*TableNode) SetHeaders
```go
func (n *TableNode) SetHeaders(headers []string)
```
SetHeaders sets the headers of the table
#### func (*TableNode) String
```go
func (n *TableNode) String() string
```
String representation for debugging

126
pkg/parser/doc.md Normal file
View File

@ -0,0 +1,126 @@
# parser
--
import "i2pgit.org/idk/go-rst/pkg/parser"
## Usage
#### type Lexer
```go
type Lexer struct {
}
```
Lexer represents a lexer for the input text.
#### func NewLexer
```go
func NewLexer() *Lexer
```
NewLexer creates a new Lexer instance.
#### func (*Lexer) Tokenize
```go
func (l *Lexer) Tokenize(line string) Token
```
Tokenize tokenizes a single line of input text.
#### type Parser
```go
type Parser struct {
}
```
Parser is a struct that holds the state of the parser.
#### func NewParser
```go
func NewParser(trans translator.Translator) *Parser
```
NewParser creates a new Parser instance.
#### func (*Parser) Parse
```go
func (p *Parser) Parse(content string) []nodes.Node
```
Parse takes a string of reStructuredText content and returns a slice of Node
instances.
#### type ParserContext
```go
type ParserContext struct {
}
```
ParserContext represents the current state of the parser.
#### func NewParserContext
```go
func NewParserContext() *ParserContext
```
NewParserContext creates a new ParserContext instance.
#### func (*ParserContext) Reset
```go
func (c *ParserContext) Reset()
```
Reset resets the parser context to its initial state.
#### type Patterns
```go
type Patterns struct {
}
```
Patterns holds compiled regular expressions for parsing Markdown syntax.
#### func NewPatterns
```go
func NewPatterns() *Patterns
```
NewPatterns initializes and returns a new instance of Patterns with compiled
regular expressions.
#### type Token
```go
type Token struct {
Type TokenType
Content string
Args []string
}
```
Token represents a single token in the input text.
#### type TokenType
```go
type TokenType int
```
TokenType represents the type of a token.
```go
const (
TokenText TokenType = iota // TokenText represents a regular text token.
TokenHeadingUnderline // TokenHeadingUnderline represents a heading underline token.
TokenTransBlock // TokenTransBlock represents a transition block token.
TokenMeta // TokenMeta represents a metadata token.
TokenDirective // TokenDirective represents a directive token.
TokenCodeBlock // TokenCodeBlock represents a code block token.
TokenBlankLine // TokenBlankLine represents a blank line token.
TokenIndent // TokenIndent represents an indent token.
)
```

188
pkg/renderer/doc.md Normal file
View File

@ -0,0 +1,188 @@
# renderer
--
import "i2pgit.org/idk/go-rst/pkg/renderer"
## Usage
#### type HTMLRenderer
```go
type HTMLRenderer struct {
}
```
HTMLRederer is a renderer that renders nodes to HTML.
#### func NewHTMLRenderer
```go
func NewHTMLRenderer() *HTMLRenderer
```
NewHTMLRederer creates a new HTMLRederer.
#### func (*HTMLRenderer) Render
```go
func (r *HTMLRenderer) Render(nodes []nodes.Node) string
```
Render renders nodes to HTML.
#### func (*HTMLRenderer) RenderPretty
```go
func (r *HTMLRenderer) RenderPretty(nodes []nodes.Node) string
```
RenderPretty renders the given nodes as pretty-formatted HTML.
#### type MarkdownRenderer
```go
type MarkdownRenderer struct {
}
```
MarkdownRenderer implements a Markdown renderer with the same interface as
HTMLRenderer
#### func NewMarkdownRenderer
```go
func NewMarkdownRenderer() *MarkdownRenderer
```
NewMarkdownRenderer creates a new Markdown renderer
#### func (*MarkdownRenderer) Render
```go
func (r *MarkdownRenderer) Render(nodes []nodes.Node) error
```
Render renders a slice of nodes to Markdown
#### func (*MarkdownRenderer) RenderChildren
```go
func (r *MarkdownRenderer) RenderChildren(node nodes.Node) error
```
RenderChildren renders child nodes
#### func (*MarkdownRenderer) RenderCode
```go
func (r *MarkdownRenderer) RenderCode(node *nodes.CodeNode) error
```
RenderCode renders a code node
#### func (*MarkdownRenderer) RenderDirective
```go
func (r *MarkdownRenderer) RenderDirective(node *nodes.DirectiveNode) error
```
RenderDirective renders a directive node
#### func (*MarkdownRenderer) RenderEmphasis
```go
func (r *MarkdownRenderer) RenderEmphasis(node *nodes.EmphasisNode) error
```
RenderEmphasis renders an emphasis node
#### func (*MarkdownRenderer) RenderHeading
```go
func (r *MarkdownRenderer) RenderHeading(node *nodes.HeadingNode) error
```
RenderHeading renders a heading node
#### func (*MarkdownRenderer) RenderLink
```go
func (r *MarkdownRenderer) RenderLink(node *nodes.LinkNode) error
```
RenderLink renders a link node
#### func (*MarkdownRenderer) RenderList
```go
func (r *MarkdownRenderer) RenderList(node *nodes.ListNode) error
```
RenderList renders a list node
#### func (*MarkdownRenderer) RenderListItem
```go
func (r *MarkdownRenderer) RenderListItem(node *nodes.ListItemNode) error
```
RenderListItem renders a list item node
#### func (*MarkdownRenderer) RenderMeta
```go
func (r *MarkdownRenderer) RenderMeta(node *nodes.MetaNode) error
```
RenderMeta renders a meta node
#### func (*MarkdownRenderer) RenderNode
```go
func (r *MarkdownRenderer) RenderNode(node nodes.Node) error
```
RenderNode renders a single node to Markdown
#### func (*MarkdownRenderer) RenderParagraph
```go
func (r *MarkdownRenderer) RenderParagraph(node *nodes.ParagraphNode) error
```
RenderParagraph renders a paragraph node
#### func (*MarkdownRenderer) RenderStrong
```go
func (r *MarkdownRenderer) RenderStrong(node *nodes.StrongNode) error
```
RenderStrong renders a strong node
#### func (*MarkdownRenderer) RenderTable
```go
func (r *MarkdownRenderer) RenderTable(node *nodes.TableNode) error
```
RenderTable renders a table node
#### func (*MarkdownRenderer) String
```go
func (r *MarkdownRenderer) String() string
```
String returns the rendered markdown as a string
#### type PDFRenderer
```go
type PDFRenderer struct {
}
```
PDFRenderer implements rendering RST nodes to PDF format
#### func NewPDFRenderer
```go
func NewPDFRenderer() *PDFRenderer
```
NewPDFRenderer creates a new PDF renderer
#### func (*PDFRenderer) Render
```go
func (r *PDFRenderer) Render(nodes []nodes.Node) error
```
Render renders a slice of nodes to PDF
#### func (*PDFRenderer) SaveToFile
```go
func (r *PDFRenderer) SaveToFile(filename string) error
```
SaveToFile saves the PDF to a file

214
pkg/renderer/markdown.go Normal file
View File

@ -0,0 +1,214 @@
// pkg/renderer/markdown.go
package renderer
import (
"bytes"
"fmt"
"strings"
"i2pgit.org/idk/go-rst/pkg/nodes"
)
// MarkdownRenderer implements a Markdown renderer with the same interface as HTMLRenderer
type MarkdownRenderer struct {
output bytes.Buffer
}
// NewMarkdownRenderer creates a new Markdown renderer
func NewMarkdownRenderer() *MarkdownRenderer {
return &MarkdownRenderer{}
}
// pkg/renderer/markdown.go
// ... (previous imports and struct definition remain the same)
// Render renders a slice of nodes to Markdown
func (r *MarkdownRenderer) Render(nodes []nodes.Node) error {
for _, node := range nodes {
if err := r.RenderNode(node); err != nil {
return err
}
}
return nil
}
// RenderNode renders a single node to Markdown
func (r *MarkdownRenderer) RenderNode(node nodes.Node) error {
switch n := node.(type) {
case *nodes.HeadingNode:
return r.RenderHeading(n)
case *nodes.ParagraphNode:
return r.RenderParagraph(n)
case *nodes.ListNode:
return r.RenderList(n)
case *nodes.ListItemNode:
return r.RenderListItem(n)
case *nodes.EmphasisNode:
return r.RenderEmphasis(n)
case *nodes.StrongNode:
return r.RenderStrong(n)
case *nodes.LinkNode:
return r.RenderLink(n)
case *nodes.CodeNode:
return r.RenderCode(n)
case *nodes.TableNode:
return r.RenderTable(n)
case *nodes.DirectiveNode:
return r.RenderDirective(n)
case *nodes.MetaNode:
return r.RenderMeta(n)
default:
return r.RenderChildren(node)
}
}
// RenderHeading renders a heading node
func (r *MarkdownRenderer) RenderHeading(node *nodes.HeadingNode) error {
r.output.WriteString("\n")
r.output.WriteString(strings.Repeat("#", node.Level()))
r.output.WriteString(" ")
r.output.WriteString(node.Content())
r.output.WriteString("\n")
return r.RenderChildren(node)
}
// RenderParagraph renders a paragraph node
func (r *MarkdownRenderer) RenderParagraph(node *nodes.ParagraphNode) error {
r.output.WriteString("\n")
r.output.WriteString(node.Content())
r.output.WriteString("\n")
return r.RenderChildren(node)
}
// RenderList renders a list node
func (r *MarkdownRenderer) RenderList(node *nodes.ListNode) error {
r.output.WriteString("\n")
return r.RenderChildren(node)
}
// RenderListItem renders a list item node
func (r *MarkdownRenderer) RenderListItem(node *nodes.ListItemNode) error {
// Default to unordered list items with "-"
r.output.WriteString("- ")
r.output.WriteString(node.Content())
r.output.WriteString("\n")
return r.RenderChildren(node)
}
// RenderEmphasis renders an emphasis node
func (r *MarkdownRenderer) RenderEmphasis(node *nodes.EmphasisNode) error {
r.output.WriteString("*")
r.output.WriteString(node.Content())
r.output.WriteString("*")
return r.RenderChildren(node)
}
// RenderStrong renders a strong node
func (r *MarkdownRenderer) RenderStrong(node *nodes.StrongNode) error {
r.output.WriteString("**")
r.output.WriteString(node.Content())
r.output.WriteString("**")
return r.RenderChildren(node)
}
// RenderLink renders a link node
func (r *MarkdownRenderer) RenderLink(node *nodes.LinkNode) error {
if title := node.Title(); title != "" {
r.output.WriteString(fmt.Sprintf("[%s](%s \"%s\")", node.Content(), node.URL(), title))
} else {
r.output.WriteString(fmt.Sprintf("[%s](%s)", node.Content(), node.URL()))
}
return nil
}
// RenderCode renders a code node
func (r *MarkdownRenderer) RenderCode(node *nodes.CodeNode) error {
r.output.WriteString("\n```")
if node.Language() != "" {
r.output.WriteString(node.Language())
}
r.output.WriteString("\n")
r.output.WriteString(node.Content())
r.output.WriteString("\n```\n")
return nil
}
// RenderTable renders a table node
func (r *MarkdownRenderer) RenderTable(node *nodes.TableNode) error {
headers := node.Headers()
rows := node.Rows()
// Headers
r.output.WriteString("\n|")
for _, header := range headers {
r.output.WriteString(fmt.Sprintf(" %s |", header))
}
r.output.WriteString("\n|")
// Separator
for range headers {
r.output.WriteString(" --- |")
}
r.output.WriteString("\n")
// Rows
for _, row := range rows {
r.output.WriteString("|")
for _, cell := range row {
r.output.WriteString(fmt.Sprintf(" %s |", cell))
}
r.output.WriteString("\n")
}
r.output.WriteString("\n")
return nil
}
// RenderDirective renders a directive node
func (r *MarkdownRenderer) RenderDirective(node *nodes.DirectiveNode) error {
switch node.Name() {
case "image":
if len(node.Arguments()) > 0 {
r.output.WriteString(fmt.Sprintf("\n![%s](%s)\n", node.Content(), node.Arguments()[0]))
}
case "note":
r.output.WriteString("\n> **Note**\n")
r.output.WriteString("> " + strings.Replace(node.RawContent(), "\n", "\n> ", -1) + "\n")
case "warning":
r.output.WriteString("\n> **Warning**\n")
r.output.WriteString("> " + strings.Replace(node.RawContent(), "\n", "\n> ", -1) + "\n")
default:
r.output.WriteString(fmt.Sprintf("\n<!-- %s: %s -->\n", node.Name(), node.RawContent()))
}
return r.RenderChildren(node)
}
// RenderMeta renders a meta node
func (r *MarkdownRenderer) RenderMeta(node *nodes.MetaNode) error {
if r.output.Len() == 0 {
// If at start of document, use YAML front matter
r.output.WriteString("---\n")
r.output.WriteString(fmt.Sprintf("%s: %s\n", node.Key(), node.Content()))
r.output.WriteString("---\n")
} else {
// Otherwise use HTML comment
r.output.WriteString(fmt.Sprintf("\n<!-- %s: %s -->\n", node.Key(), node.Content()))
}
return nil
}
// RenderChildren renders child nodes
func (r *MarkdownRenderer) RenderChildren(node nodes.Node) error {
for _, child := range node.Children() {
if err := r.RenderNode(child); err != nil {
return err
}
}
return nil
}
// String returns the rendered markdown as a string
func (r *MarkdownRenderer) String() string {
return r.output.String()
}

218
pkg/renderer/pdf.go Normal file
View File

@ -0,0 +1,218 @@
// pkg/renderer/pdf.go
package renderer
import (
"fmt"
"strings"
"github.com/jung-kurt/gofpdf"
"i2pgit.org/idk/go-rst/pkg/nodes"
)
// PDFRenderer implements rendering RST nodes to PDF format
type PDFRenderer struct {
pdf *gofpdf.Fpdf
marginLeft float64
marginTop float64
fontSize float64
lineHeight float64
indent float64
}
// NewPDFRenderer creates a new PDF renderer
func NewPDFRenderer() *PDFRenderer {
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
pdf.SetFont("Arial", "", 12)
return &PDFRenderer{
pdf: pdf,
marginLeft: 20,
marginTop: 20,
fontSize: 12,
lineHeight: 6,
indent: 10,
}
}
// Render renders a slice of nodes to PDF
func (r *PDFRenderer) Render(nodes []nodes.Node) error {
for _, node := range nodes {
if err := r.renderNode(node); err != nil {
return err
}
}
return nil
}
// renderNode handles individual node rendering
func (r *PDFRenderer) renderNode(node nodes.Node) error {
switch n := node.(type) {
case *nodes.HeadingNode:
return r.renderHeading(n)
case *nodes.ParagraphNode:
return r.renderParagraph(n)
case *nodes.ListNode:
return r.renderList(n)
case *nodes.CodeNode:
return r.renderCode(n)
case *nodes.TableNode:
return r.renderTable(n)
case *nodes.DirectiveNode:
return r.renderDirective(n)
default:
return r.renderChildren(node)
}
}
// SaveToFile saves the PDF to a file
func (r *PDFRenderer) SaveToFile(filename string) error {
return r.pdf.OutputFileAndClose(filename)
}
func (r *PDFRenderer) renderHeading(node *nodes.HeadingNode) error {
// Calculate font size based on heading level
fontSize := r.fontSize + (6 - float64(node.Level())*2)
r.pdf.SetFont("Arial", "B", fontSize)
// Add some spacing before heading
r.pdf.Ln(r.lineHeight)
r.pdf.Cell(0, r.lineHeight, node.Content())
r.pdf.Ln(r.lineHeight * 1.5)
// Reset font
r.pdf.SetFont("Arial", "", r.fontSize)
return r.renderChildren(node)
}
func (r *PDFRenderer) renderParagraph(node *nodes.ParagraphNode) error {
r.pdf.MultiCell(0, r.lineHeight, node.Content(), "", "", false)
r.pdf.Ln(r.lineHeight)
return r.renderChildren(node)
}
func (r *PDFRenderer) renderList(node *nodes.ListNode) error {
currentX := r.pdf.GetX()
currentY := r.pdf.GetY()
for i, child := range node.Children() {
if node.IsOrdered() {
// Ordered list: use numbers
r.pdf.SetXY(currentX, currentY)
r.pdf.Cell(10, r.lineHeight, fmt.Sprintf("%d.", i+1))
r.pdf.SetX(currentX + r.indent)
} else {
// Unordered list: use bullets
r.pdf.SetXY(currentX, currentY)
r.pdf.Cell(10, r.lineHeight, "•")
r.pdf.SetX(currentX + r.indent)
}
if listItem, ok := child.(*nodes.ListItemNode); ok {
r.pdf.MultiCell(0, r.lineHeight, listItem.Content(), "", "", false)
}
currentY = r.pdf.GetY() + r.lineHeight
}
r.pdf.Ln(r.lineHeight)
return nil
}
func (r *PDFRenderer) renderCode(node *nodes.CodeNode) error {
// Set monospace font for code
r.pdf.SetFont("Courier", "", r.fontSize)
// Add a light gray background
startY := r.pdf.GetY()
content := node.Content()
lines := strings.Split(content, "\n")
// Calculate height
height := float64(len(lines)) * r.lineHeight
// Draw background
r.pdf.SetFillColor(240, 240, 240)
r.pdf.Rect(r.marginLeft-2, startY-2, 170, height+4, "F")
// Add language identifier if present
if node.Language() != "" {
r.pdf.SetFont("Arial", "I", r.fontSize-2)
r.pdf.Text(r.marginLeft, startY-4, node.Language())
r.pdf.SetFont("Courier", "", r.fontSize)
}
// Render code content
for _, line := range lines {
if node.LineNumbers() {
// Add line numbers if enabled
lineNum := fmt.Sprintf("%3d ", r.pdf.PageCount())
r.pdf.Text(r.marginLeft, r.pdf.GetY(), lineNum)
r.pdf.SetX(r.marginLeft + 15)
}
r.pdf.MultiCell(0, r.lineHeight, line, "", "", false)
}
// Reset font
r.pdf.SetFont("Arial", "", r.fontSize)
r.pdf.Ln(r.lineHeight)
return nil
}
func (r *PDFRenderer) renderTable(node *nodes.TableNode) error {
headers := node.Headers()
rows := node.Rows()
// Calculate column widths
colWidth := 170.0 / float64(len(headers))
// Draw headers
r.pdf.SetFont("Arial", "B", r.fontSize)
r.pdf.SetFillColor(240, 240, 240)
for _, header := range headers {
r.pdf.CellFormat(colWidth, r.lineHeight, header, "1", 0, "", true, 0, "")
}
r.pdf.Ln(-1)
// Draw rows
r.pdf.SetFont("Arial", "", r.fontSize)
r.pdf.SetFillColor(255, 255, 255)
for _, row := range rows {
for _, cell := range row {
r.pdf.CellFormat(colWidth, r.lineHeight, cell, "1", 0, "", false, 0, "")
}
r.pdf.Ln(-1)
}
r.pdf.Ln(r.lineHeight)
return nil
}
func (r *PDFRenderer) renderDirective(node *nodes.DirectiveNode) error {
// Handle special directives
switch node.Name() {
case "image":
// Handle image directive
if len(node.Arguments()) > 0 {
imagePath := node.Arguments()[0]
r.pdf.Image(imagePath, r.pdf.GetX(), r.pdf.GetY(), 0, 0, false, "", 0, "")
r.pdf.Ln(r.lineHeight)
}
case "note", "warning", "important":
// Handle admonitions
r.pdf.SetFillColor(245, 245, 245)
startY := r.pdf.GetY()
r.pdf.MultiCell(0, r.lineHeight, node.RawContent(), "", "", true)
r.pdf.SetY(startY + r.lineHeight)
}
return r.renderChildren(node)
}
func (r *PDFRenderer) renderChildren(node nodes.Node) error {
return r.Render(node.Children())
}

62
pkg/translator/doc.md Normal file
View File

@ -0,0 +1,62 @@
# translator
--
import "i2pgit.org/idk/go-rst/pkg/translator"
## Usage
#### type NoopTranslator
```go
type NoopTranslator struct{}
```
NoopTranslator implements Translator interface but doesn't translate
#### func NewNoopTranslator
```go
func NewNoopTranslator() *NoopTranslator
```
NewNoopTranslator returns a new NoopTranslator
#### func (*NoopTranslator) Translate
```go
func (t *NoopTranslator) Translate(text string) string
```
Translate returns the same text it receives(NoopTranslator)
#### type POTranslator
```go
type POTranslator struct {
}
```
POTranslator implements Translator interface using a PO file
#### func NewPOTranslator
```go
func NewPOTranslator(poFile string) (*POTranslator, error)
```
NewPOTranslator returns a new POTranslator
#### func (*POTranslator) Translate
```go
func (t *POTranslator) Translate(text string) string
```
Translate returns the translated text if it exists in the PO file, otherwise it
returns the original text
#### type Translator
```go
type Translator interface {
Translate(text string) string
}
```
Translator is an interface for translating text