diff --git a/docs/TODO.md b/docs/TODO.md index 3e03d44..450c69e 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -13,6 +13,7 @@ * helper function to do the whole crawl process * document code better * test with godoc +* omit fields in json when nil? (Broken, etc.) ## Features diff --git a/src/docrawler/docrawler.go b/src/docrawler/docrawler.go index 48de706..08ac36f 100644 --- a/src/docrawler/docrawler.go +++ b/src/docrawler/docrawler.go @@ -16,7 +16,7 @@ func docrawl(url string) []*Page { // create first page and add to the queue homepage, _ := NewPage(nil, url) // TODO check error - queued[homepage.URL.String()] = homepage + queued[homepage.Identifier] = homepage // main loop for len(queued) > 0 { @@ -29,7 +29,7 @@ func docrawl(url string) []*Page { delete(queued, destURL) // mark this page as complete - crawled[dest.URL.String()] = dest + crawled[dest.Identifier] = dest // add page to list sitemap = append(sitemap, dest) @@ -62,8 +62,8 @@ func docrawl(url string) []*Page { } // see if we already know about this page - pCrawled, haveCrawled := crawled[p.URL.String()] - pQueued, haveQueued := queued[p.URL.String()] + pCrawled, haveCrawled := crawled[p.Identifier] + pQueued, haveQueued := queued[p.Identifier] if haveCrawled { // add the previously crawled page to children dest.Children = append(dest.Children, pCrawled) @@ -74,7 +74,7 @@ func docrawl(url string) []*Page { // add this new page (never seen) to children dest.Children = append(dest.Children, p) // .. and queue it up for crawling - queued[p.URL.String()] = p + queued[p.Identifier] = p } } } diff --git a/src/docrawler/output.go b/src/docrawler/output.go index 87f5ad1..2b6b959 100644 --- a/src/docrawler/output.go +++ b/src/docrawler/output.go @@ -44,7 +44,11 @@ func sitemapToLocations(pages []*Page) []*Location { } } - // now sort the children slices + // now uniq & sort the children slices + l.Remote = uniqStrings(l.Remote) + l.Links = uniqStrings(l.Links) + l.Broken = uniqStrings(l.Broken) + l.Assets = uniqStrings(l.Assets) sort.Strings(l.Remote) sort.Strings(l.Links) sort.Strings(l.Broken) diff --git a/src/docrawler/page.go b/src/docrawler/page.go index 9e8be32..e0dcfcb 100644 --- a/src/docrawler/page.go +++ b/src/docrawler/page.go @@ -6,12 +6,13 @@ import ( // Page is a struct which defines a single page, which URLs (links and assets) it contains, etc. type Page struct { - URL *url.URL - Title string - MediaType string - Skipped bool - Broken bool - Children []*Page + URL *url.URL + Identifier string + Title string + MediaType string + Skipped bool + Broken bool + Children []*Page } // NewPage takes a referring Page + a URL and returns a new &Page{} @@ -29,5 +30,6 @@ func NewPage(referrer *Page, rawurl string) (*Page, error) { } // create struct and return - return &Page{URL: u, Skipped: false, Broken: false}, nil + p := &Page{URL: u, Skipped: false, Broken: false, Identifier: stripAnchorFromURL(u)} + return p, nil } diff --git a/src/docrawler/url.go b/src/docrawler/url.go index d66bc44..d98657a 100644 --- a/src/docrawler/url.go +++ b/src/docrawler/url.go @@ -49,3 +49,12 @@ func resolveURL(referringURL string, currentURL string) (*url.URL, error) { // return the URL we ended up with, and the error from checkURL return uResolved, checkURL(uResolved) } + +func stripAnchorFromURL(u *url.URL) string { + // copy the url locally + ucopy := *u + + // blank out the anchor, return URI string + ucopy.Fragment = "" + return ucopy.String() +} diff --git a/src/docrawler/url_test.go b/src/docrawler/url_test.go index b0867e8..07a4a9b 100644 --- a/src/docrawler/url_test.go +++ b/src/docrawler/url_test.go @@ -68,3 +68,14 @@ func TestBadResolve(t* testing.T) { } } */ + +func TestURLStripAnchor(t *testing.T) { + u, err := url.Parse("http://swaits:pass@somehost.com:8765/blah/blah.html?x=y#foo") + if err != nil { + t.Fatal("couldn't parse a URL") + } + stripped := stripAnchorFromURL(u) + if stripped != "http://swaits:pass@somehost.com:8765/blah/blah.html?x=y" { + t.Error("stripping anchor #foo from URL failed") + } +} diff --git a/src/docrawler/util.go b/src/docrawler/util.go new file mode 100644 index 0000000..d28531b --- /dev/null +++ b/src/docrawler/util.go @@ -0,0 +1,23 @@ +package main + +// uniqStrings takes a slice of strings and removes any duplicates +// note: does not guarantee any order (or stability of order) +func uniqStrings(strs []string) []string { + // handle special case of a nil parameter + if strs == nil { + return nil + } + + // add each string to the slice as keys (used like a "set") + set := make(map[string]struct{}) + for _, s := range strs { + set[s] = struct{}{} + } + + // now copy all the keys over to a new slice and return it + result := []string{} + for key := range set { + result = append(result, key) + } + return result +} diff --git a/src/docrawler/util_test.go b/src/docrawler/util_test.go new file mode 100644 index 0000000..1ad3dca --- /dev/null +++ b/src/docrawler/util_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "sort" + "testing" +) + +func TestUniqStrings(t *testing.T) { + strs := []string{"a", "a", "b", "a", "b", "c", "d", "a", "b", "e", "d"} + uniq := uniqStrings(strs) + if len(uniq) != 5 { + t.Error("wrong number of strings after uniq'ing") + } + sort.Strings(uniq) + if uniq[0] != "a" || uniq[1] != "b" || uniq[2] != "c" || uniq[3] != "d" || uniq[4] != "e" { + t.Error("uniq values are in correct!") + } +} + +func TestUniqStringsNilParm(t *testing.T) { + uniq := uniqStrings(nil) + if uniq != nil { + t.Error("uniqStrings should return a nil slice when passed a nil slice") + } +} diff --git a/util/testfilter.sh b/util/testfilter.sh index 57cc952..e120961 100755 --- a/util/testfilter.sh +++ b/util/testfilter.sh @@ -2,6 +2,6 @@ # colorize the output of "go test" -sed ''/ok/s//$(printf "\033[32;1m&\033[0m")/'' | \ +sed ''/^ok/s//$(printf "\033[32;1m&\033[0m")/'' | \ sed ''/PASS/s//$(printf "\033[32;1m&\033[0m")/'' | \ sed ''/FAIL/s//$(printf "\033[31;1m&\033[0m")/''