ganki

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 10, 2026 License: MIT Imports: 18 Imported by: 0

README

ganki

A Go library for generating Anki .apkg files. ganki is functionally identical to the Python library genanki (v0.13.1), producing .apkg files that can be imported by Anki.

Go Reference CI Go Report Card

Installation

go get github.com/landon-thull/ganki

Quick Start

package main

import (
	"fmt"
	"log"

	"github.com/landon-thull/ganki"
)

func main() {
	// Create a model
	model := &ganki.Model{
		ID:   1559383000,
		Name: "Basic (genanki)",
		Fields: []ganki.Field{
			{Name: "Front", Font: "Arial"},
			{Name: "Back", Font: "Arial"},
		},
		Templates: []ganki.Template{
			{Name: "Card 1", QFmt: "{{Front}}", AFmt: "{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}"},
		},
		CSS: `.card {
 font-family: arial;
 font-size: 20px;
 text-align: center;
 color: black;
 background-color: white;
}
`,
	}

	// Create notes
	note := ganki.NewNote(model, []string{"What is 2+2?", "4"})

	// Create a deck
	deck := ganki.NewDeck(123456, "My Deck")
	deck.AddNote(note)

	// Create a package and write to file
	pkg := ganki.NewPackage([]*ganki.Deck{deck})
	if err := pkg.WriteToFile("output.apkg"); err != nil {
		log.Fatal(err)
	}

	fmt.Println("Created output.apkg")
}

Built-in Models

ganki provides the same built-in models as genanki:

ganki.BasicModel                       // Basic (genanki)
ganki.BasicAndReversedCardModel        // Basic (and reversed card) (genanki)
ganki.BasicOptionalReversedCardModel   // Basic (optional reversed card) (genanki)
ganki.BasicTypeInTheAnswerModel        // Basic (type in the answer) (genanki)
ganki.ClozeModel                       // Cloze (genanki)

GUID Generation

Use GUIDFor to generate deterministic GUIDs from field values:

guid := ganki.GUIDFor("field1", "field2")

This is useful when you need consistent GUIDs for the same note content.

Development

Prerequisites
  • Go 1.26+
Running Tests
# Run all tests
make test

# Run tests with verbose output
make test-verbose

# Run tests with race detection
make test-race
Linting
# Run linter
make lint

# Fix linting issues automatically
make lint-fix
Security
# Run security scan
make security
Building
# Build the library
make build

# Run go vet
make vet
Cleanup
# Remove build artifacts
make clean

# Clean up module cache
make tidy-all

License

MIT License - see LICENSE file for details.

Documentation

Overview

Package ganki provides a Go library for generating Anki .apkg files.

ganki is functionally identical to the Python library genanki (v0.13.1), producing .apkg files that can be imported by Anki.

Quick Start

Create a model, notes, deck, and package:

model := &ganki.Model{
    ID:   1559383000,
    Name: "Basic (genanki)",
    Fields: []ganki.Field{
        {Name: "Front", Font: "Arial"},
        {Name: "Back", Font: "Arial"},
    },
    Templates: []ganki.Template{
        {Name: "Card 1", QFmt: "{{Front}}", AFmt: "{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}"},
    },
    CSS: ".card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n",
}

note := ganki.NewNote(model, []string{"What is 2+2?", "4"})

deck := ganki.NewDeck(123456, "My Deck")
deck.AddNote(note)

pkg := ganki.NewPackage([]*ganki.Deck{deck})
if err := pkg.WriteToFile("output.apkg"); err != nil {
    log.Fatal(err)
}

Built-in Models

ganki provides the same built-in models as genanki:

ganki.BasicModel                       // Basic (genanki)
ganki.BasicAndReversedCardModel        // Basic (and reversed card) (genanki)
ganki.BasicOptionalReversedCardModel   // Basic (optional reversed card) (genanki)
ganki.BasicTypeInTheAnswerModel        // Basic (type in the answer) (genanki)
ganki.ClozeModel                       // Cloze (genanki)

GUID Generation

Use GUIDFor to generate deterministic GUIDs from field values:

guid := ganki.GUIDFor("field1", "field2")

Index

Constants

This section is empty.

Variables

View Source
var BasicAndReversedCardModel = &Model{
	ID:   1485830179,
	Name: "Basic (and reversed card) (genanki)",
	Fields: []Field{
		{Name: "Front", Font: "Arial"},
		{Name: "Back", Font: "Arial"},
	},
	Templates: []Template{
		{Name: "Card 1", QFmt: "{{Front}}", AFmt: "{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}"},
		{Name: "Card 2", QFmt: "{{Back}}", AFmt: "{{FrontSide}}\n\n<hr id=answer>\n\n{{Front}}"},
	},
	CSS: ".card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n",
}
View Source
var BasicModel = &Model{
	ID:   1559383000,
	Name: "Basic (genanki)",
	Fields: []Field{
		{Name: "Front", Font: "Arial"},
		{Name: "Back", Font: "Arial"},
	},
	Templates: []Template{
		{Name: "Card 1", QFmt: "{{Front}}", AFmt: "{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}"},
	},
	CSS: ".card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n",
}
View Source
var BasicOptionalReversedCardModel = &Model{
	ID:   1382232460,
	Name: "Basic (optional reversed card) (genanki)",
	Fields: []Field{
		{Name: "Front", Font: "Arial"},
		{Name: "Back", Font: "Arial"},
		{Name: "Add Reverse", Font: "Arial"},
	},
	Templates: []Template{
		{Name: "Card 1", QFmt: "{{Front}}", AFmt: "{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}"},
		{Name: "Card 2", QFmt: "{{#Add Reverse}}{{Back}}{{/Add Reverse}}", AFmt: "{{FrontSide}}\n\n<hr id=answer>\n\n{{Front}}"},
	},
	CSS: ".card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n",
}
View Source
var BasicTypeInTheAnswerModel = &Model{
	ID:   1305534440,
	Name: "Basic (type in the answer) (genanki)",
	Fields: []Field{
		{Name: "Front", Font: "Arial"},
		{Name: "Back", Font: "Arial"},
	},
	Templates: []Template{
		{Name: "Card 1", QFmt: "{{Front}}\n\n{{type:Back}}", AFmt: "{{Front}}\n\n<hr id=answer>\n\n{{type:Back}}"},
	},
	CSS: ".card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n",
}
View Source
var ClozeModel = &Model{
	ID:        1550428389,
	Name:      "Cloze (genanki)",
	ModelType: ModelCloze,
	Fields: []Field{
		{Name: "Text", Font: "Arial"},
		{Name: "Back Extra", Font: "Arial"},
	},
	Templates: []Template{
		{Name: "Cloze", QFmt: "{{cloze:Text}}", AFmt: "{{cloze:Text}}<br>\n{{Back Extra}}"},
	},
	CSS: ".card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n\n.cloze {\n font-weight: bold;\n color: blue;\n}\n.nightMode .cloze {\n color: lightblue;\n}",
}
View Source
var DefaultLatexPost = "\\end{document}"

DefaultLatexPost is the default LaTeX postamble used by Anki models.

View Source
var DefaultLatexPre = "\\documentclass[12pt]{article}\n\\special{papersize=3in,5in}\n\\usepackage[utf8]{inputenc}\n" +
	"\\usepackage{amssymb,amsmath}\n\\pagestyle{empty}\n\\setlength{\\parindent}{0in}\n" +
	"\\begin{document}\n"

DefaultLatexPre is the default LaTeX preamble used by Anki models.

Functions

func GUIDFor

func GUIDFor(values ...string) string

GUIDFor generates a globally unique identifier from the given values. The algorithm matches genanki's guid_for exactly:

  1. Join values with "__"
  2. SHA256 hash
  3. Take first 8 bytes
  4. Convert to big integer (big-endian)
  5. Encode in Anki's custom base91

If no values are provided, returns the GUID for an empty string.

func MustacheRender

func MustacheRender(template string, context map[string]string) string

MustacheRender renders a Mustache template with the given context. This is a minimal implementation supporting only the features needed for computing required fields (_req) in Anki models:

  • {{name}} — variable interpolation (unknown keys render as empty string)
  • {{#name}}...{{/name}} — section (renders content if value is truthy/non-empty)
  • {{^name}}...{{/name}} — inverted section (renders content if value is falsy/empty)

This matches the behavior of Python's chevron library for the subset we use.

Types

type Card

type Card struct {
	// Ord is the card ordinal (0-based), corresponding to the template index.
	Ord int

	// Suspend indicates whether the card should be suspended when imported.
	// Suspended cards have queue=-1 instead of queue=0.
	Suspend bool
}

Card represents a single card within an Anki note. Each card corresponds to a template in the note's model.

type Deck

type Deck struct {
	ID          int64
	Name        string
	Description string
	Notes       []*Note
	Models      map[int64]*Model
}

func NewDeck

func NewDeck(id int64, name string, opts ...DeckOption) *Deck

func (*Deck) AddModel

func (d *Deck) AddModel(m *Model)

func (*Deck) AddNote

func (d *Deck) AddNote(n *Note)

func (*Deck) ToJSON

func (d *Deck) ToJSON() map[string]interface{}

type DeckOption

type DeckOption func(*deckConfig)

func WithDescription

func WithDescription(description string) DeckOption

type Field

type Field struct {
	Name   string
	Font   string
	Media  []string
	RTL    bool
	Size   int
	Sticky bool
}

type IDGenerator

type IDGenerator struct {
	// contains filtered or unexported fields
}

IDGenerator generates sequential integer IDs, replacing Python's itertools.count(). IDs start from the given initial value and increment by 1 for each call to Next. This is used to generate unique note and card IDs within an .apkg file.

func NewIDGenerator

func NewIDGenerator(start int64) *IDGenerator

NewIDGenerator creates a new IDGenerator that starts producing IDs from start.

func (*IDGenerator) Next

func (g *IDGenerator) Next() int64

Next returns the next sequential ID.

type Model

type Model struct {
	ID             int64
	Name           string
	Fields         []Field
	Templates      []Template
	CSS            string
	ModelType      ModelType
	LatexPre       string
	LatexPost      string
	SortFieldIndex int
	// contains filtered or unexported fields
}

func (*Model) Req

func (m *Model) Req() ([]ReqEntry, error)

func (*Model) ToJSON

func (m *Model) ToJSON(timestamp int64, deckID int64) map[string]any

type ModelType

type ModelType int

ModelType represents the type of an Anki note model.

const (
	// ModelFrontBack is the standard front/back card model (type 0).
	ModelFrontBack ModelType = 0
	// ModelCloze is the cloze deletion model (type 1).
	ModelCloze ModelType = 1
)

type Note

type Note struct {
	Model     *Model
	Fields    []string
	SortField string
	Tags      []string
	GUID_     string
	Due       int
	// contains filtered or unexported fields
}

func NewNote

func NewNote(model *Model, fields []string, opts ...NoteOption) *Note

func (*Note) Cards

func (n *Note) Cards() ([]*Card, error)

func (*Note) GetGUID

func (n *Note) GetGUID() string

func (*Note) GetSortField

func (n *Note) GetSortField() string

type NoteOption

type NoteOption func(*Note)

func WithDue

func WithDue(d int) NoteOption

func WithGUID

func WithGUID(g string) NoteOption

func WithSortField

func WithSortField(sf string) NoteOption

func WithTags

func WithTags(tags []string) NoteOption

type Package

type Package struct {
	Decks      []*Deck
	MediaFiles []string
}

func NewPackage

func NewPackage(decks []*Deck, opts ...PackageOption) *Package

func (*Package) WriteToFile

func (p *Package) WriteToFile(path string, opts ...PackageOption) error

type PackageOption

type PackageOption func(*packageConfig)

func WithMediaFiles

func WithMediaFiles(files []string) PackageOption

func WithTimestamp

func WithTimestamp(t time.Time) PackageOption

type ReqEntry

type ReqEntry struct {
	TemplateOrd int
	Type        string
	FieldOrds   []int
}

type Template

type Template struct {
	Name  string
	QFmt  string
	AFmt  string
	BAFmt string
	BQFmt string
	BFont string
	BSize int
	DID   *int64
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL