shithub: mycel

Download patch

ref: cb083ed612f90d648a7a5c27b8e155beef6c7bfb
parent: 203cdba433dba38972169e3e52f638a12b199fa0
author: Philip Silva <[email protected]>
date: Sun Jun 2 13:47:00 EDT 2024

update naming

diff: cannot open b/cmd/mycel//null: file does not exist: 'b/cmd/mycel//null' diff: cannot open a/cmd/opossum//null: file does not exist: 'a/cmd/opossum//null'
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Opossum Web Browser
+# Mycel Web Browser
 
 Basic portable Web browser; only needs a Go compiler to compile. Optimized for use on 9front and 9legacy, supports plan9port and 9pi as well.
 
@@ -24,18 +24,18 @@
     # Setup TLS
     hget https://curl.haxx.se/ca/cacert.pem > /sys/lib/tls/ca.pem
     # Create mountpoints (needed on 9legacy)
-    mkdir /mnt/opossum
+    mkdir /mnt/mycel
     mkdir /mnt/sparkle
 
 ### Binary
 
-Binaries for amd64 and 386 can be downloaded from https://psilva.sdf.org/opossum.html
+Binaries for amd64 and 386 can be downloaded from https://psilva.sdf.org/mycel.html
 
 ### Compile from Source
 
 Set `$GOPROXY` to `https://proxy.golang.org` and then:
 
-    go install ./cmd/opossum
+    go install ./cmd/mycel
 
 Command line options:
 
@@ -59,7 +59,7 @@
 - Plan9Port
 
 ```
-go install ./cmd/opossum
+go install ./cmd/mycel
 ```
 
 # JS support
@@ -84,12 +84,12 @@
 go install ./cmd/sparklefs
 ```
 
-On 9legacy also the folders `/mnt/opossum` and `/mnt/sparkle` need to exist.
+On 9legacy also the folders `/mnt/mycel` and `/mnt/sparkle` need to exist.
 
 Then it can be tested with:
 
 ```
-opossum -jsinsecure https://jqueryui.com/resources/demos/tabs/default.html
+mycel -jsinsecure https://jqueryui.com/resources/demos/tabs/default.html
 ```
 
 # TODO
--- a/browser/browser.go
+++ b/browser/browser.go
@@ -5,16 +5,16 @@
 	"context"
 	"errors"
 	"fmt"
-	"github.com/psilva261/opossum"
-	"github.com/psilva261/opossum/browser/cache"
-	"github.com/psilva261/opossum/browser/duitx"
-	"github.com/psilva261/opossum/browser/fs"
-	"github.com/psilva261/opossum/browser/history"
-	"github.com/psilva261/opossum/img"
-	"github.com/psilva261/opossum/js"
-	"github.com/psilva261/opossum/logger"
-	"github.com/psilva261/opossum/nodes"
-	"github.com/psilva261/opossum/style"
+	"github.com/psilva261/mycel"
+	"github.com/psilva261/mycel/browser/cache"
+	"github.com/psilva261/mycel/browser/duitx"
+	"github.com/psilva261/mycel/browser/fs"
+	"github.com/psilva261/mycel/browser/history"
+	"github.com/psilva261/mycel/img"
+	"github.com/psilva261/mycel/js"
+	"github.com/psilva261/mycel/logger"
+	"github.com/psilva261/mycel/nodes"
+	"github.com/psilva261/mycel/style"
 	"golang.org/x/net/html"
 	"golang.org/x/net/publicsuffix"
 	"image"
@@ -34,7 +34,7 @@
 const (
 	EnterKey = 10
 
-	UserAgent = "opossum"
+	UserAgent = "mycel"
 )
 
 var debugPrintHtml = false
@@ -1692,7 +1692,7 @@
 	}
 }
 
-func (b *Browser) render(ct opossum.ContentType, buf []byte) {
+func (b *Browser) render(ct mycel.ContentType, buf []byte) {
 	log.Printf("Empty some cache...")
 	cache.Tidy()
 	imageCache = make(map[string]*draw.Image)
@@ -1723,7 +1723,7 @@
 	log.Printf("Rendering done")
 }
 
-func (b *Browser) Get(uri *url.URL) (buf []byte, contentType opossum.ContentType, err error) {
+func (b *Browser) Get(uri *url.URL) (buf []byte, contentType mycel.ContentType, err error) {
 	c, ok := cache.Get(uri.String())
 	if ok {
 		log.Printf("use %v from cache", uri)
@@ -1738,7 +1738,7 @@
 	return c.Buf, c.ContentType, err
 }
 
-func (b *Browser) get(uri *url.URL, isNewOrigin bool) (buf []byte, contentType opossum.ContentType, err error) {
+func (b *Browser) get(uri *url.URL, isNewOrigin bool) (buf []byte, contentType mycel.ContentType, err error) {
 	log.Infof("Get %v", uri.String())
 	req, err := http.NewRequestWithContext(b.ctx, "GET", uri.String(), nil)
 	if err != nil {
@@ -1747,14 +1747,14 @@
 	req.Header.Add("User-Agent", UserAgent)
 	resp, err := b.client.Do(req)
 	if err != nil {
-		return nil, opossum.ContentType{}, fmt.Errorf("error loading %v: %w", uri, err)
+		return nil, mycel.ContentType{}, fmt.Errorf("error loading %v: %w", uri, err)
 	}
 	defer resp.Body.Close()
 	buf, err = ioutil.ReadAll(resp.Body)
 	if err != nil {
-		return nil, opossum.ContentType{}, fmt.Errorf("error reading")
+		return nil, mycel.ContentType{}, fmt.Errorf("error reading")
 	}
-	contentType, err = opossum.NewContentType(resp.Header.Get("Content-Type"), resp.Request.URL)
+	contentType, err = mycel.NewContentType(resp.Header.Get("Content-Type"), resp.Request.URL)
 	if isNewOrigin {
 		of := 0
 		if scroller != nil {
@@ -1767,7 +1767,7 @@
 	return
 }
 
-func (b *Browser) PostForm(uri *url.URL, data url.Values) (buf []byte, contentType opossum.ContentType, err error) {
+func (b *Browser) PostForm(uri *url.URL, data url.Values) (buf []byte, contentType mycel.ContentType, err error) {
 	b.StatusCh <- "Posting..."
 	fb := strings.NewReader(escapeValues(b.Website.ContentType, data).Encode())
 	req, err := http.NewRequestWithContext(b.ctx, "POST", uri.String(), fb)
@@ -1778,14 +1778,14 @@
 	req.Header.Set("Content-Type", fmt.Sprintf("application/x-www-form-urlencoded; charset=%v", b.Website.Charset()))
 	resp, err := b.client.Do(req)
 	if err != nil {
-		return nil, opossum.ContentType{}, fmt.Errorf("error loading %v: %w", uri, err)
+		return nil, mycel.ContentType{}, fmt.Errorf("error loading %v: %w", uri, err)
 	}
 	defer resp.Body.Close()
 	b.History.Push(resp.Request.URL, scroller.Offset)
 	buf, err = ioutil.ReadAll(resp.Body)
 	if err != nil {
-		return nil, opossum.ContentType{}, fmt.Errorf("error reading")
+		return nil, mycel.ContentType{}, fmt.Errorf("error reading")
 	}
-	contentType, err = opossum.NewContentType(resp.Header.Get("Content-Type"), resp.Request.URL)
+	contentType, err = mycel.NewContentType(resp.Header.Get("Content-Type"), resp.Request.URL)
 	return
 }
--- a/browser/browser_test.go
+++ b/browser/browser_test.go
@@ -4,10 +4,10 @@
 	"9fans.net/go/draw"
 	"fmt"
 	"github.com/mjl-/duit"
-	"github.com/psilva261/opossum/browser/duitx"
-	"github.com/psilva261/opossum/logger"
-	"github.com/psilva261/opossum/nodes"
-	"github.com/psilva261/opossum/style"
+	"github.com/psilva261/mycel/browser/duitx"
+	"github.com/psilva261/mycel/logger"
+	"github.com/psilva261/mycel/nodes"
+	"github.com/psilva261/mycel/style"
 	"golang.org/x/net/html"
 	"image"
 	"net/http"
--- a/browser/cache/cache.go
+++ b/browser/cache/cache.go
@@ -1,7 +1,7 @@
 package cache
 
 import (
-	"github.com/psilva261/opossum"
+	"github.com/psilva261/mycel"
 	"sort"
 	"time"
 )
@@ -24,7 +24,7 @@
 
 type Item struct {
 	Addr string
-	opossum.ContentType
+	mycel.ContentType
 	Buf  []byte
 	Used time.Time
 }
--- a/browser/duitx/grid.go
+++ b/browser/duitx/grid.go
@@ -26,7 +26,7 @@
 
 	"9fans.net/go/draw"
 	"github.com/mjl-/duit"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel/logger"
 )
 
 // Grid lays out other duit.UIs in a table-like grid.
--- a/browser/duitx/scroll.go
+++ b/browser/duitx/scroll.go
@@ -28,7 +28,7 @@
 
 	"9fans.net/go/draw"
 	"github.com/mjl-/duit"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel/logger"
 )
 
 const maxAge = time.Minute
--- a/browser/experimental.go
+++ b/browser/experimental.go
@@ -2,8 +2,8 @@
 
 import (
 	"fmt"
-	"github.com/psilva261/opossum/js"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel/js"
+	"github.com/psilva261/mycel/logger"
 )
 
 func processJS2() (resHtm string, changed bool, err error) {
--- a/browser/experimental_test.go
+++ b/browser/experimental_test.go
@@ -3,12 +3,12 @@
 import (
 	"context"
 	"fmt"
-	"github.com/psilva261/opossum"
-	"github.com/psilva261/opossum/browser/fs"
-	"github.com/psilva261/opossum/logger"
-	"github.com/psilva261/opossum/js"
-	"github.com/psilva261/opossum/nodes"
-	"github.com/psilva261/opossum/style"
+	"github.com/psilva261/mycel"
+	"github.com/psilva261/mycel/browser/fs"
+	"github.com/psilva261/mycel/logger"
+	"github.com/psilva261/mycel/js"
+	"github.com/psilva261/mycel/nodes"
+	"github.com/psilva261/mycel/style"
 	"golang.org/x/net/html"
 	"io/ioutil"
 	"net/url"
@@ -38,8 +38,8 @@
 	return nil, fmt.Errorf("not implemented")
 }
 
-func (tf *TestFetcher) Get(*url.URL) ([]byte, opossum.ContentType, error) {
-	return nil, opossum.ContentType{}, fmt.Errorf("not implemented")
+func (tf *TestFetcher) Get(*url.URL) ([]byte, mycel.ContentType, error) {
+	return nil, mycel.ContentType{}, fmt.Errorf("not implemented")
 }
 
 func TestProcessJS2SkipFailure(t *testing.T) {
--- a/browser/fs/experimental.go
+++ b/browser/fs/experimental.go
@@ -5,9 +5,9 @@
 	"fmt"
 	"github.com/knusbaum/go9p/fs"
 	"github.com/knusbaum/go9p/proto"
-	"github.com/psilva261/opossum/logger"
-	"github.com/psilva261/opossum/nodes"
-	"github.com/psilva261/opossum/style"
+	"github.com/psilva261/mycel/logger"
+	"github.com/psilva261/mycel/nodes"
+	"github.com/psilva261/mycel/style"
 	"golang.org/x/net/html"
 )
 
--- a/browser/fs/fs.go
+++ b/browser/fs/fs.go
@@ -6,9 +6,9 @@
 	"fmt"
 	"github.com/knusbaum/go9p/fs"
 	"github.com/knusbaum/go9p/proto"
-	"github.com/psilva261/opossum"
-	"github.com/psilva261/opossum/logger"
-	"github.com/psilva261/opossum/nodes"
+	"github.com/psilva261/mycel"
+	"github.com/psilva261/mycel/logger"
+	"github.com/psilva261/mycel/nodes"
 	"net"
 	"net/http"
 	"os/user"
@@ -29,7 +29,7 @@
 	jsDir   *fs.StaticDir
 	rt      *Node
 	Client  *http.Client
-	Fetcher opossum.Fetcher
+	Fetcher mycel.Fetcher
 )
 
 func init() {
@@ -53,7 +53,7 @@
 		return "", "", fmt.Errorf("current user: %w", err)
 	}
 	un = u.Username
-	gn, err = opossum.Group(u)
+	gn, err = mycel.Group(u)
 	if err != nil {
 		return "", "", fmt.Errorf("group: %v", err)
 	}
--- a/browser/fs/fs_plan9.go
+++ b/browser/fs/fs_plan9.go
@@ -3,7 +3,7 @@
 import (
 	"fmt"
 	"github.com/knusbaum/go9p"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel/logger"
 	"os"
 	"syscall"
 )
@@ -21,7 +21,7 @@
 		}
 	}()
 
-	if err = syscall.Mount(int(f2.Fd()), -1, "/mnt/opossum", syscall.MCREATE, ""); err != nil {
+	if err = syscall.Mount(int(f2.Fd()), -1, "/mnt/mycel", syscall.MCREATE, ""); err != nil {
 		return fmt.Errorf("mount: %w", err)
 	}
 	return
--- a/browser/fs/fs_unix.go
+++ b/browser/fs/fs_unix.go
@@ -7,5 +7,5 @@
 )
 
 func post(srv go9p.Srv) (err error) {
-	return go9p.PostSrv("opossum", srv)
+	return go9p.PostSrv("mycel", srv)
 }
--- a/browser/website.go
+++ b/browser/website.go
@@ -2,13 +2,13 @@
 
 import (
 	"github.com/mjl-/duit"
-	"github.com/psilva261/opossum"
-	"github.com/psilva261/opossum/browser/duitx"
-	"github.com/psilva261/opossum/browser/fs"
-	"github.com/psilva261/opossum/js"
-	"github.com/psilva261/opossum/logger"
-	"github.com/psilva261/opossum/nodes"
-	"github.com/psilva261/opossum/style"
+	"github.com/psilva261/mycel"
+	"github.com/psilva261/mycel/browser/duitx"
+	"github.com/psilva261/mycel/browser/fs"
+	"github.com/psilva261/mycel/js"
+	"github.com/psilva261/mycel/logger"
+	"github.com/psilva261/mycel/nodes"
+	"github.com/psilva261/mycel/style"
 	"golang.org/x/net/html"
 	"golang.org/x/text/encoding"
 	"net/url"
@@ -22,10 +22,10 @@
 
 type Website struct {
 	duit.UI
-	opossum.ContentType
+	mycel.ContentType
 }
 
-func (w *Website) layout(f opossum.Fetcher, htm string, layouting int) {
+func (w *Website) layout(f mycel.Fetcher, htm string, layouting int) {
 	defer func() {
 		browser.StatusCh <- ""
 	}()
@@ -62,7 +62,7 @@
 				}
 				style.MergeNodeMaps(nodeMap, nm)
 			} else {
-				log.Errorf("%v/css/%v.css: Fetch CSS Rules failed: %v", opossum.PathPrefix, i, err)
+				log.Errorf("%v/css/%v.css: Fetch CSS Rules failed: %v", mycel.PathPrefix, i, err)
 			}
 		}
 
@@ -161,7 +161,7 @@
 	fs.SetDOM(nt)
 }
 
-func cssSrcs(f opossum.Fetcher, doc *html.Node) (srcs []string) {
+func cssSrcs(f mycel.Fetcher, doc *html.Node) (srcs []string) {
 	srcs = make([]string, 0, 20)
 	srcs = append(srcs, style.AddOnCSS)
 	ntAll := nodes.NewNodeTree(doc, style.Map{}, make(map[*html.Node]style.Map), nil)
@@ -236,7 +236,7 @@
 	return
 }
 
-func escapeValues(ct opossum.ContentType, q url.Values) (qe url.Values) {
+func escapeValues(ct mycel.ContentType, q url.Values) (qe url.Values) {
 	qe = make(url.Values)
 	enc := encoding.HTMLEscapeUnsupported(ct.Encoding().NewEncoder())
 
@@ -262,7 +262,7 @@
 func (b *Browser) submit(form *html.Node, submitBtn *html.Node) {
 	var err error
 	var buf []byte
-	var contentType opossum.ContentType
+	var contentType mycel.ContentType
 
 	method := "GET" // TODO
 	if m := attr(*form, "method"); m != "" {
--- a/browser/website_test.go
+++ b/browser/website_test.go
@@ -1,7 +1,7 @@
 package browser
 
 import (
-	"github.com/psilva261/opossum"
+	"github.com/psilva261/mycel"
 	"golang.org/x/net/html"
 	"net/url"
 	"strings"
@@ -52,7 +52,7 @@
 		q.Set(k, vs[0])
 	}
 
-	ct := opossum.ContentType{
+	ct := mycel.ContentType{
 		MediaType: "text/html",
 		Params: map[string]string{
 			"charset": "UTF-8",
--- /dev/null
+++ b/cmd/mycel/main.go
@@ -1,0 +1,352 @@
+package main
+
+import (
+	"9fans.net/go/draw"
+	"fmt"
+	"github.com/mjl-/duit"
+	"github.com/psilva261/mycel/browser"
+	"github.com/psilva261/mycel/js"
+	"github.com/psilva261/mycel/logger"
+	"github.com/psilva261/mycel/style"
+	"image"
+	"net/url"
+	"os"
+	"os/signal"
+	"runtime"
+	"runtime/pprof"
+	"strings"
+	"time"
+)
+
+var (
+	dui        *duit.DUI
+	b          *browser.Browser
+	cpuprofile string
+	memprofile string
+	loc        string = "http://9p.io"
+	dbg        bool
+	v          View
+	Style      = style.Map{}
+)
+
+func init() {
+	browser.EnableNoScriptTag = true
+}
+
+type View interface {
+	Render() []*duit.Kid
+}
+
+type Nav struct {
+	LocationField *duit.Field
+	StatusBar     *duit.Label
+}
+
+func NewNav() (n *Nav) {
+	n = &Nav{
+		StatusBar: &duit.Label{
+			Text: "",
+		},
+	}
+	n.LocationField = &duit.Field{
+		Text: loc,
+		Font: Style.Font(),
+		Keys: n.keys,
+	}
+	return
+}
+
+func (n *Nav) keys(k rune, m draw.Mouse) (e duit.Event) {
+	if k == browser.EnterKey && !b.Loading() {
+		a := n.LocationField.Text
+		if !strings.HasPrefix(strings.ToLower(a), "http") {
+			a = "http://" + a
+		}
+		u, err := url.Parse(a)
+		if err != nil {
+			log.Errorf("parse url: %v", err)
+			return
+		}
+		return b.LoadUrl(u)
+	}
+	return
+}
+
+func (n *Nav) Render() []*duit.Kid {
+	uis := []duit.UI{
+		&duit.Grid{
+			Columns: 3,
+			Halign:  []duit.Halign{duit.HalignLeft, duit.HalignLeft, duit.HalignRight},
+			Valign:  []duit.Valign{duit.ValignMiddle, duit.ValignMiddle, duit.ValignMiddle},
+			Kids: duit.NewKids(
+				&duit.Button{
+					Text:  "Back",
+					Font:  browser.Style.Font(),
+					Click: b.Back,
+				},
+				&duit.Button{
+					Text:  "Stop",
+					Font:  browser.Style.Font(),
+					Click: func() duit.Event {
+						b.Cancel()
+						return duit.Event{
+							Consumed: true,
+						}
+					},
+				},
+				&duit.Box{
+					Kids: duit.NewKids(
+						n.LocationField,
+					),
+				},
+			),
+		},
+		n.StatusBar,
+	}
+	if b != nil {
+		uis = append(uis, b.Website)
+	}
+	return duit.NewKids(uis...)
+}
+
+type Confirm struct {
+	text  string
+	value string
+	res   chan *string
+	done  bool
+}
+
+func (c *Confirm) Render() []*duit.Kid {
+	f := &duit.Field{
+		Text: c.value,
+	}
+	return duit.NewKids(
+		&duit.Grid{
+			Columns: 3,
+			Padding: duit.NSpace(3, duit.SpaceXY(5, 3)),
+			Halign:  []duit.Halign{duit.HalignLeft, duit.HalignLeft, duit.HalignRight},
+			Valign:  []duit.Valign{duit.ValignMiddle, duit.ValignMiddle, duit.ValignMiddle},
+			Kids: duit.NewKids(
+				&duit.Button{
+					Text: "Ok",
+					Font: browser.Style.Font(),
+					Click: func() (e duit.Event) {
+						if c.done {
+							return
+						}
+						s := f.Text
+						c.res <- &s
+						c.done = true
+						e.Consumed = true
+						v = NewNav()
+						render()
+						return
+					},
+				},
+				&duit.Button{
+					Text: "Abort",
+					Font: browser.Style.Font(),
+					Click: func() (e duit.Event) {
+						if c.done {
+							return
+						}
+						close(c.res)
+						c.done = true
+						e.Consumed = true
+						v = NewNav()
+						render()
+						return
+					},
+				},
+				f,
+			),
+		},
+		&duit.Label{
+			Text: c.text,
+		},
+	)
+}
+
+func render() {
+	white, err := dui.Display.AllocImage(image.Rect(0, 0, 1, 1), draw.ARGB32, true, 0xffffffff)
+	if err != nil {
+		log.Errorf("%v", err)
+	}
+	dui.Top.UI = &duit.Box{
+		Kids:       v.Render(),
+		Background: white,
+	}
+	if b != nil {
+		browser.PrintTree(b.Website.UI)
+	}
+	log.Printf("Render.....")
+	dui.MarkLayout(dui.Top.UI)
+	dui.MarkDraw(dui.Top.UI)
+	dui.Render()
+	log.Printf("Rendering done")
+}
+
+func Main() (err error) {
+	dui, err = duit.NewDUI("mycel", nil) // TODO: rm global var
+	if err != nil {
+		return fmt.Errorf("new dui: %w", err)
+	}
+	dui.Debug = dbg
+	resize()
+
+	style.Init(dui)
+	v = NewNav()
+	render()
+
+	b = &browser.Browser{
+		LocCh: make(chan string, 10),
+	}
+	b = browser.NewBrowser(dui, loc)
+	b.Download = func(res chan *string) {
+		v = &Confirm{
+			text:  fmt.Sprintf("Download %v", b.URL()),
+			value: "/download.file",
+			res:   res,
+		}
+		render()
+		return
+	}
+	v = NewNav()
+	render()
+
+	for {
+		select {
+		case e := <-dui.Inputs:
+			//log.Infof("e=%v", e)
+			dui.Input(e)
+			if e.Type == duit.InputResize {
+				resize()
+			}
+
+		case loc = <-b.LocCh:
+			log.Infof("loc=%v", loc)
+			ue, err := url.QueryUnescape(loc)
+			if err == nil {
+				loc = ue
+			} else {
+				log.Errorf("unescape %v: %v", loc, err)
+			}
+			if nav, ok := v.(*Nav); ok {
+				nav.LocationField.Text = loc
+			}
+
+		case msg := <-b.StatusCh:
+			if nav, ok := v.(*Nav); ok {
+				if msg == "" {
+					nav.StatusBar.Text = ""
+				} else {
+					nav.StatusBar.Text += msg + "\n"
+				}
+				dui.MarkLayout(nav.StatusBar)
+				dui.MarkDraw(nav.StatusBar)
+				dui.Render()
+			}
+
+		case err, ok := <-dui.Error:
+			//log.Infof("err=%v", err)
+			if !ok {
+				finalize()
+				return nil
+			}
+			log.Printf("main: duit: %s\n", err)
+		}
+	}
+}
+
+func usage() {
+	fmt.Printf("usage: mycel [-v|-vv] [-h] [-jsinsecure] [-cpu|-mem fn] [startPage]\n")
+	os.Exit(1)
+}
+
+func main() {
+	quiet := true
+	args := os.Args[1:]
+	for len(args) > 0 {
+		switch args[0] {
+		case "-vv":
+			quiet = false
+			dbg = true
+			args = args[1:]
+		case "-v":
+			quiet = false
+			args = args[1:]
+		case "-h":
+			usage()
+			args = args[1:]
+		case "-jsinsecure":
+			browser.ExperimentalJsInsecure = true
+			args = args[1:]
+		case "-cpu":
+			cpuprofile, args = args[1], args[2:]
+		case "-mem":
+			memprofile, args = args[1], args[2:]
+		default:
+			if len(args) > 1 {
+				usage()
+			}
+			loc, args = args[0], args[1:]
+		}
+	}
+
+	if quiet {
+		log.SetQuiet()
+	}
+
+	if cpuprofile != "" {
+		f, err := os.Create(cpuprofile)
+		if err != nil {
+			log.Fatalf("%v", err)
+		}
+		pprof.StartCPUProfile(f)
+		go func() {
+			<-time.After(time.Minute)
+			pprof.StopCPUProfile()
+			f.Close()
+			os.Exit(2)
+		}()
+	}
+
+	if memprofile != "" {
+		f, err := os.Create(memprofile)
+		if err != nil {
+			log.Fatalf("%v", err)
+		}
+		go func() {
+			<-time.After(time.Minute)
+			runtime.GC()
+			pprof.WriteHeapProfile(f)
+			f.Close()
+			os.Exit(2)
+		}()
+	}
+
+	log.Debug = dbg
+
+	done := make(chan os.Signal, 1)
+	signal.Notify(done, os.Interrupt, os.Kill)
+
+	go func() {
+		<-done
+		finalize()
+	}()
+
+	if err := Main(); err != nil {
+		log.Fatalf("Main: %v", err)
+	}
+}
+
+func finalize() {
+	js.Stop()
+	os.Exit(1)
+}
+
+func resize() {
+	size := dui.Display.ScreenImage.R.Size()
+	style.WindowWidth = size.X/dui.Scale(1)
+	style.WindowHeight = size.Y/dui.Scale(1)
+}
--- /dev/null
+++ b/cmd/mycel/main_test.go
@@ -1,0 +1,1 @@
+package main
--- a/cmd/opossum/main.go
+++ /dev/null
@@ -1,352 +1,0 @@
-package main
-
-import (
-	"9fans.net/go/draw"
-	"fmt"
-	"github.com/mjl-/duit"
-	"github.com/psilva261/opossum/browser"
-	"github.com/psilva261/opossum/js"
-	"github.com/psilva261/opossum/logger"
-	"github.com/psilva261/opossum/style"
-	"image"
-	"net/url"
-	"os"
-	"os/signal"
-	"runtime"
-	"runtime/pprof"
-	"strings"
-	"time"
-)
-
-var (
-	dui        *duit.DUI
-	b          *browser.Browser
-	cpuprofile string
-	memprofile string
-	loc        string = "http://9p.io"
-	dbg        bool
-	v          View
-	Style      = style.Map{}
-)
-
-func init() {
-	browser.EnableNoScriptTag = true
-}
-
-type View interface {
-	Render() []*duit.Kid
-}
-
-type Nav struct {
-	LocationField *duit.Field
-	StatusBar     *duit.Label
-}
-
-func NewNav() (n *Nav) {
-	n = &Nav{
-		StatusBar: &duit.Label{
-			Text: "",
-		},
-	}
-	n.LocationField = &duit.Field{
-		Text: loc,
-		Font: Style.Font(),
-		Keys: n.keys,
-	}
-	return
-}
-
-func (n *Nav) keys(k rune, m draw.Mouse) (e duit.Event) {
-	if k == browser.EnterKey && !b.Loading() {
-		a := n.LocationField.Text
-		if !strings.HasPrefix(strings.ToLower(a), "http") {
-			a = "http://" + a
-		}
-		u, err := url.Parse(a)
-		if err != nil {
-			log.Errorf("parse url: %v", err)
-			return
-		}
-		return b.LoadUrl(u)
-	}
-	return
-}
-
-func (n *Nav) Render() []*duit.Kid {
-	uis := []duit.UI{
-		&duit.Grid{
-			Columns: 3,
-			Halign:  []duit.Halign{duit.HalignLeft, duit.HalignLeft, duit.HalignRight},
-			Valign:  []duit.Valign{duit.ValignMiddle, duit.ValignMiddle, duit.ValignMiddle},
-			Kids: duit.NewKids(
-				&duit.Button{
-					Text:  "Back",
-					Font:  browser.Style.Font(),
-					Click: b.Back,
-				},
-				&duit.Button{
-					Text:  "Stop",
-					Font:  browser.Style.Font(),
-					Click: func() duit.Event {
-						b.Cancel()
-						return duit.Event{
-							Consumed: true,
-						}
-					},
-				},
-				&duit.Box{
-					Kids: duit.NewKids(
-						n.LocationField,
-					),
-				},
-			),
-		},
-		n.StatusBar,
-	}
-	if b != nil {
-		uis = append(uis, b.Website)
-	}
-	return duit.NewKids(uis...)
-}
-
-type Confirm struct {
-	text  string
-	value string
-	res   chan *string
-	done  bool
-}
-
-func (c *Confirm) Render() []*duit.Kid {
-	f := &duit.Field{
-		Text: c.value,
-	}
-	return duit.NewKids(
-		&duit.Grid{
-			Columns: 3,
-			Padding: duit.NSpace(3, duit.SpaceXY(5, 3)),
-			Halign:  []duit.Halign{duit.HalignLeft, duit.HalignLeft, duit.HalignRight},
-			Valign:  []duit.Valign{duit.ValignMiddle, duit.ValignMiddle, duit.ValignMiddle},
-			Kids: duit.NewKids(
-				&duit.Button{
-					Text: "Ok",
-					Font: browser.Style.Font(),
-					Click: func() (e duit.Event) {
-						if c.done {
-							return
-						}
-						s := f.Text
-						c.res <- &s
-						c.done = true
-						e.Consumed = true
-						v = NewNav()
-						render()
-						return
-					},
-				},
-				&duit.Button{
-					Text: "Abort",
-					Font: browser.Style.Font(),
-					Click: func() (e duit.Event) {
-						if c.done {
-							return
-						}
-						close(c.res)
-						c.done = true
-						e.Consumed = true
-						v = NewNav()
-						render()
-						return
-					},
-				},
-				f,
-			),
-		},
-		&duit.Label{
-			Text: c.text,
-		},
-	)
-}
-
-func render() {
-	white, err := dui.Display.AllocImage(image.Rect(0, 0, 1, 1), draw.ARGB32, true, 0xffffffff)
-	if err != nil {
-		log.Errorf("%v", err)
-	}
-	dui.Top.UI = &duit.Box{
-		Kids:       v.Render(),
-		Background: white,
-	}
-	if b != nil {
-		browser.PrintTree(b.Website.UI)
-	}
-	log.Printf("Render.....")
-	dui.MarkLayout(dui.Top.UI)
-	dui.MarkDraw(dui.Top.UI)
-	dui.Render()
-	log.Printf("Rendering done")
-}
-
-func Main() (err error) {
-	dui, err = duit.NewDUI("opossum", nil) // TODO: rm global var
-	if err != nil {
-		return fmt.Errorf("new dui: %w", err)
-	}
-	dui.Debug = dbg
-	resize()
-
-	style.Init(dui)
-	v = NewNav()
-	render()
-
-	b = &browser.Browser{
-		LocCh: make(chan string, 10),
-	}
-	b = browser.NewBrowser(dui, loc)
-	b.Download = func(res chan *string) {
-		v = &Confirm{
-			text:  fmt.Sprintf("Download %v", b.URL()),
-			value: "/download.file",
-			res:   res,
-		}
-		render()
-		return
-	}
-	v = NewNav()
-	render()
-
-	for {
-		select {
-		case e := <-dui.Inputs:
-			//log.Infof("e=%v", e)
-			dui.Input(e)
-			if e.Type == duit.InputResize {
-				resize()
-			}
-
-		case loc = <-b.LocCh:
-			log.Infof("loc=%v", loc)
-			ue, err := url.QueryUnescape(loc)
-			if err == nil {
-				loc = ue
-			} else {
-				log.Errorf("unescape %v: %v", loc, err)
-			}
-			if nav, ok := v.(*Nav); ok {
-				nav.LocationField.Text = loc
-			}
-
-		case msg := <-b.StatusCh:
-			if nav, ok := v.(*Nav); ok {
-				if msg == "" {
-					nav.StatusBar.Text = ""
-				} else {
-					nav.StatusBar.Text += msg + "\n"
-				}
-				dui.MarkLayout(nav.StatusBar)
-				dui.MarkDraw(nav.StatusBar)
-				dui.Render()
-			}
-
-		case err, ok := <-dui.Error:
-			//log.Infof("err=%v", err)
-			if !ok {
-				finalize()
-				return nil
-			}
-			log.Printf("main: duit: %s\n", err)
-		}
-	}
-}
-
-func usage() {
-	fmt.Printf("usage: opossum [-v|-vv] [-h] [-jsinsecure] [-cpu|-mem fn] [startPage]\n")
-	os.Exit(1)
-}
-
-func main() {
-	quiet := true
-	args := os.Args[1:]
-	for len(args) > 0 {
-		switch args[0] {
-		case "-vv":
-			quiet = false
-			dbg = true
-			args = args[1:]
-		case "-v":
-			quiet = false
-			args = args[1:]
-		case "-h":
-			usage()
-			args = args[1:]
-		case "-jsinsecure":
-			browser.ExperimentalJsInsecure = true
-			args = args[1:]
-		case "-cpu":
-			cpuprofile, args = args[1], args[2:]
-		case "-mem":
-			memprofile, args = args[1], args[2:]
-		default:
-			if len(args) > 1 {
-				usage()
-			}
-			loc, args = args[0], args[1:]
-		}
-	}
-
-	if quiet {
-		log.SetQuiet()
-	}
-
-	if cpuprofile != "" {
-		f, err := os.Create(cpuprofile)
-		if err != nil {
-			log.Fatalf("%v", err)
-		}
-		pprof.StartCPUProfile(f)
-		go func() {
-			<-time.After(time.Minute)
-			pprof.StopCPUProfile()
-			f.Close()
-			os.Exit(2)
-		}()
-	}
-
-	if memprofile != "" {
-		f, err := os.Create(memprofile)
-		if err != nil {
-			log.Fatalf("%v", err)
-		}
-		go func() {
-			<-time.After(time.Minute)
-			runtime.GC()
-			pprof.WriteHeapProfile(f)
-			f.Close()
-			os.Exit(2)
-		}()
-	}
-
-	log.Debug = dbg
-
-	done := make(chan os.Signal, 1)
-	signal.Notify(done, os.Interrupt, os.Kill)
-
-	go func() {
-		<-done
-		finalize()
-	}()
-
-	if err := Main(); err != nil {
-		log.Fatalf("Main: %v", err)
-	}
-}
-
-func finalize() {
-	js.Stop()
-	os.Exit(1)
-}
-
-func resize() {
-	size := dui.Display.ScreenImage.R.Size()
-	style.WindowWidth = size.X/dui.Scale(1)
-	style.WindowHeight = size.Y/dui.Scale(1)
-}
--- a/cmd/opossum/main_test.go
+++ /dev/null
@@ -1,1 +1,0 @@
-package main
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/psilva261/opossum
+module github.com/psilva261/mycel
 
 go 1.18
 
--- a/img/img.go
+++ b/img/img.go
@@ -6,8 +6,8 @@
 	"encoding/base64"
 	"fmt"
 	"github.com/mjl-/duit"
-	"github.com/psilva261/opossum"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel"
+	"github.com/psilva261/mycel/logger"
 	"github.com/srwiley/oksvg"
 	"github.com/srwiley/rasterx"
 	xdraw "golang.org/x/image/draw"
@@ -24,7 +24,7 @@
 
 const SrcZero = "//:0"
 
-func parseDataUri(addr string) (data []byte, ct opossum.ContentType, err error) {
+func parseDataUri(addr string) (data []byte, ct mycel.ContentType, err error) {
 	addr = strings.TrimPrefix(addr, "data:")
 	if strings.Contains(addr, "charset=UTF-8") {
 		return nil, ct, fmt.Errorf("cannot handle charset")
@@ -38,7 +38,7 @@
 	} else {
 		ctStr = parts[0]
 	}
-	if ct, err = opossum.NewContentType(ctStr, nil); err != nil {
+	if ct, err = mycel.NewContentType(ctStr, nil); err != nil {
 		return nil, ct, fmt.Errorf("content type: %v: %w", ctStr, err)
 	}
 
@@ -193,7 +193,7 @@
 }
 
 // Load and resize to w and h if != 0
-func Load(dui *duit.DUI, f opossum.Fetcher, src string, maxW, w, h int, forceSync bool) (ni *draw.Image, err error) {
+func Load(dui *duit.DUI, f mycel.Fetcher, src string, maxW, w, h int, forceSync bool) (ni *draw.Image, err error) {
 	log.Printf("Load(..., %v, maxW=%v, w=%v, h=%v, ...)", src, maxW, w, h)
 	ch := make(chan image.Image, 1)
 	var bounds draw.Rectangle
@@ -251,10 +251,10 @@
 	return
 }
 
-func load(f opossum.Fetcher, src string, maxW, w, h int) (img image.Image, err error) {
+func load(f mycel.Fetcher, src string, maxW, w, h int) (img image.Image, err error) {
 	var imgUrl *url.URL
 	var data []byte
-	var contentType opossum.ContentType
+	var contentType mycel.ContentType
 
 	if strings.HasPrefix(src, "data:") {
 		if data, contentType, err = parseDataUri(src); err != nil {
--- a/img/img_test.go
+++ b/img/img_test.go
@@ -3,8 +3,8 @@
 import (
 	"bytes"
 	"context"
-	"github.com/psilva261/opossum"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel"
+	"github.com/psilva261/mycel/logger"
 	"image"
 	"image/png"
 	"net/url"
@@ -104,8 +104,8 @@
 
 func (b *MockBrowser) LinkedUrl(string) (*url.URL, error) { return nil, nil }
 
-func (b *MockBrowser) Get(*url.URL) ([]byte, opossum.ContentType, error) {
-	return b.data, opossum.ContentType{}, nil
+func (b *MockBrowser) Get(*url.URL) ([]byte, mycel.ContentType, error) {
+	return b.data, mycel.ContentType{}, nil
 }
 
 func TestLoad(t *testing.T) {
--- a/js/js.go
+++ b/js/js.go
@@ -5,9 +5,9 @@
 	"bytes"
 	"context"
 	"fmt"
-	"github.com/psilva261/opossum"
-	"github.com/psilva261/opossum/logger"
-	"github.com/psilva261/opossum/nodes"
+	"github.com/psilva261/mycel"
+	"github.com/psilva261/mycel/logger"
+	"github.com/psilva261/mycel/nodes"
 	"golang.org/x/net/html"
 	"io"
 	"os"
@@ -19,7 +19,7 @@
 var timeout = 60 * time.Second
 
 var (
-	fetcher opossum.Fetcher
+	fetcher mycel.Fetcher
 
 	service string
 	cmd *exec.Cmd
@@ -26,7 +26,7 @@
 	cancel  context.CancelFunc
 )
 
-func SetFetcher(f opossum.Fetcher) {
+func SetFetcher(f mycel.Fetcher) {
 	fetcher = f
 }
 
@@ -171,7 +171,7 @@
 			for _, a := range n.DomSubtree.Attr {
 				switch strings.ToLower(a.Key) {
 				case "type":
-					t, err := opossum.NewContentType(a.Val, nil)
+					t, err := mycel.NewContentType(a.Val, nil)
 					if err != nil {
 						log.Printf("t: %v", err)
 					}
--- a/js/js_test.go
+++ b/js/js_test.go
@@ -3,11 +3,11 @@
 import (
 	"context"
 	"fmt"
-	"github.com/psilva261/opossum"
-	"github.com/psilva261/opossum/browser/fs"
-	"github.com/psilva261/opossum/logger"
-	"github.com/psilva261/opossum/nodes"
-	"github.com/psilva261/opossum/style"
+	"github.com/psilva261/mycel"
+	"github.com/psilva261/mycel/browser/fs"
+	"github.com/psilva261/mycel/logger"
+	"github.com/psilva261/mycel/nodes"
+	"github.com/psilva261/mycel/style"
 	"golang.org/x/net/html"
 	"io/ioutil"
 	"net/url"
@@ -46,8 +46,8 @@
 	return nil, fmt.Errorf("not implemented")
 }
 
-func (tf *TestFetcher) Get(*url.URL) ([]byte, opossum.ContentType, error) {
-	return nil, opossum.ContentType{}, fmt.Errorf("not implemented")
+func (tf *TestFetcher) Get(*url.URL) ([]byte, mycel.ContentType, error) {
+	return nil, mycel.ContentType{}, fmt.Errorf("not implemented")
 }
 
 func TestJQueryHide(t *testing.T) {
--- a/js/js_unix.go
+++ b/js/js_unix.go
@@ -6,7 +6,7 @@
 	"9fans.net/go/plan9"
 	"9fans.net/go/plan9/client"
 	"fmt"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel/logger"
 	"io"
 	"os/user"
 )
--- /dev/null
+++ b/mycel.go
@@ -1,0 +1,124 @@
+package mycel
+
+import (
+	"bytes"
+	"context"
+	"github.com/psilva261/mycel/logger"
+	"golang.org/x/text/encoding"
+	"golang.org/x/text/encoding/htmlindex"
+	"golang.org/x/text/encoding/unicode"
+	"io/ioutil"
+	"mime"
+	"net/url"
+	"strings"
+)
+
+type Fetcher interface {
+	Ctx() context.Context
+	Origin() *url.URL
+
+	// LinkedUrl relative to current page
+	LinkedUrl(string) (*url.URL, error)
+
+	Get(*url.URL) ([]byte, ContentType, error)
+}
+
+type ContentType struct {
+	MediaType string
+	Params    map[string]string
+}
+
+// NewContentType based on mime type string and url including file extension as fallback
+func NewContentType(s string, u *url.URL) (c ContentType, err error) {
+	if s == "" && u != nil && strings.Contains(u.String(), ".") {
+		l := strings.Split(u.String(), ".")
+		ext := l[len(l)-1]
+		switch ext {
+		case "jpg":
+			return NewContentType("image/jpeg", u)
+		case "png":
+			return NewContentType("image/png", u)
+		case "gif":
+			return NewContentType("image/gif", u)
+		default:
+			return ContentType{}, nil
+		}
+	}
+	c.MediaType, c.Params, err = mime.ParseMediaType(s)
+	return
+}
+
+func (c ContentType) IsEmpty() bool {
+	return c.MediaType == ""
+}
+
+func (c ContentType) IsHTML() bool {
+	return c.MediaType == "text/html"
+}
+
+func (c ContentType) IsCSS() bool {
+	return c.MediaType != "text/html"
+}
+
+func (c ContentType) IsJS() bool {
+	for _, t := range []string{"application/javascript", "application/ecmascript", "text/javascript", "text/ecmascript"} {
+		if t == c.MediaType {
+			return true
+		}
+	}
+	return false
+}
+
+func (c ContentType) IsPlain() bool {
+	return c.MediaType == "text/plain"
+}
+
+func (c ContentType) IsDownload() bool {
+	return c.MediaType == "application/octet-stream" ||
+		c.MediaType == "application/zip"
+}
+
+func (c ContentType) IsSvg() bool {
+	return c.MediaType == "image/svg+xml"
+}
+
+func (c ContentType) Charset() (cs string) {
+	cs, ok := c.Params["charset"]
+	if !ok {
+		return "UTF-8"
+	}
+	return
+}
+
+func (c ContentType) Encoding() (e encoding.Encoding) {
+	charset, ok := c.Params["charset"]
+	if !ok || charset == "utf8" || charset == "utf-8" {
+		return unicode.UTF8
+	}
+	e, err := htmlindex.Get(charset)
+	if err != nil || e == nil {
+		log.Errorf("encoding %v: %v", charset, err)
+		return unicode.UTF8
+	}
+	return
+}
+
+func (c ContentType) Utf8(buf []byte) string {
+	e := c.Encoding()
+
+	if e == unicode.UTF8 {
+		return string(buf)
+	}
+
+	r := bytes.NewReader(buf)
+	cr := e.NewDecoder().Reader(r)
+
+	updated, err := ioutil.ReadAll(cr)
+	if err == nil {
+		buf = updated
+	} else {
+		log.Errorf("utf8: unable to decode to %v: %v", e, err)
+	}
+
+	return string(buf)
+}
binary files /dev/null b/mycel.jpg differ
--- /dev/null
+++ b/mycel_plan9.go
@@ -1,0 +1,11 @@
+package mycel
+
+import (
+	"os/user"
+)
+
+const PathPrefix = "/mnt/mycel"
+
+func Group(u *user.User) (string, error) {
+	return u.Gid, nil
+}
--- /dev/null
+++ b/mycel_unix.go
@@ -1,0 +1,18 @@
+//go:build !plan9
+
+package mycel
+
+import (
+	"fmt"
+	"os/user"
+)
+
+const PathPrefix = "mycel"
+
+func Group(u *user.User) (string, error) {
+	g, err := user.LookupGroupId(u.Gid)
+	if err != nil {
+		return "", fmt.Errorf("get group: %w", err)
+	}
+	return g.Name, nil
+}
--- a/nodes/experimental.go
+++ b/nodes/experimental.go
@@ -3,7 +3,7 @@
 import (
 	"fmt"
 	"github.com/andybalholm/cascadia"
-	"github.com/psilva261/opossum"
+	"github.com/psilva261/mycel"
 	"golang.org/x/net/html"
 )
 
@@ -11,7 +11,7 @@
 func (n *Node) Path() (p string, ok bool) {
 	p, ok = n.path()
 	if ok {
-		p = opossum.PathPrefix + p
+		p = mycel.PathPrefix + p
 	}
 	return
 }
--- a/nodes/experimental_test.go
+++ b/nodes/experimental_test.go
@@ -2,8 +2,8 @@
 
 import (
 	"fmt"
-	"github.com/psilva261/opossum"
-	"github.com/psilva261/opossum/style"
+	"github.com/psilva261/mycel"
+	"github.com/psilva261/mycel/style"
 	"golang.org/x/net/html"
 	"strings"
 	"testing"
@@ -28,7 +28,7 @@
 	p := nt.Children[0].Children[1].Children[0]
 	a := p.Children[2]
 	fmt.Printf("%v\n", a.Data())
-	if p, _ := a.Path(); p != opossum.PathPrefix+"/0/1/0/2" {
+	if p, _ := a.Path(); p != mycel.PathPrefix+"/0/1/0/2" {
 		t.Fatalf("%v", p)
 	}
 }
--- a/nodes/nodes.go
+++ b/nodes/nodes.go
@@ -3,8 +3,8 @@
 import (
 	"bytes"
 	"fmt"
-	"github.com/psilva261/opossum/logger"
-	"github.com/psilva261/opossum/style"
+	"github.com/psilva261/mycel/logger"
+	"github.com/psilva261/mycel/style"
 	"golang.org/x/net/html"
 	"image"
 	"strings"
--- a/nodes/nodes_test.go
+++ b/nodes/nodes_test.go
@@ -3,7 +3,7 @@
 import (
 	"bytes"
 	"encoding/json"
-	"github.com/psilva261/opossum/style"
+	"github.com/psilva261/mycel/style"
 	"golang.org/x/net/html"
 	"strings"
 	"testing"
--- a/opossum.go
+++ /dev/null
@@ -1,124 +1,0 @@
-package opossum
-
-import (
-	"bytes"
-	"context"
-	"github.com/psilva261/opossum/logger"
-	"golang.org/x/text/encoding"
-	"golang.org/x/text/encoding/htmlindex"
-	"golang.org/x/text/encoding/unicode"
-	"io/ioutil"
-	"mime"
-	"net/url"
-	"strings"
-)
-
-type Fetcher interface {
-	Ctx() context.Context
-	Origin() *url.URL
-
-	// LinkedUrl relative to current page
-	LinkedUrl(string) (*url.URL, error)
-
-	Get(*url.URL) ([]byte, ContentType, error)
-}
-
-type ContentType struct {
-	MediaType string
-	Params    map[string]string
-}
-
-// NewContentType based on mime type string and url including file extension as fallback
-func NewContentType(s string, u *url.URL) (c ContentType, err error) {
-	if s == "" && u != nil && strings.Contains(u.String(), ".") {
-		l := strings.Split(u.String(), ".")
-		ext := l[len(l)-1]
-		switch ext {
-		case "jpg":
-			return NewContentType("image/jpeg", u)
-		case "png":
-			return NewContentType("image/png", u)
-		case "gif":
-			return NewContentType("image/gif", u)
-		default:
-			return ContentType{}, nil
-		}
-	}
-	c.MediaType, c.Params, err = mime.ParseMediaType(s)
-	return
-}
-
-func (c ContentType) IsEmpty() bool {
-	return c.MediaType == ""
-}
-
-func (c ContentType) IsHTML() bool {
-	return c.MediaType == "text/html"
-}
-
-func (c ContentType) IsCSS() bool {
-	return c.MediaType != "text/html"
-}
-
-func (c ContentType) IsJS() bool {
-	for _, t := range []string{"application/javascript", "application/ecmascript", "text/javascript", "text/ecmascript"} {
-		if t == c.MediaType {
-			return true
-		}
-	}
-	return false
-}
-
-func (c ContentType) IsPlain() bool {
-	return c.MediaType == "text/plain"
-}
-
-func (c ContentType) IsDownload() bool {
-	return c.MediaType == "application/octet-stream" ||
-		c.MediaType == "application/zip"
-}
-
-func (c ContentType) IsSvg() bool {
-	return c.MediaType == "image/svg+xml"
-}
-
-func (c ContentType) Charset() (cs string) {
-	cs, ok := c.Params["charset"]
-	if !ok {
-		return "UTF-8"
-	}
-	return
-}
-
-func (c ContentType) Encoding() (e encoding.Encoding) {
-	charset, ok := c.Params["charset"]
-	if !ok || charset == "utf8" || charset == "utf-8" {
-		return unicode.UTF8
-	}
-	e, err := htmlindex.Get(charset)
-	if err != nil || e == nil {
-		log.Errorf("encoding %v: %v", charset, err)
-		return unicode.UTF8
-	}
-	return
-}
-
-func (c ContentType) Utf8(buf []byte) string {
-	e := c.Encoding()
-
-	if e == unicode.UTF8 {
-		return string(buf)
-	}
-
-	r := bytes.NewReader(buf)
-	cr := e.NewDecoder().Reader(r)
-
-	updated, err := ioutil.ReadAll(cr)
-	if err == nil {
-		buf = updated
-	} else {
-		log.Errorf("utf8: unable to decode to %v: %v", e, err)
-	}
-
-	return string(buf)
-}
binary files a/opossum.jpg /dev/null differ
--- a/opossum_plan9.go
+++ /dev/null
@@ -1,11 +1,0 @@
-package opossum
-
-import (
-	"os/user"
-)
-
-const PathPrefix = "/mnt/opossum"
-
-func Group(u *user.User) (string, error) {
-	return u.Gid, nil
-}
--- a/opossum_unix.go
+++ /dev/null
@@ -1,18 +1,0 @@
-//go:build !plan9
-
-package opossum
-
-import (
-	"fmt"
-	"os/user"
-)
-
-const PathPrefix = "opossum"
-
-func Group(u *user.User) (string, error) {
-	g, err := user.LookupGroupId(u.Gid)
-	if err != nil {
-		return "", fmt.Errorf("get group: %w", err)
-	}
-	return g.Name, nil
-}
--- a/package.rc
+++ b/package.rc
@@ -1,5 +1,5 @@
-name64='opossum-amd64'
-name32='opossum-386'
+name64='mycel-amd64'
+name32='mycel-386'
 version=`{date -i} ^ '-' ^ `{cat .git/fs/branch/heads/master/hash | read -c 6}
 tarball64=`{pwd} ^ '/packages/' ^ $name64 ^ '-' ^ $version ^ '.tgz'
 tarball32=`{pwd} ^ '/packages/' ^ $name32 ^ '-' ^ $version ^ '.tgz'
@@ -16,15 +16,15 @@
 	chmod +t ./$name
 
 	echo Compiling $GOARCH...
-	cd cmd/opossum
+	cd cmd/mycel
 	go build $a -ldflags '-s -w' -o $name
 	cd ../..
 	cd ../sparklefs/cmd/sparklefs
 	go build $a -ldflags '-s -w' -o sparklefs
-	cd ../../../opossum
-	mv cmd/opossum/$name ../sparklefs/cmd/sparklefs/sparklefs ./$name/
+	cd ../../../mycel
+	mv cmd/mycel/$name ../sparklefs/cmd/sparklefs/sparklefs ./$name/
 	cp README.md ./$name/
-	cp opossum.jpg ./$name/
+	cp mycel.jpg ./$name/
 	tar czf $tarball $name
 	chmod +t $tarball
 	echo Created $tarball
--- a/style/css.go
+++ b/style/css.go
@@ -4,8 +4,8 @@
 	"bytes"
 	"fmt"
 	"github.com/andybalholm/cascadia"
-	"github.com/psilva261/opossum"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel"
+	"github.com/psilva261/mycel/logger"
 	"github.com/tdewolff/parse/v2"
 	"github.com/tdewolff/parse/v2/css"
 	"io"
@@ -38,7 +38,7 @@
 	Val         string
 }
 
-func Preprocess(s string) (bs []byte, ct opossum.ContentType, imports []string, err error) {
+func Preprocess(s string) (bs []byte, ct mycel.ContentType, imports []string, err error) {
 	buf := bytes.NewBufferString("")
 	l := css.NewLexer(parse.NewInputString(s))
 	ct.MediaType = "text/css"
--- a/style/experimental.go
+++ b/style/experimental.go
@@ -3,17 +3,17 @@
 import (
 	"9fans.net/go/draw"
 	"fmt"
-	"github.com/psilva261/opossum"
-	"github.com/psilva261/opossum/img"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel"
+	"github.com/psilva261/mycel/img"
+	"github.com/psilva261/mycel/logger"
 	"image"
 	"strings"
 )
 
 var colorCache = make(map[draw.Color]*draw.Image)
-var fetcher opossum.Fetcher
+var fetcher mycel.Fetcher
 
-func SetFetcher(f opossum.Fetcher) {
+func SetFetcher(f mycel.Fetcher) {
 	fetcher = f
 }
 
--- a/style/experimental_test.go
+++ b/style/experimental_test.go
@@ -2,7 +2,7 @@
 
 import (
 	"9fans.net/go/draw"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel/logger"
 	"testing"
 )
 
--- a/style/fonts_plan9.go
+++ b/style/fonts_plan9.go
@@ -5,7 +5,7 @@
 import (
 	"9fans.net/go/draw"
 	"fmt"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel/logger"
 	"io/fs"
 	"os"
 	"regexp"
--- a/style/fonts_unix.go
+++ b/style/fonts_unix.go
@@ -4,7 +4,7 @@
 
 import (
 	"fmt"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel/logger"
 	"os/exec"
 	"regexp"
 	"strconv"
--- a/style/stylesheets.go
+++ b/style/stylesheets.go
@@ -6,7 +6,7 @@
 	"fmt"
 	"github.com/andybalholm/cascadia"
 	"github.com/mjl-/duit"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel/logger"
 	"golang.org/x/image/colornames"
 	"golang.org/x/net/html"
 	"image"
--- a/style/stylesheets_test.go
+++ b/style/stylesheets_test.go
@@ -3,7 +3,7 @@
 import (
 	"fmt"
 	"github.com/mjl-/duit"
-	"github.com/psilva261/opossum/logger"
+	"github.com/psilva261/mycel/logger"
 	"golang.org/x/net/html"
 	"strings"
 	"testing"