Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .golangci.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[output]
format = "colored-line-number"

[linters]
enable = [
"interfacer", "gocyclo", "unconvert", "goimports", "unused", "varcheck",
"vetshadow", "misspell", "nakedret", "errcheck", "golint", "ineffassign",
"deadcode", "goconst", "vet", "unparam", "gofmt"
]
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ before_install:
install:
- go get -u github.com/gopherjs/gopherjs github.com/mattn/go-sqlite3 github.com/jmoiron/sqlx
- go get -u -d -tags=js github.com/flimzy/go-sql.js
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1
# Syscall support
- >
( cd $GOPATH/src/github.com/gopherjs/gopherjs/node-syscall/;
Expand All @@ -21,5 +22,6 @@ install:

script:
- diff -u <(echo -n) <(gofmt -d ./)
- go test github.com/flimzy/anki
- golangci-lint run ./...
- go test -race github.com/flimzy/anki
- gopherjs test github.com/flimzy/anki
28 changes: 16 additions & 12 deletions anki.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func (a *Apkg) ListFiles() []string {
return filenames
}

// ReadMediaFile returns the contents of the named media file.
func (a *Apkg) ReadMediaFile(name string) ([]byte, error) {
return a.media.ReadFile(name)
}
Expand Down Expand Up @@ -113,10 +114,9 @@ func (a *Apkg) populateIndex() error {
index.index[file.FileHeader.Name] = file
}

if sqlite, ok := index.index["collection.anki2"]; !ok {
var ok bool
if a.sqlite, ok = index.index["collection.anki2"]; !ok {
return errors.New("Unable to find `collection.anki2` in archive")
} else {
a.sqlite = sqlite
}

mediaFile, err := index.ReadFile("media")
Expand Down Expand Up @@ -155,18 +155,19 @@ func (a *Apkg) Close() (e error) {
return
}

// Collection returns the complete collection.
func (a *Apkg) Collection() (*Collection, error) {
var deletedDecks []ID
if rows, err := a.db.Query("SELECT oid FROM graves WHERE type=2"); err != nil {
rows, err := a.db.Query("SELECT oid FROM graves WHERE type=2")
if err != nil {
return nil, err
} else {
for rows.Next() {
id := new(ID)
if err := rows.Scan(id); err != nil {
return nil, err
}
deletedDecks = append(deletedDecks, *id)
}
var deletedDecks []ID
for rows.Next() {
id := new(ID)
if err := rows.Scan(id); err != nil {
return nil, err
}
deletedDecks = append(deletedDecks, *id)
}
collection := &Collection{}
if err := a.db.Get(collection, "SELECT * FROM col"); err != nil {
Expand Down Expand Up @@ -254,12 +255,14 @@ func (a *Apkg) Cards() (*Cards, error) {
return &Cards{rows}, err
}

// Card returns the next card in the deck.
func (c *Cards) Card() (*Card, error) {
card := &Card{}
err := c.StructScan(card)
return card, err
}

// Reviews represents the reviews log in the anki package.
type Reviews struct {
*sqlx.Rows
}
Expand Down Expand Up @@ -287,6 +290,7 @@ func (a *Apkg) Reviews() (*Reviews, error) {
return &Reviews{rows}, err
}

// Review returns the next review.
func (r *Reviews) Review() (*Review, error) {
review := &Review{}
err := r.StructScan(review)
Expand Down
20 changes: 10 additions & 10 deletions anki_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const ApkgFile = "t/Test.apkg"

func TestReadFile(t *testing.T) {
if _, err := ReadFile("does not exist"); err.Error() != "open does not exist: no such file or directory" {
t.Fatalf("Unexpected error trying to open non-existant file: %s", err)
t.Fatalf("Unexpected error trying to open non-existent file: %s", err)
}
apkg, err := ReadFile(ApkgFile)
if err != nil {
Expand Down Expand Up @@ -52,9 +52,9 @@ func TestReadReader(t *testing.T) {
t.Fatalf("Error fetching notes: %s", err)
}
for notes.Next() {
note, err := notes.Note()
note, e := notes.Note()
if err != nil {
t.Fatalf("Error reading note: %s", err)
t.Fatalf("Error reading note: %s", e)
}
if note.ID != 1388721680877 {
t.Fatalf("note spot-check failed. Expected ID 1388721680877, got %d", note.ID)
Expand All @@ -63,25 +63,25 @@ func TestReadReader(t *testing.T) {
t.Fatalf("note spot-check failed. Expected checksum 1090091728, got %d", note.Checksum)
}
}
if err := notes.Close(); err != nil {
t.Fatalf("Error closing Notes: %s", err)
if e := notes.Close(); e != nil {
t.Fatalf("Error closing Notes: %s", e)
}

reviews, err := apkg.Reviews()
if err != nil {
t.Fatalf("Error fetching reviews: %s", err)
}
for reviews.Next() {
review, err := reviews.Review()
if err != nil {
t.Fatalf("Error reading review: %s", err)
review, e := reviews.Review()
if e != nil {
t.Fatalf("Error reading review: %s", e)
}
if review.CardID != 1388721683902 {
t.Fatalf("review spot-check failed. Expected cid 1388721683902, got %d", review.CardID)
}
}
if err := reviews.Close(); err != nil {
t.Fatalf("Error closing Reviews: %s", err)
if e := reviews.Close(); e != nil {
t.Fatalf("Error closing Reviews: %s", e)
}

cards, err := apkg.Cards()
Expand Down
47 changes: 32 additions & 15 deletions anki_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,14 @@ type Model struct {
UpdateSequence int `json:"usn"` // Update sequence number: used in same way as other usn vales in db
}

// Returns the model's creation timestamp (based on its ID)
// Created returns the model's creation timestamp (based on its ID)
func (m *Model) Created() *TimestampMilliseconds {
t := &TimestampMilliseconds{}
_ = t.Scan(int64(m.ID))
return t
}

// Enum representing the available Note Type Types (confusing, eh?)
// ModelType is an enum representing the available Note Type Types (confusing, eh?)
type ModelType int

const (
Expand All @@ -122,7 +122,7 @@ const (
ModelTypeCloze
)

// A field of a model
// Field is the field of a model
//
// Excluded from this definition is the `media` field, which appears to no longer be used.
type Field struct {
Expand All @@ -134,7 +134,7 @@ type Field struct {
FontSize int `json:"size"` // Font size
}

// A card constraint defines which fields are necessary for a particular card
// CardConstraint defines which fields are necessary for a particular card
// type to be generated. This is (apparently) auto-calculated whenever a note
// is created or modified.
type CardConstraint struct {
Expand Down Expand Up @@ -172,7 +172,7 @@ type Template struct {
DeckOverride ID `json:"did"` // Deck override (null by default) (??)
}

// A collection of Decks
// Decks is a collection of Decks
type Decks map[ID]*Deck

// Scan implements the sql.Scanner interface for the Decks type.
Expand Down Expand Up @@ -214,14 +214,14 @@ type Deck struct {
Config *DeckConfig `json:"-"`
}

// Returns the deck's creation timestamp (based on its ID)
// Created returns the deck's creation timestamp (based on its ID)
func (d *Deck) Created() *TimestampMilliseconds {
t := &TimestampMilliseconds{}
_ = t.Scan(int64(d.ID))
return t
}

// Collection of per-deck configurations
// DeckConfigs is a collection of per-deck configurations
type DeckConfigs map[ID]*DeckConfig

// Scan implements the sql.Scanner interface for the DeckConfigs type.
Expand All @@ -244,9 +244,10 @@ func (dc *DeckConfigs) UnmarshalJSON(src []byte) error {
return nil
}

// Per-Deck configuration options.
// DeckConfig is a struct of per-Deck configuration options.
//
// Excluded from this definition is the `minSpace` field from Reviews, as it is no longer used.
// Excluded from this definition is the `minSpace` field from Reviews, as it is
// no longer used.
type DeckConfig struct {
ID ID `json:"id"` // Deck ID
Name string `json:"name"` // Deck Name
Expand Down Expand Up @@ -281,19 +282,25 @@ type DeckConfig struct {
} `json:"new"`
}

// Enum of available leech actions
// LeechAction is an enum of available leech actions
type LeechAction int

const (
// LeechActionSuspendCard indicates that leeches should be suspended.
LeechActionSuspendCard LeechAction = iota
LeechActoinTagOnly
// LeechActionTagOnly indicates that leeches should be tagged.
LeechActionTagOnly
)

// Enum of new card order options
// NewCardOrder is an enum of new card order options
type NewCardOrder int

const (
// NewCardOrderOrderAdded indicates that new cards will be studied in the
// order they were added.
NewCardOrderOrderAdded NewCardOrder = iota
// NewCardOrderRandomOrder indicates that new cards will be studied in
// random order.
NewCardOrderRandomOrder
)

Expand All @@ -312,7 +319,7 @@ type Note struct {
Checksum int64 `db:"csum"` // Field checksum used for duplicate check. Integer representation of first 8 digits of sha1 hash of the first field
}

// Returns the notes's creation timestamp (based on its ID)
// Created returns the notes's creation timestamp (based on its ID)
func (n *Note) Created() *TimestampMilliseconds {
t := &TimestampMilliseconds{}
_ = t.Scan(int64(n.ID))
Expand All @@ -339,6 +346,7 @@ func (t *Tags) Scan(src interface{}) error {
return nil
}

// FieldValues is a list of field values.
type FieldValues []string

// Scan implements the sql.Scanner interface for the FieldValues type.
Expand Down Expand Up @@ -391,24 +399,29 @@ type Card struct {
OriginalDeckID ID `db:"odid"` // Original Deck ID. Only used when card is in filtered deck.
}

// Returns the cards's creation timestamp (based on its ID)
// Created returns the cards's creation timestamp (based on its ID)
func (c *Card) Created() *TimestampMilliseconds {
t := &TimestampMilliseconds{}
_ = t.Scan(int64(c.ID))
return t
}

// CardType is an enum for card types.
type CardType int

const (
// CardTypeNew indicates the card has not yet been studied.
CardTypeNew CardType = iota
// CardTypeLearning indicates the card is still being learned.
CardTypeLearning
// CardTypeReview indicates the card is mature, and only being reviewed.
CardTypeReview
)

// CardQueue is an enum for the available card queues.
type CardQueue int

// CardQueue specifies the card's queue type
// The available specifies the card queues.
//
// See https://github.com/dae/anki/blob/master/anki/sched.py#L17
// and https://github.com/dae/anki/blob/master/anki/cards.py#L14
Expand Down Expand Up @@ -438,17 +451,21 @@ type Review struct {
Type ReviewType `db:"type"` // Review type: learn, review, relearn, cram
}

// ReviewEase is an enum for the review eases.
type ReviewEase int

// The available eases based on review answers.
const (
ReviewEaseWrong ReviewEase = 1
ReviewEaseHard ReviewEase = 2
ReviewEaseOK ReviewEase = 3
ReviewEaseEasy ReviewEase = 4
)

// ReviewType is an enum for the review types.
type ReviewType int

// The possible review types.
const (
ReviewTypeLearn ReviewType = iota
ReviewTypeReview
Expand Down
2 changes: 1 addition & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
// type integer not null
// );
//
// When it is obvious that a column is no longer used by Anki, it is ommitted
// When it is obvious that a column is no longer used by Anki, it is omitted
// from these Go data structures. When it is not obvious, it is included, but
// typically with a comment to the effect that its use is unknown. If you know
// of any inaccuracies or recent changes to the Anki schema, please create an
Expand Down
6 changes: 5 additions & 1 deletion sqlite-std.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ import (
"os"

"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
_ "github.com/mattn/go-sqlite3" // The sqlite3 driver
)

// DB is a wrapper for the underlying SQLite database.
type DB struct {
*sqlx.DB
tmpFile string
}

// Close closes the database handle.
func (db *DB) Close() (e error) {
if db.tmpFile != "" {
if err := os.Remove(db.tmpFile); err != nil {
Expand All @@ -35,6 +37,8 @@ func (db *DB) Close() (e error) {
return
}

// OpenDB reads an SQLite database file on src, and returns an opened database
// handle.
func OpenDB(src io.Reader) (db *DB, e error) {
db = &DB{}
dbFile, err := dumpToTemp(src)
Expand Down
1 change: 1 addition & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ func (d *DurationMinutes) Scan(src interface{}) error {
// DurationDays represents a duration in days.
type DurationDays int

// Scan implements the sql.Scanner interface for the DurationDays type.
func (d *DurationDays) Scan(src interface{}) error {
days, err := scanInt64(src)
*d = DurationDays(int(days))
Expand Down