diff --git a/ArchiveState.go b/ArchiveState.go
index 4838cfd..54070c6 100644
--- a/ArchiveState.go
+++ b/ArchiveState.go
@@ -1,11 +1,14 @@
package archive
import (
+ "bufio"
"fmt"
"html/template"
"io/ioutil"
"math"
"net/http"
+ "os"
+ "regexp"
"strings"
)
@@ -40,11 +43,6 @@ func (this *ArchiveState) selectSource(log *LogSource, slug string) {
// - Mandatory: log, ym
// - Optional: page
func (this *ArchiveState) renderView(w http.ResponseWriter) {
- if this.log == nil {
- this.renderError(w, "Invalid log source selected")
- return
- }
-
fname, err := this.svr.LogFile(this.log, this.ym)
if err != nil {
this.renderError(w, err.Error())
@@ -81,6 +79,76 @@ func (this *ArchiveState) renderView(w http.ResponseWriter) {
this.renderTemplate(w, []byte(output))
}
+// renderSearch renders the search results.
+// - Mandatory: log, query, queryIsRegex
+func (this *ArchiveState) renderSearch(w http.ResponseWriter) {
+
+ var matcher func(line string) bool
+ if this.queryIsRegex {
+ rx, err := regexp.Compile(`(?i)` + this.query)
+ if err != nil {
+ this.renderError(w, "Invalid regular expression "+this.query+" ("+err.Error()+")")
+ return
+ }
+
+ matcher = rx.MatchString
+ } else {
+ queryLower := strings.ToLower(this.query)
+ matcher = func(line string) bool {
+ return strings.Contains(strings.ToLower(line), queryLower)
+ }
+ }
+
+ this.renderTemplateHead(w)
+ totalResults := 0
+ w.Write([]byte(`
`))
+
+ limit := this.log.LatestDate().Next() // one off the end
+ for ympair := this.log.EarliestDate(); !ympair.Equals(limit); ympair = ympair.Next() {
+
+ fname, err := this.svr.LogFile(this.log, ympair)
+ if err != nil {
+ continue // no log exists for this ym
+ }
+
+ fh, err := os.Open(fname)
+ if err != nil {
+ continue // can't open this log file
+ }
+
+ func() {
+ defer fh.Close()
+
+ scanner := bufio.NewScanner(fh)
+
+ for i := 0; scanner.Scan(); i += 1 {
+ if !matcher(scanner.Text()) {
+ continue
+ }
+ totalResults += 1
+
+ page := i / this.svr.cfg.LinesPerPage
+ lineNo := i % this.svr.cfg.LinesPerPage
+ url := fmt.Sprintf(`/%s/%d/%d/page-%d#line-%d`, this.logBestSlug, ympair.Year, ympair.Month, page, lineNo)
+
+ w.Write([]byte(`- » ` + template.HTMLEscapeString(scanner.Text()) + `
`))
+ }
+
+ }()
+ }
+
+ w.Write([]byte(`
`))
+
+ if totalResults == 0 {
+ w.Write([]byte(`No search results for "` + template.HTMLEscapeString(this.query) + `"`))
+ } else {
+ w.Write([]byte(`
Found ` + fmt.Sprintf("%d", totalResults) + ` total result(s).
`))
+ }
+
+ this.renderTemplateFoot(w)
+}
+
+// renderError renders a plain text string, escaping it for HTML use.
func (this *ArchiveState) renderError(w http.ResponseWriter, msg string) {
this.renderTemplate(w, []byte(template.HTMLEscapeString(msg)))
}
diff --git a/Router.go b/Router.go
index 7ed9a9e..00336f5 100644
--- a/Router.go
+++ b/Router.go
@@ -118,12 +118,27 @@ func (this *ArchiveServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
arc.renderError(w, fmt.Sprintf("Unknown source '%s'", matches[1]))
}
- } else if this.rxSearch.MatchString(r.URL.Path) {
- arc.renderError(w, "Not implemented.") // FIXME
+ } else if matches := this.rxSearch.FindStringSubmatch(r.URL.Path); len(matches) > 0 {
+ if ls := this.lookupSource(matches[1]); ls != nil {
+ arc.selectSource(ls, matches[1])
+ arc.query, _ = url.QueryUnescape(matches[2])
+ arc.queryIsRegex = false
+ arc.renderSearch(w)
- } else if this.rxSearchRx.MatchString(r.URL.Path) {
- arc.renderError(w, "Not implemented.") // FIXME
+ } else {
+ arc.renderError(w, fmt.Sprintf("Unknown source '%s'", matches[1]))
+ }
+ } else if matches := this.rxSearchRx.FindStringSubmatch(r.URL.Path); len(matches) > 0 {
+ if ls := this.lookupSource(matches[1]); ls != nil {
+ arc.selectSource(ls, matches[1])
+ arc.query, _ = url.QueryUnescape(matches[2])
+ arc.queryIsRegex = true
+ arc.renderSearch(w)
+
+ } else {
+ arc.renderError(w, fmt.Sprintf("Unknown source '%s'", matches[1]))
+ }
} else {
arc.renderError(w, "Unknown route.")