mirror of
https://github.com/golang/go
synced 2024-11-21 20:24:50 -07:00
doc/codelab/wiki: update to work with template changes, add to run.bash
Fixes #1444. R=rsc, r CC=golang-dev https://golang.org/cl/3979045
This commit is contained in:
parent
b7949035d6
commit
add4e167e3
@ -1,6 +1,6 @@
|
|||||||
<h1>Editing {title}</h1>
|
<h1>Editing {Title}</h1>
|
||||||
|
|
||||||
<form action="/save/{title}" method="POST">
|
<form action="/save/{Title}" method="POST">
|
||||||
<div><textarea name="body" rows="20" cols="80">{body|html}</textarea></div>
|
<div><textarea name="Body" rows="20" cols="80">{Body|html}</textarea></div>
|
||||||
<div><input type="submit" value="Save"></div>
|
<div><input type="submit" value="Save"></div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -8,23 +8,23 @@ import (
|
|||||||
"template"
|
"template"
|
||||||
)
|
)
|
||||||
|
|
||||||
type page struct {
|
type Page struct {
|
||||||
title string
|
Title string
|
||||||
body []byte
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *page) save() os.Error {
|
func (p *Page) save() os.Error {
|
||||||
filename := p.title + ".txt"
|
filename := p.Title + ".txt"
|
||||||
return ioutil.WriteFile(filename, p.body, 0600)
|
return ioutil.WriteFile(filename, p.Body, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPage(title string) (*page, os.Error) {
|
func loadPage(title string) (*Page, os.Error) {
|
||||||
filename := title + ".txt"
|
filename := title + ".txt"
|
||||||
body, err := ioutil.ReadFile(filename)
|
body, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &page{title: title, body: body}, nil
|
return &Page{Title: title, Body: body}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -47,7 +47,7 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
p, err := loadPage(title)
|
p, err := loadPage(title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p = &page{title: title}
|
p = &Page{Title: title}
|
||||||
}
|
}
|
||||||
renderTemplate(w, "edit", p)
|
renderTemplate(w, "edit", p)
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ func saveHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
body := r.FormValue("body")
|
body := r.FormValue("body")
|
||||||
p := &page{title: title, body: []byte(body)}
|
p := &Page{Title: title, Body: []byte(body)}
|
||||||
err = p.save()
|
err = p.save()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.String(), http.StatusInternalServerError)
|
http.Error(w, err.String(), http.StatusInternalServerError)
|
||||||
@ -67,7 +67,7 @@ func saveHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Redirect(w, r, "/view/"+title, http.StatusFound)
|
http.Redirect(w, r, "/view/"+title, http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTemplate(w http.ResponseWriter, tmpl string, p *page) {
|
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
|
||||||
t, err := template.ParseFile(tmpl+".html", nil)
|
t, err := template.ParseFile(tmpl+".html", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.String(), http.StatusInternalServerError)
|
http.Error(w, err.String(), http.StatusInternalServerError)
|
||||||
|
@ -7,23 +7,23 @@ import (
|
|||||||
"template"
|
"template"
|
||||||
)
|
)
|
||||||
|
|
||||||
type page struct {
|
type Page struct {
|
||||||
title string
|
Title string
|
||||||
body []byte
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *page) save() os.Error {
|
func (p *Page) save() os.Error {
|
||||||
filename := p.title + ".txt"
|
filename := p.Title + ".txt"
|
||||||
return ioutil.WriteFile(filename, p.body, 0600)
|
return ioutil.WriteFile(filename, p.Body, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPage(title string) (*page, os.Error) {
|
func loadPage(title string) (*Page, os.Error) {
|
||||||
filename := title + ".txt"
|
filename := title + ".txt"
|
||||||
body, err := ioutil.ReadFile(filename)
|
body, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &page{title: title, body: body}, nil
|
return &Page{Title: title, Body: body}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const lenPath = len("/view/")
|
const lenPath = len("/view/")
|
||||||
@ -32,7 +32,7 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
title := r.URL.Path[lenPath:]
|
title := r.URL.Path[lenPath:]
|
||||||
p, err := loadPage(title)
|
p, err := loadPage(title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p = &page{title: title}
|
p = &Page{Title: title}
|
||||||
}
|
}
|
||||||
t, _ := template.ParseFile("edit.html", nil)
|
t, _ := template.ParseFile("edit.html", nil)
|
||||||
t.Execute(p, w)
|
t.Execute(p, w)
|
||||||
|
@ -8,23 +8,23 @@ import (
|
|||||||
"template"
|
"template"
|
||||||
)
|
)
|
||||||
|
|
||||||
type page struct {
|
type Page struct {
|
||||||
title string
|
Title string
|
||||||
body []byte
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *page) save() os.Error {
|
func (p *Page) save() os.Error {
|
||||||
filename := p.title + ".txt"
|
filename := p.Title + ".txt"
|
||||||
return ioutil.WriteFile(filename, p.body, 0600)
|
return ioutil.WriteFile(filename, p.Body, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPage(title string) (*page, os.Error) {
|
func loadPage(title string) (*Page, os.Error) {
|
||||||
filename := title + ".txt"
|
filename := title + ".txt"
|
||||||
body, err := ioutil.ReadFile(filename)
|
body, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &page{title: title, body: body}, nil
|
return &Page{Title: title, Body: body}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
|
func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
|
||||||
@ -39,14 +39,14 @@ func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
|
|||||||
func editHandler(w http.ResponseWriter, r *http.Request, title string) {
|
func editHandler(w http.ResponseWriter, r *http.Request, title string) {
|
||||||
p, err := loadPage(title)
|
p, err := loadPage(title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p = &page{title: title}
|
p = &Page{Title: title}
|
||||||
}
|
}
|
||||||
renderTemplate(w, "edit", p)
|
renderTemplate(w, "edit", p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
|
func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
|
||||||
body := r.FormValue("body")
|
body := r.FormValue("body")
|
||||||
p := &page{title: title, body: []byte(body)}
|
p := &Page{Title: title, Body: []byte(body)}
|
||||||
err := p.save()
|
err := p.save()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.String(), http.StatusInternalServerError)
|
http.Error(w, err.String(), http.StatusInternalServerError)
|
||||||
@ -55,7 +55,7 @@ func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
|
|||||||
http.Redirect(w, r, "/view/"+title, http.StatusFound)
|
http.Redirect(w, r, "/view/"+title, http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTemplate(w http.ResponseWriter, tmpl string, p *page) {
|
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
|
||||||
t, err := template.ParseFile(tmpl+".html", nil)
|
t, err := template.ParseFile(tmpl+".html", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.String(), http.StatusInternalServerError)
|
http.Error(w, err.String(), http.StatusInternalServerError)
|
||||||
|
@ -7,23 +7,23 @@ import (
|
|||||||
"template"
|
"template"
|
||||||
)
|
)
|
||||||
|
|
||||||
type page struct {
|
type Page struct {
|
||||||
title string
|
Title string
|
||||||
body []byte
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *page) save() os.Error {
|
func (p *Page) save() os.Error {
|
||||||
filename := p.title + ".txt"
|
filename := p.Title + ".txt"
|
||||||
return ioutil.WriteFile(filename, p.body, 0600)
|
return ioutil.WriteFile(filename, p.Body, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPage(title string) (*page, os.Error) {
|
func loadPage(title string) (*Page, os.Error) {
|
||||||
filename := title + ".txt"
|
filename := title + ".txt"
|
||||||
body, err := ioutil.ReadFile(filename)
|
body, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &page{title: title, body: body}, nil
|
return &Page{Title: title, Body: body}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const lenPath = len("/view/")
|
const lenPath = len("/view/")
|
||||||
@ -32,7 +32,7 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
title := r.URL.Path[lenPath:]
|
title := r.URL.Path[lenPath:]
|
||||||
p, err := loadPage(title)
|
p, err := loadPage(title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p = &page{title: title}
|
p = &Page{Title: title}
|
||||||
}
|
}
|
||||||
renderTemplate(w, "edit", p)
|
renderTemplate(w, "edit", p)
|
||||||
}
|
}
|
||||||
@ -46,12 +46,12 @@ func viewHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
func saveHandler(w http.ResponseWriter, r *http.Request) {
|
func saveHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
title := r.URL.Path[lenPath:]
|
title := r.URL.Path[lenPath:]
|
||||||
body := r.FormValue("body")
|
body := r.FormValue("body")
|
||||||
p := &page{title: title, body: []byte(body)}
|
p := &Page{Title: title, Body: []byte(body)}
|
||||||
p.save()
|
p.save()
|
||||||
http.Redirect(w, r, "/view/"+title, http.StatusFound)
|
http.Redirect(w, r, "/view/"+title, http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTemplate(w http.ResponseWriter, tmpl string, p *page) {
|
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
|
||||||
t, _ := template.ParseFile(tmpl+".html", nil)
|
t, _ := template.ParseFile(tmpl+".html", nil)
|
||||||
t.Execute(p, w)
|
t.Execute(p, w)
|
||||||
}
|
}
|
||||||
|
@ -8,23 +8,23 @@ import (
|
|||||||
"template"
|
"template"
|
||||||
)
|
)
|
||||||
|
|
||||||
type page struct {
|
type Page struct {
|
||||||
title string
|
Title string
|
||||||
body []byte
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *page) save() os.Error {
|
func (p *Page) save() os.Error {
|
||||||
filename := p.title + ".txt"
|
filename := p.Title + ".txt"
|
||||||
return ioutil.WriteFile(filename, p.body, 0600)
|
return ioutil.WriteFile(filename, p.Body, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPage(title string) (*page, os.Error) {
|
func loadPage(title string) (*Page, os.Error) {
|
||||||
filename := title + ".txt"
|
filename := title + ".txt"
|
||||||
body, err := ioutil.ReadFile(filename)
|
body, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &page{title: title, body: body}, nil
|
return &Page{Title: title, Body: body}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
|
func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
|
||||||
@ -39,14 +39,14 @@ func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
|
|||||||
func editHandler(w http.ResponseWriter, r *http.Request, title string) {
|
func editHandler(w http.ResponseWriter, r *http.Request, title string) {
|
||||||
p, err := loadPage(title)
|
p, err := loadPage(title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p = &page{title: title}
|
p = &Page{Title: title}
|
||||||
}
|
}
|
||||||
renderTemplate(w, "edit", p)
|
renderTemplate(w, "edit", p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
|
func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
|
||||||
body := r.FormValue("body")
|
body := r.FormValue("body")
|
||||||
p := &page{title: title, body: []byte(body)}
|
p := &Page{Title: title, Body: []byte(body)}
|
||||||
err := p.save()
|
err := p.save()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.String(), http.StatusInternalServerError)
|
http.Error(w, err.String(), http.StatusInternalServerError)
|
||||||
@ -63,7 +63,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTemplate(w http.ResponseWriter, tmpl string, p *page) {
|
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
|
||||||
err := templates[tmpl].Execute(p, w)
|
err := templates[tmpl].Execute(p, w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.String(), http.StatusInternalServerError)
|
http.Error(w, err.String(), http.StatusInternalServerError)
|
||||||
|
@ -8,5 +8,5 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
b, _ := ioutil.ReadAll(os.Stdin)
|
b, _ := ioutil.ReadAll(os.Stdin)
|
||||||
template.HTMLFormatter(os.Stdout, b, "")
|
template.HTMLFormatter(os.Stdout, "", b)
|
||||||
}
|
}
|
||||||
|
@ -71,14 +71,14 @@ declaration.
|
|||||||
<p>
|
<p>
|
||||||
Let's start by defining the data structures. A wiki consists of a series of
|
Let's start by defining the data structures. A wiki consists of a series of
|
||||||
interconnected pages, each of which has a title and a body (the page content).
|
interconnected pages, each of which has a title and a body (the page content).
|
||||||
Here, we define <code>page</code> as a struct with two fields representing
|
Here, we define <code>Page</code> as a struct with two fields representing
|
||||||
the title and body.
|
the title and body.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
type page struct {
|
type Page struct {
|
||||||
title string
|
Title string
|
||||||
body []byte
|
Body []byte
|
||||||
}
|
}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
@ -86,33 +86,33 @@ type page struct {
|
|||||||
The type <code>[]byte</code> means "a <code>byte</code> slice".
|
The type <code>[]byte</code> means "a <code>byte</code> slice".
|
||||||
(See <a href="http://golang.org/doc/effective_go.html#slices">Effective Go</a>
|
(See <a href="http://golang.org/doc/effective_go.html#slices">Effective Go</a>
|
||||||
for more on slices.)
|
for more on slices.)
|
||||||
The <code>body</code> element is a <code>[]byte</code> rather than
|
The <code>Body</code> element is a <code>[]byte</code> rather than
|
||||||
<code>string</code> because that is the type expected by the <code>io</code>
|
<code>string</code> because that is the type expected by the <code>io</code>
|
||||||
libraries we will use, as you'll see below.
|
libraries we will use, as you'll see below.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The <code>page</code> struct describes how page data will be stored in memory.
|
The <code>Page</code> struct describes how page data will be stored in memory.
|
||||||
But what about persistent storage? We can address that by creating a
|
But what about persistent storage? We can address that by creating a
|
||||||
<code>save</code> method on <code>page</code>:
|
<code>save</code> method on <code>Page</code>:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
func (p *page) save() os.Error {
|
func (p *Page) save() os.Error {
|
||||||
filename := p.title + ".txt"
|
filename := p.Title + ".txt"
|
||||||
return ioutil.WriteFile(filename, p.body, 0600)
|
return ioutil.WriteFile(filename, p.Body, 0600)
|
||||||
}
|
}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
This method's signature reads: "This is a method named <code>save</code> that
|
This method's signature reads: "This is a method named <code>save</code> that
|
||||||
takes as its receiver <code>p</code>, a pointer to <code>page</code> . It takes
|
takes as its receiver <code>p</code>, a pointer to <code>Page</code> . It takes
|
||||||
no parameters, and returns a value of type <code>os.Error</code>."
|
no parameters, and returns a value of type <code>os.Error</code>."
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
This method will save the <code>page</code>'s <code>body</code> to a text
|
This method will save the <code>Page</code>'s <code>Body</code> to a text
|
||||||
file. For simplicity, we will use the <code>title</code> as the file name.
|
file. For simplicity, we will use the <code>Title</code> as the file name.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -120,7 +120,7 @@ The <code>save</code> method returns an <code>os.Error</code> value because
|
|||||||
that is the return type of <code>WriteFile</code> (a standard library function
|
that is the return type of <code>WriteFile</code> (a standard library function
|
||||||
that writes a byte slice to a file). The <code>save</code> method returns the
|
that writes a byte slice to a file). The <code>save</code> method returns the
|
||||||
error value, to let the application handle it should anything go wrong while
|
error value, to let the application handle it should anything go wrong while
|
||||||
writing the file. If all goes well, <code>page.save()</code> will return
|
writing the file. If all goes well, <code>Page.save()</code> will return
|
||||||
<code>nil</code> (the zero-value for pointers, interfaces, and some other
|
<code>nil</code> (the zero-value for pointers, interfaces, and some other
|
||||||
types).
|
types).
|
||||||
</p>
|
</p>
|
||||||
@ -137,17 +137,17 @@ We will want to load pages, too:
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
func loadPage(title string) *page {
|
func loadPage(title string) *Page {
|
||||||
filename := title + ".txt"
|
filename := title + ".txt"
|
||||||
body, _ := ioutil.ReadFile(filename)
|
body, _ := ioutil.ReadFile(filename)
|
||||||
return &page{title: title, body: body}
|
return &Page{Title: title, Body: body}
|
||||||
}
|
}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The function <code>loadPage</code> constructs the file name from
|
The function <code>loadPage</code> constructs the file name from
|
||||||
<code>title</code>, reads the file's contents into a new
|
<code>Title</code>, reads the file's contents into a new
|
||||||
<code>page</code>, and returns a pointer to that new <code>page</code>.
|
<code>Page</code>, and returns a pointer to that new <code>page</code>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -161,23 +161,23 @@ error return value (in essence, assigning the value to nothing).
|
|||||||
<p>
|
<p>
|
||||||
But what happens if <code>ReadFile</code> encounters an error? For example,
|
But what happens if <code>ReadFile</code> encounters an error? For example,
|
||||||
the file might not exist. We should not ignore such errors. Let's modify the
|
the file might not exist. We should not ignore such errors. Let's modify the
|
||||||
function to return <code>*page</code> and <code>os.Error</code>.
|
function to return <code>*Page</code> and <code>os.Error</code>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
func loadPage(title string) (*page, os.Error) {
|
func loadPage(title string) (*Page, os.Error) {
|
||||||
filename := title + ".txt"
|
filename := title + ".txt"
|
||||||
body, err := ioutil.ReadFile(filename)
|
body, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &page{title: title, body: body}, nil
|
return &Page{Title: title, Body: body}, nil
|
||||||
}
|
}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Callers of this function can now check the second parameter; if it is
|
Callers of this function can now check the second parameter; if it is
|
||||||
<code>nil</code> then it has successfully loaded a page. If not, it will be an
|
<code>nil</code> then it has successfully loaded a Page. If not, it will be an
|
||||||
<code>os.Error</code> that can be handled by the caller (see the <a
|
<code>os.Error</code> that can be handled by the caller (see the <a
|
||||||
href="http://golang.org/pkg/os/#Error">os package documentation</a> for
|
href="http://golang.org/pkg/os/#Error">os package documentation</a> for
|
||||||
details).
|
details).
|
||||||
@ -191,17 +191,17 @@ written:
|
|||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
func main() {
|
func main() {
|
||||||
p1 := &page{title: "TestPage", body: []byte("This is a sample page.")}
|
p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")}
|
||||||
p1.save()
|
p1.save()
|
||||||
p2, _ := loadPage("TestPage")
|
p2, _ := loadPage("TestPage")
|
||||||
fmt.Println(string(p2.body))
|
fmt.Println(string(p2.Body))
|
||||||
}
|
}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
After compiling and executing this code, a file named <code>TestPage.txt</code>
|
After compiling and executing this code, a file named <code>TestPage.txt</code>
|
||||||
would be created, containing the contents of <code>p1</code>. The file would
|
would be created, containing the contents of <code>p1</code>. The file would
|
||||||
then be read into the struct <code>p2</code>, and its <code>body</code> element
|
then be read into the struct <code>p2</code>, and its <code>Body</code> element
|
||||||
printed to the screen.
|
printed to the screen.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -317,7 +317,7 @@ const lenPath = len("/view/")
|
|||||||
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
title := r.URL.Path[lenPath:]
|
title := r.URL.Path[lenPath:]
|
||||||
p, _ := loadPage(title)
|
p, _ := loadPage(title)
|
||||||
fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.title, p.body)
|
fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body)
|
||||||
}
|
}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
@ -377,7 +377,7 @@ href="http://localhost:8080/view/test">http://localhost:8080/view/test</a></code
|
|||||||
should show a page titled "test" containing the words "Hello world".
|
should show a page titled "test" containing the words "Hello world".
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Editing pages</h2>
|
<h2>Editing Pages</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
A wiki is not a wiki without the ability to edit pages. Let's create two new
|
A wiki is not a wiki without the ability to edit pages. Let's create two new
|
||||||
@ -401,7 +401,7 @@ func main() {
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
The function <code>editHandler</code> loads the page
|
The function <code>editHandler</code> loads the page
|
||||||
(or, if it doesn't exist, create an empty <code>page</code> struct),
|
(or, if it doesn't exist, create an empty <code>Page</code> struct),
|
||||||
and displays an HTML form.
|
and displays an HTML form.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -410,14 +410,14 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
title := r.URL.Path[lenPath:]
|
title := r.URL.Path[lenPath:]
|
||||||
p, err := loadPage(title)
|
p, err := loadPage(title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p = &page{title: title}
|
p = &Page{Title: title}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "<h1>Editing %s</h1>"+
|
fmt.Fprintf(w, "<h1>Editing %s</h1>"+
|
||||||
"<form action=\"/save/%s\" method=\"POST\">"+
|
"<form action=\"/save/%s\" method=\"POST\">"+
|
||||||
"<textarea name=\"body\">%s</textarea><br>"+
|
"<textarea name=\"body\">%s</textarea><br>"+
|
||||||
"<input type=\"submit\" value=\"Save\">"+
|
"<input type=\"submit\" value=\"Save\">"+
|
||||||
"</form>",
|
"</form>",
|
||||||
p.title, p.title, p.body)
|
p.Title, p.Title, p.Body)
|
||||||
}
|
}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
@ -454,10 +454,10 @@ Open a new file named <code>edit.html</code>, and add the following lines:
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<h1>Editing {title}</h1>
|
<h1>Editing {Title}</h1>
|
||||||
|
|
||||||
<form action="/save/{title}" method="POST">
|
<form action="/save/{Title}" method="POST">
|
||||||
<div><textarea name="body" rows="20" cols="80">{body|html}</textarea></div>
|
<div><textarea name="Body" rows="20" cols="80">{Body|html}</textarea></div>
|
||||||
<div><input type="submit" value="Save"></div>
|
<div><input type="submit" value="Save"></div>
|
||||||
</form>
|
</form>
|
||||||
</pre>
|
</pre>
|
||||||
@ -472,7 +472,7 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
title := r.URL.Path[lenPath:]
|
title := r.URL.Path[lenPath:]
|
||||||
p, err := loadPage(title)
|
p, err := loadPage(title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p = &page{title: title}
|
p = &Page{Title: title}
|
||||||
}
|
}
|
||||||
t, _ := template.ParseFile("edit.html", nil)
|
t, _ := template.ParseFile("edit.html", nil)
|
||||||
t.Execute(p, w)
|
t.Execute(p, w)
|
||||||
@ -486,15 +486,15 @@ The function <code>template.ParseFile</code> will read the contents of
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
The method <code>t.Execute</code> replaces all occurrences of
|
The method <code>t.Execute</code> replaces all occurrences of
|
||||||
<code>{title}</code> and <code>{body}</code> with the values of
|
<code>{Title}</code> and <code>{Body}</code> with the values of
|
||||||
<code>p.title</code> and <code>p.body</code>, and writes the resultant
|
<code>p.Title</code> and <code>p.Body</code>, and writes the resultant
|
||||||
HTML to the <code>http.ResponseWriter</code>.
|
HTML to the <code>http.ResponseWriter</code>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Note that we've used <code>{body|html}</code> in the above template.
|
Note that we've used <code>{Body|html}</code> in the above template.
|
||||||
The <code>|html</code> part asks the template engine to pass the value
|
The <code>|html</code> part asks the template engine to pass the value
|
||||||
<code>body</code> through the <code>html</code> formatter before outputting it,
|
<code>Body</code> through the <code>html</code> formatter before outputting it,
|
||||||
which escapes HTML characters (such as replacing <code>></code> with
|
which escapes HTML characters (such as replacing <code>></code> with
|
||||||
<code>&gt;</code>).
|
<code>&gt;</code>).
|
||||||
This will prevent user data from corrupting the form HTML.
|
This will prevent user data from corrupting the form HTML.
|
||||||
@ -511,11 +511,11 @@ While we're working with templates, let's create a template for our
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
<h1>{title}</h1>
|
<h1>{Title}</h1>
|
||||||
|
|
||||||
<p>[<a href="/edit/{title}">edit</a>]</p>
|
<p>[<a href="/edit/{Title}">edit</a>]</p>
|
||||||
|
|
||||||
<div>{body}</div>
|
<div>{Body}</div>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -548,12 +548,12 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
title := r.URL.Path[lenPath:]
|
title := r.URL.Path[lenPath:]
|
||||||
p, err := loadPage(title)
|
p, err := loadPage(title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p = &page{title: title}
|
p = &Page{Title: title}
|
||||||
}
|
}
|
||||||
renderTemplate(w, "edit", p)
|
renderTemplate(w, "edit", p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTemplate(w http.ResponseWriter, tmpl string, p *page) {
|
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
|
||||||
t, _ := template.ParseFile(tmpl+".html", nil)
|
t, _ := template.ParseFile(tmpl+".html", nil)
|
||||||
t.Execute(p, w)
|
t.Execute(p, w)
|
||||||
}
|
}
|
||||||
@ -568,8 +568,8 @@ The handlers are now shorter and simpler.
|
|||||||
<p>
|
<p>
|
||||||
What if you visit <code>/view/APageThatDoesntExist</code>? The program will
|
What if you visit <code>/view/APageThatDoesntExist</code>? The program will
|
||||||
crash. This is because it ignores the error return value from
|
crash. This is because it ignores the error return value from
|
||||||
<code>loadPage</code>. Instead, if the requested page doesn't exist, it should
|
<code>loadPage</code>. Instead, if the requested Page doesn't exist, it should
|
||||||
redirect the client to the edit page so the content may be created:
|
redirect the client to the edit Page so the content may be created:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
@ -589,7 +589,7 @@ The <code>http.Redirect</code> function adds an HTTP status code of
|
|||||||
header to the HTTP response.
|
header to the HTTP response.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Saving pages</h2>
|
<h2>Saving Pages</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The function <code>saveHandler</code> will handle the form submission.
|
The function <code>saveHandler</code> will handle the form submission.
|
||||||
@ -599,7 +599,7 @@ The function <code>saveHandler</code> will handle the form submission.
|
|||||||
func saveHandler(w http.ResponseWriter, r *http.Request) {
|
func saveHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
title := r.URL.Path[lenPath:]
|
title := r.URL.Path[lenPath:]
|
||||||
body := r.FormValue("body")
|
body := r.FormValue("body")
|
||||||
p := &page{title: title, body: []byte(body)}
|
p := &Page{Title: title, Body: []byte(body)}
|
||||||
p.save()
|
p.save()
|
||||||
http.Redirect(w, r, "/view/"+title, http.StatusFound)
|
http.Redirect(w, r, "/view/"+title, http.StatusFound)
|
||||||
}
|
}
|
||||||
@ -607,7 +607,7 @@ func saveHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
The page title (provided in the URL) and the form's only field,
|
The page title (provided in the URL) and the form's only field,
|
||||||
<code>body</code>, are stored in a new <code>page</code>.
|
<code>Body</code>, are stored in a new <code>Page</code>.
|
||||||
The <code>save()</code> method is then called to write the data to a file,
|
The <code>save()</code> method is then called to write the data to a file,
|
||||||
and the client is redirected to the <code>/view/</code> page.
|
and the client is redirected to the <code>/view/</code> page.
|
||||||
</p>
|
</p>
|
||||||
@ -615,7 +615,7 @@ and the client is redirected to the <code>/view/</code> page.
|
|||||||
<p>
|
<p>
|
||||||
The value returned by <code>FormValue</code> is of type <code>string</code>.
|
The value returned by <code>FormValue</code> is of type <code>string</code>.
|
||||||
We must convert that value to <code>[]byte</code> before it will fit into
|
We must convert that value to <code>[]byte</code> before it will fit into
|
||||||
the <code>page</code> struct. We use <code>[]byte(body)</code> to perform
|
the <code>Page</code> struct. We use <code>[]byte(body)</code> to perform
|
||||||
the conversion.
|
the conversion.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -634,7 +634,7 @@ First, let's handle the errors in <code>renderTemplate</code>:
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
func renderTemplate(w http.ResponseWriter, tmpl string, p *page) {
|
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
|
||||||
t, err := template.ParseFile(tmpl+".html", nil)
|
t, err := template.ParseFile(tmpl+".html", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.String(), http.StatusInternalServerError)
|
http.Error(w, err.String(), http.StatusInternalServerError)
|
||||||
@ -660,7 +660,7 @@ Now let's fix up <code>saveHandler</code>:
|
|||||||
<pre>
|
<pre>
|
||||||
func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
|
func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
|
||||||
body := r.FormValue("body")
|
body := r.FormValue("body")
|
||||||
p := &page{title: title, body: []byte(body)}
|
p := &Page{Title: title, Body: []byte(body)}
|
||||||
err := p.save()
|
err := p.save()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.String(), http.StatusInternalServerError)
|
http.Error(w, err.String(), http.StatusInternalServerError)
|
||||||
@ -725,7 +725,7 @@ the <code>Execute</code> method on the appropriate <code>Template</code> from
|
|||||||
<code>templates</code>:
|
<code>templates</code>:
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
func renderTemplate(w http.ResponseWriter, tmpl string, p *page) {
|
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
|
||||||
err := templates[tmpl].Execute(p, w)
|
err := templates[tmpl].Execute(p, w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.String(), http.StatusInternalServerError)
|
http.Error(w, err.String(), http.StatusInternalServerError)
|
||||||
@ -747,7 +747,6 @@ Then we can create a global variable to store our validation regexp:
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
|
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -761,7 +760,7 @@ the expression compilation fails, while <code>Compile</code> returns an
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
Now, let's write a function that extracts the title string from the request
|
Now, let's write a function that extracts the title string from the request
|
||||||
URL, and tests it against our <code>titleValidator</code> expression:
|
URL, and tests it against our <code>TitleValidator</code> expression:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
@ -807,7 +806,7 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
p, err := loadPage(title)
|
p, err := loadPage(title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p = &page{title: title}
|
p = &Page{Title: title}
|
||||||
}
|
}
|
||||||
renderTemplate(w, "edit", p)
|
renderTemplate(w, "edit", p)
|
||||||
}
|
}
|
||||||
@ -818,7 +817,7 @@ func saveHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
body := r.FormValue("body")
|
body := r.FormValue("body")
|
||||||
p := &page{title: title, body: []byte(body)}
|
p := &Page{Title: title, Body: []byte(body)}
|
||||||
err = p.save()
|
err = p.save()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.String(), http.StatusInternalServerError)
|
http.Error(w, err.String(), http.StatusInternalServerError)
|
||||||
@ -895,7 +894,7 @@ The closure returned by <code>makeHandler</code> is a function that takes
|
|||||||
an <code>http.ResponseWriter</code> and <code>http.Request</code> (in other
|
an <code>http.ResponseWriter</code> and <code>http.Request</code> (in other
|
||||||
words, an <code>http.HandlerFunc</code>).
|
words, an <code>http.HandlerFunc</code>).
|
||||||
The closure extracts the <code>title</code> from the request path, and
|
The closure extracts the <code>title</code> from the request path, and
|
||||||
validates it with the <code>titleValidator</code> regexp. If the
|
validates it with the <code>TitleValidator</code> regexp. If the
|
||||||
<code>title</code> is invalid, an error will be written to the
|
<code>title</code> is invalid, an error will be written to the
|
||||||
<code>ResponseWriter</code> using the <code>http.NotFound</code> function.
|
<code>ResponseWriter</code> using the <code>http.NotFound</code> function.
|
||||||
If the <code>title</code> is valid, the enclosed handler function
|
If the <code>title</code> is valid, the enclosed handler function
|
||||||
@ -936,14 +935,14 @@ func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
|
|||||||
func editHandler(w http.ResponseWriter, r *http.Request, title string) {
|
func editHandler(w http.ResponseWriter, r *http.Request, title string) {
|
||||||
p, err := loadPage(title)
|
p, err := loadPage(title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p = &page{title: title}
|
p = &Page{Title: title}
|
||||||
}
|
}
|
||||||
renderTemplate(w, "edit", p)
|
renderTemplate(w, "edit", p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
|
func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
|
||||||
body := r.FormValue("body")
|
body := r.FormValue("body")
|
||||||
p := &page{title: title, body: []byte(body)}
|
p := &Page{Title: title, Body: []byte(body)}
|
||||||
err := p.save()
|
err := p.save()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.String(), http.StatusInternalServerError)
|
http.Error(w, err.String(), http.StatusInternalServerError)
|
||||||
|
@ -7,23 +7,23 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type page struct {
|
type Page struct {
|
||||||
title string
|
Title string
|
||||||
body []byte
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *page) save() os.Error {
|
func (p *Page) save() os.Error {
|
||||||
filename := p.title + ".txt"
|
filename := p.Title + ".txt"
|
||||||
return ioutil.WriteFile(filename, p.body, 0600)
|
return ioutil.WriteFile(filename, p.Body, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPage(title string) (*page, os.Error) {
|
func loadPage(title string) (*Page, os.Error) {
|
||||||
filename := title + ".txt"
|
filename := title + ".txt"
|
||||||
body, err := ioutil.ReadFile(filename)
|
body, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &page{title: title, body: body}, nil
|
return &Page{Title: title, Body: body}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const lenPath = len("/view/")
|
const lenPath = len("/view/")
|
||||||
@ -31,21 +31,21 @@ const lenPath = len("/view/")
|
|||||||
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
title := r.URL.Path[lenPath:]
|
title := r.URL.Path[lenPath:]
|
||||||
p, _ := loadPage(title)
|
p, _ := loadPage(title)
|
||||||
fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.title, p.body)
|
fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func editHandler(w http.ResponseWriter, r *http.Request) {
|
func editHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
title := r.URL.Path[lenPath:]
|
title := r.URL.Path[lenPath:]
|
||||||
p, err := loadPage(title)
|
p, err := loadPage(title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p = &page{title: title}
|
p = &Page{Title: title}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "<h1>Editing %s</h1>"+
|
fmt.Fprintf(w, "<h1>Editing %s</h1>"+
|
||||||
"<form action=\"/save/%s\" method=\"POST\">"+
|
"<form action=\"/save/%s\" method=\"POST\">"+
|
||||||
"<textarea name=\"body\">%s</textarea><br>"+
|
"<textarea name=\"body\">%s</textarea><br>"+
|
||||||
"<input type=\"submit\" value=\"Save\">"+
|
"<input type=\"submit\" value=\"Save\">"+
|
||||||
"</form>",
|
"</form>",
|
||||||
p.title, p.title, p.body)
|
p.Title, p.Title, p.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -6,25 +6,25 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type page struct {
|
type Page struct {
|
||||||
title string
|
Title string
|
||||||
body []byte
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *page) save() os.Error {
|
func (p *Page) save() os.Error {
|
||||||
filename := p.title + ".txt"
|
filename := p.Title + ".txt"
|
||||||
return ioutil.WriteFile(filename, p.body, 0600)
|
return ioutil.WriteFile(filename, p.Body, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPage(title string) *page {
|
func loadPage(title string) *Page {
|
||||||
filename := title + ".txt"
|
filename := title + ".txt"
|
||||||
body, _ := ioutil.ReadFile(filename)
|
body, _ := ioutil.ReadFile(filename)
|
||||||
return &page{title: title, body: body}
|
return &Page{Title: title, Body: body}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
p1 := &page{title: "TestPage", body: []byte("This is a sample page.")}
|
p1 := &Page{Title: "TestPage", Body: []byte("This is a sample page.")}
|
||||||
p1.save()
|
p1.save()
|
||||||
p2 := loadPage("TestPage")
|
p2 := loadPage("TestPage")
|
||||||
fmt.Println(string(p2.body))
|
fmt.Println(string(p2.Body))
|
||||||
}
|
}
|
||||||
|
@ -6,28 +6,28 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type page struct {
|
type Page struct {
|
||||||
title string
|
Title string
|
||||||
body []byte
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *page) save() os.Error {
|
func (p *Page) save() os.Error {
|
||||||
filename := p.title + ".txt"
|
filename := p.Title + ".txt"
|
||||||
return ioutil.WriteFile(filename, p.body, 0600)
|
return ioutil.WriteFile(filename, p.Body, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPage(title string) (*page, os.Error) {
|
func loadPage(title string) (*Page, os.Error) {
|
||||||
filename := title + ".txt"
|
filename := title + ".txt"
|
||||||
body, err := ioutil.ReadFile(filename)
|
body, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &page{title: title, body: body}, nil
|
return &Page{Title: title, Body: body}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
p1 := &page{title: "TestPage", body: []byte("This is a sample page.")}
|
p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")}
|
||||||
p1.save()
|
p1.save()
|
||||||
p2, _ := loadPage("TestPage")
|
p2, _ := loadPage("TestPage")
|
||||||
fmt.Println(string(p2.body))
|
fmt.Println(string(p2.Body))
|
||||||
}
|
}
|
||||||
|
@ -7,23 +7,23 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type page struct {
|
type Page struct {
|
||||||
title string
|
Title string
|
||||||
body []byte
|
Body []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *page) save() os.Error {
|
func (p *Page) save() os.Error {
|
||||||
filename := p.title + ".txt"
|
filename := p.Title + ".txt"
|
||||||
return ioutil.WriteFile(filename, p.body, 0600)
|
return ioutil.WriteFile(filename, p.Body, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPage(title string) (*page, os.Error) {
|
func loadPage(title string) (*Page, os.Error) {
|
||||||
filename := title + ".txt"
|
filename := title + ".txt"
|
||||||
body, err := ioutil.ReadFile(filename)
|
body, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &page{title: title, body: body}, nil
|
return &Page{Title: title, Body: body}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const lenPath = len("/view/")
|
const lenPath = len("/view/")
|
||||||
@ -31,7 +31,7 @@ const lenPath = len("/view/")
|
|||||||
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
title := r.URL.Path[lenPath:]
|
title := r.URL.Path[lenPath:]
|
||||||
p, _ := loadPage(title)
|
p, _ := loadPage(title)
|
||||||
fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.title, p.body)
|
fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"go/parser"
|
"go/parser"
|
||||||
"go/printer"
|
"go/printer"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
@ -25,7 +26,8 @@ func main() {
|
|||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
// load file
|
// load file
|
||||||
file, err := parser.ParseFile(*srcFn, nil, 0)
|
fs := token.NewFileSet()
|
||||||
|
file, err := parser.ParseFile(fs, *srcFn, nil, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Exit(err)
|
log.Exit(err)
|
||||||
}
|
}
|
||||||
@ -47,7 +49,7 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
p.Fprint(b, file)
|
p.Fprint(b, fs, file)
|
||||||
// drop package declaration
|
// drop package declaration
|
||||||
if !*showPkg {
|
if !*showPkg {
|
||||||
for {
|
for {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<h1>Editing Test</h1>
|
<h1>Editing Test</h1>
|
||||||
|
|
||||||
<form action="/save/Test" method="POST">
|
<form action="/save/Test" method="POST">
|
||||||
<div><textarea name="body" rows="20" cols="80"></textarea></div>
|
<div><textarea name="Body" rows="20" cols="80"></textarea></div>
|
||||||
<div><input type="submit" value="Save"></div>
|
<div><input type="submit" value="Save"></div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<h1>{title}</h1>
|
<h1>{Title}</h1>
|
||||||
|
|
||||||
<p>[<a href="/edit/{title}">edit</a>]</p>
|
<p>[<a href="/edit/{Title}">edit</a>]</p>
|
||||||
|
|
||||||
<div>{body}</div>
|
<div>{Body}</div>
|
||||||
|
@ -71,27 +71,27 @@ declaration.
|
|||||||
<p>
|
<p>
|
||||||
Let's start by defining the data structures. A wiki consists of a series of
|
Let's start by defining the data structures. A wiki consists of a series of
|
||||||
interconnected pages, each of which has a title and a body (the page content).
|
interconnected pages, each of which has a title and a body (the page content).
|
||||||
Here, we define <code>page</code> as a struct with two fields representing
|
Here, we define <code>Page</code> as a struct with two fields representing
|
||||||
the title and body.
|
the title and body.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
!./srcextract.bin -src=part1.go -name=page
|
!./srcextract.bin -src=part1.go -name=Page
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The type <code>[]byte</code> means "a <code>byte</code> slice".
|
The type <code>[]byte</code> means "a <code>byte</code> slice".
|
||||||
(See <a href="http://golang.org/doc/effective_go.html#slices">Effective Go</a>
|
(See <a href="http://golang.org/doc/effective_go.html#slices">Effective Go</a>
|
||||||
for more on slices.)
|
for more on slices.)
|
||||||
The <code>body</code> element is a <code>[]byte</code> rather than
|
The <code>Body</code> element is a <code>[]byte</code> rather than
|
||||||
<code>string</code> because that is the type expected by the <code>io</code>
|
<code>string</code> because that is the type expected by the <code>io</code>
|
||||||
libraries we will use, as you'll see below.
|
libraries we will use, as you'll see below.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The <code>page</code> struct describes how page data will be stored in memory.
|
The <code>Page</code> struct describes how page data will be stored in memory.
|
||||||
But what about persistent storage? We can address that by creating a
|
But what about persistent storage? We can address that by creating a
|
||||||
<code>save</code> method on <code>page</code>:
|
<code>save</code> method on <code>Page</code>:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
@ -100,13 +100,13 @@ But what about persistent storage? We can address that by creating a
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
This method's signature reads: "This is a method named <code>save</code> that
|
This method's signature reads: "This is a method named <code>save</code> that
|
||||||
takes as its receiver <code>p</code>, a pointer to <code>page</code> . It takes
|
takes as its receiver <code>p</code>, a pointer to <code>Page</code> . It takes
|
||||||
no parameters, and returns a value of type <code>os.Error</code>."
|
no parameters, and returns a value of type <code>os.Error</code>."
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
This method will save the <code>page</code>'s <code>body</code> to a text
|
This method will save the <code>Page</code>'s <code>Body</code> to a text
|
||||||
file. For simplicity, we will use the <code>title</code> as the file name.
|
file. For simplicity, we will use the <code>Title</code> as the file name.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -114,7 +114,7 @@ The <code>save</code> method returns an <code>os.Error</code> value because
|
|||||||
that is the return type of <code>WriteFile</code> (a standard library function
|
that is the return type of <code>WriteFile</code> (a standard library function
|
||||||
that writes a byte slice to a file). The <code>save</code> method returns the
|
that writes a byte slice to a file). The <code>save</code> method returns the
|
||||||
error value, to let the application handle it should anything go wrong while
|
error value, to let the application handle it should anything go wrong while
|
||||||
writing the file. If all goes well, <code>page.save()</code> will return
|
writing the file. If all goes well, <code>Page.save()</code> will return
|
||||||
<code>nil</code> (the zero-value for pointers, interfaces, and some other
|
<code>nil</code> (the zero-value for pointers, interfaces, and some other
|
||||||
types).
|
types).
|
||||||
</p>
|
</p>
|
||||||
@ -136,8 +136,8 @@ We will want to load pages, too:
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
The function <code>loadPage</code> constructs the file name from
|
The function <code>loadPage</code> constructs the file name from
|
||||||
<code>title</code>, reads the file's contents into a new
|
<code>Title</code>, reads the file's contents into a new
|
||||||
<code>page</code>, and returns a pointer to that new <code>page</code>.
|
<code>Page</code>, and returns a pointer to that new <code>page</code>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -151,7 +151,7 @@ error return value (in essence, assigning the value to nothing).
|
|||||||
<p>
|
<p>
|
||||||
But what happens if <code>ReadFile</code> encounters an error? For example,
|
But what happens if <code>ReadFile</code> encounters an error? For example,
|
||||||
the file might not exist. We should not ignore such errors. Let's modify the
|
the file might not exist. We should not ignore such errors. Let's modify the
|
||||||
function to return <code>*page</code> and <code>os.Error</code>.
|
function to return <code>*Page</code> and <code>os.Error</code>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
@ -160,7 +160,7 @@ function to return <code>*page</code> and <code>os.Error</code>.
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
Callers of this function can now check the second parameter; if it is
|
Callers of this function can now check the second parameter; if it is
|
||||||
<code>nil</code> then it has successfully loaded a page. If not, it will be an
|
<code>nil</code> then it has successfully loaded a Page. If not, it will be an
|
||||||
<code>os.Error</code> that can be handled by the caller (see the <a
|
<code>os.Error</code> that can be handled by the caller (see the <a
|
||||||
href="http://golang.org/pkg/os/#Error">os package documentation</a> for
|
href="http://golang.org/pkg/os/#Error">os package documentation</a> for
|
||||||
details).
|
details).
|
||||||
@ -179,7 +179,7 @@ written:
|
|||||||
<p>
|
<p>
|
||||||
After compiling and executing this code, a file named <code>TestPage.txt</code>
|
After compiling and executing this code, a file named <code>TestPage.txt</code>
|
||||||
would be created, containing the contents of <code>p1</code>. The file would
|
would be created, containing the contents of <code>p1</code>. The file would
|
||||||
then be read into the struct <code>p2</code>, and its <code>body</code> element
|
then be read into the struct <code>p2</code>, and its <code>Body</code> element
|
||||||
printed to the screen.
|
printed to the screen.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -334,7 +334,7 @@ href="http://localhost:8080/view/test">http://localhost:8080/view/test</a></code
|
|||||||
should show a page titled "test" containing the words "Hello world".
|
should show a page titled "test" containing the words "Hello world".
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Editing pages</h2>
|
<h2>Editing Pages</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
A wiki is not a wiki without the ability to edit pages. Let's create two new
|
A wiki is not a wiki without the ability to edit pages. Let's create two new
|
||||||
@ -353,7 +353,7 @@ First, we add them to <code>main()</code>:
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
The function <code>editHandler</code> loads the page
|
The function <code>editHandler</code> loads the page
|
||||||
(or, if it doesn't exist, create an empty <code>page</code> struct),
|
(or, if it doesn't exist, create an empty <code>Page</code> struct),
|
||||||
and displays an HTML form.
|
and displays an HTML form.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -413,15 +413,15 @@ The function <code>template.ParseFile</code> will read the contents of
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
The method <code>t.Execute</code> replaces all occurrences of
|
The method <code>t.Execute</code> replaces all occurrences of
|
||||||
<code>{title}</code> and <code>{body}</code> with the values of
|
<code>{Title}</code> and <code>{Body}</code> with the values of
|
||||||
<code>p.title</code> and <code>p.body</code>, and writes the resultant
|
<code>p.Title</code> and <code>p.Body</code>, and writes the resultant
|
||||||
HTML to the <code>http.ResponseWriter</code>.
|
HTML to the <code>http.ResponseWriter</code>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Note that we've used <code>{body|html}</code> in the above template.
|
Note that we've used <code>{Body|html}</code> in the above template.
|
||||||
The <code>|html</code> part asks the template engine to pass the value
|
The <code>|html</code> part asks the template engine to pass the value
|
||||||
<code>body</code> through the <code>html</code> formatter before outputting it,
|
<code>Body</code> through the <code>html</code> formatter before outputting it,
|
||||||
which escapes HTML characters (such as replacing <code>></code> with
|
which escapes HTML characters (such as replacing <code>></code> with
|
||||||
<code>&gt;</code>).
|
<code>&gt;</code>).
|
||||||
This will prevent user data from corrupting the form HTML.
|
This will prevent user data from corrupting the form HTML.
|
||||||
@ -472,8 +472,8 @@ The handlers are now shorter and simpler.
|
|||||||
<p>
|
<p>
|
||||||
What if you visit <code>/view/APageThatDoesntExist</code>? The program will
|
What if you visit <code>/view/APageThatDoesntExist</code>? The program will
|
||||||
crash. This is because it ignores the error return value from
|
crash. This is because it ignores the error return value from
|
||||||
<code>loadPage</code>. Instead, if the requested page doesn't exist, it should
|
<code>loadPage</code>. Instead, if the requested Page doesn't exist, it should
|
||||||
redirect the client to the edit page so the content may be created:
|
redirect the client to the edit Page so the content may be created:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
@ -486,7 +486,7 @@ The <code>http.Redirect</code> function adds an HTTP status code of
|
|||||||
header to the HTTP response.
|
header to the HTTP response.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Saving pages</h2>
|
<h2>Saving Pages</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The function <code>saveHandler</code> will handle the form submission.
|
The function <code>saveHandler</code> will handle the form submission.
|
||||||
@ -498,7 +498,7 @@ The function <code>saveHandler</code> will handle the form submission.
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
The page title (provided in the URL) and the form's only field,
|
The page title (provided in the URL) and the form's only field,
|
||||||
<code>body</code>, are stored in a new <code>page</code>.
|
<code>Body</code>, are stored in a new <code>Page</code>.
|
||||||
The <code>save()</code> method is then called to write the data to a file,
|
The <code>save()</code> method is then called to write the data to a file,
|
||||||
and the client is redirected to the <code>/view/</code> page.
|
and the client is redirected to the <code>/view/</code> page.
|
||||||
</p>
|
</p>
|
||||||
@ -506,7 +506,7 @@ and the client is redirected to the <code>/view/</code> page.
|
|||||||
<p>
|
<p>
|
||||||
The value returned by <code>FormValue</code> is of type <code>string</code>.
|
The value returned by <code>FormValue</code> is of type <code>string</code>.
|
||||||
We must convert that value to <code>[]byte</code> before it will fit into
|
We must convert that value to <code>[]byte</code> before it will fit into
|
||||||
the <code>page</code> struct. We use <code>[]byte(body)</code> to perform
|
the <code>Page</code> struct. We use <code>[]byte(body)</code> to perform
|
||||||
the conversion.
|
the conversion.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -610,7 +610,7 @@ Then we can create a global variable to store our validation regexp:
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
!./srcextract.bin -src=final-noclosure.go -name=titleValidator
|
!./srcextract.bin -src=final-noclosure.go -name=TitleValidator
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -624,7 +624,7 @@ the expression compilation fails, while <code>Compile</code> returns an
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
Now, let's write a function that extracts the title string from the request
|
Now, let's write a function that extracts the title string from the request
|
||||||
URL, and tests it against our <code>titleValidator</code> expression:
|
URL, and tests it against our <code>TitleValidator</code> expression:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
@ -708,7 +708,7 @@ The closure returned by <code>makeHandler</code> is a function that takes
|
|||||||
an <code>http.ResponseWriter</code> and <code>http.Request</code> (in other
|
an <code>http.ResponseWriter</code> and <code>http.Request</code> (in other
|
||||||
words, an <code>http.HandlerFunc</code>).
|
words, an <code>http.HandlerFunc</code>).
|
||||||
The closure extracts the <code>title</code> from the request path, and
|
The closure extracts the <code>title</code> from the request path, and
|
||||||
validates it with the <code>titleValidator</code> regexp. If the
|
validates it with the <code>TitleValidator</code> regexp. If the
|
||||||
<code>title</code> is invalid, an error will be written to the
|
<code>title</code> is invalid, an error will be written to the
|
||||||
<code>ResponseWriter</code> using the <code>http.NotFound</code> function.
|
<code>ResponseWriter</code> using the <code>http.NotFound</code> function.
|
||||||
If the <code>title</code> is valid, the enclosed handler function
|
If the <code>title</code> is valid, the enclosed handler function
|
||||||
|
@ -103,6 +103,9 @@ if [[ $(uname | tr A-Z a-z | sed 's/mingw/windows/') != *windows* ]]; then
|
|||||||
fi
|
fi
|
||||||
) || exit $?
|
) || exit $?
|
||||||
|
|
||||||
|
(xcd ../doc/codelab/wiki
|
||||||
|
gomake test) || exit $?
|
||||||
|
|
||||||
for i in ../misc/dashboard/builder ../misc/goplay
|
for i in ../misc/dashboard/builder ../misc/goplay
|
||||||
do
|
do
|
||||||
(xcd $i
|
(xcd $i
|
||||||
|
Loading…
Reference in New Issue
Block a user