Merge from develop
This commit is contained in:
commit
81ae0c433b
9 files changed
+245
-130
No files matched your search
+1
-2
@@ -6,10 +6,10 @@
|
|||||||
* should only crawl these pages once
|
* should only crawl these pages once
|
||||||
* but should be included in the links part
|
* but should be included in the links part
|
||||||
* when sorting links, "uniq" them as well
|
* when sorting links, "uniq" them as well
|
||||||
|
* be smart about index.html?
|
||||||
|
|
||||||
## Cleanup
|
## Cleanup
|
||||||
|
|
||||||
* rename Page -> crawlerState
|
|
||||||
* helper function to do the whole crawl process
|
* helper function to do the whole crawl process
|
||||||
* document code better
|
* document code better
|
||||||
* test with godoc
|
* test with godoc
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* some sort of status during crawl?
|
|
||||||
* command line options, flag parsing
|
* command line options, flag parsing
|
||||||
* check RFCs on URL character sets, compliance, etc.
|
* check RFCs on URL character sets, compliance, etc.
|
||||||
* packagize parts of app
|
* packagize parts of app
|
||||||
+90
-54
@@ -2,84 +2,120 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// docrawl begins crawling the site at "url"
|
// docrawl begins crawling the site at "homeurl"
|
||||||
func docrawl(url string) []*Page {
|
func docrawl(homeurl string) itemSlice {
|
||||||
// our result structure
|
// set of what we have already crawled
|
||||||
var sitemap []*Page
|
crawled := make(map[string]struct{})
|
||||||
|
|
||||||
// what we've already parsed, what we still need to parse
|
// map (of urlString -> httpItem) of our results
|
||||||
crawled := make(map[string]*Page)
|
results := make(itemMap)
|
||||||
queued := make(map[string]*Page)
|
|
||||||
|
|
||||||
// create first page and add to the queue
|
// a channel to receive our results on
|
||||||
homepage, _ := NewPage(nil, url) // TODO check error
|
rchan := make(chan *httpItem)
|
||||||
queued[homepage.Identifier] = homepage
|
|
||||||
|
|
||||||
// main loop
|
// start a ticker which we'll use to output status and check for completion
|
||||||
for len(queued) > 0 {
|
ticker := time.Tick(250 * time.Millisecond)
|
||||||
// pop a page off of queued
|
|
||||||
var dest *Page
|
// create first page's httpItem
|
||||||
var destURL string
|
homeitem, err := newHTTPItem(nil, homeurl)
|
||||||
for destURL, dest = range queued {
|
if err != nil {
|
||||||
break
|
log.Fatal("unable to create httpItem for homeurl")
|
||||||
}
|
}
|
||||||
delete(queued, destURL)
|
|
||||||
|
|
||||||
// mark this page as complete
|
// set our number of outstanding pages (initially 1 to account for first page)
|
||||||
crawled[dest.Identifier] = dest
|
crawlingCount := 1
|
||||||
|
|
||||||
// add page to list
|
// start the home page crawl
|
||||||
sitemap = append(sitemap, dest)
|
crawled[homeitem.url.String()] = struct{}{}
|
||||||
|
go crawlItem(homeitem, rchan)
|
||||||
|
|
||||||
// make sure this URL has the same hostname as our first page
|
// wait for results
|
||||||
if dest.URL.Host != homepage.URL.Host {
|
for {
|
||||||
// skip URLs associated with other Hosts
|
select {
|
||||||
dest.Skipped = true
|
case r := <-rchan: // new results?
|
||||||
|
// add result to our results map
|
||||||
|
results[r.url.String()] = r
|
||||||
|
|
||||||
|
// decrease the outstanding page count by 1
|
||||||
|
crawlingCount--
|
||||||
|
|
||||||
|
// start crawly any new child pages we haven't yet crawled
|
||||||
|
for i, c := range r.children {
|
||||||
|
// see if we already have a result for this page, if so, point to that result
|
||||||
|
if existing, ok := results[c.url.String()]; ok {
|
||||||
|
r.children[i] = existing
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// see if we're already crawling this page (but maybe don't have results yet)
|
||||||
|
if _, ok := crawled[c.url.String()]; !ok {
|
||||||
|
// haven't crawled this one yet, do so now
|
||||||
|
crawlingCount++
|
||||||
|
crawled[c.url.String()] = struct{}{}
|
||||||
|
go crawlItem(c, rchan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-ticker: // our regular ticker. for status output and checking for completion.
|
||||||
|
// output status to console
|
||||||
|
log.Printf("Crawled %v links, have %v left.\n", len(results), crawlingCount)
|
||||||
|
|
||||||
|
// see if we're finished
|
||||||
|
if crawlingCount == 0 {
|
||||||
|
// finished! convert results map to a slice and return it
|
||||||
|
rslice := itemSlice{}
|
||||||
|
for _, v := range results {
|
||||||
|
rslice = append(rslice, v)
|
||||||
|
}
|
||||||
|
return rslice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// crawlItem crawls a single httpItem, fetching the header, hte page, parsing it,
|
||||||
|
// and filling out its structure as much as possible
|
||||||
|
func crawlItem(item *httpItem, rchan chan<- *httpItem) {
|
||||||
|
// make sure this item is the same domain (i.e. URL "host part") as its referrer
|
||||||
|
if item.refurl != nil && item.url.Host != item.refurl.Host {
|
||||||
|
// skip URLs associated with other Hosts
|
||||||
|
item.linkType = tRemote
|
||||||
|
rchan <- item
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// fetch page
|
// fetch page
|
||||||
text, err := fetchPage(dest)
|
text, err := fetchPage(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dest.Broken = true
|
rchan <- item
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse links
|
// parse links
|
||||||
title, links, err := parseLinks(text)
|
title, links, _ := parseLinks(text) // TODO remove error from parseLinks, not needed
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dest.Broken = true
|
item.linkType = tBroken
|
||||||
continue
|
rchan <- item
|
||||||
|
return
|
||||||
}
|
}
|
||||||
dest.Title = title
|
item.title = title
|
||||||
|
|
||||||
|
// walk links and add them as children to the current item
|
||||||
for _, l := range links {
|
for _, l := range links {
|
||||||
p, err := NewPage(dest, l)
|
newItem, err := newHTTPItem(item, l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue // TODO check error
|
continue // TODO bad item
|
||||||
|
}
|
||||||
|
item.children = append(item.children, newItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
// see if we already know about this page
|
// send back our item struct now that it's all filled out
|
||||||
pCrawled, haveCrawled := crawled[p.Identifier]
|
rchan <- item
|
||||||
pQueued, haveQueued := queued[p.Identifier]
|
|
||||||
if haveCrawled {
|
|
||||||
// add the previously crawled page to children
|
|
||||||
dest.Children = append(dest.Children, pCrawled)
|
|
||||||
} else if haveQueued {
|
|
||||||
// add the previously queued page to children
|
|
||||||
dest.Children = append(dest.Children, pQueued)
|
|
||||||
} else {
|
|
||||||
// add this new page (never seen) to children
|
|
||||||
dest.Children = append(dest.Children, p)
|
|
||||||
// .. and queue it up for crawling
|
|
||||||
queued[p.Identifier] = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sitemap
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -58,9 +58,9 @@ func TestBadRequest(t *testing.T) {
|
|||||||
|
|
||||||
// TestSimpleFetch makes sure we can fetch a file and we get exactly what we expect
|
// TestSimpleFetch makes sure we can fetch a file and we get exactly what we expect
|
||||||
func TestSimpleFetch(t *testing.T) {
|
func TestSimpleFetch(t *testing.T) {
|
||||||
page, err := NewPage(nil, baseURL+"fetch_test.html")
|
page, err := newHTTPItem(nil, baseURL+"fetch_test.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("problem creating New Page struct")
|
t.Error("problem creating New httpItem struct")
|
||||||
}
|
}
|
||||||
body, err := fetchPage(page)
|
body, err := fetchPage(page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -82,19 +82,19 @@ func TestSimpleMap(t *testing.T) {
|
|||||||
// cycle through every page, counting stuff, finding specific pages
|
// cycle through every page, counting stuff, finding specific pages
|
||||||
// so that we can then verify certain aspects of each page
|
// so that we can then verify certain aspects of each page
|
||||||
skipped, children := 0, 0
|
skipped, children := 0, 0
|
||||||
var homepage, aboutpage, skippage *Page
|
var homepage, aboutpage, skippage *httpItem
|
||||||
for _, p := range pages {
|
for _, p := range pages {
|
||||||
if p.Title == "Home" {
|
if p.title == "Home" {
|
||||||
homepage = p
|
homepage = p
|
||||||
} else if p.Title == "About Test" {
|
} else if p.title == "About Test" {
|
||||||
aboutpage = p
|
aboutpage = p
|
||||||
} else if p.URL.String() == "http://doesntexist23492387492837492374982734.com/" {
|
} else if p.url.String() == "http://doesntexist23492387492837492374982734.com/" {
|
||||||
skippage = p
|
skippage = p
|
||||||
}
|
}
|
||||||
if p.Skipped {
|
if p.linkType == tRemote {
|
||||||
skipped++
|
skipped++
|
||||||
}
|
}
|
||||||
children += len(p.Children)
|
children += len(p.children)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check page counts
|
// check page counts
|
||||||
@@ -112,44 +112,44 @@ func TestSimpleMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check first page URL
|
// check first page URL
|
||||||
if homepage.URL.String() != baseURL {
|
if homepage.url.String() != baseURL {
|
||||||
t.Error("page URL is invalid")
|
t.Error("page URL is invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
// specific page number of children
|
// specific page number of children
|
||||||
if len(homepage.Children) != 4 {
|
if len(homepage.children) != 4 {
|
||||||
t.Logf("got %v, wanted %v\n", len(homepage.Children), 4)
|
t.Logf("got %v, wanted %v\n", len(homepage.children), 4)
|
||||||
t.Fatal("got wrong number of links")
|
t.Fatal("got wrong number of links")
|
||||||
}
|
}
|
||||||
if len(aboutpage.Children) != 4 {
|
if len(aboutpage.children) != 4 {
|
||||||
t.Logf("got %v, wanted %v\n", len(aboutpage.Children), 4)
|
t.Logf("got %v, wanted %v\n", len(aboutpage.children), 4)
|
||||||
t.Fatal("got wrong number of links")
|
t.Fatal("got wrong number of links")
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure remote pages were skipped
|
// make sure remote pages were skipped
|
||||||
if homepage.Skipped != false {
|
if homepage.linkType == tRemote {
|
||||||
t.Logf("found a page that should NOT have been skipped %q", homepage.Children[0].URL.String())
|
t.Logf("found a page that should NOT have been skipped %q", homepage.children[0].url.String())
|
||||||
t.Error("child was skipped")
|
t.Error("child was skipped")
|
||||||
}
|
}
|
||||||
if skippage.Skipped != true {
|
if skippage.linkType != tRemote {
|
||||||
t.Logf("found a page that should have been skipped %q", pages[3].Children[2].URL.String())
|
t.Logf("found a page that should have been skipped %q", pages[3].children[2].url.String())
|
||||||
t.Error("child wasn't skipped")
|
t.Error("child wasn't skipped")
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify page content
|
// verify page content
|
||||||
// TODO find a better way to do this
|
// TODO find a better way to do this
|
||||||
//if homepage.Children[0].URL.String() != baseURL+"about.html" {
|
//if homepage.children[0].url.String() != baseURL+"about.html" {
|
||||||
//t.Logf(" Got: %q\n", homepage.Children[0].URL.String())
|
//t.Logf(" Got: %q\n", homepage.children[0].url.String())
|
||||||
//t.Logf("Wanted: %q\n", baseURL+"about.html")
|
//t.Logf("Wanted: %q\n", baseURL+"about.html")
|
||||||
//t.Error("link name is incorrect")
|
//t.Error("link name is incorrect")
|
||||||
//}
|
//}
|
||||||
//if homepage.Children[1].URL.String() != baseURL+"assets/image.png" {
|
//if homepage.children[1].url.String() != baseURL+"assets/image.png" {
|
||||||
//t.Logf(" Got: %q\n", homepage.Children[1].URL.String())
|
//t.Logf(" Got: %q\n", homepage.children[1].url.String())
|
||||||
//t.Logf("Wanted: %q\n", baseURL+"assets/image.png")
|
//t.Logf("Wanted: %q\n", baseURL+"assets/image.png")
|
||||||
//t.Error("link name is incorrect")
|
//t.Error("link name is incorrect")
|
||||||
//}
|
//}
|
||||||
//if homepage.Children[3].URL.String() != baseURL+"scripts/blah.js" {
|
//if homepage.children[3].url.String() != baseURL+"scripts/blah.js" {
|
||||||
//t.Logf(" Got: %q\n", homepage.Children[3].URL.String())
|
//t.Logf(" Got: %q\n", homepage.children[3].url.String())
|
||||||
//t.Logf("Wanted: %q\n", baseURL+"scripts/blah.js")
|
//t.Logf("Wanted: %q\n", baseURL+"scripts/blah.js")
|
||||||
//t.Error("link name is incorrect")
|
//t.Error("link name is incorrect")
|
||||||
//}
|
//}
|
||||||
@@ -157,28 +157,28 @@ func TestSimpleMap(t *testing.T) {
|
|||||||
|
|
||||||
// TestHeaderFetching tests fetchFiletype() to see if we are getting expected results
|
// TestHeaderFetching tests fetchFiletype() to see if we are getting expected results
|
||||||
func TestHeaderFetching(t *testing.T) {
|
func TestHeaderFetching(t *testing.T) {
|
||||||
basepage, err := NewPage(nil, baseURL)
|
basepage, err := newHTTPItem(nil, baseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("problem creating New Page struct")
|
t.Error("problem creating New httpItem struct")
|
||||||
}
|
}
|
||||||
if err := fetchFiletype(basepage); err != nil || basepage.MediaType != "text/html" {
|
if err := fetchFiletype(basepage); err != nil || basepage.linkType != tHTMLPage {
|
||||||
t.Error("problem fetching filetype")
|
t.Error("problem fetching filetype")
|
||||||
}
|
}
|
||||||
|
|
||||||
page, err := NewPage(basepage, "about.html")
|
page, err := newHTTPItem(basepage, "about.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("problem creating New Page struct")
|
t.Error("problem creating New httpItem struct")
|
||||||
}
|
}
|
||||||
if err := fetchFiletype(page); err != nil || page.MediaType != "text/html" {
|
if err := fetchFiletype(page); err != nil || page.linkType != tHTMLPage {
|
||||||
t.Error("problem fetching filetype")
|
t.Error("problem fetching filetype")
|
||||||
}
|
}
|
||||||
|
|
||||||
page, err = NewPage(basepage, "assets/image.png")
|
page, err = newHTTPItem(basepage, "assets/image.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("problem creating New Page struct")
|
t.Error("problem creating New httpItem struct")
|
||||||
}
|
}
|
||||||
if err := fetchFiletype(page); err != nil || page.MediaType != "image/png" {
|
if err := fetchFiletype(page); err != nil || page.linkType != tAsset {
|
||||||
t.Logf("got %q, wanted %q", page.MediaType, "image/png")
|
t.Logf("got %q, wanted %q", page.linkType, tAsset)
|
||||||
t.Error("problem fetching filetype")
|
t.Error("problem fetching filetype")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-12
@@ -9,19 +9,22 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
errContentTypeNotFound = errors.New("no Content-Type header found")
|
errContentTypeNotFound = errors.New("no Content-Type header found")
|
||||||
errFetchError = errors.New("couldn't fetch page")
|
errFetchError = errors.New("couldn't fetch item")
|
||||||
|
errFileTypeUnknown = errors.New("couldn't determine file type")
|
||||||
)
|
)
|
||||||
|
|
||||||
// fetchFiletype performs an http HEAD to get the media type, and sets it
|
// fetchFiletype performs an http HEAD to get the media type, and sets it
|
||||||
// directly in Page.MediaType
|
// directly in httpItem.mediaType
|
||||||
func fetchFiletype(page *Page) error {
|
func fetchFiletype(item *httpItem) error {
|
||||||
resp, err := http.Head(page.URL.String())
|
resp, err := http.Head(item.url.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
item.linkType = tBroken
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// check response code
|
// check response code
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
item.linkType = tBroken
|
||||||
return errFetchError
|
return errFetchError
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,35 +32,46 @@ func fetchFiletype(page *Page) error {
|
|||||||
contentType, ok := resp.Header["Content-Type"]
|
contentType, ok := resp.Header["Content-Type"]
|
||||||
if !ok {
|
if !ok {
|
||||||
// no Content-Type header found
|
// no Content-Type header found
|
||||||
|
item.linkType = tBroken
|
||||||
return errContentTypeNotFound
|
return errContentTypeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse mime type
|
// parse mime type
|
||||||
mediatype, _, err := mime.ParseMediaType(contentType[0])
|
mediatype, _, err := mime.ParseMediaType(contentType[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
item.linkType = tBroken
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// set and return
|
// success, set item type and return
|
||||||
page.MediaType = mediatype
|
if mediatype == "text/html" {
|
||||||
|
item.linkType = tHTMLPage
|
||||||
|
} else {
|
||||||
|
item.linkType = tAsset
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchPage takes a Page, GETs it, and returns the body as a string
|
// fetchPage takes a httpItem, GETs it, and returns the body as a string
|
||||||
func fetchPage(page *Page) (string, error) {
|
func fetchPage(item *httpItem) (string, error) {
|
||||||
// figure out the file type
|
// figure out the file type
|
||||||
err := fetchFiletype(page)
|
err := fetchFiletype(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// we only want to fetch html
|
// make sure we got a file type!
|
||||||
if page.MediaType != "text/html" {
|
if item.linkType == tUnknown {
|
||||||
|
return "", errFileTypeUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// we only want to fetch html, so return if it's anything else
|
||||||
|
if item.linkType != tHTMLPage {
|
||||||
return "", nil // but this isn't an error!
|
return "", nil // but this isn't an error!
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET the url
|
// GET the url
|
||||||
resp, err := http.Get(page.URL.String())
|
resp, err := http.Get(item.url.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
// TestFailedHeaderFetching tests fetchFiletype() to see if we are getting expected failures
|
// TestFailedHeaderFetching tests fetchFiletype() to see if we are getting expected failures
|
||||||
func TestFailedHeaderFetching(t *testing.T) {
|
func TestFailedHeaderFetching(t *testing.T) {
|
||||||
page, err := NewPage(nil, "http://doesntexist23492387492837492374982734.com/")
|
page, err := newHTTPItem(nil, "http://doesntexist23492387492837492374982734.com/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("problem creating New Page struct")
|
t.Error("problem creating New Page struct")
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ func TestFailedHeaderFetching(t *testing.T) {
|
|||||||
|
|
||||||
// TestFailedFetching tests fetchFiletype() to see if we are getting expected failures
|
// TestFailedFetching tests fetchFiletype() to see if we are getting expected failures
|
||||||
func TestFailedFetching(t *testing.T) {
|
func TestFailedFetching(t *testing.T) {
|
||||||
page, err := NewPage(nil, "http://doesntexist23492387492837492374982734.com/")
|
page, err := newHTTPItem(nil, "http://doesntexist23492387492837492374982734.com/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("problem creating New Page struct")
|
t.Error("problem creating New Page struct")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// itemSlice is a convenience type for a slice of items
|
||||||
|
type itemSlice []*httpItem
|
||||||
|
|
||||||
|
// itemMap convenience type for map of string's (URL) to items
|
||||||
|
type itemMap map[string]*httpItem
|
||||||
|
|
||||||
|
// itemType is an enum so we know how to classify each item
|
||||||
|
type itemType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
tUnknown itemType = iota
|
||||||
|
tHTMLPage
|
||||||
|
tAsset
|
||||||
|
tRemote
|
||||||
|
tBroken
|
||||||
|
)
|
||||||
|
|
||||||
|
// httpItem is a struct which defines a single page, which URLs (links and assets) it contains, etc.
|
||||||
|
type httpItem struct {
|
||||||
|
url *url.URL
|
||||||
|
refurl *url.URL
|
||||||
|
title string
|
||||||
|
linkType itemType
|
||||||
|
children itemSlice
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHTTPItem takes a referring httpItem + a URL and returns a new &httpItem{}
|
||||||
|
func newHTTPItem(referrer *httpItem, rawurl string) (*httpItem, error) {
|
||||||
|
// determine the base URL so we can resulve this into a full URL
|
||||||
|
baseURL := ""
|
||||||
|
if referrer != nil {
|
||||||
|
baseURL = referrer.url.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve URL into a full absolute URL (non-relative)
|
||||||
|
u, err := resolveURL(baseURL, rawurl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out referrer url
|
||||||
|
var rurl *url.URL
|
||||||
|
if referrer != nil {
|
||||||
|
rurl = referrer.url
|
||||||
|
}
|
||||||
|
|
||||||
|
// create struct and return
|
||||||
|
return &httpItem{url: u, refurl: rurl}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestBadnewHTTPItem tests newHTTPItem to see if we are getting expected failures
|
||||||
|
func TestBadnewHTTPItem(t *testing.T) {
|
||||||
|
_, err := newHTTPItem(nil, "h%20t\tp://doesntexist23492387492837492374982734.com/")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("tried creating a newHTTPItem with an invalid URL but didn't get an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-14
@@ -15,32 +15,41 @@ type Location struct {
|
|||||||
Remote []string
|
Remote []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func sitemapToLocations(pages []*Page) []*Location {
|
// implement Location slice sorting (by URL)
|
||||||
|
type byURL []*Location
|
||||||
|
|
||||||
|
// implement Sort interface on byURL (which is a []*Location)
|
||||||
|
func (l byURL) Len() int { return len(l) }
|
||||||
|
func (l byURL) Less(i, j int) bool { return l[i].URL < l[j].URL }
|
||||||
|
func (l byURL) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||||
|
|
||||||
|
// sitemapToLocations converts an itemSlice to []*Location, which is appropriate for marshalling
|
||||||
|
func sitemapToLocations(pages itemSlice) []*Location {
|
||||||
// build a map of our pages
|
// build a map of our pages
|
||||||
pageMap := make(map[string]*Page)
|
pageMap := make(map[string]*httpItem)
|
||||||
for _, p := range pages {
|
for _, p := range pages {
|
||||||
pageMap[p.URL.String()] = p
|
pageMap[p.url.String()] = p
|
||||||
}
|
}
|
||||||
|
|
||||||
// build a slice of locations (one per page)
|
// build a slice of locations (one per page)
|
||||||
var locations []*Location
|
var locations []*Location
|
||||||
for _, p := range pages {
|
for _, p := range pages {
|
||||||
if p.MediaType == "text/html" {
|
if p.linkType == tHTMLPage {
|
||||||
// create a location for this page
|
// create a location for this page
|
||||||
l := &Location{URL: p.URL.String(), Title: p.Title}
|
l := &Location{URL: p.url.String(), Title: p.title}
|
||||||
|
|
||||||
// add its children
|
// add its children
|
||||||
for _, c := range p.Children {
|
for _, c := range p.children {
|
||||||
// look up this child's media type from the root list of pages
|
// look up this child's media type from the root list of pages
|
||||||
//mediaType := pageMap[c.URL.String()].MediaType
|
//mediaType := pageMap[c.url.String()].mediaType
|
||||||
if c.Skipped {
|
if c.linkType == tRemote {
|
||||||
l.Remote = append(l.Remote, c.URL.String())
|
l.Remote = append(l.Remote, c.url.String())
|
||||||
} else if c.MediaType == "text/html" {
|
} else if c.linkType == tHTMLPage {
|
||||||
l.Links = append(l.Links, c.URL.String())
|
l.Links = append(l.Links, c.url.String())
|
||||||
} else if c.Broken {
|
} else if c.linkType == tBroken {
|
||||||
l.Broken = append(l.Broken, c.URL.String())
|
l.Broken = append(l.Broken, c.url.String())
|
||||||
} else {
|
} else {
|
||||||
l.Assets = append(l.Assets, c.URL.String())
|
l.Assets = append(l.Assets, c.url.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +68,8 @@ func sitemapToLocations(pages []*Page) []*Location {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sort the Locations themselves and return
|
||||||
|
sort.Sort(byURL(locations))
|
||||||
return locations
|
return locations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestBadNewPage tests NewPage to see if we are getting expected failures
|
|
||||||
func TestBadNewPage(t *testing.T) {
|
|
||||||
_, err := NewPage(nil, "h%20t\tp://doesntexist23492387492837492374982734.com/")
|
|
||||||
if err == nil {
|
|
||||||
t.Error("tried creating a NewPage with an invalid URL but didn't get an error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user