mirror of
https://codeberg.org/scip/epuppy.git
synced 2025-12-17 04:20:59 +01:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb671b7401 | ||
|
|
2c6e81a2c8 | ||
| c2abc4ba4d | |||
| b9b0ad8603 | |||
|
|
08f470e0d5 |
22
.github/workflows/ci.yaml
vendored
22
.github/workflows/ci.yaml
vendored
@@ -21,9 +21,29 @@ jobs:
|
||||
- name: build
|
||||
run: go build
|
||||
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
version: [1.24.9]
|
||||
os: [ubuntu-latest]
|
||||
name: Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.os }}
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '${{ matrix.version }}'
|
||||
id: go
|
||||
|
||||
- name: checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: test
|
||||
run: go test -cover ./...
|
||||
|
||||
|
||||
golangci:
|
||||
name: lint
|
||||
name: Lintercheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v6
|
||||
|
||||
67
README.md
67
README.md
@@ -1,3 +1,8 @@
|
||||
[](https://github.com/tlinden/epuppy/actions)
|
||||
[](https://github.com/tlinden/epuppy/blob/master/LICENSE)
|
||||
[](https://goreportcard.com/report/github.com/tlinden/epuppy)
|
||||
|
||||
|
||||
# epuppy - terminal epub reader
|
||||
|
||||
This is a little TUI epub ebook reader. This is a work in progress and
|
||||
@@ -27,6 +32,66 @@ long run.
|
||||
- Showing the help
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
To read an ebook, just give a filename as argument to `epuppy`.
|
||||
|
||||
Add the option `-s` to store and use a previously stored reading
|
||||
progress.
|
||||
|
||||
Sometimes you may be unhappy with the colors. Depending on your
|
||||
terminal style you can enable dark mode with `-D`, light mode is the
|
||||
default. You can also configure custom colors in a config file in
|
||||
`$HOME/.config/epuppy/confit.toml`:
|
||||
|
||||
```toml
|
||||
# color setting for dark mode
|
||||
colordark = {
|
||||
body = "#ffffff",
|
||||
title = "#7cfc00",
|
||||
chapter = "#ffff00"
|
||||
}
|
||||
|
||||
# color setting for light mode
|
||||
colorlight = {
|
||||
body = "#000000",
|
||||
title = "#8470ff",
|
||||
chapter = "#00008b"
|
||||
}
|
||||
|
||||
# always use dark mode
|
||||
dark = true
|
||||
```
|
||||
|
||||
There are also cases where your current terminal just doesn't have the
|
||||
capabilites for this stuff. I stumbled upon such a case during an SSH
|
||||
session from my Android phone to a FreeBSD server. For this you can
|
||||
either just disable colors with `-N` or by setting the environment
|
||||
variable `$NO_COLOR` to 1. Or you can just dump the text of the ebook
|
||||
and pipe it to some pager, e.g.:
|
||||
|
||||
```default
|
||||
epuppy -t someebook.epub | less
|
||||
```
|
||||
|
||||
There are also a couple of debug options etc, all options:
|
||||
|
||||
```default
|
||||
Usage epuppy [options] <epub file>
|
||||
|
||||
Options:
|
||||
-D --dark enable dark mode
|
||||
-s --store-progress remember reading position
|
||||
-n --line-numbers add line numbers
|
||||
-c --config <file> use config <file>
|
||||
-t --txt dump readable content to STDOUT
|
||||
-x --xml dump source xml to STDOUT
|
||||
-N --no-color disable colors (or use $NO_COLOR env var)
|
||||
-d --debug enable debugging
|
||||
-h --help show help message
|
||||
-v --version show program version
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
The tool does not have any dependencies. Just download the binary for
|
||||
@@ -83,4 +148,4 @@ version 3.
|
||||
|
||||
# Author
|
||||
|
||||
Copyleft (c) 2024 Thomas von Dein
|
||||
Copyleft (c) 2025 Thomas von Dein
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright © 2025 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright © 2025 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
@@ -16,16 +32,20 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
Version string = `v0.0.3`
|
||||
Usage string = `Usage epuppy [options] <epub file>
|
||||
Version string = `v0.0.5`
|
||||
Usage string = `This is epuppy, a terminal ui ebook viewer.
|
||||
|
||||
Usage: epuppy [options] <epub file>
|
||||
|
||||
Options:
|
||||
-D --dark enable dark mode
|
||||
-s --store-progress remember reading position
|
||||
-n --line-numbers add line numbers
|
||||
-c --config <file> use config <file>
|
||||
-i --cover-image display cover image
|
||||
-t --txt dump readable content to STDOUT
|
||||
-x --xml dump source xml to STDOUT
|
||||
-N --no-color disable colors (or use $NO_COLOR env var)
|
||||
-d --debug enable debugging
|
||||
-h --help show help message
|
||||
-v --version show program version`
|
||||
@@ -39,10 +59,12 @@ type Config struct {
|
||||
LineNumbers bool `koanf:"line-numbers"` // -n
|
||||
Dump bool `koanf:"txt"` // -t
|
||||
XML bool `koanf:"xml"` // -x
|
||||
NoColor bool `koanf:"no-color"` // -n
|
||||
Config string `koanf:"config"` // -c
|
||||
ColorDark ColorSetting `koanf:"colordark"` // comes from config file only
|
||||
ColorLight ColorSetting `koanf:"colorlight"` // comes from config file only
|
||||
|
||||
ShowHelp bool `koanf:"help"`
|
||||
ShowCover bool `koanf:"cover-image"` // -i
|
||||
Colors Colors // generated from user config file or internal defaults, respects dark mode
|
||||
Document string
|
||||
InitialProgress int // lines
|
||||
@@ -58,7 +80,6 @@ func InitConfig(output io.Writer) (*Config, error) {
|
||||
if err != nil {
|
||||
log.Fatalf("failed to print to output: %s", err)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// parse commandline flags
|
||||
@@ -69,6 +90,9 @@ func InitConfig(output io.Writer) (*Config, error) {
|
||||
flagset.BoolP("line-numbers", "n", false, "add line numbers")
|
||||
flagset.BoolP("txt", "t", false, "dump readable content to STDOUT")
|
||||
flagset.BoolP("xml", "x", false, "dump xml to STDOUT")
|
||||
flagset.BoolP("no-color", "N", false, "disable colors")
|
||||
flagset.BoolP("cover-image", "i", false, "show cover image")
|
||||
flagset.BoolP("help", "h", false, "show help")
|
||||
flagset.StringP("config", "c", "", "read config from file")
|
||||
|
||||
if err := flagset.Parse(os.Args[1:]); err != nil {
|
||||
@@ -117,8 +141,9 @@ func InitConfig(output io.Writer) (*Config, error) {
|
||||
if len(flagset.Args()) > 0 {
|
||||
conf.Document = flagset.Args()[0]
|
||||
} else {
|
||||
if !conf.Showversion {
|
||||
if !conf.Showversion && !conf.ShowHelp {
|
||||
flagset.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +165,11 @@ func InitConfig(output io.Writer) (*Config, error) {
|
||||
},
|
||||
conf)
|
||||
|
||||
// disable colors if requested by command line
|
||||
if conf.NoColor {
|
||||
_ = os.Setenv("NO_COLOR", "1")
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
|
||||
106
cmd/pager.go
106
cmd/pager.go
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright © 2025 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
// pager setup using bubbletea
|
||||
@@ -5,10 +21,13 @@ package cmd
|
||||
// https://github.com/charmbracelet/bubbletea/tree/main/examples/pager
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/blacktop/go-termimg"
|
||||
"github.com/charmbracelet/bubbles/help"
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
@@ -52,12 +71,16 @@ type keyMap struct {
|
||||
Right key.Binding
|
||||
Help key.Binding
|
||||
Quit key.Binding
|
||||
ToggleUI key.Binding
|
||||
Pad key.Binding
|
||||
}
|
||||
|
||||
func (k keyMap) FullHelp() [][]key.Binding {
|
||||
return [][]key.Binding{
|
||||
{k.Up, k.Down, k.Left, k.Right}, // first column
|
||||
{k.Help, k.Quit}, // second column
|
||||
// every item is one column
|
||||
{k.Up, k.Down, k.Left, k.Right},
|
||||
{k.Pad}, // fake key, we use it as spacing between columns
|
||||
{k.Help, k.Quit, k.ToggleUI},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +89,10 @@ func (k keyMap) ShortHelp() []key.Binding {
|
||||
}
|
||||
|
||||
var keys = keyMap{
|
||||
Pad: key.NewBinding(
|
||||
key.WithKeys("__"),
|
||||
key.WithHelp(" ", ""),
|
||||
),
|
||||
Up: key.NewBinding(
|
||||
key.WithKeys("up", "k"),
|
||||
key.WithHelp("↑", "move up"),
|
||||
@@ -75,11 +102,11 @@ var keys = keyMap{
|
||||
key.WithHelp("↓", "move down"),
|
||||
),
|
||||
Left: key.NewBinding(
|
||||
key.WithKeys("left", "h"),
|
||||
key.WithKeys("left"),
|
||||
key.WithHelp("←", "decrease text width"),
|
||||
),
|
||||
Right: key.NewBinding(
|
||||
key.WithKeys("right", "l"),
|
||||
key.WithKeys("right"),
|
||||
key.WithHelp("→", "increase text width"),
|
||||
),
|
||||
Help: key.NewBinding(
|
||||
@@ -90,6 +117,10 @@ var keys = keyMap{
|
||||
key.WithKeys("q", "esc", "ctrl+c"),
|
||||
key.WithHelp("q", "quit"),
|
||||
),
|
||||
ToggleUI: key.NewBinding(
|
||||
key.WithKeys("h"),
|
||||
key.WithHelp("h", "toggle ui"),
|
||||
),
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
@@ -101,8 +132,10 @@ type Doc struct {
|
||||
lastwidth int
|
||||
margin int
|
||||
marginMod bool
|
||||
hideUi bool
|
||||
meta *Meta
|
||||
config *Config
|
||||
Cover *termimg.ImageWidget
|
||||
|
||||
keys keyMap
|
||||
help help.Model
|
||||
@@ -135,6 +168,8 @@ func (m Doc) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.margin -= MarginStep
|
||||
m.marginMod = true
|
||||
}
|
||||
case key.Matches(msg, m.keys.ToggleUI):
|
||||
m.hideUi = !m.hideUi
|
||||
}
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
@@ -189,7 +224,12 @@ func (m *Doc) Rewrap() {
|
||||
|
||||
// wrapping has changed, update viewport and line count
|
||||
if content != "" {
|
||||
if m.Cover != nil {
|
||||
cover, _ := m.Cover.Render()
|
||||
m.viewport.SetContent(cover + "\n\n" + content)
|
||||
} else {
|
||||
m.viewport.SetContent(content)
|
||||
}
|
||||
m.meta.lines = len(strings.Split(content, "\n"))
|
||||
}
|
||||
|
||||
@@ -212,6 +252,10 @@ func (m Doc) View() string {
|
||||
helpView = "\n" + m.help.View(m.keys)
|
||||
}
|
||||
|
||||
if m.hideUi {
|
||||
return fmt.Sprintf("%s\n%s", m.viewport.View(), helpView)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s\n%s\n%s%s", m.headerView(), m.viewport.View(), m.footerView(), helpView)
|
||||
}
|
||||
|
||||
@@ -235,7 +279,15 @@ func max(a, b int) int {
|
||||
return b
|
||||
}
|
||||
|
||||
func Pager(conf *Config, title, message string) (int, error) {
|
||||
type Ebook struct {
|
||||
Config *Config
|
||||
Title string
|
||||
Body string
|
||||
Cover []byte
|
||||
MediaType string
|
||||
}
|
||||
|
||||
func Pager(book *Ebook) (int, error) {
|
||||
width := 80
|
||||
scrollto := 0
|
||||
|
||||
@@ -246,32 +298,44 @@ func Pager(conf *Config, title, message string) (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if conf.StoreProgress {
|
||||
scrollto = conf.InitialProgress
|
||||
if book.Config.StoreProgress {
|
||||
scrollto = book.Config.InitialProgress
|
||||
}
|
||||
|
||||
if conf.LineNumbers {
|
||||
if book.Config.LineNumbers {
|
||||
catn := ""
|
||||
for idx, line := range strings.Split(message, "\n") {
|
||||
catn += fmt.Sprintf("%d: %s\n", idx, line)
|
||||
for idx, line := range strings.Split(book.Body, "\n") {
|
||||
catn += fmt.Sprintf("%4d: %s\n", idx, line)
|
||||
}
|
||||
message = catn
|
||||
book.Body = catn
|
||||
}
|
||||
|
||||
meta := Meta{
|
||||
initialprogress: scrollto,
|
||||
lines: len(strings.Split(message, "\n")),
|
||||
lines: len(strings.Split(book.Body, "\n")),
|
||||
}
|
||||
|
||||
doc := Doc{
|
||||
content: book.Body,
|
||||
title: book.Title,
|
||||
initialwidth: width,
|
||||
meta: &meta,
|
||||
config: book.Config,
|
||||
keys: keys,
|
||||
}
|
||||
|
||||
if book.MediaType != "" && book.Config.ShowCover {
|
||||
img, _, err := image.Decode(bytes.NewReader(book.Cover))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to load cover image: %w", err)
|
||||
}
|
||||
|
||||
doc.Cover = termimg.NewImageWidgetFromImage(img)
|
||||
doc.Cover.SetSize(width, width).SetProtocol(termimg.Auto)
|
||||
}
|
||||
|
||||
p := tea.NewProgram(
|
||||
Doc{
|
||||
content: message,
|
||||
title: title,
|
||||
initialwidth: width,
|
||||
meta: &meta,
|
||||
config: conf,
|
||||
keys: keys,
|
||||
},
|
||||
doc,
|
||||
tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer"
|
||||
tea.WithMouseCellMotion(), // turn on mouse support so we can track the mouse wheel
|
||||
)
|
||||
@@ -280,7 +344,7 @@ func Pager(conf *Config, title, message string) (int, error) {
|
||||
return 0, fmt.Errorf("could not run pager: %w", err)
|
||||
}
|
||||
|
||||
if conf.Debug {
|
||||
if book.Config.Debug {
|
||||
fmt.Printf("scrollto: %d, last: %d, diff: %d\n",
|
||||
scrollto, meta.currentline, scrollto-meta.currentline)
|
||||
}
|
||||
|
||||
25
cmd/root.go
25
cmd/root.go
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright © 2025 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
@@ -27,6 +43,15 @@ func Execute(output io.Writer) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
if conf.ShowHelp {
|
||||
_, err := fmt.Fprintln(output, Usage)
|
||||
if err != nil {
|
||||
return Die(fmt.Errorf("failed to print to output: %s", err))
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
if conf.StoreProgress {
|
||||
progress, err := GetProgress(conf)
|
||||
if err == nil {
|
||||
|
||||
16
cmd/store.go
16
cmd/store.go
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright © 2025 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
|
||||
42
cmd/view.go
42
cmd/view.go
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright © 2025 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
@@ -6,6 +22,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/repr"
|
||||
"github.com/tlinden/epuppy/pkg/epub"
|
||||
)
|
||||
|
||||
@@ -28,7 +45,11 @@ func ViewText(conf *Config) (int, error) {
|
||||
return fmt.Println(string(data))
|
||||
}
|
||||
|
||||
return Pager(conf, conf.Document, string(data))
|
||||
return Pager(&Ebook{
|
||||
Config: conf,
|
||||
Title: conf.Document,
|
||||
Body: string(data),
|
||||
})
|
||||
}
|
||||
|
||||
func ViewEpub(conf *Config) (int, error) {
|
||||
@@ -37,6 +58,11 @@ func ViewEpub(conf *Config) (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if conf.Debug {
|
||||
repr.Println(book.Files())
|
||||
repr.Println(book.Ncx)
|
||||
}
|
||||
|
||||
buf := strings.Builder{}
|
||||
head := strings.Builder{}
|
||||
|
||||
@@ -58,11 +84,19 @@ func ViewEpub(conf *Config) (int, error) {
|
||||
return fmt.Println(buf.String())
|
||||
}
|
||||
|
||||
return Pager(conf, head.String(), buf.String())
|
||||
if conf.Debug {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return Pager(&Ebook{
|
||||
Config: conf,
|
||||
Title: head.String(),
|
||||
Body: buf.String(),
|
||||
Cover: book.CoverImage,
|
||||
MediaType: book.CoverMediaType,
|
||||
})
|
||||
}
|
||||
|
||||
// FIXME: since the switch to book.Files() in epub.Open() this
|
||||
// returns invalid chapter numbering
|
||||
func fetchByContent(conf *Config, buf *strings.Builder, book *epub.Book) bool {
|
||||
chapter := 1
|
||||
var gotbody bool
|
||||
|
||||
11
go.mod
11
go.mod
@@ -41,12 +41,21 @@ require (
|
||||
|
||||
require (
|
||||
github.com/antchfx/xpath v1.3.5 // indirect
|
||||
github.com/blacktop/go-termimg v0.1.20 // indirect
|
||||
github.com/charmbracelet/x/mosaic v0.0.0-20250702191427-5bdfc8f2e4ff // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/knadh/koanf/maps v0.1.2 // indirect
|
||||
github.com/makeworld-the-better-one/dither/v2 v2.4.0 // indirect
|
||||
github.com/mattn/go-sixel v0.0.5 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/soniakeys/quant v1.0.0 // indirect
|
||||
golang.org/x/image v0.25.0 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
)
|
||||
|
||||
25
go.sum
25
go.sum
@@ -8,6 +8,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
|
||||
github.com/blacktop/go-termimg v0.1.20 h1:+EAUc3c9hwE/fUYaqRV1BSLvAlOuLySgLTEBzxGbYK4=
|
||||
github.com/blacktop/go-termimg v0.1.20/go.mod h1:nwxrOjfFcBjtS358oIGBLfscSLnCpNdRlMVRxsnZwMU=
|
||||
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
||||
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
@@ -22,8 +24,11 @@ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0G
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/charmbracelet/x/mosaic v0.0.0-20250702191427-5bdfc8f2e4ff h1:OVBKPzoa0k5ZVMoor27BReRZxER1IEDtLHXkRjaHElg=
|
||||
github.com/charmbracelet/x/mosaic v0.0.0-20250702191427-5bdfc8f2e4ff/go.mod h1:5qLP4S++M5quSc/xbvWWW8vKkgKwOqOT/IVhAas26XI=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
@@ -47,6 +52,8 @@ github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM=
|
||||
github.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/makeworld-the-better-one/dither/v2 v2.4.0 h1:Az/dYXiTcwcRSe59Hzw4RI1rSnAZns+1msaCXetrMFE=
|
||||
github.com/makeworld-the-better-one/dither/v2 v2.4.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
@@ -54,6 +61,8 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sixel v0.0.5 h1:55w2FR5ncuhKhXrM5ly1eiqMQfZsnAHIpYNGZX03Cv8=
|
||||
github.com/mattn/go-sixel v0.0.5/go.mod h1:h2Sss+DiUEHy0pUqcIB6PFXo5Cy8sTQEFr3a9/5ZLNw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
@@ -66,6 +75,8 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -74,8 +85,14 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y=
|
||||
github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
@@ -89,6 +106,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
@@ -104,6 +123,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -154,6 +175,10 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
16
main.go
16
main.go
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright © 2025 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
38
main_test.go
Normal file
38
main_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright © 2025 Thomas von Dein
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rogpeppe/go-internal/testscript"
|
||||
)
|
||||
|
||||
// see https://bitfieldconsulting.com/golang/test-scripts
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testscript.Main(m, map[string]func(){
|
||||
"epuppy": main,
|
||||
})
|
||||
}
|
||||
|
||||
func TestEpuppy(t *testing.T) {
|
||||
testscript.Run(t, testscript.Params{
|
||||
Dir: "t",
|
||||
})
|
||||
}
|
||||
2
mkrel.sh
2
mkrel.sh
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright © 2024 Thomas von Dein
|
||||
# Copyright © 2025 Thomas von Dein
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
||||
21
pkg/epub/README.md
Normal file
21
pkg/epub/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# epub module
|
||||
|
||||
This is a modified version of the [epub
|
||||
module](https://github.com/kapmahc/epub/). I fixed a couple of issues
|
||||
and I added code to retrieve the actual epub content, which the
|
||||
original module doesn't implement. Since the last update on that
|
||||
module was 9 years ago (as I write this), it doesn't make sense to
|
||||
send a PR. And it also makes no sense to me to publish it separately.
|
||||
|
||||
|
||||
# ORIGINAL README
|
||||
|
||||
## epub
|
||||
A pure go implementation of epub file format.
|
||||
|
||||
|
||||
|
||||
### Documents
|
||||
- <http://idpf.org/epub>
|
||||
- <http://www.cnblogs.com/Alex80/p/5127104.html>
|
||||
- <http://www.cnblogs.com/diligenceday/p/4999315.html>
|
||||
@@ -17,6 +17,9 @@ type Book struct {
|
||||
Mimetype string `json:"-"`
|
||||
Content []Content
|
||||
fd *zip.ReadCloser
|
||||
CoverImage []byte
|
||||
CoverFile string
|
||||
CoverMediaType string
|
||||
}
|
||||
|
||||
// Open open resource file
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package epub
|
||||
|
||||
//Container META-INF/container.xml file
|
||||
// Container META-INF/container.xml file
|
||||
type Container struct {
|
||||
Rootfile Rootfile `xml:"rootfiles>rootfile" json:"rootfile"`
|
||||
}
|
||||
|
||||
//Rootfile root file
|
||||
// Rootfile root file
|
||||
type Rootfile struct {
|
||||
Path string `xml:"full-path,attr" json:"path"`
|
||||
Type string `xml:"media-type,attr" json:"type"`
|
||||
|
||||
@@ -8,9 +8,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
cleanentitles = regexp.MustCompile(`&.+;`)
|
||||
cleanentitles = regexp.MustCompile(`&[a-z]+;`)
|
||||
empty = regexp.MustCompile(`(?s)^[\s ]*$`)
|
||||
newlines = regexp.MustCompile(`[\r\n]+`)
|
||||
cleanmarkup = regexp.MustCompile(`<[^<>]+>`)
|
||||
)
|
||||
|
||||
// Content nav-point content
|
||||
@@ -22,30 +23,45 @@ type Content struct {
|
||||
XML []byte
|
||||
}
|
||||
|
||||
// parse XML, look for title and <p>.*</p> stuff
|
||||
func (c *Content) String(content []byte) error {
|
||||
// parse XML, look for title and <p>.*</p> stuff
|
||||
doc, err := xmlquery.Parse(
|
||||
strings.NewReader(
|
||||
cleanentitles.ReplaceAllString(string(content), " ")))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Title == "" {
|
||||
// extract the title
|
||||
for _, item := range xmlquery.Find(doc, "//title") {
|
||||
c.Title = strings.TrimSpace(item.InnerText())
|
||||
}
|
||||
}
|
||||
|
||||
// extract all paragraphs, ignore any formatting and re-fill the
|
||||
// paragraph, that is, we replaces all newlines inside with one
|
||||
// paragraph, that is, we replace all newlines inside with one
|
||||
// space.
|
||||
txt := strings.Builder{}
|
||||
var have_p bool
|
||||
for _, item := range xmlquery.Find(doc, "//p") {
|
||||
if !empty.MatchString(item.InnerText()) {
|
||||
have_p = true
|
||||
txt.WriteString(newlines.ReplaceAllString(item.InnerText(), " ") + "\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
if !have_p {
|
||||
// try <div></div>, which some ebooks use, so get all divs,
|
||||
// remove markup and paragraphify the parts
|
||||
for _, item := range xmlquery.Find(doc, "//div") {
|
||||
if !empty.MatchString(item.InnerText()) {
|
||||
cleaned := cleanmarkup.ReplaceAllString(item.InnerText(), "")
|
||||
txt.WriteString(newlines.ReplaceAllString(cleaned, " ") + "\n\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.Body = strings.TrimSpace(txt.String())
|
||||
c.XML = content
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/repr"
|
||||
)
|
||||
|
||||
// Open open a epub file
|
||||
@@ -45,32 +47,64 @@ func Open(fn string, dumpxml bool) (*Book, error) {
|
||||
if err != nil {
|
||||
return &bk, err
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
if mf.ID == "cover-image" {
|
||||
bk.CoverFile = mf.Href
|
||||
bk.CoverMediaType = mf.MediaType
|
||||
}
|
||||
}
|
||||
|
||||
type section struct {
|
||||
file, title string
|
||||
}
|
||||
|
||||
sections := []section{}
|
||||
|
||||
if len(bk.Ncx.Points) > 0 {
|
||||
for _, block := range bk.Ncx.Points {
|
||||
sections = append(sections,
|
||||
section{
|
||||
file: "OEBPS/" + block.Content.Src,
|
||||
title: block.Text,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for _, file := range bk.Files() {
|
||||
content, err := bk.readBytes(file)
|
||||
sections = append(sections,
|
||||
section{
|
||||
file: file,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, section := range sections {
|
||||
content, err := bk.readBytes(section.file)
|
||||
if err != nil {
|
||||
return &bk, err
|
||||
}
|
||||
|
||||
ct := Content{Src: file}
|
||||
if strings.Contains(string(content), "<?xml") {
|
||||
if strings.Contains(section.file, bk.CoverFile) {
|
||||
bk.CoverImage = content
|
||||
}
|
||||
|
||||
ct := Content{Src: section.file, Title: section.title}
|
||||
|
||||
if strings.Contains(string(content), "<?xml") || strings.Contains(string(content), "<!DOCTYPE") {
|
||||
if err := ct.String(content); err != nil {
|
||||
return &bk, err
|
||||
}
|
||||
}
|
||||
|
||||
bk.Content = append(bk.Content, ct)
|
||||
|
||||
if dumpxml {
|
||||
fmt.Println(string(ct.XML))
|
||||
}
|
||||
|
||||
bk.Content = append(bk.Content, ct)
|
||||
}
|
||||
|
||||
if dumpxml {
|
||||
repr.Println(sections)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package epub
|
||||
|
||||
//Opf content.opf
|
||||
// Opf content.opf
|
||||
type Opf struct {
|
||||
Metadata Metadata `xml:"metadata" json:"metadata"`
|
||||
Manifest []Manifest `xml:"manifest>item" json:"manifest"`
|
||||
Spine Spine `xml:"spine" json:"spine"`
|
||||
}
|
||||
|
||||
//Metadata metadata
|
||||
// Metadata metadata
|
||||
type Metadata struct {
|
||||
Title []string `xml:"title" json:"title"`
|
||||
Language []string `xml:"language" json:"language"`
|
||||
@@ -53,7 +53,7 @@ type Metafield struct {
|
||||
Content string `xml:"content,attr" json:"content"`
|
||||
}
|
||||
|
||||
//Manifest manifest
|
||||
// Manifest manifest
|
||||
type Manifest struct {
|
||||
ID string `xml:"id,attr" json:"id"`
|
||||
Href string `xml:"href,attr" json:"href"`
|
||||
|
||||
BIN
pkg/epub/test.epub
Normal file
BIN
pkg/epub/test.epub
Normal file
Binary file not shown.
7
t/basic.txtar
Normal file
7
t/basic.txtar
Normal file
@@ -0,0 +1,7 @@
|
||||
# no args shall fail
|
||||
! exec epuppy
|
||||
|
||||
# help
|
||||
exec epuppy -h
|
||||
stdout 'This is epuppy'
|
||||
|
||||
1698
t/read-parse.txtar
Normal file
1698
t/read-parse.txtar
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user