mirror of
https://codeberg.org/scip/epuppy.git
synced 2025-12-16 03:51:01 +01:00
lots of fixes and additions:
- add color and color config support - fix epub parsing - add variable margin (left and right arrow keys) - add help screen (hit `?`) - add config file support
This commit is contained in:
63
cmd/colors.go
Normal file
63
cmd/colors.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
type ColorSetting struct {
|
||||
Title string `koanf:"title"`
|
||||
Chapter string `koanf:"chapter"`
|
||||
Body string `koanf:"body"`
|
||||
}
|
||||
|
||||
type Colors struct {
|
||||
Title lipgloss.Style
|
||||
Chapter lipgloss.Style
|
||||
Body lipgloss.Style
|
||||
}
|
||||
|
||||
func SetColorconfig(defaultdark, defaultlight ColorSetting, conf *Config) Colors {
|
||||
var defaults, user ColorSetting
|
||||
|
||||
switch conf.Darkmode {
|
||||
case true:
|
||||
defaults = defaultdark
|
||||
user = conf.ColorDark
|
||||
default:
|
||||
defaults = defaultlight
|
||||
user = conf.ColorLight
|
||||
}
|
||||
|
||||
var colors Colors
|
||||
var fg string
|
||||
|
||||
border := lipgloss.RoundedBorder()
|
||||
border.Right = "├"
|
||||
styletitle := lipgloss.NewStyle().BorderStyle(border).Padding(0, 1)
|
||||
|
||||
if user.Title != "" {
|
||||
fg = user.Title
|
||||
} else {
|
||||
fg = defaults.Title
|
||||
}
|
||||
|
||||
colors.Title = styletitle.Foreground(lipgloss.Color(fg))
|
||||
|
||||
if user.Chapter != "" {
|
||||
fg = user.Chapter
|
||||
} else {
|
||||
fg = defaults.Chapter
|
||||
}
|
||||
|
||||
colors.Chapter = lipgloss.NewStyle().Foreground(lipgloss.Color(fg))
|
||||
|
||||
if user.Body != "" {
|
||||
fg = user.Body
|
||||
} else {
|
||||
fg = defaults.Body
|
||||
}
|
||||
|
||||
colors.Body = lipgloss.NewStyle().Foreground(lipgloss.Color(fg))
|
||||
|
||||
return colors
|
||||
}
|
||||
@@ -7,20 +7,29 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/alecthomas/repr"
|
||||
"github.com/knadh/koanf/parsers/toml"
|
||||
"github.com/knadh/koanf/providers/file"
|
||||
"github.com/knadh/koanf/providers/posflag"
|
||||
"github.com/knadh/koanf/v2"
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const (
|
||||
Version string = `v0.0.1`
|
||||
Version string = `v0.0.2`
|
||||
Usage string = `epuppy [-vd] <epub file>`
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Showversion bool `koanf:"version"` // -v
|
||||
Debug bool `koanf:"debug"` // -d
|
||||
StoreProgress bool `koanf:"store-progress"` // -s
|
||||
Showversion bool `koanf:"version"` // -v
|
||||
Debug bool `koanf:"debug"` // -d
|
||||
StoreProgress bool `koanf:"store-progress"` // -s
|
||||
Darkmode bool `koanf:"dark"` // -D
|
||||
Config string `koanf:"config"` // -c
|
||||
ColorDark ColorSetting `koanf:"colordark"` // comes from config file only
|
||||
ColorLight ColorSetting `koanf:"colorlight"` // comes from config file only
|
||||
|
||||
Colors Colors // generated from user config file or internal defaults, respects dark mode
|
||||
Document string
|
||||
InitialProgress int // lines
|
||||
}
|
||||
@@ -41,12 +50,41 @@ func InitConfig(output io.Writer) (*Config, error) {
|
||||
// parse commandline flags
|
||||
flagset.BoolP("version", "v", false, "show program version")
|
||||
flagset.BoolP("debug", "d", false, "enable debugging")
|
||||
flagset.BoolP("dark", "D", false, "enable dark mode")
|
||||
flagset.BoolP("store-progress", "s", false, "store reading progress")
|
||||
flagset.StringP("config", "c", "", "read config from file")
|
||||
|
||||
if err := flagset.Parse(os.Args[1:]); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse program arguments: %w", err)
|
||||
}
|
||||
|
||||
// generate a list of config files to try to load, including the
|
||||
// one provided via -c, if any
|
||||
var configfiles []string
|
||||
|
||||
configfile, _ := flagset.GetString("config")
|
||||
home, _ := os.UserHomeDir()
|
||||
|
||||
if configfile != "" {
|
||||
configfiles = []string{configfile}
|
||||
} else {
|
||||
configfiles = []string{
|
||||
"/etc/epuppy.toml", "/usr/local/etc/epuppy.toml", // unix variants
|
||||
filepath.Join(home, ".config", "epuppy", "config.toml"),
|
||||
}
|
||||
}
|
||||
|
||||
// Load the config file[s]
|
||||
for _, cfgfile := range configfiles {
|
||||
if path, err := os.Stat(cfgfile); !os.IsNotExist(err) {
|
||||
if !path.IsDir() {
|
||||
if err := kloader.Load(file.Provider(cfgfile), toml.Parser()); err != nil {
|
||||
return nil, fmt.Errorf("error loading config file: %w", err)
|
||||
}
|
||||
}
|
||||
} // else: we ignore the file if it doesn't exists
|
||||
}
|
||||
|
||||
// command line setup
|
||||
if err := kloader.Load(posflag.Provider(flagset, ".", kloader), nil); err != nil {
|
||||
return nil, fmt.Errorf("error loading flags: %w", err)
|
||||
@@ -63,6 +101,24 @@ func InitConfig(output io.Writer) (*Config, error) {
|
||||
conf.Document = flagset.Args()[0]
|
||||
}
|
||||
|
||||
if conf.Debug {
|
||||
repr.Println(conf)
|
||||
}
|
||||
|
||||
// setup color config
|
||||
conf.Colors = SetColorconfig(
|
||||
ColorSetting{ // Dark
|
||||
Title: "#ff4500",
|
||||
Chapter: "#ff4500",
|
||||
Body: "#cdb79e",
|
||||
},
|
||||
ColorSetting{ // Light
|
||||
Title: "#ff0000",
|
||||
Chapter: "#8b0000",
|
||||
Body: "#696969",
|
||||
},
|
||||
conf)
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
|
||||
102
cmd/pager.go
102
cmd/pager.go
@@ -9,6 +9,8 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/help"
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
@@ -32,6 +34,11 @@ var (
|
||||
viewstyle = lipgloss.NewStyle()
|
||||
)
|
||||
|
||||
const (
|
||||
MarginStep = 5
|
||||
MinSize = 40
|
||||
)
|
||||
|
||||
type Meta struct {
|
||||
lines int
|
||||
currentline int
|
||||
@@ -39,13 +46,67 @@ type Meta struct {
|
||||
document string
|
||||
}
|
||||
|
||||
type keyMap struct {
|
||||
Up key.Binding
|
||||
Down key.Binding
|
||||
Left key.Binding
|
||||
Right key.Binding
|
||||
Help key.Binding
|
||||
Quit 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
|
||||
}
|
||||
}
|
||||
|
||||
func (k keyMap) ShortHelp() []key.Binding {
|
||||
return []key.Binding{k.Help, k.Quit}
|
||||
}
|
||||
|
||||
var keys = keyMap{
|
||||
Up: key.NewBinding(
|
||||
key.WithKeys("up", "k"),
|
||||
key.WithHelp("↑", "move up"),
|
||||
),
|
||||
Down: key.NewBinding(
|
||||
key.WithKeys("down", "j"),
|
||||
key.WithHelp("↓", "move down"),
|
||||
),
|
||||
Left: key.NewBinding(
|
||||
key.WithKeys("left", "h"),
|
||||
key.WithHelp("←", "decrease text width"),
|
||||
),
|
||||
Right: key.NewBinding(
|
||||
key.WithKeys("right", "l"),
|
||||
key.WithHelp("→", "increase text width"),
|
||||
),
|
||||
Help: key.NewBinding(
|
||||
key.WithKeys("?"),
|
||||
key.WithHelp("?", "toggle help"),
|
||||
),
|
||||
Quit: key.NewBinding(
|
||||
key.WithKeys("q", "esc", "ctrl+c"),
|
||||
key.WithHelp("q", "quit"),
|
||||
),
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
content string
|
||||
title string
|
||||
ready bool
|
||||
viewport viewport.Model
|
||||
initialwidth int
|
||||
lastwidth int
|
||||
margin int
|
||||
marginMod bool
|
||||
meta *Meta
|
||||
config *Config
|
||||
|
||||
keys keyMap
|
||||
help help.Model
|
||||
}
|
||||
|
||||
func (m Doc) Init() tea.Cmd {
|
||||
@@ -60,8 +121,21 @@ func (m Doc) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
if k := msg.String(); k == "ctrl+c" || k == "q" || k == "esc" {
|
||||
switch {
|
||||
case key.Matches(msg, m.keys.Help):
|
||||
m.help.ShowAll = !m.help.ShowAll
|
||||
case key.Matches(msg, m.keys.Quit):
|
||||
return m, tea.Quit
|
||||
case key.Matches(msg, m.keys.Left):
|
||||
if m.lastwidth-(m.margin*2) >= MinSize {
|
||||
m.margin += MarginStep
|
||||
m.marginMod = true
|
||||
}
|
||||
case key.Matches(msg, m.keys.Right):
|
||||
if m.margin >= MarginStep {
|
||||
m.margin -= MarginStep
|
||||
m.marginMod = true
|
||||
}
|
||||
}
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
@@ -85,10 +159,11 @@ func (m Doc) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
} else {
|
||||
m.viewport.Width = msg.Width
|
||||
m.viewport.Height = msg.Height - verticalMarginHeight
|
||||
m.viewport.SetContent(wordwrap.String(m.content, msg.Width))
|
||||
}
|
||||
}
|
||||
|
||||
m.Rewrap()
|
||||
|
||||
// Handle keyboard and mouse events in the viewport
|
||||
m.viewport, cmd = m.viewport.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
@@ -96,6 +171,17 @@ func (m Doc) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// re-calculate word wrapping, also add left margin
|
||||
func (m *Doc) Rewrap() {
|
||||
if m.lastwidth != m.viewport.Width || m.marginMod {
|
||||
m.viewport.SetContent(wordwrap.String(m.content, m.viewport.Width-(m.margin*2)))
|
||||
m.lastwidth = m.viewport.Width
|
||||
m.marginMod = false
|
||||
|
||||
m.viewport.Style = viewstyle.MarginLeft(m.margin)
|
||||
}
|
||||
}
|
||||
|
||||
func (m Doc) View() string {
|
||||
if !m.ready {
|
||||
return "\n Initializing..."
|
||||
@@ -105,17 +191,23 @@ func (m Doc) View() string {
|
||||
// FIXME: doesn't work correctly yet
|
||||
m.meta.currentline = int(float64(m.meta.lines) * m.viewport.ScrollPercent())
|
||||
|
||||
return fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView())
|
||||
var helpView string
|
||||
if m.help.ShowAll {
|
||||
helpView = "\n" + m.help.View(m.keys)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s\n%s\n%s%s", m.headerView(), m.viewport.View(), m.footerView(), helpView)
|
||||
}
|
||||
|
||||
func (m Doc) headerView() string {
|
||||
title := titleStyle.Render(m.title)
|
||||
title := m.config.Colors.Title.Render(m.title)
|
||||
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title)))
|
||||
return lipgloss.JoinHorizontal(lipgloss.Center, title, line)
|
||||
}
|
||||
|
||||
func (m Doc) footerView() string {
|
||||
info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100))
|
||||
|
||||
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(info)))
|
||||
return lipgloss.JoinHorizontal(lipgloss.Center, line, info)
|
||||
}
|
||||
@@ -148,7 +240,7 @@ func Pager(conf *Config, title, message string) (int, error) {
|
||||
}
|
||||
|
||||
p := tea.NewProgram(
|
||||
Doc{content: message, title: title, initialwidth: width, meta: &meta},
|
||||
Doc{content: message, title: title, initialwidth: width, meta: &meta, config: conf, keys: keys},
|
||||
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
|
||||
)
|
||||
|
||||
@@ -3,11 +3,11 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Die(err error) int {
|
||||
log.Fatal("Error: ", err.Error())
|
||||
fmt.Fprintln(os.Stderr, "Error: ", err.Error())
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
15
cmd/view.go
15
cmd/view.go
@@ -1,6 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/tlinden/epuppy/pkg/epub"
|
||||
@@ -28,12 +29,18 @@ func View(conf *Config) (int, error) {
|
||||
head.WriteString(" ")
|
||||
}
|
||||
|
||||
for _, point := range book.Ncx.Points {
|
||||
if len(point.Content.Body) > 0 {
|
||||
buf.WriteString("### " + point.Content.Title)
|
||||
// FIXME: since the switch to book.Files() in epub.Open() this
|
||||
// returns invalid chapter numbering
|
||||
chapter := 1
|
||||
|
||||
for _, content := range book.Content {
|
||||
if len(content.Body) > 0 {
|
||||
buf.WriteString(conf.Colors.Chapter.
|
||||
Render(fmt.Sprintf("Chapter %d: %s", chapter, content.Title)))
|
||||
buf.WriteString("\r\n\r\n")
|
||||
buf.WriteString(point.Content.Body)
|
||||
buf.WriteString(conf.Colors.Body.Render(content.Body))
|
||||
buf.WriteString("\r\n\r\n\r\n\r\n")
|
||||
chapter++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
config.toml
Normal file
16
config.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
# 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
|
||||
4
go.mod
4
go.mod
@@ -35,12 +35,16 @@ require (
|
||||
|
||||
require (
|
||||
github.com/alecthomas/repr v0.5.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.2 // indirect
|
||||
github.com/knadh/koanf/parsers/toml v0.1.0 // indirect
|
||||
github.com/knadh/koanf/providers/file v1.2.0 // indirect
|
||||
github.com/knadh/koanf/providers/posflag v1.0.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
golang.org/x/term v0.36.0 // indirect
|
||||
)
|
||||
|
||||
8
go.sum
8
go.sum
@@ -70,6 +70,8 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
@@ -170,6 +172,10 @@ github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
|
||||
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
||||
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
|
||||
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6ODgOub/6LCI=
|
||||
github.com/knadh/koanf/parsers/toml v0.1.0/go.mod h1:yUprhq6eo3GbyVXFFMdbfZSo928ksS+uo0FFqNMnO18=
|
||||
github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U=
|
||||
github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
|
||||
github.com/knadh/koanf/providers/posflag v1.0.1 h1:EnMxHSrPkYCFnKgBUl5KBgrjed8gVFrcXDzaW4l/C6Y=
|
||||
github.com/knadh/koanf/providers/posflag v1.0.1/go.mod h1:3Wn3+YG3f4ljzRyCUgIwH7G0sZ1pMjCOsNBovrbKmAk=
|
||||
github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM=
|
||||
@@ -237,6 +243,8 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
|
||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
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/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
||||
@@ -14,6 +14,7 @@ type Book struct {
|
||||
Opf Opf `json:"opf"`
|
||||
Container Container `json:"-"`
|
||||
Mimetype string `json:"-"`
|
||||
Content []Content
|
||||
|
||||
fd *zip.ReadCloser
|
||||
}
|
||||
@@ -49,7 +50,12 @@ func (p *Book) readXML(n string, v interface{}) error {
|
||||
}
|
||||
defer fd.Close()
|
||||
dec := xml.NewDecoder(fd)
|
||||
return dec.Decode(v)
|
||||
|
||||
if err := dec.Decode(v); err != nil {
|
||||
return fmt.Errorf("XML decoder error %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Book) readBytes(n string) ([]byte, error) {
|
||||
@@ -59,8 +65,12 @@ func (p *Book) readBytes(n string) ([]byte, error) {
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
return io.ReadAll(fd)
|
||||
data, err := io.ReadAll(fd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read contents from %s %w", n, err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (p *Book) open(n string) (io.ReadCloser, error) {
|
||||
@@ -69,5 +79,6 @@ func (p *Book) open(n string) (io.ReadCloser, error) {
|
||||
return f.Open()
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("file %s not exist", n)
|
||||
}
|
||||
|
||||
46
pkg/epub/content.go
Normal file
46
pkg/epub/content.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package epub
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Content nav-point content
|
||||
type Content struct {
|
||||
Src string `xml:"src,attr" json:"src"`
|
||||
Empty bool
|
||||
Body string
|
||||
Title string
|
||||
XML []byte
|
||||
}
|
||||
|
||||
func (c *Content) String(content []byte) error {
|
||||
title := Title{}
|
||||
|
||||
err := xml.Unmarshal(content, &title)
|
||||
if err != nil {
|
||||
if !strings.HasPrefix(err.Error(), "XML syntax error") {
|
||||
return fmt.Errorf("XML parser error %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
c.Title = strings.TrimSpace(title.Content)
|
||||
|
||||
txt := cleantitle.ReplaceAllString(string(content), "")
|
||||
txt = cleanh1.ReplaceAllString(txt, "")
|
||||
txt = cleanmarkup.ReplaceAllString(txt, "")
|
||||
txt = cleanentities.ReplaceAllString(txt, " ")
|
||||
txt = cleancomments.ReplaceAllString(txt, "")
|
||||
|
||||
txt = strings.TrimSpace(txt)
|
||||
|
||||
c.Body = cleanspace.ReplaceAllString(txt, "")
|
||||
c.XML = content
|
||||
|
||||
if len(c.Body) == 0 {
|
||||
c.Empty = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
package epub
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -22,50 +20,10 @@ type Ncx struct {
|
||||
|
||||
// NavPoint nav point
|
||||
type NavPoint struct {
|
||||
Text string `xml:"navLabel>text" json:"text"`
|
||||
Content Content `xml:"content" json:"content"`
|
||||
Points []NavPoint `xml:"navPoint" json:"points"`
|
||||
Text string `xml:"navLabel>text" json:"text"`
|
||||
Points []NavPoint `xml:"navPoint" json:"points"`
|
||||
}
|
||||
|
||||
type Title struct {
|
||||
Content string `xml:"head>title"`
|
||||
}
|
||||
|
||||
// Content nav-point content
|
||||
type Content struct {
|
||||
Src string `xml:"src,attr" json:"src"`
|
||||
Empty bool
|
||||
Body string
|
||||
Title string
|
||||
XML []byte
|
||||
}
|
||||
|
||||
func (c *Content) String(content []byte) error {
|
||||
title := Title{}
|
||||
|
||||
err := xml.Unmarshal(content, &title)
|
||||
if err != nil {
|
||||
if !strings.HasPrefix(err.Error(), "XML syntax error") {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.Title = title.Content
|
||||
|
||||
txt := cleantitle.ReplaceAllString(string(content), "")
|
||||
txt = cleanh1.ReplaceAllString(txt, "")
|
||||
txt = cleanmarkup.ReplaceAllString(txt, "")
|
||||
txt = cleanentities.ReplaceAllString(txt, " ")
|
||||
txt = cleancomments.ReplaceAllString(txt, "")
|
||||
|
||||
txt = strings.TrimSpace(txt)
|
||||
|
||||
c.Body = cleanspace.ReplaceAllString(txt, "")
|
||||
c.XML = content
|
||||
|
||||
if len(c.Body) == 0 {
|
||||
c.Empty = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package epub
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Open open a epub file
|
||||
@@ -34,19 +35,29 @@ func Open(fn string) (*Book, error) {
|
||||
for _, mf := range bk.Opf.Manifest {
|
||||
if mf.ID == bk.Opf.Spine.Toc {
|
||||
err = bk.readXML(bk.filename(mf.Href), &bk.Ncx)
|
||||
if err != nil {
|
||||
return &bk, err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, ncx := range bk.Ncx.Points {
|
||||
content, err := bk.readBytes(bk.filename(ncx.Content.Src))
|
||||
for _, file := range bk.Files() {
|
||||
content, err := bk.readBytes(file)
|
||||
if err != nil {
|
||||
return &bk, err
|
||||
}
|
||||
|
||||
if err := ncx.Content.String(content); err != nil {
|
||||
return &bk, err
|
||||
ct := Content{Src: file}
|
||||
|
||||
if strings.Contains(string(content), "DOCTYPE") {
|
||||
if err := ct.String(content); err != nil {
|
||||
return &bk, err
|
||||
}
|
||||
}
|
||||
|
||||
bk.Content = append(bk.Content, ct)
|
||||
}
|
||||
|
||||
return &bk, nil
|
||||
|
||||
Reference in New Issue
Block a user