diff --git a/.gitignore b/.gitignore index 4ded78e..38a5a3b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ chm2docset +_fixtures/Sample.docset/Contents/Resources/docSet.dsidx diff --git a/README.md b/README.md index 6dd3248..2488931 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ How to use ```sh brew install chmlib +go get -u go github.com/ngs/chm2docset go install github.com/ngs/chm2docset chm2docset -platform docset-platform -out /path/to/MyRef.docset /path/to/MyReference.chm ``` diff --git a/_fixtures/Sample.docset/Contents/Resources/Documents/sub/test4.htm b/_fixtures/Sample.docset/Contents/Resources/Documents/sub/test4.htm new file mode 100644 index 0000000..d55add6 --- /dev/null +++ b/_fixtures/Sample.docset/Contents/Resources/Documents/sub/test4.htm @@ -0,0 +1,11 @@ + + + test + + 4 + + + + test4 + + diff --git a/_fixtures/Sample.docset/Contents/Resources/Documents/test1.htm b/_fixtures/Sample.docset/Contents/Resources/Documents/test1.htm new file mode 100644 index 0000000..ad4d54d --- /dev/null +++ b/_fixtures/Sample.docset/Contents/Resources/Documents/test1.htm @@ -0,0 +1,9 @@ + + + test 1 + + + + test1 + + diff --git a/_fixtures/Sample.docset/Contents/Resources/Documents/test2.htm b/_fixtures/Sample.docset/Contents/Resources/Documents/test2.htm new file mode 100644 index 0000000..64824e6 --- /dev/null +++ b/_fixtures/Sample.docset/Contents/Resources/Documents/test2.htm @@ -0,0 +1,10 @@ + + + test 2 +yo + + + + test2 + + diff --git a/_fixtures/Sample.docset/Contents/Resources/Documents/test3.htm b/_fixtures/Sample.docset/Contents/Resources/Documents/test3.htm new file mode 100644 index 0000000..74740dd --- /dev/null +++ b/_fixtures/Sample.docset/Contents/Resources/Documents/test3.htm @@ -0,0 +1,7 @@ + + + + + test3 + + diff --git a/_fixtures/bin/extract_chmLib b/_fixtures/bin/extract_chmLib new file mode 100755 index 0000000..81647ab --- /dev/null +++ b/_fixtures/bin/extract_chmLib @@ -0,0 +1,4 @@ +#!/bin/sh + +[ -d tmp ] || mkdir tmp +echo $@ > tmp/fixtureinput.txt diff --git a/chm2docset.go b/chm2docset.go index ee1bc18..e79ba64 100644 --- a/chm2docset.go +++ b/chm2docset.go @@ -16,6 +16,8 @@ import ( _ "github.com/mattn/go-sqlite3" ) +var logFatal = log.Fatal + func usage() { fmt.Fprintf(os.Stderr, "usage: %s [inputfile]\n", os.Args[0]) flag.PrintDefaults() @@ -24,7 +26,7 @@ func usage() { func failOnError(err error) { if err != nil { - log.Fatal(err) + logFatal(err) } } @@ -128,7 +130,7 @@ func (opts *Options) PlistContent() string { // WritePlist writes plist file func (opts *Options) WritePlist() error { - return ioutil.WriteFile(opts.PlistPath(), []byte(opts.PlistContent()), 644) + return ioutil.WriteFile(opts.PlistPath(), []byte(opts.PlistContent()), 0644) } // Clean removes existing output @@ -136,18 +138,22 @@ func (opts *Options) Clean() error { return os.RemoveAll(opts.DocsetPath()) } +// CreateDirectory creates directory +func (opts *Options) CreateDirectory() error { + return os.MkdirAll(opts.ContentPath(), 0755) +} + // ExtractSource extracts source to destination func (opts *Options) ExtractSource() error { - if err := os.MkdirAll(opts.ContentPath(), 0755); err != nil { - return err - } cmd := exec.Command("extract_chmLib", opts.SourcePath, opts.ContentPath()) return cmd.Run() } // CreateDatabase creates database func (opts *Options) CreateDatabase() error { + os.Remove(opts.DatabasePath()) titleRE := regexp.MustCompile("([^<]+)") + spacesRE := regexp.MustCompile("[\\s\\t]+") db, err := sql.Open("sqlite3", opts.DatabasePath()) if err != nil { return err @@ -178,7 +184,10 @@ func (opts *Options) CreateDatabase() error { content := string(b) res := titleRE.FindAllStringSubmatch(content, -1) if len(res) >= 1 && len(res[0]) >= 2 { - _, err := stmt.Exec(res[0][1], "Guide", strings.TrimPrefix(path, opts.ContentPath())) + ttl := strings.Replace(res[0][1], "\n", " ", -1) + ttl = spacesRE.ReplaceAllString(ttl, " ") + ttl = strings.TrimSpace(ttl) + _, err := stmt.Exec(ttl, "Guide", strings.TrimPrefix(path, opts.ContentPath())) if err != nil { return err } @@ -194,6 +203,7 @@ func (opts *Options) CreateDatabase() error { func main() { opts := NewOptions() opts.Clean() + failOnError(opts.CreateDirectory()) failOnError(opts.ExtractSource()) failOnError(opts.CreateDatabase()) failOnError(opts.WritePlist()) diff --git a/chm2docset_test.go b/chm2docset_test.go index 339aaf6..f20524a 100644 --- a/chm2docset_test.go +++ b/chm2docset_test.go @@ -1,14 +1,22 @@ package main import ( + "database/sql" + "io" + "io/ioutil" + "log" "os" "reflect" "testing" ) +func cleanTmp() { + os.RemoveAll("tmp") +} + type Test struct { - expected interface{} actual interface{} + expected interface{} } func (test Test) Compare(t *testing.T) { @@ -23,6 +31,89 @@ func (test Test) DeepEqual(t *testing.T) { } } +// Copies file source to destination dest. +func CopyFile(source string, dest string) (err error) { + sf, err := os.Open(source) + if err != nil { + return err + } + defer sf.Close() + df, err := os.Create(dest) + if err != nil { + return err + } + defer df.Close() + _, err = io.Copy(df, sf) + if err == nil { + if si, e := os.Stat(source); e != nil { + err = os.Chmod(dest, si.Mode()) + } + } + + return err +} + +// Recursively copies a directory tree, attempting to preserve permissions. +// Source directory must exist, destination directory must *not* exist. +func CopyDir(source string, dest string) (err error) { + + // get properties of source dir + fi, err := os.Stat(source) + if err != nil { + return err + } + + if !fi.IsDir() { + return &CustomError{"Source is not a directory"} + } + + // ensure dest dir does not already exist + + _, err = os.Open(dest) + if !os.IsNotExist(err) { + return &CustomError{"Destination already exists"} + } + + // create dest dir + + err = os.MkdirAll(dest, fi.Mode()) + if err != nil { + return err + } + + entries, err := ioutil.ReadDir(source) + + for _, entry := range entries { + + sfp := source + "/" + entry.Name() + dfp := dest + "/" + entry.Name() + if entry.IsDir() { + err = CopyDir(sfp, dfp) + if err != nil { + log.Println(err) + } + } else { + // perform copy + err = CopyFile(sfp, dfp) + if err != nil { + log.Println(err) + } + } + + } + return +} + +// A struct for returning custom error messages +type CustomError struct { + What string +} + +// Returns the error message defined in What as a string +func (e *CustomError) Error() string { + return e.What +} + func TestNewOptionsSourceUnspecified(t *testing.T) { os.Args = []string{"chm2docset"} opts := NewOptions() @@ -125,3 +216,91 @@ func TestPlistContent(t *testing.T) { `}.Compare(t) } + +func TestWritePlist(t *testing.T) { + opts := &Options{ + SourcePath: "/foo/bar/baz.chm", + Outdir: "tmp/foo.docset", + } + opts.CreateDirectory() + err := opts.WritePlist() + if err != nil { + t.Errorf("Expected nil but got %v", err) + } + b, err := ioutil.ReadFile("tmp/foo.docset/Contents/Info.plist") + if err != nil { + t.Errorf("Expected nil but got %v", err) + } + Test{string(b), ` + + + + dashIndexFilePath + Welcome.htm + CFBundleIdentifier + io.ngs.documentation.baz + CFBundleName + baz + DocSetPlatformFamily + + isDashDocset + + +`}.Compare(t) + opts.Clean() + if stat, err := os.Stat("tmp/foo.docset"); os.IsExist(err) { + t.Errorf("Expect not exist but exists %v", stat) + } + cleanTmp() +} + +func TestExtractSource(t *testing.T) { + opts := &Options{ + SourcePath: "/foo/bar/baz.chm", + Outdir: "tmp/foo.docset", + } + opts.CreateDirectory() + os.Setenv("PATH", "_fixtures/bin:"+os.Getenv("PATH")) + err := opts.ExtractSource() + if err != nil { + t.Errorf("Expected nil but got %v", err) + } + b, err := ioutil.ReadFile("tmp/fixtureinput.txt") + if err != nil { + t.Errorf("Expected nil but got %v", err) + } + Test{string(b), "/foo/bar/baz.chm tmp/foo.docset/Contents/Resources/Documents\n"}.Compare(t) + cleanTmp() +} + +func TestCreateDatabase(t *testing.T) { + opts := &Options{ + SourcePath: "/foo/bar/baz.chm", + Outdir: "tmp/Sample.docset", + } + opts.Clean() + CopyDir("_fixtures/Sample.docset", "tmp/Sample.docset") + opts.CreateDatabase() + db, _ := sql.Open("sqlite3", opts.DatabasePath()) + rows, _ := db.Query("SELECT * FROM searchIndex") + columns, _ := rows.Columns() + Test{columns, []string{"id", "name", "type", "path"}}.DeepEqual(t) + grid := [][]string{} + for rows.Next() { + var id string + var name string + var indexType string + var path string + err := rows.Scan(&id, &name, &indexType, &path) + if err != nil { + t.Errorf("Got errror %v", err) + } + grid = append(grid, []string{id, name, indexType, path}) + } + Test{grid, [][]string{ + {"1", "test 4", "Guide", "/sub/test4.htm"}, + {"2", "test 1", "Guide", "/test1.htm"}, + {"3", "test 2 yo", "Guide", "/test2.htm"}, + }}.DeepEqual(t) + cleanTmp() +}