github.com/vlifesystems/rulehunter@v0.0.0-20180501090014-673078aa4a83/html/html.go (about)

     1  // Copyright (C) 2016-2018 vLife Systems Ltd <http://vlifesystems.com>
     2  // Licensed under an MIT licence.  Please see LICENSE.md for details.
     3  
     4  package html
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"html/template"
    10  	"os"
    11  	"path/filepath"
    12  	"reflect"
    13  	"regexp"
    14  	"strings"
    15  
    16  	"github.com/vlifesystems/rulehunter/config"
    17  	"github.com/vlifesystems/rulehunter/report"
    18  )
    19  
    20  // File mode permission used as standard for the html content:
    21  // No special permission bits
    22  // User: Read, Write Execute
    23  // Group: Read
    24  // Other: None
    25  const modePerm = 0740
    26  
    27  // The number of times to try to load a report.  This is useful because
    28  // sometimes a report will be being written while trying to load it
    29  // and therefore the load will fail.
    30  const maxReportLoadAttempts = 5
    31  
    32  var nonAlphaNumOrSpaceRegexp = regexp.MustCompile("[^[:alnum:] ]")
    33  var spaceRegexp = regexp.MustCompile(" ")
    34  var multipleHyphenRegexp = regexp.MustCompile("-+")
    35  
    36  func escapeString(s string) string {
    37  	newS := nonAlphaNumOrSpaceRegexp.ReplaceAllString(s, "")
    38  	newS = spaceRegexp.ReplaceAllString(newS, "-")
    39  	newS = multipleHyphenRegexp.ReplaceAllString(newS, "-")
    40  	newS = strings.Trim(newS, " -")
    41  	return strings.ToLower(newS)
    42  }
    43  
    44  // CreatePageError indicates that an html page can't be created
    45  type CreatePageError struct {
    46  	Filename string
    47  	Op       string
    48  	Err      error
    49  }
    50  
    51  func (wpe CreatePageError) Error() string {
    52  	return fmt.Sprintf(
    53  		"can't create html page for filename: %s, %s (%s)",
    54  		wpe.Filename, wpe.Err, wpe.Op)
    55  }
    56  
    57  func writeTemplate(
    58  	cfg *config.Config,
    59  	filename string,
    60  	tpl string,
    61  	tplData interface{},
    62  ) error {
    63  	funcMap := template.FuncMap{
    64  		"ToTitle": strings.Title,
    65  		"IsLast": func(x int, a interface{}) bool {
    66  			return x == reflect.ValueOf(a).Len()-1
    67  		},
    68  	}
    69  	t, err := template.New("webpage").Funcs(funcMap).Parse(tpl)
    70  	if err != nil {
    71  		return CreatePageError{Filename: filename, Op: "parse", Err: err}
    72  	}
    73  
    74  	fullFilename := filepath.Join(cfg.WWWDir, filename)
    75  	dir := filepath.Dir(fullFilename)
    76  	if err := os.MkdirAll(dir, modePerm); err != nil {
    77  		return CreatePageError{Filename: filename, Op: "mkdir", Err: err}
    78  	}
    79  
    80  	f, err := os.Create(fullFilename)
    81  	if err != nil {
    82  		return CreatePageError{Filename: filename, Op: "create", Err: err}
    83  	}
    84  	defer f.Close()
    85  
    86  	if err := t.Execute(f, tplData); err != nil {
    87  		return CreatePageError{Filename: filename, Op: "execute", Err: err}
    88  	}
    89  	return nil
    90  }
    91  
    92  func genReportURLDir(
    93  	mode report.ModeKind,
    94  	category string,
    95  	title string,
    96  ) string {
    97  	escapedTitle := escapeString(title)
    98  	escapedCategory := escapeString(category)
    99  	if category != "" {
   100  		return fmt.Sprintf("reports/category/%s/%s/%s/",
   101  			escapedCategory, escapedTitle, mode.String())
   102  	}
   103  	return fmt.Sprintf("reports/nocategory/%s/%s/", escapedTitle, mode.String())
   104  }
   105  
   106  func countFiles(files []os.FileInfo) int {
   107  	numFiles := 0
   108  	for _, file := range files {
   109  		if !file.IsDir() {
   110  			numFiles++
   111  		}
   112  	}
   113  	return numFiles
   114  }
   115  
   116  func makeHtmlNav(menuItem string) template.HTML {
   117  	var doc bytes.Buffer
   118  	validMenuItems := []string{
   119  		"front",
   120  		"reports",
   121  		"tag",
   122  		"category",
   123  		"activity",
   124  	}
   125  
   126  	foundValidItem := false
   127  	for _, validMenuItem := range validMenuItems {
   128  		if validMenuItem == menuItem {
   129  			foundValidItem = true
   130  		}
   131  	}
   132  	if !foundValidItem {
   133  		panic(fmt.Sprintf("menuItem not valid: %s", menuItem))
   134  	}
   135  
   136  	t, err := template.New("webpage").Parse(navTpl)
   137  	if err != nil {
   138  		panic(fmt.Sprintf("Couldn't create nav html: %s", err))
   139  	}
   140  
   141  	tplData := struct{ MenuItem string }{menuItem}
   142  
   143  	if err := t.Execute(&doc, tplData); err != nil {
   144  		panic(fmt.Sprintf("Couldn't create nav html: %s", err))
   145  	}
   146  	return template.HTML(doc.String())
   147  }
   148  
   149  func makeHtmlHead(cfg *config.Config) template.HTML {
   150  	var doc bytes.Buffer
   151  	t, err := template.New("webpage").Parse(headTpl)
   152  	if err != nil {
   153  		panic(fmt.Sprintf("Couldn't create head html: %s", err))
   154  	}
   155  
   156  	tplData := struct {
   157  		BaseURL   string
   158  		JSComment template.HTML
   159  	}{BaseURL: cfg.BaseURL, JSComment: template.HTML(headJSComment)}
   160  
   161  	if err := t.Execute(&doc, tplData); err != nil {
   162  		panic(fmt.Sprintf("Couldn't create head html: %s", err))
   163  	}
   164  	return template.HTML(doc.String())
   165  }
   166  
   167  func makeHtml(cfg *config.Config, menuItem string) map[string]template.HTML {
   168  	r := make(map[string]template.HTML)
   169  	r["head"] = makeHtmlHead(cfg)
   170  	r["nav"] = makeHtmlNav(menuItem)
   171  	r["footer"] = template.HTML(footerHtml)
   172  	r["bootstrapJS"] = template.HTML(bootstrapJSHtml)
   173  	return r
   174  }