summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2019-08-05 22:43:20 +0700
committerShulhan <ms@kilabit.info>2019-08-05 22:43:20 +0700
commit616712b8a644fc346681d112ce3f866315371c83 (patch)
tree42e514d8df8cb8b1821b78f968820890e0773acb
parentf9fe8ce6ed15a05a7ee15a12ed34973f33372b72 (diff)
downloadciigo-616712b8a644fc346681d112ce3f866315371c83.tar.xz
all: add support for markdown markup language
Due to the popularity of markdown format, we decide to support converting markdown file in ciigo. While at it ignore all files or directories that start with dot, which are hidden file in unix-like system.
-rw-r--r--ciigo.go28
-rw-r--r--content/index.adoc46
-rw-r--r--fileadoc.go66
-rw-r--r--filehtml.go4
-rw-r--r--generate.go38
-rw-r--r--go.mod3
-rw-r--r--go.sum4
-rw-r--r--htmlgenerator.go71
-rw-r--r--server.go65
9 files changed, 201 insertions, 124 deletions
diff --git a/ciigo.go b/ciigo.go
index 5194aae..c725096 100644
--- a/ciigo.go
+++ b/ciigo.go
@@ -4,7 +4,7 @@
//
// Package ciigo is a program to write static web server with embedded files
-// using asciidoc markup language.
+// using asciidoc and markdown markup languages.
//
// For more information see the README file at the page repository
// https://github.com/shuLhan/ciigo.
@@ -12,6 +12,28 @@
package ciigo
const (
- dirAssets = "assets"
- dirRoot = "./content"
+ dirAssets = "assets"
+ dirRoot = "./content"
+ extAsciidoc = ".adoc"
+ extMarkdown = ".md"
)
+
+const (
+ markupKindUnknown byte = iota
+ markupKindAsciidoc
+ markupKindMarkdown
+)
+
+func isExtensionMarkup(ext string) bool {
+ return ext == extAsciidoc || ext == extMarkdown
+}
+
+func markupKind(ext string) byte {
+ switch ext {
+ case extAsciidoc:
+ return markupKindAsciidoc
+ case extMarkdown:
+ return markupKindMarkdown
+ }
+ return markupKindUnknown
+}
diff --git a/content/index.adoc b/content/index.adoc
index 64b6e28..8b96204 100644
--- a/content/index.adoc
+++ b/content/index.adoc
@@ -2,7 +2,13 @@
:stylesheet: /assets/style.css
`ciigo` is a program to write static web server with embedded files using
-https://asciidoctor.org/docs/what-is-asciidoc/[asciidoc markup language].
+generated markup format.
+
+Currently, ciigo support
+https://asciidoctor.org/docs/what-is-asciidoc/[asciidoc]
+and
+https://commonmark.org/[markdown]
+as markup format.
== Document Structure
@@ -14,8 +20,8 @@ conventions,
* `content`: the root directory of static and generated content to be
embedded and served.
- This is where asciidoc files must be saved.
- Any files inside this directory (except in "content/assets") will be
+ This is where markup files must be saved.
+ Any files inside this directory, except in "content/assets", will be
ignored.
* `content/assets`: contains static files (images, js, css) that can be
@@ -24,7 +30,7 @@ conventions,
* `templates`: contains dynamic templates. Currently. there is only single
template: `html.tmpl`, that wrap the generated HTML.
-* All asciidoc files inside content must have an ".adoc" extension.
+* All markup files inside content must have an ".adoc" extension.
== Getting Started
@@ -33,7 +39,7 @@ This section describe step by step instructions on how to build and create
pages to be viewed for local development using `ciigo`.
. Clone the `ciigo` repository.
-For example, lets say we have clone the `ciigo` repository into
+For example, lets say we have cloned the `ciigo` repository into
`$HOME/go/src/github.com/shuLhan/ciigo`.
. Create new Go repository for building a website.
@@ -68,8 +74,8 @@ $ cp $HOME/go/src/github.com/shuLhan/ciigo/content/assets/style.css ./content/as
$ cp $HOME/go/src/github.com/shuLhan/ciigo/templates/html.tmpl ./templates/
----
-. Create a Go source code to generate all asciidoc files inside "content"
- directory.
+. Create a Go source code in the root repository to generate all markup files
+ inside "content" directory into HTML.
Lets named it `generate.go` with the following content,
+
----
@@ -102,7 +108,7 @@ func main() {
}
----
-. Create a new asciidoc file `index.adoc` inside "content" directory.
+. Create a new markup file `index.adoc` inside "content" directory.
Each directory, or sub directory, should have `index.adoc` to be able to
accessed by browser,
+
@@ -113,36 +119,35 @@ func main() {
Hello, world!
----
-. Build the binary on local directory with `DEBUG` environment variable is
- set,
+. Run `go generate` to convert all files with extension `.adoc` (or `.md`)
+ into HTML and embed it into `./cmd/mysite/static.go`
++
+ $ go generate
+
+. Now run the `./cmd/mysite` with `DEBUG` environment variable is set,
+
----
$ export DEBUG=1
-$ go build ./cmd/mysite
+$ go run ./cmd/mysite
----
+
Any non zero value on `DEBUG` environment signal the running program to watch
changes in ".adoc" files inside "content" directory and serve the generated
HTML directly.
-. Run the `mysite` binary on current directory,
-+
-----
-$ ./mysite
-----
-
. Open the web browser at `localhost:8080` to view the generated HTML.
You should see "Hello, world!" as the main page.
Thats it!
Create or update any ".adoc" files in "content" directory, the program will
-automatically generated the HTML file.
+automatically generated the HTML file, but you still need to refresh the web
+browser to load the new generated file.
== Deployment
-First, we need to convert all asciidoc files inside "content" into HTML and
+First, we need to convert all markup files inside "content" into HTML and
dump the content of all static files inside "content",
----
@@ -192,3 +197,6 @@ The following section list only direct third party library.
* https://github.com/bytesparadise/libasciidoc[libasciidoc].
https://raw.githubusercontent.com/bytesparadise/libasciidoc/master/LICENSE[License].
+
+* https://github.com/yuin/goldmark[goldmark].
+ https://raw.githubusercontent.com/yuin/goldmark/master/LICENSE[License].
diff --git a/fileadoc.go b/fileadoc.go
index fab7541..e6e550d 100644
--- a/fileadoc.go
+++ b/fileadoc.go
@@ -5,49 +5,54 @@
package ciigo
import (
- "context"
"fmt"
- "io"
"log"
"os"
+ "path"
"strings"
-
- "github.com/bytesparadise/libasciidoc"
)
-type fileAdoc struct {
- path string // path contains full path to ".adoc" file.
- info os.FileInfo // info contains FileInfo of ".adoc" file.
- basePath string // basePath contains full path to file without ".adoc" extension.
- metadata map[string]interface{} // metadata contains ".adoc" metadata.
+type markupFile struct {
+ kind byte // kind define the type of markup file.
+ path string // path contains full path to markup file.
+ info os.FileInfo // info contains FileInfo of markup file.
+ basePath string // basePath contains full path to file without markup extension.
+ metadata map[string]interface{} // metadata contains markup metadata.
}
-func newFileAdoc(path string, fi os.FileInfo) (adoc *fileAdoc, err error) {
- if len(path) == 0 {
- return nil, fmt.Errorf("ciigo: newFileAdoc: empty path")
+func newMarkupFile(filePath string, fi os.FileInfo) (fmarkup *markupFile, err error) {
+ if len(filePath) == 0 {
+ return nil, fmt.Errorf("ciigo: newMarkupFile: empty path")
}
if fi == nil {
- fi, err = os.Stat(path)
+ fi, err = os.Stat(filePath)
if err != nil {
- return nil, fmt.Errorf("ciigo: newFileAdoc: " + err.Error())
+ return nil, fmt.Errorf("newMarkupFile: " + err.Error())
}
}
- adoc = &fileAdoc{
- path: path,
- info: fi,
- basePath: strings.TrimSuffix(path, ".adoc"),
+ ext := strings.ToLower(path.Ext(filePath))
+
+ fmarkup = &markupFile{
+ kind: markupKind(ext),
+ path: filePath,
+ info: fi,
}
+ if fmarkup.kind == markupKindUnknown {
+ return nil, fmt.Errorf("newMarkupFile: unknown markup file " + filePath)
+ }
+
+ fmarkup.basePath = strings.TrimSuffix(filePath, ext)
- return adoc, nil
+ return fmarkup, nil
}
//
// isHTMLLatest will return true if generated HTML is exist and its
-// modification time is equal or greater than their asciidoc file; otherwise
+// modification time is equal or greater than their markup file; otherwise
// it will return false.
//
-func (fa *fileAdoc) isHTMLLatest(htmlPath string) bool {
+func (fa *markupFile) isHTMLLatest(htmlPath string) bool {
htmlInfo, err := os.Stat(htmlPath)
if err != nil {
if os.IsNotExist(err) {
@@ -65,22 +70,3 @@ func (fa *fileAdoc) isHTMLLatest(htmlPath string) bool {
return htmlTime.Equal(infoTime) || htmlTime.After(infoTime)
}
-
-//
-// toHTML convert the asciidoc file to HTML and store its metadata in
-//
-func (fa *fileAdoc) toHTML(htmlPath string, out io.Writer, force bool) {
- if fa.isHTMLLatest(htmlPath) && !force {
- return
- }
-
- var (
- ctx = context.Background()
- err error
- )
-
- fa.metadata, err = libasciidoc.ConvertFileToHTML(ctx, fa.path, out)
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/filehtml.go b/filehtml.go
index 9e023ec..d88f2da 100644
--- a/filehtml.go
+++ b/filehtml.go
@@ -35,10 +35,10 @@ func (fhtml *fileHTML) reset() {
}
//
-// unpackAdoc convert the asciidoc metadata to its HTML representation and
+// unpackMarkup convert the markup metadata to its HTML representation and
// rawBody to template.HTML.
//
-func (fhtml *fileHTML) unpackAdoc(fa *fileAdoc) {
+func (fhtml *fileHTML) unpackMarkup(fa *markupFile) {
fhtml.Metadata = make(map[string]string)
for k, v := range fa.metadata {
diff --git a/generate.go b/generate.go
index c1e3cc0..bfd97b0 100644
--- a/generate.go
+++ b/generate.go
@@ -18,17 +18,20 @@ import (
//
// Generate a static Go file to be used for building binary.
//
-// It will convert all asciidoc files inside root directory into HTML files,
+// It will convert all markup files inside root directory into HTML files,
// recursively; and read all the HTML files and files in "content/assets" and
// convert them into Go file in "out".
//
func Generate(root, out string) {
htmlg := newHTMLGenerator()
- adocs := listAdocFiles(root)
- htmlg.convertAdocs(adocs, false)
+ markupFiles := listMarkupFiles(root)
+
+ htmlg.convertMarkupFiles(markupFiles, false)
excs := []string{
`.*\.adoc$`,
+ `.*\.md$`,
+ `^\..*`,
}
mfs, err := memfs.New(nil, excs, true)
@@ -36,19 +39,25 @@ func Generate(root, out string) {
log.Fatal("ciigo: Generate: " + err.Error())
}
- mfs.Mount(root)
+ err = mfs.Mount(root)
+ if err != nil {
+ log.Fatal("ciigo: Generate: " + err.Error())
+ }
- mfs.GoGenerate("", out)
+ err = mfs.GoGenerate("", out)
+ if err != nil {
+ log.Fatal("ciigo: Generate: " + err.Error())
+ }
}
//
-// listAdocFiles find any ".adoc" file inside the content directory,
+// listMarkupFiles find any markup files inside the content directory,
// recursively.
//
-func listAdocFiles(dir string) (adocs []*fileAdoc) {
+func listMarkupFiles(dir string) (markupFiles []*markupFile) {
d, err := os.Open(dir)
if err != nil {
- log.Fatal("ciigo: listAdocFiles: os.Open: ", err)
+ log.Fatal("ciigo: listMarkupFiles: os.Open: ", err)
}
fis, err := d.Readdir(0)
@@ -62,27 +71,28 @@ func listAdocFiles(dir string) (adocs []*fileAdoc) {
if name == dirAssets {
continue
}
- if fi.IsDir() {
+ if fi.IsDir() && name[0] != '.' {
newdir := filepath.Join(dir, fi.Name())
- adocs = append(adocs, listAdocFiles(newdir)...)
+ markupFiles = append(markupFiles, listMarkupFiles(newdir)...)
continue
}
ext := strings.ToLower(filepath.Ext(name))
- if ext != ".adoc" {
+ if !isExtensionMarkup(ext) {
continue
}
if fi.Size() == 0 {
continue
}
- adoc := &fileAdoc{
+ markupf := &markupFile{
+ kind: markupKind(ext),
path: filepath.Join(dir, name),
info: fi,
basePath: filepath.Join(dir, strings.TrimSuffix(name, ext)),
}
- adocs = append(adocs, adoc)
+ markupFiles = append(markupFiles, markupf)
}
- return adocs
+ return markupFiles
}
diff --git a/go.mod b/go.mod
index 1a4f5d8..ff028e3 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
github.com/sergi/go-diff v1.0.0 // indirect
github.com/shuLhan/share v0.6.2-0.20190517170056-d85106c3bdfd
github.com/sirupsen/logrus v1.4.2 // indirect
+ github.com/yuin/goldmark v1.0.8
+ github.com/yuin/goldmark-meta v0.0.0-20190725094849-3a19f64f8435
golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5 // indirect
- gopkg.in/yaml.v2 v2.2.2 // indirect
)
diff --git a/go.sum b/go.sum
index 46b47df..c9b59a6 100644
--- a/go.sum
+++ b/go.sum
@@ -28,6 +28,10 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/yuin/goldmark v1.0.8 h1:VYm6VjdG4bwvxLpjfZrWIE4lO29wxewXI963dk4hHmA=
+github.com/yuin/goldmark v1.0.8/go.mod h1:hDgn8A2EV4OniExoeJs1fSrmEc/T7w8+Teyq8YkThxQ=
+github.com/yuin/goldmark-meta v0.0.0-20190725094849-3a19f64f8435 h1:+MGwOlc7MIzy3RE/SM8LVQmytUSbyY1CWoKXjXF8I6E=
+github.com/yuin/goldmark-meta v0.0.0-20190725094849-3a19f64f8435/go.mod h1:iz8w8IRSQG3VPCOg6UR8gddkyOz5y3mlwM2SAKsgLKs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
diff --git a/htmlgenerator.go b/htmlgenerator.go
index 4cf53af..c6b4621 100644
--- a/htmlgenerator.go
+++ b/htmlgenerator.go
@@ -5,10 +5,18 @@
package ciigo
import (
+ "bytes"
+ "context"
"fmt"
"html/template"
+ "io/ioutil"
"log"
"os"
+
+ "github.com/bytesparadise/libasciidoc"
+ "github.com/yuin/goldmark"
+ meta "github.com/yuin/goldmark-meta"
+ "github.com/yuin/goldmark/parser"
)
//
@@ -16,12 +24,18 @@ import (
//
type htmlGenerator struct {
path string
+ mdg goldmark.Markdown
tmpl *template.Template
}
func newHTMLGenerator() (htmlg *htmlGenerator) {
htmlg = &htmlGenerator{
path: "./templates/html.tmpl",
+ mdg: goldmark.New(
+ goldmark.WithExtensions(
+ meta.Meta,
+ ),
+ ),
}
err := htmlg.loadTemplate()
@@ -38,29 +52,58 @@ func (htmlg *htmlGenerator) loadTemplate() (err error) {
return
}
-func (htmlg *htmlGenerator) convertAdocs(adocs []*fileAdoc, force bool) {
+func (htmlg *htmlGenerator) convertMarkupFiles(markupFiles []*markupFile, force bool) {
fhtml := &fileHTML{}
- for _, adoc := range adocs {
+ for _, fmarkup := range markupFiles {
fhtml.reset()
- fhtml.path = adoc.basePath + ".html"
+ fhtml.path = fmarkup.basePath + ".html"
- fmt.Printf("ciigo: converting %q to %q ... ", adoc.path, fhtml.path)
+ fmt.Printf("ciigo: converting %q to %q ... ", fmarkup.path, fhtml.path)
- adoc.toHTML(fhtml.path, &fhtml.rawBody, force)
- if fhtml.rawBody.Len() == 0 {
- fmt.Println("skip")
- continue
- }
+ htmlg.convert(fmarkup, fhtml, force)
- fhtml.unpackAdoc(adoc)
- fhtml.Body = template.HTML(fhtml.rawBody.String()) //nolint: gosec
+ fmt.Println("OK")
+ fmt.Printf(" metadata: %+v\n", fmarkup.metadata)
+ }
+}
- htmlg.write(fhtml)
+func (htmlg *htmlGenerator) convert(fmarkup *markupFile, fhtml *fileHTML, force bool) {
+ if fmarkup.isHTMLLatest(fhtml.path) && !force {
+ return
+ }
- fmt.Println("OK")
- fmt.Printf(" metadata: %+v\n", adoc.metadata)
+ in, err := ioutil.ReadFile(fmarkup.path)
+ if err != nil {
+ log.Fatal("htmlGenerator.convert: " + err.Error())
}
+
+ switch fmarkup.kind {
+ case markupKindAsciidoc:
+ ctx := context.Background()
+ bufin := bytes.NewBuffer(in)
+ fmarkup.metadata, err = libasciidoc.ConvertToHTML(ctx,
+ bufin, &fhtml.rawBody)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ case markupKindMarkdown:
+ ctx := parser.NewContext()
+ err := htmlg.mdg.Convert(in, &fhtml.rawBody, parser.WithContext(ctx))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmarkup.metadata = meta.Get(ctx)
+ }
+ if fhtml.rawBody.Len() == 0 {
+ fmt.Println("skip")
+ return
+ }
+
+ fhtml.unpackMarkup(fmarkup)
+ htmlg.write(fhtml)
}
//
diff --git a/server.go b/server.go
index 36b5a55..25d4f78 100644
--- a/server.go
+++ b/server.go
@@ -8,6 +8,7 @@ import (
"fmt"
"log"
"path"
+ "strings"
"time"
"github.com/shuLhan/share/lib/debug"
@@ -19,11 +20,11 @@ import (
// Server contains the HTTP server.
//
type Server struct {
- http *libhttp.Server
- opts *libhttp.ServerOptions
- htmlg *htmlGenerator
- adocs []*fileAdoc
- dw *libio.DirWatcher
+ http *libhttp.Server
+ opts *libhttp.ServerOptions
+ htmlg *htmlGenerator
+ markupFiles []*markupFile
+ dw *libio.DirWatcher
}
//
@@ -44,6 +45,7 @@ func NewServer(address string) (srv *Server) {
Root: dirRoot,
Excludes: []string{
`.*\.adoc$`,
+ `.*\.md$`,
},
Development: debug.Value > 0,
}
@@ -55,7 +57,7 @@ func NewServer(address string) (srv *Server) {
if srv.opts.Development {
srv.htmlg = newHTMLGenerator()
- srv.adocs = listAdocFiles(dirRoot)
+ srv.markupFiles = listMarkupFiles(dirRoot)
}
return srv
@@ -69,6 +71,9 @@ func (srv *Server) Start() {
srv.autoGenerate()
}
+ fmt.Printf("ciigo: starting HTTP server at %s for %s\n",
+ srv.opts.Address, srv.opts.Root)
+
err := srv.http.Start()
if err != nil {
log.Fatal("web: Start: " + err.Error())
@@ -81,12 +86,13 @@ func (srv *Server) autoGenerate() {
Delay: time.Second,
Includes: []string{
`.*\.adoc$`,
+ `.*\.md$`,
},
Excludes: []string{
`assets/.*`,
`.*\.html$`,
},
- Callback: srv.onChangeAdoc,
+ Callback: srv.onChangeMarkupFile,
}
err := srv.dw.Start()
@@ -101,64 +107,61 @@ func (srv *Server) autoGenerate() {
}
//
-// onChangeAdoc watch the ".adoc" file in the "content" directory, and
-// re-generate the HTML file if changed.
+// onChangeMarkupFile watch the markup files inside the "content" directory,
+// and re-generate them into HTML file when changed.
//
-func (srv *Server) onChangeAdoc(ns *libio.NodeState) {
+func (srv *Server) onChangeMarkupFile(ns *libio.NodeState) {
if ns.State == libio.FileStateDeleted {
- fmt.Printf("ciigo: onChangeAdoc: %q deleted\n", ns.Node.SysPath)
+ fmt.Printf("ciigo: onChangeMarkupFile: %q deleted\n", ns.Node.SysPath)
return
}
- ext := path.Ext(ns.Node.SysPath)
- if ext != ".adoc" {
+ ext := strings.ToLower(path.Ext(ns.Node.SysPath))
+ if !isExtensionMarkup(ext) {
return
}
- fmt.Println("ciigo: onChangeAdoc: " + ns.Node.SysPath)
+ fmt.Println("ciigo: onChangeMarkupFile: " + ns.Node.SysPath)
var (
- adoc *fileAdoc
- err error
+ fmarkup *markupFile
+ err error
)
switch ns.State {
case libio.FileStateCreated:
- adoc, err = newFileAdoc(ns.Node.SysPath, nil)
+ fmarkup, err = newMarkupFile(ns.Node.SysPath, nil)
if err != nil {
log.Println(err)
return
}
- srv.adocs = append(srv.adocs, adoc)
+ srv.markupFiles = append(srv.markupFiles, fmarkup)
case libio.FileStateModified:
- for x := 0; x < len(srv.adocs); x++ {
- if srv.adocs[x].path == ns.Node.SysPath {
- adoc = srv.adocs[x]
+ for x := 0; x < len(srv.markupFiles); x++ {
+ if srv.markupFiles[x].path == ns.Node.SysPath {
+ fmarkup = srv.markupFiles[x]
break
}
}
- if adoc == nil {
- adoc, err = newFileAdoc(ns.Node.SysPath, nil)
+ if fmarkup == nil {
+ fmarkup, err = newMarkupFile(ns.Node.SysPath, nil)
if err != nil {
log.Println(err)
return
}
- srv.adocs = append(srv.adocs, adoc)
+ srv.markupFiles = append(srv.markupFiles, fmarkup)
}
}
fhtml := &fileHTML{
- path: adoc.basePath + ".html",
+ path: fmarkup.basePath + ".html",
}
fhtml.rawBody.Reset()
- adoc.toHTML(fhtml.path, &fhtml.rawBody, true)
- fhtml.unpackAdoc(adoc)
-
- srv.htmlg.write(fhtml)
+ srv.htmlg.convert(fmarkup, fhtml, true)
}
func (srv *Server) onChangeHTMLTemplate(ns *libio.NodeState) {
@@ -175,7 +178,7 @@ func (srv *Server) onChangeHTMLTemplate(ns *libio.NodeState) {
return
}
- fmt.Println("web: regenerate all .adoc files ... ")
+ fmt.Println("web: regenerate all markup files ... ")
- srv.htmlg.convertAdocs(srv.adocs, true)
+ srv.htmlg.convertMarkupFiles(srv.markupFiles, true)
}