diff options
| -rw-r--r-- | ciigo.go | 265 | ||||
| -rw-r--r-- | convert_options.go | 2 | ||||
| -rw-r--r-- | server.go | 81 | ||||
| -rw-r--r-- | watcher.go | 2 | ||||
| -rw-r--r-- | watcher_test.go | 2 |
5 files changed, 180 insertions, 172 deletions
@@ -17,6 +17,7 @@ import ( "regexp" "strings" + libhttp "git.sr.ht/~shulhan/pakakeh.go/lib/http" "git.sr.ht/~shulhan/pakakeh.go/lib/memfs" ) @@ -36,39 +37,27 @@ var defExcludes = []string{ `^\..*`, } +// Ciigo provides customizable and reusable instance of ciigo for embedding, +// converting, and/or serving HTTP server. +// This type is introduced so one can add HTTP handler or endpoint along +// with serving the files. +type Ciigo struct { + HTTPServer *libhttp.Server + converter *Converter + watcher *watcher + serveOpts ServeOptions +} + // Convert all markup files inside directory "dir" recursively into HTML // files using ConvertOptions HTMLTemplate file as base template. // If HTMLTemplate is empty it will default to use embedded HTML template. // See template_index_html.go for template format. func Convert(opts *ConvertOptions) (err error) { - var ( - logp = `Convert` - - converter *Converter - fileMarkups map[string]*FileMarkup - ) - if opts == nil { opts = &ConvertOptions{} } - err = opts.init() - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - - converter, err = NewConverter(opts.HTMLTemplate) - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - - fileMarkups, err = listFileMarkups(opts.Root, opts.excRE) - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - - converter.convertFileMarkups(fileMarkups, false) - - return nil + var ciigo = &Ciigo{} + return ciigo.Convert(*opts) } // GoEmbed generate a static Go file that embed all files inside Root except @@ -82,89 +71,25 @@ func Convert(opts *ConvertOptions) (err error) { // template. // See template_index_html.go for template format. func GoEmbed(opts *EmbedOptions) (err error) { - var ( - logp = `GoEmbed` - - converter *Converter - fileMarkups map[string]*FileMarkup - mfs *memfs.MemFS - mfsOpts *memfs.Options - convertForce bool - ) - if opts == nil { opts = &EmbedOptions{} } - err = opts.init() - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - - converter, err = NewConverter(opts.HTMLTemplate) - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - - fileMarkups, err = listFileMarkups(opts.Root, opts.excRE) - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - - convertForce = isHTMLTemplateNewer(opts) - - converter.convertFileMarkups(fileMarkups, convertForce) - - mfsOpts = &memfs.Options{ - Root: opts.Root, - Excludes: defExcludes, - Embed: opts.EmbedOptions, - } - - mfs, err = memfs.New(mfsOpts) - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - - if len(opts.HTMLTemplate) > 0 { - _, err = mfs.AddFile(internalTemplatePath, opts.HTMLTemplate) - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - } - - err = mfs.GoEmbed() - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - - return nil + var ciigo = &Ciigo{} + return ciigo.GoEmbed(*opts) } -// Serve the content at directory "dir" using HTTP server at specific -// "address". +// Serve the content under directory "[ServeOptions].ConvertOptions.Root" +// using HTTP server at specific "[ServeOptions].Address". func Serve(opts *ServeOptions) (err error) { - var ( - logp = `Serve` - srv *server - ) - if opts == nil { opts = &ServeOptions{} } - err = opts.init() + var ciigo = &Ciigo{} + err = ciigo.InitHTTPServer(*opts) if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) + return err } - - srv, err = newServer(opts) - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - err = srv.start() - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - return nil + return ciigo.Serve() } // Watch any changes on markup files on directory Root recursively and @@ -176,42 +101,16 @@ func Serve(opts *ServeOptions) (err error) { // If the HTML template file deleted, it will replace them with internal, // default HTML template. func Watch(opts *ConvertOptions) (err error) { - var ( - logp = `Watch` - - converter *Converter - w *watcher - ) - if opts == nil { opts = &ConvertOptions{} } - err = opts.init() - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - - converter, err = NewConverter(opts.HTMLTemplate) - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - - w, err = newWatcher(converter, opts) - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - - err = w.start() - if err != nil { - return fmt.Errorf(`%s: %w`, logp, err) - } - - return nil + var ciigo = &Ciigo{} + return ciigo.Watch(*opts) } // isHTMLTemplateNewer will return true if HTMLTemplate is not defined or // newer than embedded GoFileName. -func isHTMLTemplateNewer(opts *EmbedOptions) bool { +func isHTMLTemplateNewer(opts EmbedOptions) bool { var ( logp = `isHTMLTemplateNewer` @@ -348,3 +247,117 @@ func isExcluded(path string, excs []*regexp.Regexp) bool { } return false } + +// Convert all markup files inside directory [ConvertOptions.Root] +// recursively into HTML files using [ConvertOptions.HTMLTemplate] file as +// base template. +// If HTMLTemplate is empty it use the default embedded HTML template. +// See template_index_html.go for template format. +func (ciigo *Ciigo) Convert(opts ConvertOptions) (err error) { + var logp = `Convert` + + err = opts.init() + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + + ciigo.serveOpts.ConvertOptions = opts + + ciigo.converter, err = NewConverter(opts.HTMLTemplate) + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + + var fileMarkups map[string]*FileMarkup + + fileMarkups, err = listFileMarkups(opts.Root, opts.excRE) + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + + ciigo.converter.convertFileMarkups(fileMarkups, false) + + return nil +} + +// GoEmbed embed the file system (directories and files) inside the +// [ConvertOptions.Root] into a Go code. +// One can exclude files by writing regular expression in +// [ConvertOptions.Exclude]. +func (ciigo *Ciigo) GoEmbed(embedOpts EmbedOptions) (err error) { + var logp = `GoEmbed` + + err = embedOpts.init() + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + + ciigo.converter, err = NewConverter(embedOpts.HTMLTemplate) + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + + var fileMarkups map[string]*FileMarkup + + fileMarkups, err = listFileMarkups(embedOpts.Root, embedOpts.excRE) + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + + var convertForce = isHTMLTemplateNewer(embedOpts) + + ciigo.converter.convertFileMarkups(fileMarkups, convertForce) + + var mfsOpts = &memfs.Options{ + Root: embedOpts.Root, + Excludes: defExcludes, + Embed: embedOpts.EmbedOptions, + } + + var mfs *memfs.MemFS + + mfs, err = memfs.New(mfsOpts) + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + + if len(embedOpts.HTMLTemplate) > 0 { + _, err = mfs.AddFile(internalTemplatePath, embedOpts.HTMLTemplate) + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + } + + err = mfs.GoEmbed() + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + return nil +} + +// Watch start a watcher on [ConvertOptions.Root] directory that monitor any +// changes to markup files and convert them to HTML files. +func (ciigo *Ciigo) Watch(convertOpts ConvertOptions) (err error) { + var logp = `Watch` + + err = convertOpts.init() + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + + ciigo.converter, err = NewConverter(convertOpts.HTMLTemplate) + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + + ciigo.watcher, err = newWatcher(ciigo.converter, convertOpts) + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + + err = ciigo.watcher.start() + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } + return nil +} diff --git a/convert_options.go b/convert_options.go index 13741ee..0423644 100644 --- a/convert_options.go +++ b/convert_options.go @@ -9,7 +9,7 @@ import ( ) const ( - // DefaultRoot define default Root value for GenerateOptions. + // DefaultRoot define default Root value for ConvertOptions. DefaultRoot = `.` ) @@ -14,21 +14,19 @@ import ( "git.sr.ht/~shulhan/pakakeh.go/lib/memfs" ) -// server contains the HTTP server that serve the generated HTML files. -type server struct { - http *libhttp.Server - converter *Converter - watcher *watcher - opts ServeOptions -} - -// newServer create an HTTP server to serve HTML files in directory "root". +// InitHTTPServer create an HTTP server to serve HTML files in directory +// defined in "[ConvertOptions].Root". // // The address parameter is optional, if not set its default to ":8080". // The htmlTemplate parameter is optional, if not set its default to // embedded HTML template. -func newServer(opts *ServeOptions) (srv *server, err error) { - var logp = `newServer` +func (ciigo *Ciigo) InitHTTPServer(opts ServeOptions) (err error) { + var logp = `initServer` + + err = opts.init() + if err != nil { + return fmt.Errorf(`%s: %w`, logp, err) + } if opts.Mfs == nil { opts.IsDevelopment = true @@ -39,15 +37,13 @@ func newServer(opts *ServeOptions) (srv *server, err error) { } opts.Mfs, err = memfs.New(mfsopts) if err != nil { - return nil, fmt.Errorf(`%s: %w`, logp, err) + return fmt.Errorf(`%s: %w`, logp, err) } } else { opts.Mfs.Opts.TryDirect = opts.IsDevelopment } - srv = &server{ - opts: *opts, - } + ciigo.serveOpts = opts var httpdOpts = libhttp.ServerOptions{ Memfs: opts.Mfs, @@ -55,9 +51,9 @@ func newServer(opts *ServeOptions) (srv *server, err error) { EnableIndexHTML: opts.EnableIndexHTML, } - srv.http, err = libhttp.NewServer(httpdOpts) + ciigo.HTTPServer, err = libhttp.NewServer(httpdOpts) if err != nil { - return nil, fmt.Errorf(`%s: %w`, logp, err) + return fmt.Errorf(`%s: %w`, logp, err) } var epInSearch = libhttp.Endpoint{ @@ -65,12 +61,12 @@ func newServer(opts *ServeOptions) (srv *server, err error) { Path: `/_internal/search`, RequestType: libhttp.RequestTypeQuery, ResponseType: libhttp.ResponseTypeHTML, - Call: srv.onSearch, + Call: ciigo.onSearch, } - err = srv.http.RegisterEndpoint(epInSearch) + err = ciigo.HTTPServer.RegisterEndpoint(epInSearch) if err != nil { - return nil, fmt.Errorf(`%s: %w`, logp, err) + return fmt.Errorf(`%s: %w`, logp, err) } var pathHTMLTemplate string @@ -79,9 +75,9 @@ func newServer(opts *ServeOptions) (srv *server, err error) { pathHTMLTemplate = opts.HTMLTemplate } - srv.converter, err = NewConverter(pathHTMLTemplate) + ciigo.converter, err = NewConverter(pathHTMLTemplate) if err != nil { - return nil, fmt.Errorf(`%s: %w`, logp, err) + return fmt.Errorf(`%s: %w`, logp, err) } if !opts.IsDevelopment { @@ -89,54 +85,53 @@ func newServer(opts *ServeOptions) (srv *server, err error) { tmplNode, _ = opts.Mfs.Get(internalTemplatePath) if tmplNode != nil { - srv.converter.tmpl, err = srv.converter.tmpl.Parse(string(tmplNode.Content)) + ciigo.converter.tmpl, err = ciigo.converter.tmpl.Parse(string(tmplNode.Content)) if err != nil { - return nil, fmt.Errorf(`%s: %s`, logp, err) + return fmt.Errorf(`%s: %s`, logp, err) } } } if opts.IsDevelopment { - srv.watcher, err = newWatcher(srv.converter, &opts.ConvertOptions) + ciigo.watcher, err = newWatcher(ciigo.converter, opts.ConvertOptions) if err != nil { - return nil, fmt.Errorf(`%s: %w`, logp, err) + return fmt.Errorf(`%s: %w`, logp, err) } - srv.converter.convertFileMarkups(srv.watcher.fileMarkups, false) + ciigo.converter.convertFileMarkups(ciigo.watcher.fileMarkups, false) } - return srv, nil + return nil } -// start the web server. -func (srv *server) start() (err error) { - var ( - logp = `start` - ) +// Serve start the HTTP web server. +func (ciigo *Ciigo) Serve() (err error) { + var logp = `Serve` - if srv.opts.IsDevelopment { - err = srv.watcher.start() + if ciigo.serveOpts.IsDevelopment { + err = ciigo.watcher.start() if err != nil { return fmt.Errorf(`%s: %w`, logp, err) } } log.Printf(`ciigo: starting HTTP server at http://%s for %q`, - srv.http.Options.Address, srv.http.Options.Memfs.Opts.Root) + ciigo.HTTPServer.Options.Address, + ciigo.HTTPServer.Options.Memfs.Opts.Root) - err = srv.http.Start() + err = ciigo.HTTPServer.Start() if err != nil { return fmt.Errorf(`%s: %w`, logp, err) } - if srv.opts.IsDevelopment { - srv.watcher.stop() + if ciigo.serveOpts.IsDevelopment { + ciigo.watcher.stop() } return nil } -func (srv *server) onSearch(epr *libhttp.EndpointRequest) (resBody []byte, err error) { +func (ciigo *Ciigo) onSearch(epr *libhttp.EndpointRequest) (resBody []byte, err error) { var ( logp = `onSearch` @@ -147,9 +142,9 @@ func (srv *server) onSearch(epr *libhttp.EndpointRequest) (resBody []byte, err e ) q = epr.HTTPRequest.Form.Get(`q`) - results = srv.http.Options.Memfs.Search(strings.Fields(q), 0) + results = ciigo.HTTPServer.Options.Memfs.Search(strings.Fields(q), 0) - err = srv.converter.tmplSearch.Execute(&buf, results) + err = ciigo.converter.tmplSearch.Execute(&buf, results) if err != nil { return nil, fmt.Errorf(`%s: %w`, logp, err) } @@ -160,7 +155,7 @@ func (srv *server) onSearch(epr *libhttp.EndpointRequest) (resBody []byte, err e buf.Reset() - err = srv.converter.tmpl.Execute(&buf, fhtml) + err = ciigo.converter.tmpl.Execute(&buf, fhtml) if err != nil { return nil, fmt.Errorf(`%s: %w`, logp, err) } @@ -43,7 +43,7 @@ type watcher struct { // +-- watchHTMLTemplate +--> DELETE --> Converter.htmlTemplateUseInternal() // | // +--> UPDATE --> Converter.convertFileMarkups() -func newWatcher(converter *Converter, convertOpts *ConvertOptions) (w *watcher, err error) { +func newWatcher(converter *Converter, convertOpts ConvertOptions) (w *watcher, err error) { var ( logp = `newWatcher` ) diff --git a/watcher_test.go b/watcher_test.go index 1f8b2e3..063eb8f 100644 --- a/watcher_test.go +++ b/watcher_test.go @@ -36,7 +36,7 @@ func TestWatcher(t *testing.T) { var testWatcher *watcher - testWatcher, err = newWatcher(converter, &convertOpts) + testWatcher, err = newWatcher(converter, convertOpts) if err != nil { t.Fatal(err) } |
