├── .hgignore
├── .hgtags
├── static
├── partials
│ ├── toc-button.html
│ ├── lesson.html
│ ├── list.html
│ ├── toc.html
│ └── editor.html
├── img
│ ├── burger.png
│ ├── favicon.ico
│ └── gopher.png
├── lib
│ ├── codemirror
│ │ ├── README.md
│ │ ├── LICENSE
│ │ ├── AUTHORS
│ │ ├── lib
│ │ │ └── codemirror.css
│ │ └── mode
│ │ │ └── go
│ │ │ └── go.js
│ └── angular-ui.min.js
├── js
│ ├── app.js
│ ├── values.js
│ ├── controllers.js
│ ├── directives.js
│ └── services.js
└── css
│ └── app.css
├── content
├── img
│ ├── tree.png
│ ├── newton.png
│ └── newton3.png
├── flowcontrol
│ ├── forever.go
│ ├── defer.go
│ ├── for.go
│ ├── for-continued.go
│ ├── for-is-gos-while.go
│ ├── exercise-loops-and-functions.go
│ ├── defer-multi.go
│ ├── if.go
│ ├── if-with-a-short-statement.go
│ ├── switch-with-no-condition.go
│ ├── switch.go
│ ├── if-and-else.go
│ └── switch-evaluation-order.go
├── welcome
│ ├── hello.go
│ └── sandbox.go
├── basics
│ ├── type-inference.go
│ ├── exported-names.go
│ ├── packages.go
│ ├── variables.go
│ ├── zero.go
│ ├── imports.go
│ ├── functions.go
│ ├── functions-continued.go
│ ├── named-results.go
│ ├── variables-with-initializers.go
│ ├── multiple-results.go
│ ├── short-variable-declarations.go
│ ├── type-conversions.go
│ ├── constants.go
│ ├── basic-types.go
│ └── numeric-constants.go
├── moretypes
│ ├── structs.go
│ ├── exercise-slices.go
│ ├── array.go
│ ├── nil-slices.go
│ ├── range.go
│ ├── struct-fields.go
│ ├── struct-pointers.go
│ ├── slices.go
│ ├── function-values.go
│ ├── exercise-maps.go
│ ├── range-continued.go
│ ├── exercise-fibonacci-closure.go
│ ├── map-literals-continued.go
│ ├── maps.go
│ ├── slicing-slices.go
│ ├── map-literals.go
│ ├── struct-literals.go
│ ├── function-closures.go
│ ├── making-slices.go
│ ├── mutating-maps.go
│ ├── pointers.go
│ └── append.go
├── concurrency
│ ├── buffered-channels.go
│ ├── goroutines.go
│ ├── exercise-equivalent-binary-trees.go
│ ├── range-and-close.go
│ ├── channels.go
│ ├── default-selection.go
│ ├── select.go
│ └── exercise-web-crawler.go
├── methods
│ ├── exercise-images.go
│ ├── exercise-http-handlers.go
│ ├── exercise-reader.go
│ ├── images.go
│ ├── exercise-errors.go
│ ├── exercise-rot-reader.go
│ ├── methods.go
│ ├── methods-continued.go
│ ├── exercise-stringer.go
│ ├── stringer.go
│ ├── reader.go
│ ├── web-servers.go
│ ├── methods-with-pointer-receivers.go
│ ├── errors.go
│ ├── interfaces-are-satisfied-implicitly.go
│ └── interfaces.go
├── welcome.article
├── flowcontrol.article
├── basics.article
├── concurrency.article
├── moretypes.article
└── methods.article
├── codereview.cfg
├── AUTHORS
├── CONTRIBUTORS
├── README
├── solutions
├── README
├── maps.go
├── fib.go
├── slices.go
├── loops.go
├── stringers.go
├── image.go
├── errors.go
├── http.go
├── rot13.go
├── binarytrees.go
└── webcrawler.go
├── app.yaml
├── reader
└── validate.go
├── pic
└── pic.go
├── tree
└── tree.go
├── template
├── action.tmpl
└── index.tmpl
├── gotour
├── fmt.go
├── appengine.go
├── secure.go
├── local.go
└── tour.go
├── wc
└── wc.go
├── TRANSLATE
├── LICENSE
└── TODO
/.hgignore:
--------------------------------------------------------------------------------
1 | syntax:glob
2 | last-change
3 | *.orig
4 | *.rej
5 |
--------------------------------------------------------------------------------
/.hgtags:
--------------------------------------------------------------------------------
1 | dd4b1d7bebbd608f280c15b2fe76d9da3ac91c10 go.r60
2 |
--------------------------------------------------------------------------------
/static/partials/toc-button.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/content/img/tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minux/go-tour-zh/HEAD/content/img/tree.png
--------------------------------------------------------------------------------
/content/img/newton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minux/go-tour-zh/HEAD/content/img/newton.png
--------------------------------------------------------------------------------
/content/img/newton3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minux/go-tour-zh/HEAD/content/img/newton3.png
--------------------------------------------------------------------------------
/static/img/burger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minux/go-tour-zh/HEAD/static/img/burger.png
--------------------------------------------------------------------------------
/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minux/go-tour-zh/HEAD/static/img/favicon.ico
--------------------------------------------------------------------------------
/static/img/gopher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/minux/go-tour-zh/HEAD/static/img/gopher.png
--------------------------------------------------------------------------------
/content/flowcontrol/forever.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | func main() {
6 | for {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/codereview.cfg:
--------------------------------------------------------------------------------
1 | defaultcc: golang-codereviews@googlegroups.com
2 | contributors: http://go.googlecode.com/hg/CONTRIBUTORS
3 |
--------------------------------------------------------------------------------
/content/welcome/hello.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | fmt.Println("Hello, 世界")
9 | }
10 |
--------------------------------------------------------------------------------
/static/partials/lesson.html:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/content/basics/type-inference.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | v := 42 // change me!
7 | fmt.Printf("v is of type %T\n", v)
8 | }
9 |
--------------------------------------------------------------------------------
/content/flowcontrol/defer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | defer fmt.Println("world")
7 |
8 | fmt.Println("hello")
9 | }
10 |
--------------------------------------------------------------------------------
/content/basics/exported-names.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math"
8 | )
9 |
10 | func main() {
11 | fmt.Println(math.pi)
12 | }
13 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | # This source code refers to The Go Authors for copyright purposes.
2 | # The master list of authors is in the main Go distribution,
3 | # visible at http://tip.golang.org/AUTHORS.
4 |
--------------------------------------------------------------------------------
/CONTRIBUTORS:
--------------------------------------------------------------------------------
1 | # This source code was written by the Go contributors.
2 | # The master list of contributors is in the main Go distribution,
3 | # visible at http://tip.golang.org/CONTRIBUTORS.
4 |
--------------------------------------------------------------------------------
/content/basics/packages.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math/rand"
8 | )
9 |
10 | func main() {
11 | fmt.Println("My favorite number is", rand.Intn(10))
12 | }
13 |
--------------------------------------------------------------------------------
/content/basics/variables.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | var c, python, java bool
8 |
9 | func main() {
10 | var i int
11 | fmt.Println(i, c, python, java)
12 | }
13 |
--------------------------------------------------------------------------------
/content/basics/zero.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | var i int
7 | var f float64
8 | var b bool
9 | var s string
10 | fmt.Printf("%v %v %v %q\n", i, f, b, s)
11 | }
12 |
--------------------------------------------------------------------------------
/content/basics/imports.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math"
8 | )
9 |
10 | func main() {
11 | fmt.Printf("Now you have %g problems.", math.Nextafter(2, 3))
12 | }
13 |
--------------------------------------------------------------------------------
/content/flowcontrol/for.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | sum := 0
9 | for i := 0; i < 10; i++ {
10 | sum += i
11 | }
12 | fmt.Println(sum)
13 | }
14 |
--------------------------------------------------------------------------------
/content/basics/functions.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func add(x int, y int) int {
8 | return x + y
9 | }
10 |
11 | func main() {
12 | fmt.Println(add(42, 13))
13 | }
14 |
--------------------------------------------------------------------------------
/content/flowcontrol/for-continued.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | sum := 1
9 | for ; sum < 1000; {
10 | sum += sum
11 | }
12 | fmt.Println(sum)
13 | }
14 |
--------------------------------------------------------------------------------
/content/flowcontrol/for-is-gos-while.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | sum := 1
9 | for sum < 1000 {
10 | sum += sum
11 | }
12 | fmt.Println(sum)
13 | }
14 |
--------------------------------------------------------------------------------
/content/moretypes/structs.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | type Vertex struct {
8 | X int
9 | Y int
10 | }
11 |
12 | func main() {
13 | fmt.Println(Vertex{1, 2})
14 | }
15 |
--------------------------------------------------------------------------------
/content/basics/functions-continued.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func add(x, y int) int {
8 | return x + y
9 | }
10 |
11 | func main() {
12 | fmt.Println(add(42, 13))
13 | }
14 |
--------------------------------------------------------------------------------
/content/moretypes/exercise-slices.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "code.google.com/p/go-tour/pic"
6 |
7 | func Pic(dx, dy int) [][]uint8 {
8 | }
9 |
10 | func main() {
11 | pic.Show(Pic)
12 | }
13 |
--------------------------------------------------------------------------------
/content/concurrency/buffered-channels.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | c := make(chan int, 2)
9 | c <- 1
10 | c <- 2
11 | fmt.Println(<-c)
12 | fmt.Println(<-c)
13 | }
14 |
--------------------------------------------------------------------------------
/content/flowcontrol/exercise-loops-and-functions.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func Sqrt(x float64) float64 {
10 | }
11 |
12 | func main() {
13 | fmt.Println(Sqrt(2))
14 | }
15 |
--------------------------------------------------------------------------------
/content/moretypes/array.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | var a [2]string
9 | a[0] = "Hello"
10 | a[1] = "World"
11 | fmt.Println(a[0], a[1])
12 | fmt.Println(a)
13 | }
14 |
--------------------------------------------------------------------------------
/content/moretypes/nil-slices.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | var z []int
9 | fmt.Println(z, len(z), cap(z))
10 | if z == nil {
11 | fmt.Println("nil!")
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/content/flowcontrol/defer-multi.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | fmt.Println("counting")
7 |
8 | for i := 0; i < 10; i++ {
9 | defer fmt.Println(i)
10 | }
11 |
12 | fmt.Println("done")
13 | }
14 |
--------------------------------------------------------------------------------
/content/welcome/sandbox.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "time"
8 | )
9 |
10 | func main() {
11 | fmt.Println("Welcome to the playground!")
12 |
13 | fmt.Println("The time is", time.Now())
14 | }
15 |
--------------------------------------------------------------------------------
/content/methods/exercise-images.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "code.google.com/p/go-tour/pic"
7 | "image"
8 | )
9 |
10 | type Image struct{}
11 |
12 | func main() {
13 | m := Image{}
14 | pic.ShowImage(m)
15 | }
16 |
--------------------------------------------------------------------------------
/content/moretypes/range.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
8 |
9 | func main() {
10 | for i, v := range pow {
11 | fmt.Printf("2**%d = %d\n", i, v)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/content/moretypes/struct-fields.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | type Vertex struct {
8 | X int
9 | Y int
10 | }
11 |
12 | func main() {
13 | v := Vertex{1, 2}
14 | v.X = 4
15 | fmt.Println(v.X)
16 | }
17 |
--------------------------------------------------------------------------------
/content/basics/named-results.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func split(sum int) (x, y int) {
8 | x = sum * 4 / 9
9 | y = sum - x
10 | return
11 | }
12 |
13 | func main() {
14 | fmt.Println(split(17))
15 | }
16 |
--------------------------------------------------------------------------------
/content/basics/variables-with-initializers.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | var i, j int = 1, 2
8 |
9 | func main() {
10 | var c, python, java = true, false, "no!"
11 | fmt.Println(i, j, c, python, java)
12 | }
13 |
--------------------------------------------------------------------------------
/content/methods/exercise-http-handlers.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "log"
7 | "net/http"
8 | )
9 |
10 | func main() {
11 | // your http.Handle calls here
12 | log.Fatal(http.ListenAndServe("localhost:4000", nil))
13 | }
14 |
--------------------------------------------------------------------------------
/content/basics/multiple-results.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func swap(x, y string) (string, string) {
8 | return y, x
9 | }
10 |
11 | func main() {
12 | a, b := swap("hello", "world")
13 | fmt.Println(a, b)
14 | }
15 |
--------------------------------------------------------------------------------
/content/methods/exercise-reader.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "code.google.com/p/go-tour/reader"
4 |
5 | type MyReader struct{}
6 |
7 | // TODO: Add a Read([]byte) (int, error) method to MyReader.
8 |
9 | func main() {
10 | reader.Validate(MyReader{})
11 | }
12 |
--------------------------------------------------------------------------------
/content/methods/images.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "image"
8 | )
9 |
10 | func main() {
11 | m := image.NewRGBA(image.Rect(0, 0, 100, 100))
12 | fmt.Println(m.Bounds())
13 | fmt.Println(m.At(0, 0).RGBA())
14 | }
15 |
--------------------------------------------------------------------------------
/content/moretypes/struct-pointers.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | type Vertex struct {
8 | X int
9 | Y int
10 | }
11 |
12 | func main() {
13 | v := Vertex{1, 2}
14 | p := &v
15 | p.X = 1e9
16 | fmt.Println(v)
17 | }
18 |
--------------------------------------------------------------------------------
/content/basics/short-variable-declarations.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | var i, j int = 1, 2
9 | k := 3
10 | c, python, java := true, false, "no!"
11 |
12 | fmt.Println(i, j, k, c, python, java)
13 | }
14 |
--------------------------------------------------------------------------------
/content/methods/exercise-errors.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func Sqrt(x float64) (float64, error) {
10 | return 0, nil
11 | }
12 |
13 | func main() {
14 | fmt.Println(Sqrt(2))
15 | fmt.Println(Sqrt(-2))
16 | }
17 |
--------------------------------------------------------------------------------
/content/moretypes/slices.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | p := []int{2, 3, 5, 7, 11, 13}
9 | fmt.Println("p ==", p)
10 |
11 | for i := 0; i < len(p); i++ {
12 | fmt.Printf("p[%d] == %d\n", i, p[i])
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/content/basics/type-conversions.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math"
8 | )
9 |
10 | func main() {
11 | var x, y int = 3, 4
12 | var f float64 = math.Sqrt(float64(x*x + y*y))
13 | var z int = int(f)
14 | fmt.Println(x, y, z)
15 | }
16 |
--------------------------------------------------------------------------------
/content/moretypes/function-values.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math"
8 | )
9 |
10 | func main() {
11 | hypot := func(x, y float64) float64 {
12 | return math.Sqrt(x*x + y*y)
13 | }
14 |
15 | fmt.Println(hypot(3, 4))
16 | }
17 |
--------------------------------------------------------------------------------
/content/moretypes/exercise-maps.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "code.google.com/p/go-tour/wc"
7 | )
8 |
9 | func WordCount(s string) map[string]int {
10 | return map[string]int{"x": 1}
11 | }
12 |
13 | func main() {
14 | wc.Test(WordCount)
15 | }
16 |
--------------------------------------------------------------------------------
/content/moretypes/range-continued.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | pow := make([]int, 10)
9 | for i := range pow {
10 | pow[i] = 1 << uint(i)
11 | }
12 | for _, value := range pow {
13 | fmt.Printf("%d\n", value)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/content/basics/constants.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | const Pi = 3.14
8 |
9 | func main() {
10 | const World = "世界"
11 | fmt.Println("Hello", World)
12 | fmt.Println("Happy", Pi, "Day")
13 |
14 | const Truth = true
15 | fmt.Println("Go rules?", Truth)
16 | }
17 |
--------------------------------------------------------------------------------
/content/moretypes/exercise-fibonacci-closure.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | // fibonacci 函数会返回一个返回 int 的函数。
8 | func fibonacci() func() int {
9 | }
10 |
11 | func main() {
12 | f := fibonacci()
13 | for i := 0; i < 10; i++ {
14 | fmt.Println(f())
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | This is Go Tour, an introduction to the Go programming language.
2 |
3 | Unless otherwise noted, the go-tour source files are distributed
4 | under the BSD-style license found in the LICENSE file.
5 |
6 | Contributions should follow the same procedure as for the Go project:
7 | http://golang.org/doc/contribute.html
8 |
--------------------------------------------------------------------------------
/content/flowcontrol/if.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math"
8 | )
9 |
10 | func sqrt(x float64) string {
11 | if x < 0 {
12 | return sqrt(-x) + "i"
13 | }
14 | return fmt.Sprint(math.Sqrt(x))
15 | }
16 |
17 | func main() {
18 | fmt.Println(sqrt(2), sqrt(-4))
19 | }
20 |
--------------------------------------------------------------------------------
/content/concurrency/goroutines.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "time"
8 | )
9 |
10 | func say(s string) {
11 | for i := 0; i < 5; i++ {
12 | time.Sleep(100 * time.Millisecond)
13 | fmt.Println(s)
14 | }
15 | }
16 |
17 | func main() {
18 | go say("world")
19 | say("hello")
20 | }
21 |
--------------------------------------------------------------------------------
/content/methods/exercise-rot-reader.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "io"
7 | "os"
8 | "strings"
9 | )
10 |
11 | type rot13Reader struct {
12 | r io.Reader
13 | }
14 |
15 | func main() {
16 | s := strings.NewReader("Lbh penpxrq gur pbqr!")
17 | r := rot13Reader{s}
18 | io.Copy(os.Stdout, &r)
19 | }
20 |
--------------------------------------------------------------------------------
/content/methods/methods.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math"
8 | )
9 |
10 | type Vertex struct {
11 | X, Y float64
12 | }
13 |
14 | func (v *Vertex) Abs() float64 {
15 | return math.Sqrt(v.X*v.X + v.Y*v.Y)
16 | }
17 |
18 | func main() {
19 | v := &Vertex{3, 4}
20 | fmt.Println(v.Abs())
21 | }
22 |
--------------------------------------------------------------------------------
/content/moretypes/map-literals-continued.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | type Vertex struct {
8 | Lat, Long float64
9 | }
10 |
11 | var m = map[string]Vertex{
12 | "Bell Labs": {40.68433, -74.39967},
13 | "Google": {37.42202, -122.08408},
14 | }
15 |
16 | func main() {
17 | fmt.Println(m)
18 | }
19 |
--------------------------------------------------------------------------------
/content/concurrency/exercise-equivalent-binary-trees.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "code.google.com/p/go-tour/tree"
6 |
7 | // Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
8 | func Walk(t *tree.Tree, ch chan int)
9 |
10 | // Same 检测树 t1 和 t2 是否含有相同的值。
11 | func Same(t1, t2 *tree.Tree) bool
12 |
13 | func main() {
14 | }
15 |
--------------------------------------------------------------------------------
/content/moretypes/maps.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | type Vertex struct {
8 | Lat, Long float64
9 | }
10 |
11 | var m map[string]Vertex
12 |
13 | func main() {
14 | m = make(map[string]Vertex)
15 | m["Bell Labs"] = Vertex{
16 | 40.68433, -74.39967,
17 | }
18 | fmt.Println(m["Bell Labs"])
19 | }
20 |
--------------------------------------------------------------------------------
/content/moretypes/slicing-slices.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | p := []int{2, 3, 5, 7, 11, 13}
9 | fmt.Println("p ==", p)
10 | fmt.Println("p[1:4] ==", p[1:4])
11 |
12 | // 省略下标代表从 0 开始
13 | fmt.Println("p[:3] ==", p[:3])
14 |
15 | // 省略上标代表到 len(s) 结束
16 | fmt.Println("p[4:] ==", p[4:])
17 | }
18 |
--------------------------------------------------------------------------------
/content/methods/methods-continued.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math"
8 | )
9 |
10 | type MyFloat float64
11 |
12 | func (f MyFloat) Abs() float64 {
13 | if f < 0 {
14 | return float64(-f)
15 | }
16 | return float64(f)
17 | }
18 |
19 | func main() {
20 | f := MyFloat(-math.Sqrt2)
21 | fmt.Println(f.Abs())
22 | }
23 |
--------------------------------------------------------------------------------
/content/flowcontrol/if-with-a-short-statement.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math"
8 | )
9 |
10 | func pow(x, n, lim float64) float64 {
11 | if v := math.Pow(x, n); v < lim {
12 | return v
13 | }
14 | return lim
15 | }
16 |
17 | func main() {
18 | fmt.Println(
19 | pow(3, 2, 10),
20 | pow(3, 3, 20),
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/content/methods/exercise-stringer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | type IPAddr [4]byte
6 |
7 | // TODO: Add a "String() string" method to IPAddr.
8 |
9 | func main() {
10 | addrs := map[string]IPAddr{
11 | "loopback": {127, 0, 0, 1},
12 | "googleDNS": {8, 8, 8, 8},
13 | }
14 | for n, a := range addrs {
15 | fmt.Printf("%v: %v\n", n, a)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/content/methods/stringer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | type Person struct {
6 | Name string
7 | Age int
8 | }
9 |
10 | func (p Person) String() string {
11 | return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
12 | }
13 |
14 | func main() {
15 | a := Person{"Arthur Dent", 42}
16 | z := Person{"Zaphod Beeblebrox", 9001}
17 | fmt.Println(a, z)
18 | }
19 |
--------------------------------------------------------------------------------
/content/moretypes/map-literals.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | type Vertex struct {
8 | Lat, Long float64
9 | }
10 |
11 | var m = map[string]Vertex{
12 | "Bell Labs": Vertex{
13 | 40.68433, -74.39967,
14 | },
15 | "Google": Vertex{
16 | 37.42202, -122.08408,
17 | },
18 | }
19 |
20 | func main() {
21 | fmt.Println(m)
22 | }
23 |
--------------------------------------------------------------------------------
/content/flowcontrol/switch-with-no-condition.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "time"
8 | )
9 |
10 | func main() {
11 | t := time.Now()
12 | switch {
13 | case t.Hour() < 12:
14 | fmt.Println("Good morning!")
15 | case t.Hour() < 17:
16 | fmt.Println("Good afternoon.")
17 | default:
18 | fmt.Println("Good evening.")
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/content/moretypes/struct-literals.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | type Vertex struct {
8 | X, Y int
9 | }
10 |
11 | var (
12 | v1 = Vertex{1, 2} // 类型为 Vertex
13 | v2 = Vertex{X: 1} // Y:0 被省略
14 | v3 = Vertex{} // X:0 和 Y:0
15 | p = &Vertex{1, 2} // 类型为 *Vertex
16 | )
17 |
18 | func main() {
19 | fmt.Println(v1, p, v2, v3)
20 | }
21 |
--------------------------------------------------------------------------------
/content/moretypes/function-closures.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func adder() func(int) int {
8 | sum := 0
9 | return func(x int) int {
10 | sum += x
11 | return sum
12 | }
13 | }
14 |
15 | func main() {
16 | pos, neg := adder(), adder()
17 | for i := 0; i < 10; i++ {
18 | fmt.Println(
19 | pos(i),
20 | neg(-2*i),
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/content/methods/reader.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "strings"
7 | )
8 |
9 | func main() {
10 | r := strings.NewReader("Hello, Reader!")
11 |
12 | b := make([]byte, 8)
13 | for {
14 | n, err := r.Read(b)
15 | fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
16 | fmt.Printf("b[:n] = %q\n", b[:n])
17 | if err == io.EOF {
18 | break
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/content/basics/basic-types.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math/cmplx"
8 | )
9 |
10 | var (
11 | ToBe bool = false
12 | MaxInt uint64 = 1<<64 - 1
13 | z complex128 = cmplx.Sqrt(-5 + 12i)
14 | )
15 |
16 | func main() {
17 | const f = "%T(%v)\n"
18 | fmt.Printf(f, ToBe, ToBe)
19 | fmt.Printf(f, MaxInt, MaxInt)
20 | fmt.Printf(f, z, z)
21 | }
22 |
--------------------------------------------------------------------------------
/content/concurrency/range-and-close.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | func fibonacci(n int, c chan int) {
10 | x, y := 0, 1
11 | for i := 0; i < n; i++ {
12 | c <- x
13 | x, y = y, x+y
14 | }
15 | close(c)
16 | }
17 |
18 | func main() {
19 | c := make(chan int, 10)
20 | go fibonacci(cap(c), c)
21 | for i := range c {
22 | fmt.Println(i)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/content/basics/numeric-constants.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | const (
8 | Big = 1 << 100
9 | Small = Big >> 99
10 | )
11 |
12 | func needInt(x int) int { return x*10 + 1 }
13 | func needFloat(x float64) float64 {
14 | return x * 0.1
15 | }
16 |
17 | func main() {
18 | fmt.Println(needInt(Small))
19 | fmt.Println(needFloat(Small))
20 | fmt.Println(needFloat(Big))
21 | }
22 |
--------------------------------------------------------------------------------
/content/flowcontrol/switch.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "runtime"
8 | )
9 |
10 | func main() {
11 | fmt.Print("Go runs on ")
12 | switch os := runtime.GOOS; os {
13 | case "darwin":
14 | fmt.Println("OS X.")
15 | case "linux":
16 | fmt.Println("Linux.")
17 | default:
18 | // freebsd, openbsd,
19 | // plan9, windows...
20 | fmt.Printf("%s.", os)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/content/flowcontrol/if-and-else.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math"
8 | )
9 |
10 | func pow(x, n, lim float64) float64 {
11 | if v := math.Pow(x, n); v < lim {
12 | return v
13 | } else {
14 | fmt.Printf("%g >= %g\n", v, lim)
15 | }
16 | // 这里开始就不能使用 v 了
17 | return lim
18 | }
19 |
20 | func main() {
21 | fmt.Println(
22 | pow(3, 2, 10),
23 | pow(3, 3, 20),
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/content/moretypes/making-slices.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | a := make([]int, 5)
9 | printSlice("a", a)
10 | b := make([]int, 0, 5)
11 | printSlice("b", b)
12 | c := b[:2]
13 | printSlice("c", c)
14 | d := c[2:5]
15 | printSlice("d", d)
16 | }
17 |
18 | func printSlice(s string, x []int) {
19 | fmt.Printf("%s len=%d cap=%d %v\n",
20 | s, len(x), cap(x), x)
21 | }
22 |
--------------------------------------------------------------------------------
/content/methods/web-servers.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "log"
8 | "net/http"
9 | )
10 |
11 | type Hello struct{}
12 |
13 | func (h Hello) ServeHTTP(
14 | w http.ResponseWriter,
15 | r *http.Request) {
16 | fmt.Fprint(w, "Hello!")
17 | }
18 |
19 | func main() {
20 | var h Hello
21 | err := http.ListenAndServe("localhost:4000", h)
22 | if err != nil {
23 | log.Fatal(err)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/solutions/README:
--------------------------------------------------------------------------------
1 | This directory contains the solutions to the exercises presented in the Go tour.
2 |
3 | IMPORTANT: The main point of the Go tour is to challenge you, so please try to
4 | solve all problems by yourself before reading these solutions!!!
5 |
6 | In any case, these are not the only valid solutions. But they have been reviewed
7 | by the Go team. Therefore you can use them as a guide on how to solve the
8 | most basic problems in Go.
9 |
--------------------------------------------------------------------------------
/content/concurrency/channels.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func sum(a []int, c chan int) {
8 | sum := 0
9 | for _, v := range a {
10 | sum += v
11 | }
12 | c <- sum // 将和送入 c
13 | }
14 |
15 | func main() {
16 | a := []int{7, 2, 8, -9, 4, 0}
17 |
18 | c := make(chan int)
19 | go sum(a[:len(a)/2], c)
20 | go sum(a[len(a)/2:], c)
21 | x, y := <-c, <-c // 从 c 中获取
22 |
23 | fmt.Println(x, y, x+y)
24 | }
25 |
--------------------------------------------------------------------------------
/content/moretypes/mutating-maps.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | m := make(map[string]int)
9 |
10 | m["Answer"] = 42
11 | fmt.Println("The value:", m["Answer"])
12 |
13 | m["Answer"] = 48
14 | fmt.Println("The value:", m["Answer"])
15 |
16 | delete(m, "Answer")
17 | fmt.Println("The value:", m["Answer"])
18 |
19 | v, ok := m["Answer"]
20 | fmt.Println("The value:", v, "Present?", ok)
21 | }
22 |
--------------------------------------------------------------------------------
/content/moretypes/pointers.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func main() {
8 | i, j := 42, 2701
9 |
10 | p := &i // point to i
11 | fmt.Println(*p) // read i through the pointer
12 | *p = 21 // set i through the pointer
13 | fmt.Println(i) // see the new value of i
14 |
15 | p = &j // point to j
16 | *p = *p / 37 // divide j through the pointer
17 | fmt.Println(j) // see the new value of j
18 | }
19 |
--------------------------------------------------------------------------------
/content/methods/methods-with-pointer-receivers.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math"
8 | )
9 |
10 | type Vertex struct {
11 | X, Y float64
12 | }
13 |
14 | func (v *Vertex) Scale(f float64) {
15 | v.X = v.X * f
16 | v.Y = v.Y * f
17 | }
18 |
19 | func (v *Vertex) Abs() float64 {
20 | return math.Sqrt(v.X*v.X + v.Y*v.Y)
21 | }
22 |
23 | func main() {
24 | v := &Vertex{3, 4}
25 | v.Scale(5)
26 | fmt.Println(v, v.Abs())
27 | }
28 |
--------------------------------------------------------------------------------
/content/flowcontrol/switch-evaluation-order.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "time"
8 | )
9 |
10 | func main() {
11 | fmt.Println("When's Saturday?")
12 | today := time.Now().Weekday()
13 | switch time.Saturday {
14 | case today + 0:
15 | fmt.Println("Today.")
16 | case today + 1:
17 | fmt.Println("Tomorrow.")
18 | case today + 2:
19 | fmt.Println("In two days.")
20 | default:
21 | fmt.Println("Too far away.")
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/content/concurrency/default-selection.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "time"
8 | )
9 |
10 | func main() {
11 | tick := time.Tick(100 * time.Millisecond)
12 | boom := time.After(500 * time.Millisecond)
13 | for {
14 | select {
15 | case <-tick:
16 | fmt.Println("tick.")
17 | case <-boom:
18 | fmt.Println("BOOM!")
19 | return
20 | default:
21 | fmt.Println(" .")
22 | time.Sleep(50 * time.Millisecond)
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/content/methods/errors.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "time"
8 | )
9 |
10 | type MyError struct {
11 | When time.Time
12 | What string
13 | }
14 |
15 | func (e *MyError) Error() string {
16 | return fmt.Sprintf("at %v, %s",
17 | e.When, e.What)
18 | }
19 |
20 | func run() error {
21 | return &MyError{
22 | time.Now(),
23 | "it didn't work",
24 | }
25 | }
26 |
27 | func main() {
28 | if err := run(); err != nil {
29 | fmt.Println(err)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/content/methods/interfaces-are-satisfied-implicitly.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "os"
8 | )
9 |
10 | type Reader interface {
11 | Read(b []byte) (n int, err error)
12 | }
13 |
14 | type Writer interface {
15 | Write(b []byte) (n int, err error)
16 | }
17 |
18 | type ReadWriter interface {
19 | Reader
20 | Writer
21 | }
22 |
23 | func main() {
24 | var w Writer
25 |
26 | // os.Stdout 实现了 Writer
27 | w = os.Stdout
28 |
29 | fmt.Fprintf(w, "hello, writer\n")
30 | }
31 |
--------------------------------------------------------------------------------
/content/concurrency/select.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import "fmt"
6 |
7 | func fibonacci(c, quit chan int) {
8 | x, y := 0, 1
9 | for {
10 | select {
11 | case c <- x:
12 | x, y = y, x+y
13 | case <-quit:
14 | fmt.Println("quit")
15 | return
16 | }
17 | }
18 | }
19 |
20 | func main() {
21 | c := make(chan int)
22 | quit := make(chan int)
23 | go func() {
24 | for i := 0; i < 10; i++ {
25 | fmt.Println(<-c)
26 | }
27 | quit <- 0
28 | }()
29 | fibonacci(c, quit)
30 | }
31 |
--------------------------------------------------------------------------------
/solutions/maps.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build ignore
6 |
7 | package main
8 |
9 | import (
10 | "strings"
11 |
12 | "code.google.com/p/go-tour/wc"
13 | )
14 |
15 | func WordCount(s string) map[string]int {
16 | m := make(map[string]int)
17 | for _, f := range strings.Fields(s) {
18 | m[f]++
19 | }
20 | return m
21 | }
22 |
23 | func main() {
24 | wc.Test(WordCount)
25 | }
26 |
--------------------------------------------------------------------------------
/content/moretypes/append.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | var a []int
7 | printSlice("a", a)
8 |
9 | // append works on nil slices.
10 | a = append(a, 0)
11 | printSlice("a", a)
12 |
13 | // the slice grows as needed.
14 | a = append(a, 1)
15 | printSlice("a", a)
16 |
17 | // we can add more than one element at a time.
18 | a = append(a, 2, 3, 4)
19 | printSlice("a", a)
20 | }
21 |
22 | func printSlice(s string, x []int) {
23 | fmt.Printf("%s len=%d cap=%d %v\n",
24 | s, len(x), cap(x), x)
25 | }
26 |
--------------------------------------------------------------------------------
/app.yaml:
--------------------------------------------------------------------------------
1 | application: go-tour-zh
2 | version: 1
3 | runtime: go
4 | api_version: go1
5 |
6 | default_expiration: "7d"
7 |
8 | handlers:
9 |
10 | # Keep these static file handlers in sync with gotour/local.go.
11 | - url: /favicon.ico
12 | static_files: static/img/favicon.ico
13 | upload: static/img/favicon.ico
14 | - url: /content/img
15 | static_dir: content/img
16 | - url: /static
17 | static_dir: static
18 | application_readable: true
19 |
20 | - url: /(.*|list|lesson/.*|compile|fmt|script\.js)
21 | script: _go_app
22 |
23 | nobuild_files: (solutions|content)/.*
24 |
--------------------------------------------------------------------------------
/solutions/fib.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build ignore
6 |
7 | package main
8 |
9 | import "fmt"
10 |
11 | // fibonacci is a function that returns
12 | // a function that returns an int.
13 | func fibonacci() func() int {
14 | f, g := 0, 1
15 | return func() int {
16 | f, g = g, f+g
17 | return f
18 | }
19 | }
20 |
21 | func main() {
22 | f := fibonacci()
23 | for i := 0; i < 10; i++ {
24 | fmt.Println(f())
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/solutions/slices.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build ignore
6 |
7 | package main
8 |
9 | import "code.google.com/p/go-tour/pic"
10 |
11 | func Pic(dx, dy int) [][]uint8 {
12 | p := make([][]uint8, dy)
13 | for i := range p {
14 | p[i] = make([]uint8, dx)
15 | }
16 |
17 | for y, row := range p {
18 | for x := range row {
19 | row[x] = uint8(x * y)
20 | }
21 | }
22 |
23 | return p
24 | }
25 |
26 | func main() {
27 | pic.Show(Pic)
28 | }
29 |
--------------------------------------------------------------------------------
/solutions/loops.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build ignore
6 |
7 | package main
8 |
9 | import (
10 | "fmt"
11 | "math"
12 | )
13 |
14 | const delta = 1e-6
15 |
16 | func Sqrt(x float64) float64 {
17 | z := x
18 | n := 0.0
19 | for math.Abs(n-z) > delta {
20 | n, z = z, z-(z*z-x)/(2*z)
21 | }
22 | return z
23 | }
24 |
25 | func main() {
26 | const x = 2
27 | mine, theirs := Sqrt(x), math.Sqrt(x)
28 | fmt.Println(mine, theirs, mine-theirs)
29 | }
30 |
--------------------------------------------------------------------------------
/solutions/stringers.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build ignore
6 |
7 | package main
8 |
9 | import "fmt"
10 |
11 | type IPAddr [4]byte
12 |
13 | func (ip IPAddr) String() string {
14 | return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
15 | }
16 |
17 | func main() {
18 | addrs := map[string]IPAddr{
19 | "loopback": {127, 0, 0, 1},
20 | "googleDNS": {8, 8, 8, 8},
21 | }
22 | for n, a := range addrs {
23 | fmt.Printf("%v: %v\n", n, a)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/static/partials/list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
{{m.title}}
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/reader/validate.go:
--------------------------------------------------------------------------------
1 | package reader
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | )
8 |
9 | func Validate(r io.Reader) {
10 | b := make([]byte, 1024)
11 | i, o := 0, 0
12 | for ; i < 1<<20 && o < 1<<20; i++ { // test 1mb
13 | n, err := r.Read(b)
14 | for i, v := range b[:n] {
15 | if v != 'A' {
16 | fmt.Fprintf(os.Stderr, "got byte %x at offset %v, want 'A'\n", v, o+i)
17 | return
18 | }
19 | }
20 | o += n
21 | if err != nil {
22 | fmt.Fprintf(os.Stderr, "read error: %v\n", err)
23 | return
24 | }
25 | }
26 | if o == 0 {
27 | fmt.Fprintf(os.Stderr, "read zero bytes after %d Read calls\n", i)
28 | return
29 | }
30 | fmt.Println("OK!")
31 | }
32 |
--------------------------------------------------------------------------------
/static/lib/codemirror/README.md:
--------------------------------------------------------------------------------
1 | # CodeMirror
2 | [](http://travis-ci.org/marijnh/CodeMirror)
3 | [](http://badge.fury.io/js/codemirror)
4 |
5 | CodeMirror is a JavaScript component that provides a code editor in
6 | the browser. When a mode is available for the language you are coding
7 | in, it will color your code, and optionally help with indentation.
8 |
9 | The project page is http://codemirror.net
10 | The manual is at http://codemirror.net/doc/manual.html
11 | The contributing guidelines are in [CONTRIBUTING.md](https://github.com/marijnh/CodeMirror/blob/master/CONTRIBUTING.md)
12 |
--------------------------------------------------------------------------------
/content/methods/interfaces.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | "math"
8 | )
9 |
10 | type Abser interface {
11 | Abs() float64
12 | }
13 |
14 | func main() {
15 | var a Abser
16 | f := MyFloat(-math.Sqrt2)
17 | v := Vertex{3, 4}
18 |
19 | a = f // a MyFloat 实现了 Abser
20 | a = &v // a *Vertex 实现了 Abser
21 |
22 | // 下面一行,v 是一个 Vertex(而不是 *Vertex)
23 | // 所以没有实现 Abser。
24 | a = v
25 |
26 | fmt.Println(a.Abs())
27 | }
28 |
29 | type MyFloat float64
30 |
31 | func (f MyFloat) Abs() float64 {
32 | if f < 0 {
33 | return float64(-f)
34 | }
35 | return float64(f)
36 | }
37 |
38 | type Vertex struct {
39 | X, Y float64
40 | }
41 |
42 | func (v *Vertex) Abs() float64 {
43 | return math.Sqrt(v.X*v.X + v.Y*v.Y)
44 | }
45 |
--------------------------------------------------------------------------------
/solutions/image.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build ignore
6 |
7 | package main
8 |
9 | import (
10 | "image"
11 | "image/color"
12 |
13 | "code.google.com/p/go-tour/pic"
14 | )
15 |
16 | type Image struct {
17 | Height, Width int
18 | }
19 |
20 | func (m Image) ColorModel() color.Model {
21 | return color.RGBAModel
22 | }
23 |
24 | func (m Image) Bounds() image.Rectangle {
25 | return image.Rect(0, 0, m.Height, m.Width)
26 | }
27 |
28 | func (m Image) At(x, y int) color.Color {
29 | c := uint8(x ^ y)
30 | return color.RGBA{c, c, 255, 255}
31 | }
32 |
33 | func main() {
34 | m := Image{256, 256}
35 | pic.ShowImage(m)
36 | }
37 |
--------------------------------------------------------------------------------
/solutions/errors.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build ignore
6 |
7 | package main
8 |
9 | import (
10 | "fmt"
11 | "math"
12 | )
13 |
14 | type ErrNegativeSqrt float64
15 |
16 | func (e ErrNegativeSqrt) Error() string {
17 | return fmt.Sprintf("Sqrt: negative number %g", e)
18 | }
19 |
20 | const delta = 1e-10
21 |
22 | func Sqrt(f float64) (float64, error) {
23 | if f < 0 {
24 | return 0, ErrNegativeSqrt(f)
25 | }
26 | z := f
27 | for {
28 | n := z - (z*z-f)/(2*z)
29 | if math.Abs(n-z) < delta {
30 | break
31 | }
32 | z = n
33 | }
34 | return z, nil
35 | }
36 |
37 | func main() {
38 | fmt.Println(Sqrt(2))
39 | fmt.Println(Sqrt(-2))
40 | }
41 |
--------------------------------------------------------------------------------
/static/partials/toc.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{m.title}}
5 |
6 |
7 | {{m.lesson[l].Title}}
8 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/solutions/http.go:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build ignore
6 |
7 | package main
8 |
9 | import (
10 | "fmt"
11 | "log"
12 | "net/http"
13 | )
14 |
15 | type String string
16 |
17 | func (s String) ServeHTTP(w http.ResponseWriter, r *http.Request) {
18 | fmt.Fprint(w, s)
19 | }
20 |
21 | type Struct struct {
22 | Greeting string
23 | Punct string
24 | Who string
25 | }
26 |
27 | func (s *Struct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
28 | fmt.Fprintf(w, "%s%s %s", s.Greeting, s.Punct, s.Who)
29 | }
30 |
31 | func main() {
32 | http.Handle("/string", String("I'm a frayed knot."))
33 | http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
34 | err := http.ListenAndServe("localhost:4000", nil)
35 | if err != nil {
36 | log.Fatal(err)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/solutions/rot13.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build ignore
6 |
7 | package main
8 |
9 | import (
10 | "io"
11 | "os"
12 | "strings"
13 | )
14 |
15 | func rot13(b byte) byte {
16 | var a, z byte
17 | switch {
18 | case 'a' <= b && b <= 'z':
19 | a, z = 'a', 'z'
20 | case 'A' <= b && b <= 'Z':
21 | a, z = 'A', 'Z'
22 | default:
23 | return b
24 | }
25 | return (b-a+13)%(z-a+1) + a
26 | }
27 |
28 | type rot13Reader struct {
29 | r io.Reader
30 | }
31 |
32 | func (r rot13Reader) Read(p []byte) (n int, err error) {
33 | n, err = r.r.Read(p)
34 | for i := 0; i < n; i++ {
35 | p[i] = rot13(p[i])
36 | }
37 | return
38 | }
39 |
40 | func main() {
41 | s := strings.NewReader(
42 | "Lbh penpxrq gur pbqr!")
43 | r := rot13Reader{s}
44 | io.Copy(os.Stdout, &r)
45 | }
46 |
--------------------------------------------------------------------------------
/pic/pic.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package pic
6 |
7 | import (
8 | "bytes"
9 | "encoding/base64"
10 | "fmt"
11 | "image"
12 | "image/png"
13 | )
14 |
15 | func Show(f func(int, int) [][]uint8) {
16 | const (
17 | dx = 256
18 | dy = 256
19 | )
20 | data := f(dx, dy)
21 | m := image.NewNRGBA(image.Rect(0, 0, dx, dy))
22 | for y := 0; y < dy; y++ {
23 | for x := 0; x < dx; x++ {
24 | v := data[y][x]
25 | i := y*m.Stride + x*4
26 | m.Pix[i] = v
27 | m.Pix[i+1] = v
28 | m.Pix[i+2] = 255
29 | m.Pix[i+3] = 255
30 | }
31 | }
32 | ShowImage(m)
33 | }
34 |
35 | func ShowImage(m image.Image) {
36 | var buf bytes.Buffer
37 | err := png.Encode(&buf, m)
38 | if err != nil {
39 | panic(err)
40 | }
41 | enc := base64.StdEncoding.EncodeToString(buf.Bytes())
42 | fmt.Println("IMAGE:" + enc)
43 | }
44 |
--------------------------------------------------------------------------------
/static/js/app.js:
--------------------------------------------------------------------------------
1 | /* Copyright 2012 The Go Authors. All rights reserved.
2 | * Use of this source code is governed by a BSD-style
3 | * license that can be found in the LICENSE file.
4 | */
5 | 'use strict';
6 |
7 | angular.module('tour', ['ui', 'tour.services', 'tour.controllers', 'tour.directives', 'tour.values', 'ng']).
8 |
9 | config(['$routeProvider', '$locationProvider',
10 | function($routeProvider, $locationProvider) {
11 | $routeProvider.
12 | when('/', {
13 | redirectTo: '/welcome/1'
14 | }).
15 | when('/list', {
16 | templateUrl: '/static/partials/list.html',
17 | }).
18 | when('/:lessonId/:pageNumber', {
19 | templateUrl: '/static/partials/editor.html',
20 | controller: 'EditorCtrl'
21 | }).
22 | when('/:lessonId', {
23 | redirectTo: '/:lessonId/1'
24 | }).
25 | otherwise({
26 | redirectTo: '/'
27 | });
28 |
29 | $locationProvider.html5Mode(true);
30 | }
31 | ]);
32 |
--------------------------------------------------------------------------------
/tree/tree.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package tree
6 |
7 | import (
8 | "fmt"
9 | "math/rand"
10 | )
11 |
12 | // A Tree is a binary tree with integer values.
13 | type Tree struct {
14 | Left *Tree
15 | Value int
16 | Right *Tree
17 | }
18 |
19 | // New returns a new, random binary tree holding the values k, 2k, ..., 10k.
20 | func New(k int) *Tree {
21 | var t *Tree
22 | for _, v := range rand.Perm(10) {
23 | t = insert(t, (1+v)*k)
24 | }
25 | return t
26 | }
27 |
28 | func insert(t *Tree, v int) *Tree {
29 | if t == nil {
30 | return &Tree{nil, v, nil}
31 | }
32 | if v < t.Value {
33 | t.Left = insert(t.Left, v)
34 | } else {
35 | t.Right = insert(t.Right, v)
36 | }
37 | return t
38 | }
39 |
40 | func (t *Tree) String() string {
41 | if t == nil {
42 | return "()"
43 | }
44 | s := ""
45 | if t.Left != nil {
46 | s += t.Left.String() + " "
47 | }
48 | s += fmt.Sprint(t.Value)
49 | if t.Right != nil {
50 | s += " " + t.Right.String()
51 | }
52 | return "(" + s + ")"
53 | }
54 |
--------------------------------------------------------------------------------
/template/action.tmpl:
--------------------------------------------------------------------------------
1 | {{/*{
2 | This is the action template.
3 | It determines how the formatting actions are rendered.
4 | */}}
5 |
6 | {{define "section"}}
7 | {{.Title}}
8 | {{range .Elem}}{{elem $.Template .}}{{end}}
9 | {{end}}
10 |
11 | {{define "list"}}
12 |
13 | {{range .Bullet}}
14 | {{style .}}
15 | {{end}}
16 |
17 | {{end}}
18 |
19 | {{define "text"}}
20 | {{if .Pre}}
21 | {{range .Lines}}{{.}}{{end}}
22 | {{else}}
23 |
24 | {{range $i, $l := .Lines}}{{if $i}}{{template "newline"}}
25 | {{end}}{{style $l}}{{end}}
26 |
27 | {{end}}
28 | {{end}}
29 |
30 | {{define "code"}}
31 | {{if .Play}}
32 | {{/* playable code is not displayed in the slides */}}
33 | {{else}}
34 | {{.Text}}
35 | {{end}}
36 | {{end}}
37 |
38 | {{define "image"}}
39 |
40 | {{end}}
41 |
42 | {{define "link"}}
43 | {{style .Label}}
44 | {{end}}
45 |
46 | {{define "html"}}{{.HTML}}{{end}}
47 |
48 | {{define "newline"}}
49 | {{/* No automatic line break. Paragraphs are free-form. */}}
50 | {{end}}
51 |
--------------------------------------------------------------------------------
/gotour/fmt.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package main
6 |
7 | import (
8 | "bytes"
9 | "encoding/json"
10 | "go/ast"
11 | "go/parser"
12 | "go/printer"
13 | "go/token"
14 | "net/http"
15 | )
16 |
17 | func init() {
18 | http.HandleFunc("/fmt", fmtHandler)
19 | }
20 |
21 | type fmtResponse struct {
22 | Body string
23 | Error string
24 | }
25 |
26 | func fmtHandler(w http.ResponseWriter, r *http.Request) {
27 | resp := new(fmtResponse)
28 | body, err := gofmt(r.FormValue("body"))
29 | if err != nil {
30 | resp.Error = err.Error()
31 | } else {
32 | resp.Body = body
33 | }
34 | json.NewEncoder(w).Encode(resp)
35 | }
36 |
37 | func gofmt(body string) (string, error) {
38 | fset := token.NewFileSet()
39 | f, err := parser.ParseFile(fset, "prog.go", body, parser.ParseComments)
40 | if err != nil {
41 | return "", err
42 | }
43 | ast.SortImports(fset, f)
44 | var buf bytes.Buffer
45 | config := &printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
46 | err = config.Fprint(&buf, fset, f)
47 | if err != nil {
48 | return "", err
49 | }
50 | return buf.String(), nil
51 | }
52 |
--------------------------------------------------------------------------------
/static/lib/codemirror/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2013 by Marijn Haverbeke and others
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
21 | Please note that some subdirectories of the CodeMirror distribution
22 | include their own LICENSE files, and are released under different
23 | licences.
24 |
--------------------------------------------------------------------------------
/wc/wc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package wc
6 |
7 | import "fmt"
8 |
9 | // Test runs a test suite against f.
10 | func Test(f func(string) map[string]int) {
11 | ok := true
12 | for _, c := range testCases {
13 | got := f(c.in)
14 | if len(c.want) != len(got) {
15 | ok = false
16 | } else {
17 | for k := range c.want {
18 | if c.want[k] != got[k] {
19 | ok = false
20 | }
21 | }
22 | }
23 | if !ok {
24 | fmt.Printf("FAIL\n f(%q) =\n %#v\n want:\n %#v",
25 | c.in, got, c.want)
26 | break
27 | }
28 | fmt.Printf("PASS\n f(%q) = \n %#v\n", c.in, got)
29 | }
30 | }
31 |
32 | var testCases = []struct {
33 | in string
34 | want map[string]int
35 | }{
36 | {"I am learning Go!", map[string]int{
37 | "I": 1, "am": 1, "learning": 1, "Go!": 1,
38 | }},
39 | {"The quick brown fox jumped over the lazy dog.", map[string]int{
40 | "The": 1, "quick": 1, "brown": 1, "fox": 1, "jumped": 1,
41 | "over": 1, "the": 1, "lazy": 1, "dog.": 1,
42 | }},
43 | {"I ate a donut. Then I ate another donut.", map[string]int{
44 | "I": 2, "ate": 2, "a": 1, "donut.": 2, "Then": 1, "another": 1,
45 | }},
46 | {"A man a plan a canal panama.", map[string]int{
47 | "A": 1, "man": 1, "a": 2, "plan": 1, "canal": 1, "panama.": 1,
48 | }},
49 | }
50 |
--------------------------------------------------------------------------------
/solutions/binarytrees.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build ignore
6 |
7 | package main
8 |
9 | import (
10 | "fmt"
11 |
12 | "code.google.com/p/go-tour/tree"
13 | )
14 |
15 | func walkImpl(t *tree.Tree, ch chan int) {
16 | if t.Left != nil {
17 | walkImpl(t.Left, ch)
18 | }
19 | ch <- t.Value
20 | if t.Right != nil {
21 | walkImpl(t.Right, ch)
22 | }
23 | }
24 |
25 | // Walk walks the tree t sending all values
26 | // from the tree to the channel ch.
27 | func Walk(t *tree.Tree, ch chan int) {
28 | walkImpl(t, ch)
29 | // Need to close the channel here
30 | close(ch)
31 | }
32 |
33 | // Same determines whether the trees
34 | // t1 and t2 contain the same values.
35 | func Same(t1, t2 *tree.Tree) bool {
36 | w1, w2 := make(chan int), make(chan int)
37 |
38 | go Walk(t1, w1)
39 | go Walk(t2, w2)
40 |
41 | for {
42 | v1, ok1 := <-w1
43 | v2, ok2 := <-w2
44 | if v1 != v2 || ok1 != ok2 {
45 | return false
46 | }
47 | if !ok1 {
48 | break
49 | }
50 | }
51 | return true
52 | }
53 |
54 | func main() {
55 | fmt.Print("tree.New(1) == tree.New(1): ")
56 | if Same(tree.New(1), tree.New(1)) {
57 | fmt.Println("PASSED")
58 | } else {
59 | fmt.Println("FAILED")
60 | }
61 |
62 | fmt.Print("tree.New(1) != tree.New(2): ")
63 | if !Same(tree.New(1), tree.New(2)) {
64 | fmt.Println("PASSED")
65 | } else {
66 | fmt.Println("FAILED")
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/TRANSLATE:
--------------------------------------------------------------------------------
1 | Translating the Tour
2 |
3 | A Tour of Go is a Go program that runs as a stand-alone web server or
4 | an App Engine app. The version available at tour.golang.org is run from
5 | App Engine. There are several localized versions of the tour, such as
6 | this Chinese translation, also running on App Engine:
7 |
8 | http://go-tour-zh.appspot.com
9 |
10 | The Tour contains a slide named "Go local", which lists several of
11 | these translations. If you are a native speaker of a language not on
12 | that list and have some experience with Go, please consider providing
13 | a translation of the Tour in your own language.
14 |
15 | To translate the tour:
16 |
17 | 1. Translate the files in content/
18 | 2. Provide localized version for the UI strings in static/js/values.js
19 | 3. Sign up to App Engine and create an app named go-tour-LL,
20 | where LL is the two-letter country code that applies best
21 | to your chosen language. (This shouldn't cost you anything;
22 | the Tour App usually runs inside App Engine's free quota.)
23 | 4. Deploy your version of the Tour to that app.
24 | 5. Announce to golang-nuts@googlegroups.com
25 |
26 | The Tour content changes occasionally, and you should keep your
27 | translation up to date. To follow the development of the tour,
28 | subscribe to the go-tour-commits mailing list:
29 |
30 | https://groups.google.com/group/go-tour-commits
31 |
32 | All new commits to the go-tour repository are mailed there.
33 |
34 | Finally, if you have any questions about the Tour or Go,
35 | please mail golang-nuts@googlegroups.com.
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 The Go Authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/content/concurrency/exercise-web-crawler.go:
--------------------------------------------------------------------------------
1 | // +build OMIT
2 |
3 | package main
4 |
5 | import (
6 | "fmt"
7 | )
8 |
9 | type Fetcher interface {
10 | // Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
11 | Fetch(url string) (body string, urls []string, err error)
12 | }
13 |
14 | // Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
15 | func Crawl(url string, depth int, fetcher Fetcher) {
16 | // TODO: 并行的抓取 URL。
17 | // TODO: 不重复抓取页面。
18 | // 下面并没有实现上面两种情况:
19 | if depth <= 0 {
20 | return
21 | }
22 | body, urls, err := fetcher.Fetch(url)
23 | if err != nil {
24 | fmt.Println(err)
25 | return
26 | }
27 | fmt.Printf("found: %s %q\n", url, body)
28 | for _, u := range urls {
29 | Crawl(u, depth-1, fetcher)
30 | }
31 | return
32 | }
33 |
34 | func main() {
35 | Crawl("http://golang.org/", 4, fetcher)
36 | }
37 |
38 | // fakeFetcher 是返回若干结果的 Fetcher。
39 | type fakeFetcher map[string]*fakeResult
40 |
41 | type fakeResult struct {
42 | body string
43 | urls []string
44 | }
45 |
46 | func (f fakeFetcher) Fetch(url string) (string, []string, error) {
47 | if res, ok := f[url]; ok {
48 | return res.body, res.urls, nil
49 | }
50 | return "", nil, fmt.Errorf("not found: %s", url)
51 | }
52 |
53 | // fetcher 是填充后的 fakeFetcher。
54 | var fetcher = fakeFetcher{
55 | "http://golang.org/": &fakeResult{
56 | "The Go Programming Language",
57 | []string{
58 | "http://golang.org/pkg/",
59 | "http://golang.org/cmd/",
60 | },
61 | },
62 | "http://golang.org/pkg/": &fakeResult{
63 | "Packages",
64 | []string{
65 | "http://golang.org/",
66 | "http://golang.org/cmd/",
67 | "http://golang.org/pkg/fmt/",
68 | "http://golang.org/pkg/os/",
69 | },
70 | },
71 | "http://golang.org/pkg/fmt/": &fakeResult{
72 | "Package fmt",
73 | []string{
74 | "http://golang.org/",
75 | "http://golang.org/pkg/",
76 | },
77 | },
78 | "http://golang.org/pkg/os/": &fakeResult{
79 | "Package os",
80 | []string{
81 | "http://golang.org/",
82 | "http://golang.org/pkg/",
83 | },
84 | },
85 | }
86 |
--------------------------------------------------------------------------------
/template/index.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Go 指南
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
52 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/gotour/appengine.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build appengine
6 |
7 | package main
8 |
9 | import (
10 | "bufio"
11 | "bytes"
12 | "io"
13 | "net/http"
14 | "strings"
15 |
16 | "appengine"
17 |
18 | _ "github.com/Go-zh/tools/playground"
19 | )
20 |
21 | const runUrl = "http://golang.org/compile"
22 |
23 | func init() {
24 | http.HandleFunc("/lesson/", lessonHandler)
25 | http.HandleFunc("/", rootHandler)
26 |
27 | if err := initTour(".", "HTTPTransport"); err != nil {
28 | panic(err)
29 | }
30 | }
31 |
32 | func rootHandler(w http.ResponseWriter, r *http.Request) {
33 | c := appengine.NewContext(r)
34 | if err := renderUI(w); err != nil {
35 | c.Criticalf("UI render: %v", err)
36 | }
37 | }
38 |
39 | func lessonHandler(w http.ResponseWriter, r *http.Request) {
40 | c := appengine.NewContext(r)
41 | lesson := strings.TrimPrefix(r.URL.Path, "/lesson/")
42 | if err := writeLesson(lesson, w); err != nil {
43 | if err == lessonNotFound {
44 | http.NotFound(w, r)
45 | } else {
46 | c.Criticalf("tour render: %v", err)
47 | }
48 | }
49 | }
50 |
51 | // prepContent returns a Reader that produces the content from the given
52 | // Reader, but strips the prefix "#appengine: " from each line. It also drops
53 | // any non-blank like that follows a series of 1 or more lines with the prefix.
54 | func prepContent(in io.Reader) io.Reader {
55 | var prefix = []byte("#appengine: ")
56 | out, w := io.Pipe()
57 | go func() {
58 | r := bufio.NewReader(in)
59 | drop := false
60 | for {
61 | b, err := r.ReadBytes('\n')
62 | if err != nil && err != io.EOF {
63 | w.CloseWithError(err)
64 | return
65 | }
66 | if bytes.HasPrefix(b, prefix) {
67 | b = b[len(prefix):]
68 | drop = true
69 | } else if drop {
70 | if len(b) > 1 {
71 | b = nil
72 | }
73 | drop = false
74 | }
75 | if len(b) > 0 {
76 | w.Write(b)
77 | }
78 | if err == io.EOF {
79 | w.Close()
80 | return
81 | }
82 | }
83 | }()
84 | return out
85 | }
86 |
87 | // socketAddr returns the WebSocket handler address.
88 | // The App Engine version does not provide a WebSocket handler.
89 | func socketAddr() string { return "" }
90 |
--------------------------------------------------------------------------------
/static/js/values.js:
--------------------------------------------------------------------------------
1 | /* Copyright 2012 The Go Authors. All rights reserved.
2 | * Use of this source code is governed by a BSD-style
3 | * license that can be found in the LICENSE file.
4 | */
5 | 'use strict';
6 |
7 | angular.module('tour.values', []).
8 |
9 | // List of modules with description and lessons in it.
10 | value('tableOfContents', [{
11 | 'id': 'mechanics',
12 | 'title': '指南的使用',
13 | 'description': '欢迎来到 Go 编程语言 指南。本指南涵盖了该语言的大部分重要特性,主要包括:
',
14 | 'lessons': ['welcome']
15 | }, {
16 | 'id': 'basics',
17 | 'title': '基础',
18 | 'description': '一开始,将学习关于语言的所有基础内容。
定义变量、调用函数、以及在你学习下一课之前所需要了解的全部内容。
',
19 | 'lessons': ['basics', 'flowcontrol', 'moretypes']
20 | }, {
21 | 'id': 'methods',
22 | 'title': '方法和接口',
23 | 'description': '学习如何为类型定义方法;如何定义接口;以及如何将所有内容贯通起来。
',
24 | 'lessons': ['methods']
25 | }, {
26 | 'id': 'concurrency',
27 | 'title': '并发',
28 | 'description': '作为语言的核心部分,Go 提供了并发的特性。
这一部分概览了 goroutein 和 channel,以及如何使用它们来实现不同的并发模式。
',
29 | 'lessons': ['concurrency']
30 | }]).
31 |
32 | // translation
33 | value('translation', {
34 | 'off': '关闭',
35 | 'on': '开启',
36 | 'syntax': '语法高亮',
37 | 'lineno': '行号',
38 | 'reset': '重置',
39 | 'format': '格式化源代码',
40 | 'kill': '杀死进程',
41 | 'run': '运行',
42 | 'compile': '编译并运行',
43 | 'more': '选项',
44 | 'toc': '目录',
45 | 'prev': '向前',
46 | 'next': '向后',
47 | 'waiting': '等待远端服务器响应...',
48 | 'errcomm': '与远端服务器通讯失败。',
49 | }).
50 |
51 | // Config for codemirror plugin
52 | value('ui.config', {
53 | codemirror: {
54 | mode: 'text/x-go',
55 | matchBrackets: true,
56 | lineNumbers: true,
57 | autofocus: true,
58 | indentWithTabs: true,
59 | indentUnit: 4,
60 | tabSize: 4,
61 | lineWrapping: true,
62 | extraKeys: {
63 | 'Shift-Enter': function() {
64 | $('#run').click();
65 | },
66 | 'Ctrl-Enter': function() {
67 | $('#format').click();
68 | },
69 | 'PageDown': function() {
70 | return false;
71 | },
72 | 'PageUp': function() {
73 | return false;
74 | },
75 | },
76 | // TODO: is there a better way to do this?
77 | // AngularJS values can't depend on factories.
78 | onChange: function() {
79 | if (window.codeChanged !== null) window.codeChanged();
80 | }
81 | }
82 | });
83 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | This file lists topics that already exist (prefixed by *) and topics that
2 | should be added (prefixed by -). It should be kept up-to-date with tour.article.
3 |
4 | * Hello, 世界
5 | * Go local
6 | * Packages
7 | * Imports
8 | - "Imported but not used" errors (later perhaps)
9 | * Exported names
10 | * Functions
11 | * Functions continued
12 | * Multiple results
13 | * Named results
14 | - Variables (single declaration first)
15 | * Variables
16 | * Variables with initializers
17 | * Short variable declarations
18 | * Basic types
19 | * Type inference
20 | * Type conversions
21 | * Zero values
22 | * Constants
23 | * Numeric Constants
24 | * For
25 | * For continued
26 | * For is Go's "while"
27 | * Forever
28 | * If
29 | * If with a short statement
30 | * If and else
31 | * Exercise: Loops and Functions
32 | * Structs
33 | * Struct Fields
34 | * Pointers
35 | * Struct Literals
36 | * The new function
37 | * Arrays
38 | * Slices
39 | * Slicing slices
40 | * Making slices
41 | * Append
42 | - Copy
43 | * Nil slices
44 | * Range
45 | * Range continued
46 | - The blank identifier
47 | - Slices of slices.
48 | * Exercise: Slices
49 | * Maps
50 | * Map literals
51 | * Map literals continued
52 | * Mutating Maps
53 | - Maps and range
54 | * Exercise: Maps
55 | * Function values
56 | * Function closures
57 | * Exercise: Fibonacci closure
58 | * Switch
59 | * Switch evaluation order
60 | * Switch with no condition
61 | - Complex numbers
62 | * Advanced Exercise: Complex cube roots
63 | - the type keyword
64 | * Methods and Interfaces
65 | * Methods
66 | * Methods continued
67 | * Methods with pointer receivers
68 | * Interfaces
69 | * Interfaces are satisfied implicitly
70 | - Interface assignment
71 | - Empty interface
72 | * Errors
73 | * Exercise: Errors
74 | * Web servers
75 | * Exercise: HTTP Handlers
76 | * Images
77 | * Exercise: Images
78 | * Exercise: Rot13 Reader
79 | - Sort (see sort package examples)
80 | - Exercise: Sort
81 | - Embedding
82 | - Exercise: Embedding (sort by different fields)
83 | - Type assertion
84 | - Type switch
85 | - Exercise: Visitor (walk a tree?)
86 | * Concurrency
87 | * Goroutines
88 | * Channels
89 | * Buffered Channels
90 | * Range and Close
91 | * Select
92 | * Default Selection
93 | * Exercise: Equivalent Binary Trees
94 | * Exercise: Equivalent Binary Trees
95 | * Exercise: Web Crawler
96 | - More language features
97 | * Defer
98 | - Panic and recover
99 | - init functions
100 | - Tools
101 | - Godoc
102 | - Gofmt
103 | * Where to Go from here...
104 |
105 |
--------------------------------------------------------------------------------
/content/welcome.article:
--------------------------------------------------------------------------------
1 | 欢迎!
2 | 学习如何使用本指南:包括如何在不同的课程之间切换,以及如何运行代码。
3 |
4 | Go 作者组
5 | http://golang.org
6 |
7 | * Hello, 世界
8 |
9 | 欢迎来到[[http://golang.org/][Go 编程语言]]指南。
10 |
11 | 点击页面顶部左边的[[javascript:highlight(".logo")][Go 指南]],
12 | 可以访问该指南的模块列表。
13 |
14 | 任何时候,都可以通过点击页面顶部右边的[[javascript:highlightAndClick(".nav")][菜单]]浏览内容清单。
15 |
16 | 在指南后有若干个练习需要读者完成。
17 |
18 | 你可以使用
19 |
20 | - [[javascript:highlight(".prev-page")]["上一页"]] 或 `PageUp` 键跳转到前一个页面,
21 |
22 | - [[javascript:highlight(".next-page")]["下一页"]] 或 `PageDown` 键跳转到下一个页面。
23 |
24 | 该指南可以进行交互。点击
25 | “[[javascript:highlightAndClick("#run")][运行]]”按钮(或按 Shift + Enter)可以在
26 | #appengine: 远程服务器上
27 | 你的电脑上
28 | 编译并执行程序。
29 | 结果展示在代码的下面。
30 |
31 | 这些例子程序展示了 Go 的各个方面。在指南中的程序可以成为你积累经验的开始。
32 |
33 | 编辑程序并且再次执行它。
34 |
35 | 注意当你点击[[javascript:highlightAndClick("#format")][格式化]]或`Ctrl - Enter`
36 | 时,编辑器中的文本会被 [[http://golang.org/cmd/gofmt/][gofmt]] 工具格式化。
37 | 你可以点击[[javascript:highlightAndClick(".syntax-checkbox")][语法]]开启或关闭语法高亮。
38 |
39 | 如果你准备好了,请点击页面底部的[[javascript:highlightAndClick(".next-page")][右箭头]]或按 `PageDown` 键继续。
40 |
41 | .play welcome/hello.go
42 |
43 | * Go 本地化
44 |
45 | 该指南也有其他语言版本:
46 |
47 | - [[http://go-tour-br.appspot.com/][Brazilian Portuguese — Português do Brasil]]
48 | - [[http://go-tour-ca.appspot.com/][Catalan — Català]]
49 | - [[http://go-tour-es.appspot.com/][Spanish — Español]]
50 | - [[http://go-tour-fr.appspot.com/][French - Français]]
51 | - [[http://go-tour-he.appspot.com/][Hebrew — עִבְרִית]]
52 | - [[http://go-tour-jp.appspot.com/][Japanese — 日本語]]
53 | - [[http://go-tour-kr.appspot.com/][Korean — 한국어]]
54 | - [[http://go-tour-ro.appspot.com/][Romanian - Română]]
55 | - [[http://tour.go-zh.org/][Simplified Chinese — 中文(简体)]]
56 | - [[http://go-tour-zh-tw.appspot.com/][Traditional Chinese — 中文(繁體)]]
57 |
58 | 点击[[javascript:highlightAndClick(".next-page")][“下一页”]]按钮或者按 `PageDown` 键继续。
59 |
60 | #appengine: * Go Playground
61 | #appengine:
62 | #appengine: 这个指南构建在 [[http://play.golang.org/][Go Playground]] 之上,这是一个运行在
63 | #appengine: [[http://golang.org/][golang.org]] 的服务器上的一个 Web 服务。
64 | #appengine:
65 | #appengine: 服务接收 Go 程序的输入,且在沙盒里编译、链接和运行,
66 | #appengine: 然后返回输出。
67 | #appengine:
68 | #appengine: 对于在 Playground 运行的程序的限制是:
69 | #appengine:
70 | #appengine: - 在 Playground 中,时间从 2009-11-10 23:00:00 UTC(了解这个日期的重要含义是留给读者的练习)。这使得根据可预见的输出来缓存程序变得容易。
71 | #appengine:
72 | #appengine: - 对于运行时间、CPU 和内存的使用同样也有限制,并且程序不能访问外部网络中的主机。
73 | #appengine:
74 | #appengine: Playground 使用最后发布的 Go 的稳定版本。
75 | #appengine:
76 | #appengine: 参阅“[[http://blog.golang.org/playground][Go Playground 的内部机制(英文)]]”了解更多信息。
77 | #appengine:
78 | #appengine: .play welcome/sandbox.go
79 |
80 | * 恭喜!
81 |
82 | 你已经完成了本指南的第一部分!
83 |
84 | 现在点击[[/list][模块]]列表看看接下来学习什么,或者继续[[javascript:click('.next-page')][后面的课程]]。
85 |
--------------------------------------------------------------------------------
/content/flowcontrol.article:
--------------------------------------------------------------------------------
1 | 流程控制语句:for、if、else 和 switch
2 | 学习如何用条件、循环和开关语句控制代码的流程。
3 |
4 | Go 作者组
5 | http://golang.org
6 |
7 | * for
8 |
9 | Go 只有一种循环结构——`for` 循环。
10 |
11 | 基本的 `for` 循环除了没有了 `( )` 之外(甚至强制不能使用它们),看起来跟 C 或者 Java 中做的一样,而 `{ }` 是必须的。
12 |
13 | .play flowcontrol/for.go
14 |
15 | * for(续)
16 |
17 | 跟 C 或者 Java 中一样,可以让前置、后置语句为空。
18 |
19 | .play flowcontrol/for-continued.go
20 |
21 | * for 是 Go 的 “while”
22 |
23 | 基于此可以省略分号:C 的 `while` 在 Go 中叫做 `for`。
24 |
25 | .play flowcontrol/for-is-gos-while.go
26 |
27 | * 死循环
28 |
29 | 如果省略了循环条件,循环就不会结束,因此可以用更简洁地形式表达死循环。
30 |
31 | .play flowcontrol/forever.go
32 |
33 | * if
34 |
35 | `if` 语句除了没有了 `( )` 之外(甚至强制不能使用它们),看起来跟 C 或者 Java 中的一样,而 `{ }` 是必须的。
36 |
37 | (耳熟吗?)
38 |
39 | .play flowcontrol/if.go
40 |
41 | * if 的便捷语句
42 |
43 | 跟 `for` 一样,`if` 语句可以在条件之前执行一个简单的语句。
44 |
45 | 由这个语句定义的变量的作用域仅在 `if` 范围之内。
46 |
47 | (在最后的 `return` 语句处使用 `v` 看看。)
48 |
49 | .play flowcontrol/if-with-a-short-statement.go
50 |
51 | * if 和 else
52 |
53 | 在 `if` 的便捷语句定义的变量同样可以在任何对应的 `else` 块中使用。
54 |
55 | .play flowcontrol/if-and-else.go
56 |
57 | * 练习:循环和函数
58 |
59 | 作为练习函数和循环的简单途径,用牛顿法实现开方函数。
60 |
61 | 在这个例子中,牛顿法是通过选择一个初始点 _z_ 然后重复这一过程求 `Sqrt(x)` 的近似值:
62 |
63 | .image /content/img/newton.png
64 |
65 | 为了做到这个,只需要重复计算 10 次,并且观察不同的值(1,2,3,……)是如何逐步逼近结果的。
66 | 然后,修改循环条件,使得当值停止改变(或改变非常小)的时候退出循环。观察迭代次数是否变化。结果与 [[http://golang.org/pkg/math/#Sqrt][math.Sqrt] 接近吗?
67 |
68 | 提示:定义并初始化一个浮点值,向其提供一个浮点语法或使用转换:
69 |
70 | z := float64(1)
71 | z := 1.0
72 |
73 | .play flowcontrol/exercise-loops-and-functions.go
74 |
75 | * switch
76 |
77 | 一个结构体(`struct`)就是一个字段的集合。
78 |
79 | 除非以 `fallthrough` 语句结束,否则分支会自动终止。
80 |
81 | .play flowcontrol/switch.go
82 |
83 | * switch 的执行顺序
84 |
85 | switch 的条件从上到下的执行,当匹配成功的时候停止。
86 |
87 | (例如,
88 |
89 | switch i {
90 | case 0:
91 | case f():
92 | }
93 |
94 | 当 `i==0` 时不会调用 `f`。)
95 |
96 | #appengine: 注意:Go playground 中的时间总是从 2009-11-10 23:00:00 UTC 开始,
97 | #appengine: 如何校验这个值作为一个练习留给读者完成。
98 |
99 | .play flowcontrol/switch-evaluation-order.go
100 |
101 | * 没有条件的 switch
102 |
103 | 没有条件的 switch 同 `switch true` 一样。
104 |
105 | 这一构造使得可以用更清晰的形式来编写长的 if-then-else 链。
106 |
107 | .play flowcontrol/switch-with-no-condition.go
108 |
109 | * defer
110 |
111 | defer 语句会延迟函数的执行直到上层函数返回。
112 |
113 | 延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。
114 |
115 | .play flowcontrol/defer.go
116 |
117 | * defer 栈
118 |
119 | 延迟的函数调用被压入一个栈中。当函数返回时,
120 | 会按照后进先出的顺序调用被延迟的函数调用。
121 |
122 | 阅读[[http://blog.golang.org/defer-panic-and-recover][博文]]了解更多关于 defer 语句的信息。
123 |
124 | .play flowcontrol/defer-multi.go
125 |
126 | * 恭喜!
127 |
128 | 你已经完成了本课程!
129 |
130 | 你可以返回[[/list][模块]]列表看看接下来学习什么,或者继续[[javascript:click('.next-page')][后面的课程]]。
131 |
--------------------------------------------------------------------------------
/static/partials/editor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
<
10 |
{{curPage}}/{{toc.lessons[lessonId].Pages.length}}
11 |
>
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/gotour/secure.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build secure
6 |
7 | package main
8 |
9 | import (
10 | "bufio"
11 | "bytes"
12 | "flag"
13 | "io"
14 | "log"
15 | "net/http"
16 | "path/filepath"
17 | "strings"
18 |
19 | _ "github.com/Go-zh/tools/playground"
20 | )
21 |
22 | const runUrl = "http://golang.org/compile"
23 |
24 | var (
25 | basePath = flag.String("base", "..", "base path of tour")
26 | httpAddr = flag.String("http", "127.0.0.1:8083", "HTTP service address (e.g., '127.0.0.1:8083')")
27 | )
28 |
29 | func main() {
30 | flag.Parse()
31 |
32 | http.HandleFunc("/lesson/", lessonHandler)
33 | http.HandleFunc("/", rootHandler)
34 |
35 | // Keep these static file handlers in sync with ../app.yaml.
36 | static := http.FileServer(http.Dir(*basePath))
37 | http.Handle("/content/img/", static)
38 | http.Handle("/static/", static)
39 | imgDir := filepath.Join(*basePath, "static", "img")
40 | http.Handle("/favicon.ico", http.FileServer(http.Dir(imgDir)))
41 |
42 | if err := initTour(*basePath, "HTTPTransport"); err != nil {
43 | log.Fatal(err)
44 | }
45 |
46 | log.Fatal(http.ListenAndServe(*httpAddr, nil))
47 | }
48 |
49 | func rootHandler(w http.ResponseWriter, r *http.Request) {
50 | if err := renderUI(w); err != nil {
51 | log.Printf("UI render: %v", err)
52 | }
53 | }
54 |
55 | func lessonHandler(w http.ResponseWriter, r *http.Request) {
56 | lesson := strings.TrimPrefix(r.URL.Path, "/lesson/")
57 | if err := writeLesson(lesson, w); err != nil {
58 | if err == lessonNotFound {
59 | http.NotFound(w, r)
60 | } else {
61 | log.Printf("tour render: %v", err)
62 | }
63 | }
64 | }
65 |
66 | // prepContent returns a Reader that produces the content from the given
67 | // Reader, but strips the prefix "#appengine: " from each line. It also drops
68 | // any non-blank like that follows a series of 1 or more lines with the prefix.
69 | func prepContent(in io.Reader) io.Reader {
70 | var prefix = []byte("#appengine: ")
71 | out, w := io.Pipe()
72 | go func() {
73 | r := bufio.NewReader(in)
74 | drop := false
75 | for {
76 | b, err := r.ReadBytes('\n')
77 | if err != nil && err != io.EOF {
78 | w.CloseWithError(err)
79 | return
80 | }
81 | if bytes.HasPrefix(b, prefix) {
82 | b = b[len(prefix):]
83 | drop = true
84 | } else if drop {
85 | if len(b) > 1 {
86 | b = nil
87 | }
88 | drop = false
89 | }
90 | if len(b) > 0 {
91 | w.Write(b)
92 | }
93 | if err == io.EOF {
94 | w.Close()
95 | return
96 | }
97 | }
98 | }()
99 | return out
100 | }
101 |
102 | // socketAddr returns the WebSocket handler address.
103 | // The App Engine version does not provide a WebSocket handler.
104 | func socketAddr() string { return "" }
105 |
--------------------------------------------------------------------------------
/static/js/controllers.js:
--------------------------------------------------------------------------------
1 | /* Copyright 2012 The Go Authors. All rights reserved.
2 | * Use of this source code is governed by a BSD-style
3 | * license that can be found in the LICENSE file.
4 | */
5 | 'use strict';
6 |
7 | /* Controllers */
8 |
9 |
10 | angular.module('tour.controllers', []).
11 |
12 | // Navigation controller
13 | controller('EditorCtrl', ['$scope', '$routeParams', '$location', 'toc', 'i18n', 'run', 'fmt', 'editor', 'analytics', 'storage',
14 | function($scope, $routeParams, $location, toc, i18n, run, fmt, editor, analytics, storage) {
15 | var lessons = [];
16 | toc.lessons.then(function(v) {
17 | lessons = v;
18 | $scope.gotoPage($scope.curPage);
19 |
20 | // Store changes on the current file to local storage.
21 | $scope.$watch(function() {
22 | var f = file();
23 | return f && f.Content;
24 | }, function(val) {
25 | var key = $scope.lessonId + '.' + ($scope.curPage - 1) + '.' + $scope.curFile;
26 | storage.set(key, val);
27 | });
28 | });
29 |
30 | $scope.toc = toc;
31 | $scope.lessonId = $routeParams.lessonId;
32 | $scope.curPage = parseInt($routeParams.pageNumber);
33 | $scope.curFile = 0;
34 |
35 | $scope.nextPage = function() {
36 | $scope.gotoPage($scope.curPage + 1);
37 | };
38 | $scope.prevPage = function() {
39 | $scope.gotoPage($scope.curPage - 1);
40 | };
41 | $scope.gotoPage = function(page) {
42 | var l = $routeParams.lessonId;
43 | if (page >= 1 && page <= lessons[$scope.lessonId].Pages.length) {
44 | $scope.curPage = page;
45 | } else {
46 | l = (page < 1) ? toc.prevLesson(l) : toc.nextLesson(l);
47 | if (l === '') { // If there's not previous or next
48 | $location.path('/list');
49 | return;
50 | }
51 | page = (page < 1) ? lessons[l].Pages.length : 1;
52 | }
53 | $location.path('/' + l + '/' + page);
54 | $scope.openFile($scope.curFile);
55 | analytics.trackView();
56 | };
57 | $scope.openFile = function(file) {
58 | $scope.curFile = file;
59 | editor.paint();
60 | };
61 |
62 | function log(mode, text) {
63 | $('.output.active').html('' + text + ' ');
64 | }
65 |
66 | function clearOutput() {
67 | $('.output.active').html('');
68 | }
69 |
70 | function file() {
71 | return lessons[$scope.lessonId].Pages[$scope.curPage - 1].Files[$scope.curFile];
72 | }
73 |
74 | $scope.run = function() {
75 | log('info', i18n.l('waiting'));
76 | var f = file();
77 | run(f.Content, $('.output.active > pre')[0], {
78 | path: f.Name
79 | });
80 | };
81 |
82 | $scope.format = function() {
83 | log('info', i18n.l('waiting'));
84 | fmt(file().Content).then(
85 | function(data) {
86 | if (data.data.Error !== '') {
87 | log('stderr', data.data.Error);
88 | return;
89 | }
90 | clearOutput();
91 | file().Content = data.data.Body;
92 | },
93 | function(error) {
94 | log('stderr', error);
95 | });
96 | };
97 |
98 | $scope.reset = function() {
99 | file().Content = file().OrigContent;
100 | };
101 | }
102 | ]);
103 |
--------------------------------------------------------------------------------
/solutions/webcrawler.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build ignore
6 |
7 | package main
8 |
9 | import (
10 | "errors"
11 | "fmt"
12 | "sync"
13 | )
14 |
15 | type Fetcher interface {
16 | // Fetch returns the body of URL and
17 | // a slice of URLs found on that page.
18 | Fetch(url string) (body string, urls []string, err error)
19 | }
20 |
21 | // fetched tracks URLs that have been (or are being) fetched.
22 | // The lock must be held while reading from or writing to the map.
23 | // See http://golang.org/ref/spec#Struct_types section on embedded types.
24 | var fetched = struct {
25 | m map[string]error
26 | sync.Mutex
27 | }{m: make(map[string]error)}
28 |
29 | var loading = errors.New("url load in progress") // sentinel value
30 |
31 | // Crawl uses fetcher to recursively crawl
32 | // pages starting with url, to a maximum of depth.
33 | func Crawl(url string, depth int, fetcher Fetcher) {
34 | if depth <= 0 {
35 | fmt.Printf("<- Done with %v, depth 0.\n", url)
36 | return
37 | }
38 |
39 | fetched.Lock()
40 | if _, ok := fetched.m[url]; ok {
41 | fetched.Unlock()
42 | fmt.Printf("<- Done with %v, already fetched.\n", url)
43 | return
44 | }
45 | // We mark the url to be loading to avoid others reloading it at the same time.
46 | fetched.m[url] = loading
47 | fetched.Unlock()
48 |
49 | // We load it concurrently.
50 | body, urls, err := fetcher.Fetch(url)
51 |
52 | // And update the status in a synced zone.
53 | fetched.Lock()
54 | fetched.m[url] = err
55 | fetched.Unlock()
56 |
57 | if err != nil {
58 | fmt.Printf("<- Error on %v: %v\n", url, err)
59 | return
60 | }
61 | fmt.Printf("Found: %s %q\n", url, body)
62 | done := make(chan bool)
63 | for i, u := range urls {
64 | fmt.Printf("-> Crawling child %v/%v of %v : %v.\n", i, len(urls), url, u)
65 | go func(url string) {
66 | Crawl(url, depth-1, fetcher)
67 | done <- true
68 | }(u)
69 | }
70 | for i, u := range urls {
71 | fmt.Printf("<- [%v] %v/%v Waiting for child %v.\n", url, i, len(urls), u)
72 | <-done
73 | }
74 | fmt.Printf("<- Done with %v\n", url)
75 | }
76 |
77 | func main() {
78 | Crawl("http://golang.org/", 4, fetcher)
79 |
80 | fmt.Println("Fetching stats\n--------------")
81 | for url, err := range fetched.m {
82 | if err != nil {
83 | fmt.Printf("%v failed: %v\n", url, err)
84 | } else {
85 | fmt.Printf("%v was fetched\n", url)
86 | }
87 | }
88 | }
89 |
90 | // fakeFetcher is Fetcher that returns canned results.
91 | type fakeFetcher map[string]*fakeResult
92 |
93 | type fakeResult struct {
94 | body string
95 | urls []string
96 | }
97 |
98 | func (f *fakeFetcher) Fetch(url string) (string, []string, error) {
99 | if res, ok := (*f)[url]; ok {
100 | return res.body, res.urls, nil
101 | }
102 | return "", nil, fmt.Errorf("not found: %s", url)
103 | }
104 |
105 | // fetcher is a populated fakeFetcher.
106 | var fetcher = &fakeFetcher{
107 | "http://golang.org/": &fakeResult{
108 | "The Go Programming Language",
109 | []string{
110 | "http://golang.org/pkg/",
111 | "http://golang.org/cmd/",
112 | },
113 | },
114 | "http://golang.org/pkg/": &fakeResult{
115 | "Packages",
116 | []string{
117 | "http://golang.org/",
118 | "http://golang.org/cmd/",
119 | "http://golang.org/pkg/fmt/",
120 | "http://golang.org/pkg/os/",
121 | },
122 | },
123 | "http://golang.org/pkg/fmt/": &fakeResult{
124 | "Package fmt",
125 | []string{
126 | "http://golang.org/",
127 | "http://golang.org/pkg/",
128 | },
129 | },
130 | "http://golang.org/pkg/os/": &fakeResult{
131 | "Package os",
132 | []string{
133 | "http://golang.org/",
134 | "http://golang.org/pkg/",
135 | },
136 | },
137 | }
138 |
--------------------------------------------------------------------------------
/content/basics.article:
--------------------------------------------------------------------------------
1 | 包、变量和函数。
2 | 学习 Go 程序的基本组件。
3 |
4 | Go 作者组
5 | http://golang.org
6 |
7 | * 包
8 |
9 | 每个 Go 程序都是由包组成的。
10 |
11 | 程序运行的入口是包 `main`。
12 |
13 | 这个程序使用并导入了包 `"fmt"` 和 `"math/rand"`。
14 |
15 | 按照惯例,包名与导入路径的最后一个目录一致。例如,`"math/rand"` 包由 `package`rand` 语句开始。
16 |
17 | #appengine: *注意:* 这个程序的运行环境是固定的,因此
18 | #appengine: `rand.Intn` 总是会返回相同的数字。
19 | #appengine:
20 | #appengine: (为了得到不同的数字,需要生成不同的种子数,参阅 [[http://golang.org/pkg/math/rand/#Seed][`rand.Seed`]]。)
21 |
22 | .play basics/packages.go
23 |
24 | * 导入
25 |
26 | 这个代码用圆括号组合了导入,这是“打包”导入语句。
27 |
28 | 同样可以编写多个导入语句,例如:
29 |
30 | import "fmt"
31 | import "math"
32 |
33 | 不过使用打包的导入语句是更好的形式。
34 |
35 | .play basics/imports.go
36 |
37 | * 导出名
38 |
39 | 在导入了一个包之后,就可以用其导出的名称来调用它。
40 |
41 | 在 Go 中,首字母大写的名称是被导出的。
42 |
43 | `Foo` 和 `FOO` 都是被导出的名称。名称 `foo` 是不会被导出的。
44 |
45 | 执行代码。然后将 `math.pi` 改名为 `math.Pi` 再试着执行一下。
46 |
47 | .play basics/exported-names.go
48 |
49 | * 函数
50 |
51 | 函数可以没有参数或接受多个参数。
52 |
53 | 在这个例子中,`add` 接受两个 `int` 类型的参数。
54 |
55 | 注意类型在变量名 _之后_。
56 |
57 | (参考 [[http://golang.org/doc/articles/gos_declaration_syntax.html][这篇关于 Go 语法定义的文章]]了解类型以这种形式出现的原因。)
58 |
59 | .play basics/functions.go
60 |
61 | * 函数(续)
62 |
63 | 当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略。
64 |
65 | 在这个例子中 ,
66 |
67 | x int, y int
68 |
69 | 被缩写为
70 |
71 | x, y int
72 |
73 | .play basics/functions-continued.go
74 |
75 | x, y int
76 |
77 | .play basics/functions-continued.go
78 |
79 | * 多值返回
80 |
81 | 函数可以返回任意数量的返回值。
82 |
83 | `swap` 函数返回了两个字符串。
84 |
85 | .play basics/multiple-results.go
86 |
87 | * 命名返回值
88 |
89 | Go 的返回值可以被命名,并且像变量那样使用。
90 |
91 | 返回值的名称应当具有一定的意义,可以作为文档使用。
92 |
93 | 没有参数的 `return` 语句返回结果的当前值。也就是`直接`返回。
94 |
95 | 直接返回语句仅应当用在像下面这样的短函数中。在长的函数中它们会影响代码的可读性。
96 |
97 | .play basics/named-results.go
98 |
99 | * 变量
100 |
101 | `var` 语句定义了一个变量的列表;跟函数的参数列表一样,类型在后面。
102 |
103 | 就像在这个例子中看到的一样,`var` 语句可以定义在包或函数级别。
104 |
105 | .play basics/variables.go
106 |
107 | * 初始化变量
108 |
109 | 变量定义可以包含初始值,每个变量对应一个。
110 |
111 | 如果初始化是使用表达式,则可以省略类型;变量从初始值中获得类型。
112 |
113 | .play basics/variables-with-initializers.go
114 |
115 | * 短声明变量
116 |
117 | 在函数中,`:=` 简洁赋值语句在明确类型的地方,可以用于替代 `var` 定义。
118 |
119 | 函数外的每个语句都必须以关键字开始(`var`、`func`、等等),`:=` 结构不能使用在函数外。
120 |
121 | .play basics/short-variable-declarations.go
122 |
123 | * 基本类型
124 |
125 | Go 的基本类型有Basic types
126 |
127 | bool
128 |
129 | string
130 |
131 | int int8 int16 int32 int64
132 | uint uint8 uint16 uint32 uint64 uintptr
133 |
134 | byte // uint8 的别名
135 |
136 | rune // int32 的别名
137 | // 代表一个Unicode码
138 |
139 | float32 float64
140 |
141 | complex64 complex128
142 |
143 | 这个例子演示了具有不同类型的变量。
144 | 同时与导入语句一样,变量的定义“打包”在一个语法块中。
145 |
146 | .play basics/basic-types.go
147 |
148 | * 零值
149 |
150 | 变量在定义时没有明确的初始化时会赋值为_零值_。
151 |
152 | 零值是:
153 |
154 | - 数值类型为 `0`,
155 | - 布尔类型为 `false`,
156 | - 字符串为 `""`(空字符串)。
157 |
158 | .play basics/zero.go
159 |
160 | * 类型转换
161 |
162 | 表达式 `T(v)` 将值 `v` 转换为类型 `T`。
163 |
164 | 一些关于数值的转换:
165 |
166 | var i int = 42
167 | var f float64 = float64(i)
168 | var u uint = uint(f)
169 |
170 | 或者,更加简单的形式:
171 |
172 | i := 42
173 | f := float64(i)
174 | u := uint(f)
175 |
176 | 与 C 不同的是 Go 的在不同类型之间的项目赋值时需要显式转换。
177 | 试着移除例子中 `float64` 或 `int` 的转换看看会发生什么。
178 |
179 | .play basics/type-conversions.go
180 |
181 | * 类型推导
182 |
183 | 在定义一个变量但不指定其类型时(使用没有类型的 `var` 或 `:=` 语句),
184 | 变量的类型由右值推导得出。
185 |
186 | 当右值定义了类型时,新变量的类型与其相同:
187 |
188 | var i int
189 | j := i // j 也是一个 int
190 |
191 | 但是当右边包含了未指名类型的数字常量时,新的变量就可能是 `int`、`float64` 或 `complex128`。
192 | 这取决于常量的精度:
193 |
194 | i := 42 // int
195 | f := 3.142 // float64
196 | g := 0.867 + 0.5i // complex128
197 |
198 | 尝试修改演示代码中 `v` 的初始值,并观察这是如何影响其类型的。
199 |
200 | .play basics/type-inference.go
201 |
202 | * 常量
203 |
204 | 常量的定义与变量类似,只不过使用 `const` 关键字。
205 |
206 | 常量可以是字符、字符串、布尔或数字类型的值。
207 |
208 | 常量不能使用 `:=` 语法定义。
209 |
210 | .play basics/constants.go
211 |
212 | * 数值常量
213 |
214 | 数值常量是高精度的 _值_。
215 |
216 | 一个未指定类型的常量由上下文来决定其类型。
217 |
218 | 也尝试一下输出 `needInt(Big)` 吧。
219 |
220 | .play basics/numeric-constants.go
221 |
222 | * 恭喜!
223 |
224 | 你已经完成了本课程!
225 |
226 | 你可以返回[[/list][模块]]列表看看接下来学习什么,或者继续[[javascript:click('.next-page')][后面的课程]]。
227 |
--------------------------------------------------------------------------------
/static/lib/codemirror/AUTHORS:
--------------------------------------------------------------------------------
1 | List of CodeMirror contributors. Updated before every release.
2 |
3 | 4r2r
4 | Aaron Brooks
5 | Adam King
6 | adanlobato
7 | Adán Lobato
8 | aeroson
9 | Ahmad Amireh
10 | Ahmad M. Zawawi
11 | ahoward
12 | Akeksandr Motsjonov
13 | Albert Xing
14 | Alexander Pavlov
15 | Alexander Schepanovski
16 | alexey-k
17 | Alex Piggott
18 | Amy
19 | Ananya Sen
20 | Andre von Houck
21 | Andrey Lushnikov
22 | Andy Kimball
23 | Andy Li
24 | angelozerr
25 | angelo.zerr@gmail.com
26 | Ankit Ahuja
27 | Ansel Santosa
28 | Anthony Grimes
29 | areos
30 | Atul Bhouraskar
31 | Aurelian Oancea
32 | Bastian Müller
33 | benbro
34 | Benjamin DeCoste
35 | Ben Keen
36 | boomyjee
37 | borawjm
38 | Brandon Frohs
39 | Brian Sletten
40 | Bruce Mitchener
41 | Chandra Sekhar Pydi
42 | Charles Skelton
43 | Chris Coyier
44 | Chris Granger
45 | Chris Morgan
46 | Christopher Brown
47 | CodeAnimal
48 | ComFreek
49 | dagsta
50 | Dan Heberden
51 | Daniel, Dao Quang Minh
52 | Daniel Faust
53 | Daniel Huigens
54 | Daniel Neel
55 | Daniel Parnell
56 | Danny Yoo
57 | David Mignot
58 | David Pathakjee
59 | deebugger
60 | Deep Thought
61 | Dominator008
62 | Domizio Demichelis
63 | Drew Bratcher
64 | Drew Hintz
65 | Drew Khoury
66 | Dror BG
67 | duralog
68 | edsharp
69 | ekhaled
70 | Eric Allam
71 | eustas
72 | Fauntleroy
73 | fbuchinger
74 | feizhang365
75 | Felipe Lalanne
76 | Felix Raab
77 | Filip Noetzel
78 | flack
79 | Ford_Lawnmower
80 | Gabriel Nahmias
81 | galambalazs
82 | Gautam Mehta
83 | Glenn Ruehle
84 | Golevka
85 | Gordon Smith
86 | greengiant
87 | Guillaume Massé
88 | Hans Engel
89 | Hardest
90 | Hasan Karahan
91 | Hocdoc
92 | Ian Beck
93 | Ian Wehrman
94 | Ian Wetherbee
95 | Ice White
96 | ICHIKAWA, Yuji
97 | Ingo Richter
98 | Irakli Gozalishvili
99 | Ivan Kurnosov
100 | Jacob Lee
101 | Jakub Vrana
102 | James Campos
103 | James Thorne
104 | Jamie Hill
105 | Jan Jongboom
106 | jankeromnes
107 | Jan Keromnes
108 | Jan T. Sott
109 | Jason
110 | Jason Grout
111 | Jason Johnston
112 | Jason San Jose
113 | Jason Siefken
114 | Jean Boussier
115 | jeffkenton
116 | Jeff Pickhardt
117 | jem (graphite)
118 | Jochen Berger
119 | John Connor
120 | John Lees-Miller
121 | John Snelson
122 | jongalloway
123 | Joost-Wim Boekesteijn
124 | Joseph Pecoraro
125 | Joshua Newman
126 | jots
127 | Juan Benavides Romero
128 | Jucovschi Constantin
129 | jwallers@gmail.com
130 | kaniga
131 | Ken Newman
132 | Ken Rockot
133 | Kevin Sawicki
134 | Klaus Silveira
135 | Koh Zi Han, Cliff
136 | komakino
137 | Konstantin Lopuhin
138 | koops
139 | ks-ifware
140 | Lanny
141 | leaf corcoran
142 | Leonya Khachaturov
143 | Liam Newman
144 | List of contributors. Updated before every release.
145 | LM
146 | Lorenzo Stoakes
147 | lynschinzer
148 | Maksim Lin
149 | Maksym Taran
150 | Marat Dreizin
151 | Marco Aurélio
152 | Marijn Haverbeke
153 | Mario Pietsch
154 | Mark Lentczner
155 | Mason Malone
156 | Mateusz Paprocki
157 | mats cronqvist
158 | Matthew Beale
159 | Matthias BUSSONNIER
160 | Matt McDonald
161 | Matt Pass
162 | Matt Sacks
163 | Maximilian Hils
164 | Max Kirsch
165 | mbarkhau
166 | Metatheos
167 | Micah Dubinko
168 | Michael Lehenbauer
169 | Michael Zhou
170 | Mighty Guava
171 | Miguel Castillo
172 | Mike
173 | Mike Brevoort
174 | Mike Diaz
175 | Mike Ivanov
176 | Mike Kadin
177 | MinRK
178 | misfo
179 | mps
180 | Narciso Jaramillo
181 | Nathan Williams
182 | nerbert
183 | nguillaumin
184 | Niels van Groningen
185 | Nikita Beloglazov
186 | Nikita Vasilyev
187 | nlwillia
188 | pablo
189 | Page
190 | Patrick Strawderman
191 | Paul Garvin
192 | Paul Ivanov
193 | Pavel Feldman
194 | Paweł Bartkiewicz
195 | peteguhl
196 | peterkroon
197 | Peter Kroon
198 | prasanthj
199 | Prasanth J
200 | Rahul
201 | Randy Edmunds
202 | Richard Z.H. Wang
203 | robertop23
204 | Robert Plummer
205 | Ruslan Osmanov
206 | sabaca
207 | Samuel Ainsworth
208 | sandeepshetty
209 | santec
210 | Sascha Peilicke
211 | satchmorun
212 | sathyamoorthi
213 | SCLINIC\jdecker
214 | shaund
215 | shaun gilchrist
216 | Shmuel Englard
217 | sonson
218 | spastorelli
219 | Stas Kobzar
220 | Stefan Borsje
221 | Steffen Beyer
222 | Steve O'Hara
223 | Tarmil
224 | tfjgeorge
225 | Thaddee Tyl
226 | think
227 | Thomas Dvornik
228 | Thomas Schmid
229 | Tim Baumann
230 | Timothy Farrell
231 | Timothy Hatcher
232 | Tomas Varaneckas
233 | Tom Erik Støwer
234 | Tom MacWright
235 | Tony Jian
236 | Vestimir Markov
237 | vf
238 | Volker Mische
239 | William Jamieson
240 | Wojtek Ptak
241 | Xavier Mendez
242 | Yunchi Luo
243 | Yuvi Panda
244 | Zachary Dremann
245 |
--------------------------------------------------------------------------------
/content/concurrency.article:
--------------------------------------------------------------------------------
1 | 并发
2 | Go 将并发作为语言的核心构成。本课将对其进行介绍,并提供了一些示例来展示如何使用它们。
3 |
4 | Go 作者组
5 | http://golang.org
6 |
7 | * goroutine
8 |
9 | _goroutine_ 是由 Go 运行时环境管理的轻量级线程。
10 |
11 | go f(x, y, z)
12 |
13 | 开启一个新的 goroutine 执行
14 |
15 | f(x, y, z)
16 |
17 | `f`,`x`,`y` 和 `z` 是当前 goroutine 中定义的,但是在新的 goroutine 中运行 `f`。
18 |
19 | goroutine 在相同的地址空间中运行,因此访问共享内存必须进行同步。[[http://golang.org/pkg/sync/][`sync`]] 提供了这种可能,不过在 Go 中并不经常用到,因为有其他的办法。(在接下来的内容中会涉及到。)
20 |
21 | .play concurrency/goroutines.go
22 |
23 | * channel
24 |
25 | channel 是有类型的管道,可以用 channel 操作符 `<-` 对其发送或者接收值。
26 |
27 | ch <- v // 将 v 送入 channel ch。
28 | v := <-ch // 从 ch 接收,并且赋值给 v。
29 |
30 | (“箭头”就是数据流的方向。)
31 |
32 | 和 map 与 slice 一样,channel 使用前必须创建:
33 |
34 | ch := make(chan int)
35 |
36 | 默认情况下,在另一端准备好之前,发送和接收都会阻塞。这使得 goroutine 可以在没有明确的锁或竞态变量的情况下进行同步。
37 |
38 | .play concurrency/channels.go
39 |
40 | * 缓冲 channel
41 |
42 | channel 可以是 _带缓冲的_。为 `make` 提供第二个参数作为缓冲长度来初始化一个缓冲 channel:
43 |
44 | ch := make(chan int, 100)
45 |
46 | 向缓冲 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。当缓冲区清空的时候接受阻塞。
47 |
48 | 修改例子使得缓冲区被填满,然后看看会发生什么。
49 |
50 | .play concurrency/buffered-channels.go
51 |
52 | * range 和 close
53 |
54 | 发送者可以 `close` 一个 channel 来表示再没有值会被发送了。接收者可以通过赋值语句的第二参数来测试 channel 是否被关闭:当没有值可以接收并且 channel 已经被关闭,那么经过
55 |
56 | v, ok := <-ch
57 |
58 | 之后 `ok` 会被设置为 `false`。
59 |
60 | 循环 `for i := range c` 会不断从 channel 接收值,直到它被关闭。
61 |
62 | *注意:* 只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic。
63 | *还要注意:* channel 与文件不同;通常情况下无需关闭它们。只有在需要告诉接收者没有更多的数据的时候才有必要进行关闭,例如中断一个 `range`。
64 |
65 | .play concurrency/range-and-close.go
66 |
67 | * select
68 |
69 | `select` 语句使得一个 goroutine 在多个通讯操作上等待。
70 |
71 | `select` 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。当多个都准备好的时候,会随机选择一个。
72 |
73 | .play concurrency/select.go
74 |
75 | * 默认选择
76 |
77 | 当 `select` 中的其他条件分支都没有准备好的时候,`default` 分支会被执行。
78 |
79 | 为了非阻塞的发送或者接收,可使用 `default` 分支:
80 |
81 | select {
82 | case i := <-c:
83 | // 使用 i
84 | default:
85 | // 从 c 读取会阻塞
86 | }
87 |
88 | .play concurrency/default-selection.go
89 |
90 | * 练习:等价二叉树
91 |
92 | 可以用多种不同的二叉树的叶子节点存储相同的数列值。例如,这里有两个二叉树保存了序列 1,1,2,3,5,8,13。
93 |
94 | .image /content/img/tree.png
95 |
96 | 用于检查两个二叉树是否存储了相同的序列的函数在多数语言中都是相当复杂的。这里将使用 Go 的并发和 channel 来编写一个简单的解法。
97 |
98 | 这个例子使用了 `tree` 包,定义了类型:
99 |
100 | type Tree struct {
101 | Left *Tree
102 | Value int
103 | Right *Tree
104 | }
105 |
106 | * 练习:等价二叉树
107 |
108 | *1.* 实现 `Walk` 函数。
109 |
110 | *2.* 测试 `Walk` 函数。
111 |
112 | 函数 `tree.New(k)` 构造了一个随机结构的二叉树,保存了值 `k`,`2k`,`3k`,...,`10k`。
113 | 创建一个新的 channel `ch` 并且对其进行步进:
114 |
115 | go Walk(tree.New(1), ch)
116 |
117 | 然后从 channel 中读取并且打印 10 个值。应当是值 1,2,3,...,10。
118 |
119 | *3.* 用 `Walk` 实现 `Same` 函数来检测是否 `t1` 和 `t2` 存储了相同的值。
120 |
121 | *4.* 测试 `Same` 函数。
122 |
123 | `Same(tree.New(1), tree.New(1))` 应当返回 true,而 `Same(tree.New(1), tree.New(2))` 应当返回 false。
124 |
125 | .play concurrency/exercise-equivalent-binary-trees.go
126 |
127 | * 练习:Web 爬虫
128 |
129 | 在这个练习中,将会使用 Go 的并发特性来并行执行 web 爬虫。
130 |
131 | 修改 `Crawl` 函数来并行的抓取 URLs,并且保证不重复。
132 |
133 | .play concurrency/exercise-web-crawler.go
134 |
135 | * Where to Go from here...
136 |
137 | #appengine: 你可以从
138 | #appengine: [[http://golang.org/doc/install/][安装 Go]] 开始,或者下载
139 | #appengine: [[http://code.google.com/appengine/downloads.html#Google_App_Engine_SDK_for_Go][Go App Engine SDK]].
140 |
141 | #appengine: 一旦安装了 Go,
142 | [[http://golang.org/doc/][Go 文档]] 是一个极好的
143 | #appengine: 应当继续阅读的内容。
144 | 开始。
145 | 它包含了参考、指南、视频等等更多资料。
146 |
147 | 了解如何组织 Go 代码并在其上工作,参阅 [[http://www.youtube.com/watch?v=XCsL89YtqCs][这个视频]],或者阅读 [[http://golang.org/doc/code.html][如何编写 Go 代码]]。
148 |
149 | 在标准库上需要帮助的话,参考 [[http://golang.org/pkg/][包手册]]。语言本身的帮助,阅读 [[http://golang.org/ref/spec][语言规范]]是件令人愉快的事情。
150 |
151 | 进一步探索 Go 的并发模型,参阅
152 | [[http://www.youtube.com/watch?v=f6kdp27TYZs][Go 并发模型]]
153 | ([[http://talks.golang.org/2012/concurrency.slide][幻灯片]])
154 | 以及
155 | [[https://www.youtube.com/watch?v=QDDwwePbDtw][深入 Go 并发模型]]
156 | ([[http://talks.golang.org/2013/advconc.slide][幻灯片]])
157 | 并且阅读
158 | [[http://golang.org/doc/codewalk/sharemem/][使用通讯共享内存]]
159 | 的代码之旅。
160 |
161 | 想要开始编写 Web 应用,参阅
162 | [[http://vimeo.com/53221558][一个简单的编程环境]]
163 | ([[http://talks.golang.org/2012/simple.slide][幻灯片]])
164 | 并且阅读
165 | [[http://golang.org/doc/articles/wiki/][编写 Web 应用]] 的指南.
166 |
167 | [[http://golang.org/doc/codewalk/functions/][GO 中的一等公民函数]] 展示了有趣的函数类型。
168 |
169 | [[http://blog.golang.org/][Go Blog]] 有着众多的关于 Go 的文章信息。
170 |
171 | [[http://www.mikespook.com/tag/golang/][mikespook 的博客]]有大量中文的关于 Go 的文章和翻译。
172 |
173 | 开源电子书 [[https://github.com/astaxie/build-web-application-with-golang][Go Web 编程]] 和 [[https://github.com/Unknwon/the-way-to-go_ZH_CN][Go入门指南]] 能够帮助你更加深入的了解和学习 Go 语言。
174 |
175 | 访问 [[http://golang.org][golang.org]] 了解更多内容。
176 |
177 | 关于本项目(中文)的任何意见、建议,请在[[https://bitbucket.org/mikespook/go-tour-zh/issues][这里]]提交 Issues。
178 |
--------------------------------------------------------------------------------
/content/moretypes.article:
--------------------------------------------------------------------------------
1 | 复杂类型: struct、slice 和 map。
2 | 学习如何基于已有类型定义新的类型:本课涵盖了结构体、数组、slice 和 map。
3 |
4 | Go 作者组
5 | http://golang.org
6 |
7 | * 指针
8 |
9 | Go 具有指针。
10 | 指针保存了变量的内存地址。
11 |
12 | 类型 `*T` 是指向类型 `T` 的值的指针。其零值是 `nil`。
13 |
14 | var p *int
15 |
16 | `&` 符号会生成一个指向其作用对象的指针。
17 |
18 | i := 42
19 | p = &i
20 |
21 | `*` 符号表示指针指向的底层的值。
22 |
23 | fmt.Println(*p) // 通过指针 p 读取 i
24 | *p = 21 // 通过指针 p 设置 i
25 |
26 | 这也就是通常所说的“间接引用”或“非直接引用”。
27 |
28 | 与 C 不同,Go 没有指针运算。
29 |
30 | .play moretypes/pointers.go
31 |
32 | * 结构体
33 |
34 | 一个结构体(`struct`)就是一个字段的集合。
35 |
36 | (而 `type` 的含义跟其字面意思相符。)
37 |
38 | .play moretypes/structs.go
39 |
40 | * 结构体字段
41 |
42 | 结构体字段使用点号来访问。
43 |
44 | .play moretypes/struct-fields.go
45 |
46 | * 结构体指针
47 |
48 | 结构体字段可以通过结构体指针来访问。
49 |
50 | 通过指针间接的访问是透明的。
51 |
52 | .play moretypes/struct-pointers.go
53 |
54 | * 结构体文法
55 |
56 | 结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。
57 |
58 | 使用 `Name:` 语法可以仅列出部分字段。(字段名的顺序无关。)
59 |
60 | 特殊的前缀 `&` 返回一个指向结构体的指针。
61 |
62 | .play moretypes/struct-literals.go
63 |
64 | * 数组
65 |
66 | 类型 `[n]T` 是一个有 `n` 个类型为 `T` 的值的数组。
67 |
68 | 表达式
69 |
70 | var a [10]int
71 |
72 | 定义变量 `a` 是一个有十个整数的数组。
73 |
74 | 数组的长度是其类型的一部分,因此数组不能改变大小。
75 | 这看起来是一个制约,但是请不要担心;
76 | Go 提供了更加便利的方式来使用数组。
77 |
78 | .play moretypes/array.go
79 |
80 | * slice
81 |
82 | 一个 slice 会指向一个序列的值,并且包含了长度信息。
83 |
84 | `[]T` 是一个元素类型为 `T` 的 slice。
85 |
86 | .play moretypes/slices.go
87 |
88 | * 对 slice 切片
89 |
90 | slice 可以重新切片,创建一个新的 slice 值指向相同的数组。
91 |
92 | 表达式
93 |
94 | s[lo:hi]
95 |
96 | 表示从 `lo` 到 `hi-1` 的 slice 元素,含两端。因此
97 |
98 | s[lo:lo]
99 |
100 | 是空的,而
101 |
102 | s[lo:lo+1]
103 |
104 | 有一个元素。
105 |
106 | .play moretypes/slicing-slices.go
107 |
108 | * 构造 slice
109 |
110 | slice 由函数 `make` 创建。这会分配一个零长度的数组并且返回一个 slice 指向这个数组:
111 |
112 | a := make([]int, 5) // len(a)=5
113 |
114 | 为了指定容量,可传递第三个参数到 `make`:
115 |
116 | b := make([]int, 0, 5) // len(b)=0, cap(b)=5
117 |
118 | b = b[:cap(b)] // len(b)=5, cap(b)=5
119 | b = b[1:] // len(b)=4, cap(b)=4
120 |
121 | .play moretypes/making-slices.go
122 |
123 | * nil slice
124 |
125 | slice 的零值是 `nil`。
126 |
127 | 一个 nil 的 slice 的长度和容量是 0。
128 |
129 | .play moretypes/nil-slices.go
130 |
131 | * 向 slice 添加元素
132 |
133 | 向 slice 添加元素是一种常见的操作,因此 Go 提供了一个内建函数 `append`。
134 | 内建函数的[[http://golang.org/pkg/builtin/#append][文档]]对 `append` 有详细介绍。
135 |
136 | func append(s []T, vs ...T) []T
137 |
138 | `append` 的第一个参数 `s` 是一个类型为 `T` 的数组,其余类型为 `T` 的值将会添加到 slice。
139 |
140 | `append` 的结果是一个包含原 slice 所有元素加上新添加的元素的 slice。
141 |
142 | 如果 `s` 的底层数组太小,而不能容纳所有值时,会分配一个更大的数组。
143 | 返回的 slice 会指向这个新分配的数组。
144 |
145 | (了解更多关于 slice 的内容,参阅文章[[http://golang.org/doc/articles/slices_usage_and_internals.html][slice:使用和内幕]]。)
146 |
147 | .play moretypes/append.go
148 |
149 | * range
150 |
151 | `for` 循环的 `range` 格式可以对 slice 或者 map 进行迭代循环。
152 |
153 | .play moretypes/range.go
154 |
155 | * range(续)
156 |
157 | 可以通过赋值给 `_` 来忽略序号和值。
158 |
159 | 如果只需要索引值,去掉“, value”的部分即可。
160 |
161 | .play moretypes/range-continued.go
162 |
163 | * 练习:slice
164 |
165 | 实现 `Pic`。它返回一个 slice 的长度 `dy`,和 slice 中每个元素的长度的 8 位无符号整数 `dx`。当执行这个程序,它会将整数转换为灰度(好吧,蓝度)图片进行展示。
166 |
167 | 图片的实现已经完成。可能用到的函数包括 `(x+y)/2`、`x*y` 和 `x^y`(使用 [[http://golang.org/pkg/math/#Pow][`math.Pow`]] 计算最后的函数)。
168 |
169 | (需要使用循环来分配 `[][]uint8` 中的每个 `[]uint8`。)
170 |
171 | (使用 `uint8(intValue)` 在类型之间进行转换。)
172 |
173 | .play moretypes/exercise-slices.go
174 |
175 | * map
176 |
177 | map 映射键到值。
178 |
179 | map 在使用之前必须用 `make` 而不是 `new` 来创建;值为 `nil` 的 map 是空的,并且不能赋值。
180 |
181 | .play moretypes/maps.go
182 |
183 | * map 的文法
184 |
185 | map 的文法跟结构体文法相似,不过必须有键名。
186 |
187 | .play moretypes/map-literals.go
188 |
189 | * map 的文法(续)
190 |
191 | 如果顶级的类型只有类型名的话,可以在文法的元素中省略键名。
192 |
193 | .play moretypes/map-literals-continued.go
194 |
195 | * 修改 map
196 |
197 | 在 map `m` 中插入或修改一个元素:
198 |
199 | m[key] = elem
200 |
201 | 获得元素:
202 |
203 | elem = m[key]
204 |
205 | 删除元素:
206 |
207 | delete(m, key)
208 |
209 | 通过双赋值检测某个键存在:
210 |
211 | elem, ok = m[key]
212 |
213 | 如果 `key` 在 `m` 中,`ok` 为 `true`。否则,`ok` 为 `false`,并且 `elem` 是 map 的元素类型的零值。
214 |
215 | 同样的,当从 map 中读取某个不存在的键时,结果是 map 的元素类型的零值。
216 |
217 | .play moretypes/mutating-maps.go
218 |
219 | * 练习:map
220 |
221 | 实现 `WordCount`。它应当返回一个含有 `s` 中每个 “词” 个数的 map。函数 `wc.Test` 针对这个函数执行一个测试用例,并输出成功还是失败。
222 |
223 | 你会发现 [[http://golang.org/pkg/strings/#Fields][strings.Fields]] 很有帮助。
224 |
225 | .play moretypes/exercise-maps.go
226 |
227 | * 函数值
228 |
229 | 函数也是值。
230 |
231 | .play moretypes/function-values.go
232 |
233 | * 函数的闭包
234 |
235 | Go 函数可以是闭包的。闭包是一个函数值,它来自函数体的外部的变量引用。
236 | 函数可以对这个引用值进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。
237 |
238 | 例如,函数 `adder` 返回一个闭包。每个闭包都被绑定到其各自的 `sum` 变量上。
239 |
240 | .play moretypes/function-closures.go
241 |
242 | * 练习:斐波纳契闭包
243 |
244 | 现在来通过函数做些有趣的事情。
245 |
246 | 实现一个 `fibonacci` 函数,返回一个函数(一个闭包)可以返回连续的斐波纳契数。
247 |
248 | .play moretypes/exercise-fibonacci-closure.go
249 |
250 | * 恭喜!
251 |
252 | 你已经完成了本课程!
253 |
254 | 你可以返回[[/list][模块]]列表看看接下来学习什么,或者继续[[javascript:click('.next-page')][后面的课程]]。
255 |
256 |
--------------------------------------------------------------------------------
/gotour/local.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // +build !appengine,!secure
6 |
7 | package main
8 |
9 | import (
10 | "flag"
11 | "fmt"
12 | "go/build"
13 | "io"
14 | "log"
15 | "net"
16 | "net/http"
17 | "net/url"
18 | "os"
19 | "os/exec"
20 | "path/filepath"
21 | "runtime"
22 | "strings"
23 | "time"
24 |
25 | "github.com/Go-zh/tools/playground/socket"
26 |
27 | // Imports so that go build/install automatically installs them.
28 | _ "github.com/Go-zh/tour/pic"
29 | _ "github.com/Go-zh/tour/tree"
30 | _ "github.com/Go-zh/tour/wc"
31 | )
32 |
33 | const (
34 | basePkg = "github.com/Go-zh/tour"
35 | socketPath = "/socket"
36 | )
37 |
38 | var (
39 | httpListen = flag.String("http", "127.0.0.1:3999", "host:port to listen on")
40 | openBrowser = flag.Bool("openbrowser", true, "open browser automatically")
41 | )
42 |
43 | var (
44 | // GOPATH containing the tour packages
45 | gopath = os.Getenv("GOPATH")
46 |
47 | httpAddr string
48 | )
49 |
50 | // isRoot reports whether path is the root directory of the tour tree.
51 | // To be the root, it must have content and template subdirectories.
52 | func isRoot(path string) bool {
53 | _, err := os.Stat(filepath.Join(path, "content", "welcome.article"))
54 | if err == nil {
55 | _, err = os.Stat(filepath.Join(path, "template", "index.tmpl"))
56 | }
57 | return err == nil
58 | }
59 |
60 | func findRoot() (string, error) {
61 | ctx := build.Default
62 | p, err := ctx.Import(basePkg, "", build.FindOnly)
63 | if err == nil && isRoot(p.Dir) {
64 | return p.Dir, nil
65 | }
66 | tourRoot := filepath.Join(runtime.GOROOT(), "misc", "tour")
67 | ctx.GOPATH = tourRoot
68 | p, err = ctx.Import(basePkg, "", build.FindOnly)
69 | if err == nil && isRoot(tourRoot) {
70 | gopath = tourRoot
71 | return tourRoot, nil
72 | }
73 | return "", fmt.Errorf("could not find go-tour content; check $GOROOT and $GOPATH")
74 | }
75 |
76 | func main() {
77 | flag.Parse()
78 |
79 | // find and serve the go tour files
80 | root, err := findRoot()
81 | if err != nil {
82 | log.Fatalf("Couldn't find tour files: %v", err)
83 | }
84 |
85 | log.Println("Serving content from", root)
86 |
87 | host, port, err := net.SplitHostPort(*httpListen)
88 | if err != nil {
89 | log.Fatal(err)
90 | }
91 | if host == "" {
92 | host = "localhost"
93 | }
94 | if host != "127.0.0.1" && host != "localhost" {
95 | log.Print(localhostWarning)
96 | }
97 | httpAddr = host + ":" + port
98 |
99 | if err := initTour(root, "SocketTransport"); err != nil {
100 | log.Fatal(err)
101 | }
102 |
103 | http.HandleFunc("/", rootHandler)
104 | http.HandleFunc("/lesson/", lessonHandler)
105 |
106 | origin := &url.URL{Scheme: "http", Host: host + ":" + port}
107 | http.Handle(socketPath, socket.NewHandler(origin))
108 |
109 | // Keep these static file handlers in sync with ../app.yaml.
110 | static := http.FileServer(http.Dir(root))
111 | http.Handle("/content/img/", static)
112 | http.Handle("/static/", static)
113 | imgDir := filepath.Join(root, "static", "img")
114 | http.Handle("/favicon.ico", http.FileServer(http.Dir(imgDir)))
115 |
116 | go func() {
117 | url := "http://" + httpAddr
118 | if waitServer(url) && *openBrowser && startBrowser(url) {
119 | log.Printf("A browser window should open. If not, please visit %s", url)
120 | } else {
121 | log.Printf("Please open your web browser and visit %s", url)
122 | }
123 | }()
124 | log.Fatal(http.ListenAndServe(httpAddr, nil))
125 | }
126 |
127 | // rootHandler returns a handler for all the requests except the ones for lessons.
128 | func rootHandler(w http.ResponseWriter, r *http.Request) {
129 | if err := renderUI(w); err != nil {
130 | log.Println(err)
131 | }
132 | }
133 |
134 | // lessonHandler handler the HTTP requests for lessons.
135 | func lessonHandler(w http.ResponseWriter, r *http.Request) {
136 | lesson := strings.TrimPrefix(r.URL.Path, "/lesson/")
137 | if err := writeLesson(lesson, w); err != nil {
138 | if err == lessonNotFound {
139 | http.NotFound(w, r)
140 | } else {
141 | log.Println(err)
142 | }
143 | }
144 | }
145 |
146 | const localhostWarning = `
147 | WARNING! WARNING! WARNING!
148 |
149 | I appear to be listening on an address that is not localhost.
150 | Anyone with access to this address and port will have access
151 | to this machine as the user running gotour.
152 |
153 | If you don't understand this message, hit Control-C to terminate this process.
154 |
155 | WARNING! WARNING! WARNING!
156 | `
157 |
158 | type response struct {
159 | Output string `json:"output"`
160 | Errors string `json:"compile_errors"`
161 | }
162 |
163 | func init() {
164 | socket.Environ = environ
165 | }
166 |
167 | // environ returns the original execution environment with GOPATH
168 | // replaced (or added) with the value of the global var gopath.
169 | func environ() (env []string) {
170 | for _, v := range os.Environ() {
171 | if !strings.HasPrefix(v, "GOPATH=") {
172 | env = append(env, v)
173 | }
174 | }
175 | env = append(env, "GOPATH="+gopath)
176 | return
177 | }
178 |
179 | // waitServer waits some time for the http Server to start
180 | // serving url. The return value reports whether it starts.
181 | func waitServer(url string) bool {
182 | tries := 20
183 | for tries > 0 {
184 | resp, err := http.Get(url)
185 | if err == nil {
186 | resp.Body.Close()
187 | return true
188 | }
189 | time.Sleep(100 * time.Millisecond)
190 | tries--
191 | }
192 | return false
193 | }
194 |
195 | // startBrowser tries to open the URL in a browser, and returns
196 | // whether it succeed.
197 | func startBrowser(url string) bool {
198 | // try to start the browser
199 | var args []string
200 | switch runtime.GOOS {
201 | case "darwin":
202 | args = []string{"open"}
203 | case "windows":
204 | args = []string{"cmd", "/c", "start"}
205 | default:
206 | args = []string{"xdg-open"}
207 | }
208 | cmd := exec.Command(args[0], append(args[1:], url)...)
209 | return cmd.Start() == nil
210 | }
211 |
212 | // prepContent for the local tour simply returns the content as-is.
213 | func prepContent(r io.Reader) io.Reader { return r }
214 |
215 | // socketAddr returns the WebSocket handler address.
216 | func socketAddr() string { return "ws://" + httpAddr + socketPath }
217 |
--------------------------------------------------------------------------------
/content/methods.article:
--------------------------------------------------------------------------------
1 | 方法和接口
2 | 本课包含了方法和接口,可以用它们来定义对象和其行为。
3 |
4 | Go 作者组
5 | http://golang.org
6 |
7 | * 方法
8 |
9 | Go 没有类。然而,仍然可以在结构体类型上定义方法。
10 |
11 | _方法接收者_ 出现在 `func` 关键字和方法名之间的参数中。
12 |
13 | .play methods/methods.go
14 |
15 | * 方法(续)
16 |
17 | 你可以对包中的 _任意_ 类型定义任意方法,而不仅仅是针对结构体。
18 |
19 | 但是,不能对来自其他包的类型或基础类型定义方法。
20 |
21 | .play methods/methods-continued.go
22 |
23 | * 接收者为指针的方法
24 |
25 | 方法可以与命名类型或命名类型的指针关联。
26 |
27 | 刚刚看到的两个 `Abs` 方法。一个是在 `*Vertex` 指针类型上,而另一个在 `MyFloat` 值类型上。
28 | 有两个原因需要使用指针接收者。首先避免在每个方法调用中拷贝值(如果值类型是大的结构体的话会更有效率)。其次,方法可以修改接收者指向的值。
29 |
30 | 尝试修改 `Abs` 的定义,同时 `Scale` 方法使用 `Vertex` 代替 `*Vertex` 作为接收者。
31 |
32 | 当 `v` 是 `Vertex` 的时候 `Scale` 方法没有任何作用。`Scale` 修改 `v`。当 `v` 是一个值(非指针),方法看到的是 `Vertex` 的副本,并且无法修改原始值。
33 |
34 | `Abs` 的工作方式是一样的。只不过,仅仅读取 `v`。所以读取的是原始值(通过指针)还是那个值的副本并没有关系。
35 |
36 | .play methods/methods-with-pointer-receivers.go
37 |
38 | * 接口
39 |
40 | 接口类型是由一组方法定义的集合。
41 |
42 | 接口类型的值可以存放实现这些方法的任何值。
43 |
44 | *注意:* 列子代码的 22 行存在一个错误。
45 | 由于 `Abs` 只定义在 `*Vertex(指针类型)` 上,
46 | 所以 `Vertex(值类型)` 不满足 `Abser`。
47 |
48 | .play methods/interfaces.go
49 |
50 | * 隐式接口
51 |
52 | 类型通过实现那些方法来实现接口。
53 | 没有显式声明的必要;所以也就没有关键字“implements“。
54 |
55 | 隐式接口解藕了实现接口的包和定义接口的包:互不依赖。
56 |
57 | 因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。
58 |
59 | [[http://golang.org/pkg/io/][包 io]] 定义了 `Reader` 和 `Writer`;其实不一定要这么做。
60 |
61 | .play methods/interfaces-are-satisfied-implicitly.go
62 |
63 | * Stringers
64 |
65 | 一个普遍存在的接口是 [[//golang.org/pkg/fmt/][`fmt`]] 包中定义的 [[//golang.org/pkg/fmt/#Stringer][`Stringer`]]。
66 |
67 | type Stringer interface {
68 | String() string
69 | }
70 |
71 | `Stringer` 是一个可以用字符串描述自己的类型。`fmt`包
72 | (还有许多其他包)使用这个来进行输出。
73 |
74 | .play methods/stringer.go
75 |
76 | * 练习:Stringers
77 |
78 | 让 `IPAddr` 类型实现 `fmt.Stringer` 以便用点分格式输出地址。
79 |
80 | 例如,`IPAddr{1,`2,`3,`4}` 应当输出 `"1.2.3.4"`。
81 |
82 | .play methods/exercise-stringer.go
83 |
84 | * 错误
85 |
86 | Go 程序使用 `error` 值来表示错误状态。
87 |
88 | 与 `fmt.Stringer` 类似,`error` 类型是一个内建接口:
89 |
90 | type error interface {
91 | Error() string
92 | }
93 |
94 | (与 `fmt.Stringer` 类似,`fmt` 包在输出时也会试图匹配 `error`。)
95 |
96 | 通常函数会返回一个 `error` 值,调用的它的代码应当判断这个错误是否等于 `nil`,
97 | 来进行错误处理。
98 |
99 | i, err := strconv.Atoi("42")
100 | if err != nil {
101 | fmt.Printf("couldn't convert number: %v\n", err)
102 | }
103 | fmt.Println("Converted integer:", i)
104 |
105 | `error` 为 nil 时表示成功;非 nil 的 `error` 表示错误。
106 |
107 | .play methods/errors.go
108 |
109 | * 练习:错误
110 |
111 | 从之前的练习中复制 `Sqrt` 函数,并修改使其返回 `error` 值。
112 |
113 | `Sqrt` 接收到一个负数时,应当返回一个非 nil 的错误值。复数同样也不被支持。
114 |
115 | 创建一个新类型
116 |
117 | type ErrNegativeSqrt float64
118 |
119 | 为其实现
120 |
121 | func (e ErrNegativeSqrt) Error() string
122 |
123 | 使其成为一个 `error`, 该方法就可以让 `ErrNegativeSqrt(-2).Error()` 返回 `"cannot Sqrt negative number: -2"`。
124 |
125 | *注意:* 在 `Error` 方法内调用 `fmt.Sprint(e)` 将会让程序陷入死循环。可以通过先转换 `e` 来避免这个问题:`fmt.Sprint(float64(e))`。请思考这是为什么呢?
126 |
127 | 修改 `Sqrt` 函数,使其接受一个负数时,返回 `ErrNegativeSqrt` 值。
128 |
129 | .play methods/exercise-errors.go
130 |
131 | * Readers
132 |
133 | `io` 包指定了 `io.Reader` 接口,
134 | 它表示从数据流结尾读取。
135 |
136 | Go 标准库包含了这个接口的[[http://golang.org/search?q=Read#Global][许多实现]],
137 | 包括文件、网络连接、压缩、加密等等。
138 |
139 | `io.Reader` 接口有一个 `Read` 方法:
140 |
141 | func (T) Read(b []byte) (n int, err error)
142 |
143 | `Read` 用数据填充指定的字节 slice,并且返回填充的字节数和错误信息。
144 | 在遇到数据流结尾时,返回 `io.EOF` 错误。
145 |
146 | 例子代码创建了一个
147 | [[//golang.org/pkg/strings/#Reader][`strings.Reader`]]。
148 | 并且以每次 8 字节的速度读取它的输出。
149 |
150 | .play methods/reader.go
151 |
152 | * 练习:Reader
153 |
154 | 实现一个 `Reader` 类型,它不断生成 ASCII 字符 `'A'` 的流。
155 |
156 | .play methods/exercise-reader.go
157 |
158 | * 练习:rot13Reader
159 |
160 | 一个常见模式是 [[http://golang.org/pkg/io/#Reader][io.Reader]]
161 | 包裹另一个 `io.Reader`,然后通过某种形式修改数据流。
162 |
163 | 例如,[[http://golang.org/pkg/compress/gzip/#NewReader][gzip.NewReader]]
164 | 函数接受 `io.Reader`(压缩的数据流)并且返回同样实现了 `io.Reader` 的 `*gzip.Reader`(解压缩后的数据流)。
165 |
166 | 编写一个实现了 `io.Reader` 的 `rot13Reader`,
167 | 并从一个 `io.Reader` 读取,
168 | 利用 [[http://en.wikipedia.org/wiki/ROT13][rot13]] 代换密码对数据流进行修改。
169 |
170 | 已经帮你构造了 `rot13Reader` 类型。
171 | 通过实现 `Read` 方法使其匹配 `io.Reader`。
172 |
173 | .play methods/exercise-rot-reader.go
174 |
175 | * Web 服务器
176 |
177 | [[http://golang.org/pkg/net/http/][包 http]] 通过任何实现了 `http.Handler` 的值来响应 HTTP 请求:
178 |
179 | package http
180 |
181 | type Handler interface {
182 | ServeHTTP(w ResponseWriter, r *Request)
183 | }
184 |
185 | 在这个例子中,类型 `Hello` 实现了 `http.Handler`。
186 |
187 | 访问 [[http://localhost:4000/][http://localhost:4000/]] 会看到来自程序的问候。
188 |
189 | #appengine: *注意:* 这个例子无法在基于 web 的指南用户界面运行。为了尝试编写
190 | #appengine: web 服务器,可能需要[[http://golang.org/doc/install/][安装 Go]]。
191 |
192 | .play methods/web-servers.go
193 |
194 | * 练习:HTTP 处理
195 |
196 | 实现下面的类型,并在其上定义 ServeHTTP 方法。在 web 服务器中注册它们来处理指定的路径。
197 |
198 | type String string
199 |
200 | type Struct struct {
201 | Greeting string
202 | Punct string
203 | Who string
204 | }
205 |
206 | 例如,可以使用如下方式注册处理方法:
207 |
208 | http.Handle("/string", String("I'm a frayed knot."))
209 | http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
210 |
211 | #appengine: *注意:* 这个例子无法在基于 web 的用户界面下运行。
212 | #appengine: 为了尝试编写 web 服务,你可能需要
213 | #appengine: [[http://golang.org/doc/install/][安装 Go]]。
214 |
215 | .play methods/exercise-http-handlers.go
216 |
217 | * 图片
218 |
219 | [[http://golang.org/pkg/image/#Image][Package image]] 定义了 `Image` 接口:
220 |
221 | package image
222 |
223 | type Image interface {
224 | ColorModel() color.Model
225 | Bounds() Rectangle
226 | At(x, y int) color.Color
227 | }
228 |
229 | *注意*:`Bounds` 方法的 `Rectangle` 返回值实际上是一个
230 | [[http://golang.org/pkg/image/#Rectangle][`image.Rectangle`]],
231 | 其定义在 `image` 包中。
232 |
233 | (参阅[[http://golang.org/pkg/image/#Image][文档]]了解全部信息。)
234 |
235 | `color.Color` 和 `color.Model` 也是接口,但是通常因为直接使用预定义的实现 `image.RGBA` 和 `image.RGBAModel` 而被忽视了。这些接口和类型由[[http://golang.org/pkg/image/color/][image/color 包]]定义。
236 |
237 | .play methods/images.go
238 |
239 | * 练习:图片
240 |
241 | 还记得之前编写的图片生成器吗?现在来另外编写一个,不过这次将会返回 `image.Image` 来代替 slice 的数据。
242 |
243 | 自定义的 `Image` 类型,要实现[[http://golang.org/pkg/image/#Image][必要的方法]],并且调用 `pic.ShowImage`。
244 |
245 | `Bounds` 应当返回一个 `image.Rectangle`,例如 `image.Rect(0, 0, w, h)`。
246 |
247 | `ColorModel` 应当返回 `color.RGBAModel`。
248 |
249 | `At` 应当返回一个颜色;在这个例子里,在最后一个图片生成器的值 `v` 匹配 `color.RGBA{v, v, 255, 255}`。
250 |
251 | .play methods/exercise-images.go
252 |
253 | * 恭喜!
254 |
255 | 你已经完成了本课程!
256 |
257 | 你可以返回[[/list][模块]]列表看看接下来学习什么,或者继续[[javascript:click('.next-page')][后面的课程]]。
258 |
--------------------------------------------------------------------------------
/static/js/directives.js:
--------------------------------------------------------------------------------
1 | /* Copyright 2012 The Go Authors. All rights reserved.
2 | * Use of this source code is governed by a BSD-style
3 | * license that can be found in the LICENSE file.
4 | */
5 | 'use strict';
6 |
7 | /* Directives */
8 |
9 | angular.module('tour.directives', []).
10 |
11 | // onpageup executes the given expression when Page Up is released.
12 | directive('onpageup', function() {
13 | return function(scope, elm, attrs) {
14 | elm.attr('tabindex', 0);
15 | elm.keyup(function(evt) {
16 | var key = evt.key || evt.keyCode;
17 | if (key == 33) {
18 | scope.$apply(attrs.onpageup);
19 | evt.preventDefault();
20 | }
21 | });
22 | };
23 | }).
24 |
25 | // onpagedown executes the given expression when Page Down is released.
26 | directive('onpagedown', function() {
27 | return function(scope, elm, attrs) {
28 | elm.attr('tabindex', 0);
29 | elm.keyup(function(evt) {
30 | var key = evt.key || evt.keyCode;
31 | if (key == 34) {
32 | scope.$apply(attrs.onpagedown);
33 | evt.preventDefault();
34 | }
35 | });
36 | };
37 | }).
38 |
39 | // autofocus sets the focus on the given element when the condition is true.
40 | directive('autofocus', function() {
41 | return function(scope, elm, attrs) {
42 | elm.attr('tabindex', 0);
43 | scope.$watch(function() {
44 | return scope.$eval(attrs.autofocus);
45 | }, function(val) {
46 | if (val === true) $(elm).focus();
47 | });
48 | };
49 | }).
50 |
51 | // syntax-checkbox activates and deactivates
52 | directive('syntaxCheckbox', ['editor',
53 | function(editor) {
54 | return function(scope, elm) {
55 | elm.click(function() {
56 | editor.toggleSyntax();
57 | scope.$digest();
58 | });
59 | scope.editor = editor;
60 | };
61 | }
62 | ]).
63 |
64 | // verticalSlide creates a sliding separator between the left and right elements.
65 | // e.g.:
66 | //
67 | //
68 | //
69 | directive('verticalSlide', ['editor',
70 | function(editor) {
71 | return function(scope, elm, attrs) {
72 | var moveTo = function(x) {
73 | if (x < 0) {
74 | x = 0;
75 | }
76 | if (x > $(window).width()) {
77 | x = $(window).width();
78 | }
79 | elm.css('left', x);
80 | $(attrs.left).width(x);
81 | $(attrs.right).offset({
82 | left: x
83 | });
84 | editor.x = x;
85 | };
86 |
87 | elm.draggable({
88 | axis: 'x',
89 | drag: function(event) {
90 | moveTo(event.clientX);
91 | return true;
92 | },
93 | containment: 'parent',
94 | });
95 |
96 | if (editor.x !== undefined) {
97 | moveTo(editor.x);
98 | }
99 | };
100 | }
101 | ]).
102 |
103 | // horizontalSlide creates a sliding separator between the top and bottom elements.
104 | //
105 | //
106 | // Some content
107 | directive('horizontalSlide', ['editor',
108 | function(editor) {
109 | return function(scope, elm, attrs) {
110 | var moveTo = function(y) {
111 | var top = $(attrs.top).offset().top;
112 | if (y < top) {
113 | y = top;
114 | }
115 | elm.css('top', y - top);
116 | $(attrs.top).height(y - top);
117 | $(attrs.bottom).offset({
118 | top: y,
119 | height: 0
120 | });
121 | editor.y = y;
122 | };
123 | elm.draggable({
124 | axis: 'y',
125 | drag: function(event) {
126 | moveTo(event.clientY);
127 | return true;
128 | },
129 | containment: 'parent',
130 | });
131 |
132 | if (editor.y !== undefined) {
133 | moveTo(editor.y);
134 | }
135 | };
136 | }
137 | ]).
138 |
139 | directive('tableOfContentsButton', function() {
140 | var speed = 250;
141 | return {
142 | restrict: 'A',
143 | templateUrl: '/static/partials/toc-button.html',
144 | link: function(scope, elm, attrs) {
145 | elm.on('click', function() {
146 | var toc = $(attrs.tableOfContentsButton);
147 | // hide all non active lessons before displaying the toc.
148 | var visible = toc.css('display') != 'none';
149 | if (!visible) {
150 | toc.find('.toc-lesson:not(.active) .toc-page').hide();
151 | toc.find('.toc-lesson.active .toc-page').show();
152 | }
153 | toc.toggle('slide', {
154 | direction: 'right'
155 | }, speed);
156 |
157 | // if fullscreen hide the rest of the content when showing the atoc.
158 | var fullScreen = toc.width() == $(window).width();
159 | if (fullScreen) $('#editor-container')[visible ? 'show' : 'hide']();
160 | });
161 | }
162 | };
163 | }).
164 |
165 | // side bar with dynamic table of contents
166 | directive('tableOfContents', ['$routeParams', 'toc',
167 | function($routeParams, toc) {
168 | var speed = 250;
169 | return {
170 | restrict: 'A',
171 | templateUrl: '/static/partials/toc.html',
172 | link: function(scope, elm) {
173 | scope.toc = toc;
174 | scope.params = $routeParams;
175 |
176 | scope.toggleLesson = function(id) {
177 | var l = $('#toc-l-' + id + ' .toc-page');
178 | l[l.css('display') == 'none' ? 'slideDown' : 'slideUp']();
179 | };
180 |
181 | scope.$watch(function() {
182 | return scope.params.lessonId + scope.params.lessonId;
183 | }, function() {
184 | $('.toc-lesson:not(#toc-l-' + scope.params.lessonId + ') .toc-page').slideUp(speed);
185 | });
186 |
187 | scope.hideTOC = function(fullScreenOnly) {
188 | var fullScreen = elm.find('.toc').width() == $(window).width();
189 | if (fullScreenOnly && !fullScreen) {
190 | return;
191 | }
192 | $('.toc').toggle('slide', {
193 | direction: 'right'
194 | }, speed);
195 | };
196 | }
197 | };
198 | }
199 | ]);
--------------------------------------------------------------------------------
/static/lib/codemirror/lib/codemirror.css:
--------------------------------------------------------------------------------
1 | /* BASICS */
2 |
3 | .CodeMirror {
4 | /* Set height, width, borders, and global font properties here */
5 | font-family: monospace;
6 | height: 300px;
7 | }
8 | .CodeMirror-scroll {
9 | /* Set scrolling behaviour here */
10 | overflow: auto;
11 | }
12 |
13 | /* PADDING */
14 |
15 | .CodeMirror-lines {
16 | padding: 4px 0; /* Vertical padding around content */
17 | }
18 | .CodeMirror pre {
19 | padding: 0 4px; /* Horizontal padding of content */
20 | }
21 |
22 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
23 | background-color: white; /* The little square between H and V scrollbars */
24 | }
25 |
26 | /* GUTTER */
27 |
28 | .CodeMirror-gutters {
29 | border-right: 1px solid #ddd;
30 | background-color: #f7f7f7;
31 | white-space: nowrap;
32 | }
33 | .CodeMirror-linenumbers {}
34 | .CodeMirror-linenumber {
35 | padding: 0 3px 0 5px;
36 | min-width: 20px;
37 | text-align: right;
38 | color: #999;
39 | }
40 |
41 | /* CURSOR */
42 |
43 | .CodeMirror div.CodeMirror-cursor {
44 | border-left: 1px solid black;
45 | z-index: 3;
46 | }
47 | /* Shown when moving in bi-directional text */
48 | .CodeMirror div.CodeMirror-secondarycursor {
49 | border-left: 1px solid silver;
50 | }
51 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
52 | width: auto;
53 | border: 0;
54 | background: #7e7;
55 | z-index: 1;
56 | }
57 | /* Can style cursor different in overwrite (non-insert) mode */
58 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
59 |
60 | .cm-tab { display: inline-block; }
61 |
62 | /* DEFAULT THEME */
63 |
64 | .cm-s-default .cm-keyword {color: #708;}
65 | .cm-s-default .cm-atom {color: #219;}
66 | .cm-s-default .cm-number {color: #164;}
67 | .cm-s-default .cm-def {color: #00f;}
68 | .cm-s-default .cm-variable {color: black;}
69 | .cm-s-default .cm-variable-2 {color: #05a;}
70 | .cm-s-default .cm-variable-3 {color: #085;}
71 | .cm-s-default .cm-property {color: black;}
72 | .cm-s-default .cm-operator {color: black;}
73 | .cm-s-default .cm-comment {color: #a50;}
74 | .cm-s-default .cm-string {color: #a11;}
75 | .cm-s-default .cm-string-2 {color: #f50;}
76 | .cm-s-default .cm-meta {color: #555;}
77 | .cm-s-default .cm-error {color: #f00;}
78 | .cm-s-default .cm-qualifier {color: #555;}
79 | .cm-s-default .cm-builtin {color: #30a;}
80 | .cm-s-default .cm-bracket {color: #997;}
81 | .cm-s-default .cm-tag {color: #170;}
82 | .cm-s-default .cm-attribute {color: #00c;}
83 | .cm-s-default .cm-header {color: blue;}
84 | .cm-s-default .cm-quote {color: #090;}
85 | .cm-s-default .cm-hr {color: #999;}
86 | .cm-s-default .cm-link {color: #00c;}
87 |
88 | .cm-negative {color: #d44;}
89 | .cm-positive {color: #292;}
90 | .cm-header, .cm-strong {font-weight: bold;}
91 | .cm-em {font-style: italic;}
92 | .cm-link {text-decoration: underline;}
93 |
94 | .cm-invalidchar {color: #f00;}
95 |
96 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
97 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
98 | .CodeMirror-activeline-background {background: #e8f2ff;}
99 |
100 | /* STOP */
101 |
102 | /* The rest of this file contains styles related to the mechanics of
103 | the editor. You probably shouldn't touch them. */
104 |
105 | .CodeMirror {
106 | line-height: 1;
107 | position: relative;
108 | overflow: hidden;
109 | background: white;
110 | color: black;
111 | }
112 |
113 | .CodeMirror-scroll {
114 | /* 30px is the magic margin used to hide the element's real scrollbars */
115 | /* See overflow: hidden in .CodeMirror */
116 | margin-bottom: -30px; margin-right: -30px;
117 | padding-bottom: 30px; padding-right: 30px;
118 | height: 100%;
119 | outline: none; /* Prevent dragging from highlighting the element */
120 | position: relative;
121 | }
122 | .CodeMirror-sizer {
123 | position: relative;
124 | }
125 |
126 | /* The fake, visible scrollbars. Used to force redraw during scrolling
127 | before actuall scrolling happens, thus preventing shaking and
128 | flickering artifacts. */
129 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
130 | position: absolute;
131 | z-index: 6;
132 | display: none;
133 | }
134 | .CodeMirror-vscrollbar {
135 | right: 0; top: 0;
136 | overflow-x: hidden;
137 | overflow-y: scroll;
138 | }
139 | .CodeMirror-hscrollbar {
140 | bottom: 0; left: 0;
141 | overflow-y: hidden;
142 | overflow-x: scroll;
143 | }
144 | .CodeMirror-scrollbar-filler {
145 | right: 0; bottom: 0;
146 | }
147 | .CodeMirror-gutter-filler {
148 | left: 0; bottom: 0;
149 | }
150 |
151 | .CodeMirror-gutters {
152 | position: absolute; left: 0; top: 0;
153 | padding-bottom: 30px;
154 | z-index: 3;
155 | }
156 | .CodeMirror-gutter {
157 | white-space: normal;
158 | height: 100%;
159 | padding-bottom: 30px;
160 | margin-bottom: -32px;
161 | display: inline-block;
162 | /* Hack to make IE7 behave */
163 | *zoom:1;
164 | *display:inline;
165 | }
166 | .CodeMirror-gutter-elt {
167 | position: absolute;
168 | cursor: default;
169 | z-index: 4;
170 | }
171 |
172 | .CodeMirror-lines {
173 | cursor: text;
174 | }
175 | .CodeMirror pre {
176 | /* Reset some styles that the rest of the page might have set */
177 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
178 | border-width: 0;
179 | background: transparent;
180 | font-family: inherit;
181 | font-size: inherit;
182 | margin: 0;
183 | white-space: pre;
184 | word-wrap: normal;
185 | line-height: inherit;
186 | color: inherit;
187 | z-index: 2;
188 | position: relative;
189 | overflow: visible;
190 | }
191 | .CodeMirror-wrap pre {
192 | word-wrap: break-word;
193 | white-space: pre-wrap;
194 | word-break: normal;
195 | }
196 | .CodeMirror-code pre {
197 | border-right: 30px solid transparent;
198 | width: -webkit-fit-content;
199 | width: -moz-fit-content;
200 | width: fit-content;
201 | }
202 | .CodeMirror-wrap .CodeMirror-code pre {
203 | border-right: none;
204 | width: auto;
205 | }
206 | .CodeMirror-linebackground {
207 | position: absolute;
208 | left: 0; right: 0; top: 0; bottom: 0;
209 | z-index: 0;
210 | }
211 |
212 | .CodeMirror-linewidget {
213 | position: relative;
214 | z-index: 2;
215 | overflow: auto;
216 | }
217 |
218 | .CodeMirror-widget {
219 | }
220 |
221 | .CodeMirror-wrap .CodeMirror-scroll {
222 | overflow-x: hidden;
223 | }
224 |
225 | .CodeMirror-measure {
226 | position: absolute;
227 | width: 100%; height: 0px;
228 | overflow: hidden;
229 | visibility: hidden;
230 | }
231 | .CodeMirror-measure pre { position: static; }
232 |
233 | .CodeMirror div.CodeMirror-cursor {
234 | position: absolute;
235 | visibility: hidden;
236 | border-right: none;
237 | width: 0;
238 | }
239 | .CodeMirror-focused div.CodeMirror-cursor {
240 | visibility: visible;
241 | }
242 |
243 | .CodeMirror-selected { background: #d9d9d9; }
244 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
245 |
246 | .cm-searching {
247 | background: #ffa;
248 | background: rgba(255, 255, 0, .4);
249 | }
250 |
251 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */
252 | .CodeMirror span { *vertical-align: text-bottom; }
253 |
254 | @media print {
255 | /* Hide the cursor when printing */
256 | .CodeMirror div.CodeMirror-cursor {
257 | visibility: hidden;
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/static/lib/codemirror/mode/go/go.js:
--------------------------------------------------------------------------------
1 | function goMode(commentsOnly) {
2 | return function(config) {
3 | var indentUnit = config.indentUnit;
4 |
5 | var keywords = {
6 | "break":true, "case":true, "chan":true, "const":true, "continue":true,
7 | "default":true, "defer":true, "else":true, "fallthrough":true, "for":true,
8 | "func":true, "go":true, "goto":true, "if":true, "import":true,
9 | "interface":true, "map":true, "package":true, "range":true, "return":true,
10 | "select":true, "struct":true, "switch":true, "type":true, "var":true,
11 | "bool":true, "byte":true, "complex64":true, "complex128":true, "error":true,
12 | "float32":true, "float64":true, "int8":true, "int16":true, "int32":true,
13 | "int64":true, "rune":true, "string":true, "uint8":true, "uint16":true, "uint32":true,
14 | "uint64":true, "int":true, "uint":true, "uintptr":true
15 | };
16 |
17 | var atoms = {
18 | "true":true, "false":true, "iota":true, "nil":true, "append":true,
19 | "cap":true, "close":true, "complex":true, "copy":true, "delete":true, "imag":true,
20 | "len":true, "make":true, "new":true, "panic":true, "print":true,
21 | "println":true, "real":true, "recover":true
22 | };
23 |
24 | var isOperatorChar = /[+\-*&^%:=<>!|\/]/;
25 |
26 | var curPunc;
27 |
28 | function tokenBase(stream, state) {
29 | var ch = stream.next();
30 | if (ch == '"' || ch == "'" || ch == "`") {
31 | state.tokenize = tokenString(ch);
32 | return state.tokenize(stream, state);
33 | }
34 | if (/[\d\.]/.test(ch)) {
35 | if (ch == ".") {
36 | stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/);
37 | } else if (ch == "0") {
38 | stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/);
39 | } else {
40 | stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/);
41 | }
42 | return "number";
43 | }
44 | if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
45 | curPunc = ch;
46 | return null;
47 | }
48 | if (ch == "/") {
49 | if (stream.eat("*")) {
50 | state.tokenize = tokenComment;
51 | return tokenComment(stream, state);
52 | }
53 | if (stream.eat("/")) {
54 | stream.skipToEnd();
55 | return "comment";
56 | }
57 | }
58 | if (isOperatorChar.test(ch)) {
59 | stream.eatWhile(isOperatorChar);
60 | return "operator";
61 | }
62 | stream.eatWhile(/[\w\$_]/);
63 | var cur = stream.current();
64 | if (keywords.propertyIsEnumerable(cur)) {
65 | if (cur == "case" || cur == "default") curPunc = "case";
66 | return "keyword";
67 | }
68 | if (atoms.propertyIsEnumerable(cur)) return "atom";
69 | return "variable";
70 | }
71 |
72 | function tokenString(quote) {
73 | return function(stream, state) {
74 | var escaped = false,
75 | next, end = false;
76 | while ((next = stream.next()) != null) {
77 | if (next == quote && !escaped) {
78 | end = true;
79 | break;
80 | }
81 | escaped = !escaped && next == "\\";
82 | }
83 | if (end || !(escaped || quote == "`"))
84 | state.tokenize = tokenBase;
85 | return "string";
86 | };
87 | }
88 |
89 | function tokenComment(stream, state) {
90 | var maybeEnd = false,
91 | ch;
92 | while (ch = stream.next()) {
93 | if (ch == "/" && maybeEnd) {
94 | state.tokenize = tokenBase;
95 | break;
96 | }
97 | maybeEnd = (ch == "*");
98 | }
99 | return "comment";
100 | }
101 |
102 | function Context(indented, column, type, align, prev) {
103 | this.indented = indented;
104 | this.column = column;
105 | this.type = type;
106 | this.align = align;
107 | this.prev = prev;
108 | }
109 |
110 | function pushContext(state, col, type) {
111 | return state.context = new Context(state.indented, col, type, null, state.context);
112 | }
113 |
114 | function popContext(state) {
115 | var t = state.context.type;
116 | if (t == ")" || t == "]" || t == "}")
117 | state.indented = state.context.indented;
118 | return state.context = state.context.prev;
119 | }
120 |
121 | // Interface
122 |
123 | return {
124 | startState: function(basecolumn) {
125 | return {
126 | tokenize: null,
127 | context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
128 | indented: 0,
129 | startOfLine: true
130 | };
131 | },
132 |
133 | token: function(stream, state) {
134 | var ctx = state.context;
135 | if (stream.sol()) {
136 | if (ctx.align == null) ctx.align = false;
137 | state.indented = stream.indentation();
138 | state.startOfLine = true;
139 | if (ctx.type == "case") ctx.type = "}";
140 | }
141 | if (stream.eatSpace()) return null;
142 | curPunc = null;
143 | var style = (state.tokenize || tokenBase)(stream, state);
144 | if (style == "comment") return style;
145 | if (ctx.align == null) ctx.align = true;
146 |
147 | if (curPunc == "{") pushContext(state, stream.column(), "}");
148 | else if (curPunc == "[") pushContext(state, stream.column(), "]");
149 | else if (curPunc == "(") pushContext(state, stream.column(), ")");
150 | else if (curPunc == "case") ctx.type = "case";
151 | else if (curPunc == "}" && ctx.type == "}") ctx = popContext(state);
152 | else if (curPunc == ctx.type) popContext(state);
153 | state.startOfLine = false;
154 | if (commentsOnly) return "";
155 | return style;
156 | },
157 |
158 | indent: function(state, textAfter) {
159 | if (state.tokenize != tokenBase && state.tokenize != null) return 0;
160 | var ctx = state.context,
161 | firstChar = textAfter && textAfter.charAt(0);
162 | if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) {
163 | state.context.type = "}";
164 | return ctx.indented;
165 | }
166 | var closing = firstChar == ctx.type;
167 | if (ctx.align) return ctx.column + (closing ? 0 : 1);
168 | else return ctx.indented + (closing ? 0 : indentUnit);
169 | },
170 |
171 | electricChars: "{}:",
172 | blockCommentStart: "/*",
173 | blockCommentEnd: "*/",
174 | lineComment: "//"
175 | };
176 | }
177 | }
178 |
179 | CodeMirror.defineMode("go", goMode(false));
180 | CodeMirror.defineMIME("text/x-go", "go");
181 |
182 | CodeMirror.defineMode("goComments", goMode(true));
183 | CodeMirror.defineMIME("text/x-go-comments", "goComments");
184 |
--------------------------------------------------------------------------------
/static/js/services.js:
--------------------------------------------------------------------------------
1 | /* Copyright 2012 The Go Authors. All rights reserved.
2 | * Use of this source code is governed by a BSD-style
3 | * license that can be found in the LICENSE file.
4 | */
5 | 'use strict';
6 |
7 | /* Services */
8 |
9 | angular.module('tour.services', []).
10 |
11 | // Google Analytics
12 | factory('analytics', ['$window',
13 | function(win) {
14 | var track = win.trackPageview || (function() {});
15 | return {
16 | trackView: track
17 | };
18 | }
19 | ]).
20 |
21 | // Internationalization
22 | factory('i18n', ['translation',
23 | function(translation) {
24 | return {
25 | l: function(key) {
26 | if (translation[key]) return translation[key];
27 | return '(no translation for ' + key + ')';
28 | }
29 | };
30 | }
31 | ]).
32 |
33 | // Running code
34 | factory('run', ['$window', 'editor',
35 | function(win, editor) {
36 | var writeInterceptor = function(writer) {
37 | return function(write) {
38 | if (write.Kind == 'stderr') {
39 | var lines = write.Body.split('\n');
40 | for (var i in lines) {
41 | var match = lines[i].match(/.*\.go:([0-9]+): ([^\n]*)/);
42 | if (match !== null) {
43 | editor.highlight(match[1], match[2]);
44 | }
45 | }
46 | }
47 | writer(write);
48 | };
49 | };
50 | return function(code, output, options) {
51 | // PlaygroundOutput is defined in playground.js which is prepended
52 | // to the generated script.js in gotour/tour.go.
53 | // The next line removes the jshint warning.
54 | // global PlaygroundOutput
55 | win.transport.Run(code, writeInterceptor(PlaygroundOutput(output)), options);
56 | };
57 | }
58 | ]).
59 |
60 | // Formatting code
61 | factory('fmt', ['$http',
62 | function($http) {
63 | return function(body) {
64 | var params = $.param({
65 | 'body': body
66 | });
67 | var headers = {
68 | 'Content-Type': 'application/x-www-form-urlencoded'
69 | };
70 | return $http.post('/fmt', params, {
71 | headers: headers
72 | });
73 | };
74 | }
75 | ]).
76 |
77 | // Local storage, persistent to page refreshing.
78 | factory('storage', ['$window',
79 | function(win) {
80 | try {
81 | // This will raise an exception if cookies are disabled.
82 | win.localStorage = win.localStorage;
83 | return {
84 | get: function(key) {
85 | return win.localStorage.getItem(key);
86 | },
87 | set: function(key, val) {
88 | win.localStorage.setItem(key, val);
89 | }
90 | };
91 | } catch (e) {
92 | return {
93 | get: function() {
94 | return null;
95 | },
96 | set: function() {}
97 | };
98 | }
99 | }
100 | ]).
101 |
102 | // Editor context service, kept through the whole app.
103 | factory('editor', ['$window', 'storage',
104 | function(win, storage) {
105 | var ctx = {
106 | syntax: storage.get('syntax') === 'true',
107 | toggleSyntax: function() {
108 | ctx.syntax = !ctx.syntax;
109 | storage.set('syntax', ctx.syntax);
110 | ctx.paint();
111 | },
112 | paint: function() {
113 | var mode = ctx.syntax && 'text/x-go' || 'text/x-go-comment';
114 | // Wait for codemirror to start.
115 | var set = function() {
116 | if ($('.CodeMirror').length > 0) {
117 | var cm = $('.CodeMirror')[0].CodeMirror;
118 | if (cm.getOption('mode') == mode) {
119 | cm.refresh();
120 | return;
121 | }
122 | cm.setOption('mode', mode);
123 | }
124 | win.setTimeout(set, 10);
125 | };
126 | set();
127 | },
128 | highlight: function(line, message) {
129 | $('.CodeMirror-code > div:nth-child(' + line + ')')
130 | .addClass('line-error').attr('title', message);
131 | },
132 | onChange: function() {
133 | $('.line-error').removeClass('line-error').attr('title', null);
134 | }
135 | };
136 | // Set in the window so the onChange function in the codemirror config
137 | // can call it.
138 | win.codeChanged = ctx.onChange;
139 | return ctx;
140 | }
141 | ]).
142 |
143 | // Table of contents management and navigation
144 | factory('toc', ['$http', '$q', '$log', 'tableOfContents', 'storage',
145 | function($http, $q, $log, tableOfContents, storage) {
146 | var modules = tableOfContents;
147 |
148 | var lessons = {};
149 |
150 | var prevLesson = function(id) {
151 | var mod = lessons[id].module;
152 | var idx = mod.lessons.indexOf(id);
153 | if (idx < 0) return '';
154 | if (idx > 0) return mod.lessons[idx - 1];
155 |
156 | idx = modules.indexOf(mod);
157 | if (idx <= 0) return '';
158 | mod = modules[idx - 1];
159 | return mod.lessons[mod.lessons.length - 1];
160 | };
161 |
162 | var nextLesson = function(id) {
163 | var mod = lessons[id].module;
164 | var idx = mod.lessons.indexOf(id);
165 | if (idx < 0) return '';
166 | if (idx + 1 < mod.lessons.length) return mod.lessons[idx + 1];
167 |
168 | idx = modules.indexOf(mod);
169 | if (idx < 0 || modules.length <= idx + 1) return '';
170 | mod = modules[idx + 1];
171 | return mod.lessons[0];
172 | };
173 |
174 | $http.get('/lesson/').then(
175 | function(data) {
176 | lessons = data.data;
177 | for (var m = 0; m < modules.length; m++) {
178 | var module = modules[m];
179 | module.lesson = {};
180 | for (var l = 0; l < modules[m].lessons.length; l++) {
181 | var lessonName = module.lessons[l];
182 | var lesson = lessons[lessonName];
183 | lesson.module = module;
184 | module.lesson[lessonName] = lesson;
185 |
186 | // replace file contents with locally stored copies.
187 | for (var p = 0; p < lesson.Pages.length; p++) {
188 | var page = lesson.Pages[p];
189 | for (var f = 0; f < page.Files.length; f++) {
190 | page.Files[f].OrigContent = page.Files[f].Content;
191 | var val = storage.get(lessonName + '.' + p + '.' + f);
192 | if (val !== null) {
193 | page.Files[f].Content = val;
194 | }
195 | }
196 | }
197 | }
198 | }
199 | moduleQ.resolve(modules);
200 | lessonQ.resolve(lessons);
201 | },
202 | function(error) {
203 | $log.error('error loading lessons : ', error);
204 | moduleQ.reject(error);
205 | lessonQ.reject(error);
206 | }
207 | );
208 |
209 | var moduleQ = $q.defer();
210 | var lessonQ = $q.defer();
211 |
212 | return {
213 | modules: moduleQ.promise,
214 | lessons: lessonQ.promise,
215 | prevLesson: prevLesson,
216 | nextLesson: nextLesson
217 | };
218 | }
219 | ]);
220 |
--------------------------------------------------------------------------------
/gotour/tour.go:
--------------------------------------------------------------------------------
1 | // Copyright 2013 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package main
6 |
7 | import (
8 | "bytes"
9 | "compress/gzip"
10 | "encoding/json"
11 | "fmt"
12 | "html/template"
13 | "io"
14 | "io/ioutil"
15 | "net/http"
16 | "os"
17 | "path/filepath"
18 | "strings"
19 | "time"
20 |
21 | "github.com/Go-zh/tools/godoc/static"
22 | "github.com/Go-zh/tools/present"
23 | )
24 |
25 | var (
26 | uiContent []byte
27 | lessons = make(map[string][]byte)
28 | lessonNotFound = fmt.Errorf("lesson not found")
29 | )
30 |
31 | // initTour loads tour.article and the relevant HTML templates from the given
32 | // tour root, and renders the template to the tourContent global variable.
33 | func initTour(root, transport string) error {
34 | // Make sure playground is enabled before rendering.
35 | present.PlayEnabled = true
36 |
37 | // Set up templates.
38 | action := filepath.Join(root, "template", "action.tmpl")
39 | tmpl, err := present.Template().ParseFiles(action)
40 | if err != nil {
41 | return fmt.Errorf("parse templates: %v", err)
42 | }
43 |
44 | // Init lessons.
45 | contentPath := filepath.Join(root, "content")
46 | if err := initLessons(tmpl, contentPath); err != nil {
47 | return fmt.Errorf("init lessons: %v", err)
48 | }
49 |
50 | // Init UI
51 | index := filepath.Join(root, "template", "index.tmpl")
52 | ui, err := template.ParseFiles(index)
53 | if err != nil {
54 | return fmt.Errorf("parse index.tmpl: %v", err)
55 | }
56 | buf := new(bytes.Buffer)
57 |
58 | data := struct {
59 | SocketAddr string
60 | Transport template.JS
61 | }{socketAddr(), template.JS(transport)}
62 |
63 | if err := ui.Execute(buf, data); err != nil {
64 | return fmt.Errorf("render UI: %v", err)
65 | }
66 | uiContent = buf.Bytes()
67 |
68 | return initScript(root)
69 | }
70 |
71 | // initLessonss finds all the lessons in the passed directory, renders them,
72 | // using the given template and saves the content in the lessons map.
73 | func initLessons(tmpl *template.Template, content string) error {
74 | dir, err := os.Open(content)
75 | if err != nil {
76 | return err
77 | }
78 | files, err := dir.Readdirnames(0)
79 | if err != nil {
80 | return err
81 | }
82 | for _, f := range files {
83 | if filepath.Ext(f) != ".article" {
84 | continue
85 | }
86 | content, err := parseLesson(tmpl, filepath.Join(content, f))
87 | if err != nil {
88 | return fmt.Errorf("parsing %v: %v", f, err)
89 | }
90 | name := strings.TrimSuffix(f, ".article")
91 | lessons[name] = content
92 | }
93 | return nil
94 | }
95 |
96 | // File defines the JSON form of a code file in a page.
97 | type File struct {
98 | Name string
99 | Content string
100 | }
101 |
102 | // Page defines the JSON form of a tour lesson page.
103 | type Page struct {
104 | Title string
105 | Content string
106 | Files []File
107 | }
108 |
109 | // Lesson defines the JSON form of a tour lesson.
110 | type Lesson struct {
111 | Title string
112 | Description string
113 | Pages []Page
114 | }
115 |
116 | // parseLesson parses and returns a lesson content given its name and
117 | // the template to render it.
118 | func parseLesson(tmpl *template.Template, path string) ([]byte, error) {
119 | f, err := os.Open(path)
120 | if err != nil {
121 | return nil, err
122 | }
123 | defer f.Close()
124 | doc, err := present.Parse(prepContent(f), path, 0)
125 | if err != nil {
126 | return nil, err
127 | }
128 |
129 | lesson := Lesson{
130 | doc.Title,
131 | doc.Subtitle,
132 | make([]Page, len(doc.Sections)),
133 | }
134 |
135 | for i, sec := range doc.Sections {
136 | p := &lesson.Pages[i]
137 | w := new(bytes.Buffer)
138 | if err := sec.Render(w, tmpl); err != nil {
139 | return nil, fmt.Errorf("render section: %v", err)
140 | }
141 | p.Title = sec.Title
142 | p.Content = w.String()
143 | codes := findPlayCode(sec)
144 | p.Files = make([]File, len(codes))
145 | for i, c := range codes {
146 | f := &p.Files[i]
147 | f.Name = c.FileName
148 | f.Content = string(c.Raw)
149 | }
150 | }
151 |
152 | w := new(bytes.Buffer)
153 | if err := json.NewEncoder(w).Encode(lesson); err != nil {
154 | return nil, fmt.Errorf("encode lesson: %v", err)
155 | }
156 | return w.Bytes(), nil
157 | }
158 |
159 | // findPlayCode returns a slide with all the Code elements in the given
160 | // Elem with Play set to true.
161 | func findPlayCode(e present.Elem) []*present.Code {
162 | var r []*present.Code
163 | switch v := e.(type) {
164 | case present.Code:
165 | if v.Play {
166 | r = append(r, &v)
167 | }
168 | case present.Section:
169 | for _, s := range v.Elem {
170 | r = append(r, findPlayCode(s)...)
171 | }
172 | }
173 | return r
174 | }
175 |
176 | // writeLesson writes the tour content to the provided Writer.
177 | func writeLesson(name string, w io.Writer) error {
178 | if uiContent == nil {
179 | panic("writeLesson called before successful initTour")
180 | }
181 | if len(name) == 0 {
182 | return writeAllLessons(w)
183 | }
184 | l, ok := lessons[name]
185 | if !ok {
186 | return lessonNotFound
187 | }
188 | _, err := w.Write(l)
189 | return err
190 | }
191 |
192 | func writeAllLessons(w io.Writer) error {
193 | if _, err := fmt.Fprint(w, "{"); err != nil {
194 | return err
195 | }
196 | nLessons := len(lessons)
197 | for k, v := range lessons {
198 | if _, err := fmt.Fprintf(w, "%q:%s", k, v); err != nil {
199 | return err
200 | }
201 | nLessons--
202 | if nLessons != 0 {
203 | if _, err := fmt.Fprint(w, ","); err != nil {
204 | return err
205 | }
206 | }
207 | }
208 | _, err := fmt.Fprint(w, "}")
209 | return err
210 | }
211 |
212 | // renderUI writes the tour UI to the provided Writer.
213 | func renderUI(w io.Writer) error {
214 | if uiContent == nil {
215 | panic("renderUI called before successful initTour")
216 | }
217 | _, err := w.Write(uiContent)
218 | return err
219 | }
220 |
221 | // nocode returns true if the provided Section contains
222 | // no Code elements with Play enabled.
223 | func nocode(s present.Section) bool {
224 | for _, e := range s.Elem {
225 | if c, ok := e.(present.Code); ok && c.Play {
226 | return false
227 | }
228 | }
229 | return true
230 | }
231 |
232 | // initScript concatenates all the javascript files needed to render
233 | // the tour UI and serves the result on /script.js.
234 | func initScript(root string) error {
235 | modTime := time.Now()
236 | b := new(bytes.Buffer)
237 |
238 | content, ok := static.Files["playground.js"]
239 | if !ok {
240 | return fmt.Errorf("playground.js not found in static files")
241 | }
242 | b.WriteString(content)
243 |
244 | // Keep this list in dependency order
245 | files := []string{
246 | "static/lib/jquery.min.js",
247 | "static/lib/jquery-ui.min.js",
248 | "static/lib/angular.min.js",
249 | "static/lib/codemirror/lib/codemirror.js",
250 | "static/lib/codemirror/mode/go/go.js",
251 | "static/lib/angular-ui.min.js",
252 | "static/js/app.js",
253 | "static/js/controllers.js",
254 | "static/js/directives.js",
255 | "static/js/services.js",
256 | "static/js/values.js",
257 | }
258 |
259 | for _, file := range files {
260 | f, err := ioutil.ReadFile(filepath.Join(root, file))
261 | if err != nil {
262 | return fmt.Errorf("couldn't open %v: %v", file, err)
263 | }
264 | _, err = b.Write(f)
265 | if err != nil {
266 | return fmt.Errorf("error concatenating %v: %v", file, err)
267 | }
268 | }
269 |
270 | var gzBuf bytes.Buffer
271 | gz, err := gzip.NewWriterLevel(&gzBuf, gzip.BestCompression)
272 | if err != nil {
273 | return err
274 | }
275 | gz.Write(b.Bytes())
276 | gz.Close()
277 |
278 | http.HandleFunc("/script.js", func(w http.ResponseWriter, r *http.Request) {
279 | w.Header().Set("Content-type", "application/javascript")
280 | // Set expiration time in one week.
281 | w.Header().Set("Cache-control", "max-age=604800")
282 | if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
283 | http.ServeContent(w, r, "", modTime, bytes.NewReader(b.Bytes()))
284 | } else {
285 | w.Header().Set("Content-Encoding", "gzip")
286 | http.ServeContent(w, r, "", modTime, bytes.NewReader(gzBuf.Bytes()))
287 | }
288 | })
289 |
290 | return nil
291 | }
292 |
--------------------------------------------------------------------------------
/static/css/app.css:
--------------------------------------------------------------------------------
1 | /* Generic elements */
2 | html, body {
3 | margin: 0;
4 | padding: 0;
5 | font-size: 16px;
6 | height: 100%;
7 | font-family: sans-serif;
8 | line-height: 24px;
9 | word-wrap: break-word;
10 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
11 | /* Prevent font scaling in landscape */
12 | -webkit-text-size-adjust: none;
13 | -webkit-font-smoothing:antialiased;
14 | }
15 | * {
16 | outline: none;
17 | }
18 | a {
19 | color: inherit;
20 | text-decoration: none;
21 | }
22 | h1, h2, h3, h4 {
23 | color: #333;
24 | line-height: 32px;
25 | margin: 0;
26 | }
27 | pre, code {
28 | font-family:'Inconsolata', monospace;
29 | border-radius: 4px;
30 | color: #333;
31 | background-color: #fafafa;
32 | }
33 | pre {
34 | padding: 10px;
35 | }
36 | code {
37 | padding: 2px;
38 | }
39 | .left {
40 | display: block;
41 | float: left;
42 | margin-right: 10px;
43 | }
44 | .right {
45 | display: block;
46 | float: right;
47 | margin-left: 10px;
48 | }
49 | .bar {
50 | display: block;
51 | overflow: hidden;
52 | -moz-user-select: none;
53 | -webkit-user-select: none;
54 | -ms-user-select: none;
55 | user-select: none;
56 | }
57 | .wrapper {
58 | position: fixed;
59 | overflow: auto;
60 | top: 48px;
61 | bottom: 0;
62 | left: 0;
63 | right: 0;
64 | }
65 | .container {
66 | max-width: 800px;
67 | width: 90%;
68 | margin: 0 auto 36px auto;
69 | padding: 16px 5%;
70 | background: #ffffff;
71 | }
72 | .container a {
73 | color: #375eab;
74 | }
75 | .relative-content {
76 | display: block;
77 | position: relative;
78 | height: 100%;
79 | }
80 | .highlight {
81 | background: #b5533b !important;
82 | color: yellow !important;
83 | }
84 | .hidden {
85 | display: none;
86 | }
87 | p {
88 | margin: 16px 0;
89 | }
90 | li {
91 | margin: 8px 0;
92 | }
93 | ul {
94 | list-style: none;
95 | margin: 0;
96 | padding-left: 32px;
97 | }
98 | /* Navigation bars */
99 | .top-bar {
100 | position: fixed;
101 | left: 0;
102 | right: 0;
103 | top: 0;
104 | z-index: 1000;
105 | font-size: 1.4em;
106 | padding: 8px 24px;
107 | line-height: 32px;
108 | color: #222;
109 | background: #E0EBF5;
110 | }
111 | .nav {
112 | float: right;
113 | padding: 5px;
114 | height: 20px;
115 | width: 20px;
116 | cursor: pointer;
117 | }
118 | /* Module list */
119 | .page-header {
120 | font-size: 1.2em;
121 | line-height: 32px;
122 | margin: 32px 0;
123 | }
124 | @media (max-width: 515px) {
125 | .page-header {
126 | font-size: 0.75em;
127 | }
128 | }
129 | .module {
130 | margin: 32px 0;
131 | }
132 | .module-title {
133 | font-size: 1.3em;
134 | font-weight: bold;
135 | color: #333;
136 | margin: 0;
137 | }
138 | .lesson {
139 | background: #E0EBF5;
140 | padding: 8px 16px;
141 | margin: 16px 0;
142 | position: relative;
143 | }
144 | .lesson-title {
145 | display: inline-block;
146 | font-size: 1.2em;
147 | font-weight: bold;
148 | margin: 16px 0 0 0;
149 | padding-right: 48px;
150 | }
151 | /* Lesson viewer */
152 | .slide-content {
153 | padding: 16px;
154 | }
155 | .module-bar {
156 | font-size: 1.5em;
157 | padding: 8px 0;
158 | text-align: center;
159 | line-height: 24px;
160 | font-size: 24px;
161 | }
162 | #right-side .module-bar a {
163 | color: #375eab;
164 | position: relative;
165 | font-weight: bold;
166 | margin: 5px;
167 | }
168 | .menu-button {
169 | display: inline-block;
170 | text-decoration: none;
171 | cursor: pointer;
172 | font-size: 0.9em;
173 | border-radius: 2px;
174 | background-color: #E0EBF5;
175 | border: 1px solid rgba(0, 0, 0, 0.1);
176 | margin: 2px;
177 | height: 24px;
178 | padding: 1px 8px;
179 | line-height: 24px;
180 | color: #444;
181 | -moz-user-select: none;
182 | -webkit-user-select: none;
183 | -ms-user-select: none;
184 | user-select: none;
185 | }
186 | .menu-button:hover:not(.active) {
187 | border: 1px solid #C6C6C6;
188 | background-color: #fafafa;
189 | }
190 | .menu-button.active {
191 | background: #fff;
192 | }
193 | .menu-button[syntax-checkbox]:after {
194 | content:' - 关';
195 | }
196 | .menu-button[syntax-checkbox].active:after {
197 | content:' - 开';
198 | }
199 | #right-side a {
200 | color: #375eab;
201 | text-decoration: none;
202 | }
203 | #file-menu .menu-button {
204 | float: right;
205 | }
206 | #run {
207 | background-color: #375eab;
208 | color: #fff;
209 | }
210 | #run:hover:not(:active) {
211 | background-color: #fff;
212 | color: #375eab;
213 | }
214 | .output:not(.active) {
215 | display: none;
216 | }
217 | .output > pre {
218 | font-family:'Inconsolata', monospace;
219 | background: #fafafa;
220 | margin: 0;
221 | }
222 | .output .system {
223 | color: #888;
224 | }
225 | .output .stderr {
226 | color: #D00A0A;
227 | }
228 | .output-menu .menu-button {
229 | float: left;
230 | }
231 | .output-menu, #file-menu {
232 | background: #fafafa;
233 | }
234 | #explorer {
235 | height: 32px;
236 | padding-left: 30px;
237 | background: #fafafa;
238 | }
239 | #explorer .menu-button.active {
240 | cursor: default;
241 | }
242 | #explorer .syntax-checkbox {
243 | float: right;
244 | }
245 | /* CodeMirror */
246 |
247 | #file-editor {
248 | background: #FFFFD8;
249 | overflow: auto;
250 | }
251 | #file-editor > textarea {
252 | display: none;
253 | }
254 | #file-editor .CodeMirror {
255 | height: auto;
256 | }
257 | #file-editor .CodeMirror-lines, #file-editor .CodeMirror-gutters {
258 | background: #FFFFD8;
259 | font-family:'Inconsolata', monospace;
260 | line-height: 1.2em;
261 | }
262 | .CodeMirror-code > .line-error {
263 | background: #FF8080;
264 | }
265 | .CodeMirror-code > .line-error .CodeMirror-linenumber {
266 | color: #FF5555;
267 | font-weight: bolder;
268 | }
269 | #file-editor .CodeMirror-gutters {
270 | width: 32px;
271 | }
272 | @media (min-width: 601px) {
273 | #editor-container {
274 | position: fixed;
275 | top: 48px;
276 | left: 0px;
277 | right: 0px;
278 | bottom: 0px;
279 | overflow: hidden;
280 | background: #fafafa;
281 | }
282 | #left-side {
283 | position: absolute;
284 | top: 0;
285 | bottom: 0;
286 | left: 0;
287 | width: 50%;
288 | overflow: hidden;
289 | background-image: url(/static/img/gopher.png);
290 | background-repeat: no-repeat;
291 | background-position: bottom;
292 | background-color: #fafafa;
293 | }
294 | div[vertical-slide] {
295 | position: absolute;
296 | top: 0px;
297 | bottom: 0px;
298 | width: 5px;
299 | background: #e0ebf5;
300 | left: 50%;
301 | right: 50%;
302 | z-index: 100;
303 | cursor: move;
304 | }
305 | #right-side {
306 | position: absolute;
307 | top: 0;
308 | bottom: 0;
309 | right: 0;
310 | left: 50%;
311 | background: #fff;
312 | }
313 | #right-side .slide-content {
314 | position: absolute;
315 | left: 0;
316 | right: 0;
317 | top: 0;
318 | bottom: 30px;
319 | overflow: auto;
320 | }
321 | .module-bar {
322 | position: absolute;
323 | left: 0;
324 | right: 0;
325 | bottom: 0;
326 | padding: 4px 0;
327 | margin: 0;
328 | }
329 | #top-part {
330 | position: absolute;
331 | left: 0;
332 | right: 0;
333 | top: 0;
334 | bottom: 33%;
335 | background: #e0ebf5;
336 | }
337 | #file-editor {
338 | position: absolute;
339 | left: 0;
340 | right: 0;
341 | top: 0;
342 | bottom: 0;
343 | }
344 | div[horizontal-slide] {
345 | position: absolute;
346 | left: 0;
347 | right: 0;
348 | bottom: 33%;
349 | height: 5px;
350 | background: #e0ebf5;
351 | z-index: 100;
352 | cursor: move;
353 | }
354 | #bottom-part {
355 | position: absolute;
356 | left: 0;
357 | right: 0;
358 | bottom: 0;
359 | top: 67%;
360 | min-height: 100px;
361 | z-index: 50;
362 | }
363 | #explorer {
364 | position: absolute;
365 | top: 0;
366 | left: 0;
367 | right: 0;
368 | }
369 | #explorer + div {
370 | top: 32px;
371 | }
372 | #file-menu {
373 | position: absolute;
374 | top:0;
375 | right: 0;
376 | left: 0;
377 | background: #fafafa;
378 | }
379 | .output {
380 | position: absolute;
381 | top: 34px;
382 | bottom: 0;
383 | left: 0;
384 | right: 0;
385 | margin: 0;
386 | padding: 0;
387 | overflow: auto;
388 | }
389 | }
390 | @media (max-width: 600px) {
391 | #top-part {
392 | border: 1px solid #ccc;
393 | }
394 | #left-side {
395 | background: #e0ebf5;
396 | }
397 | #right-side {
398 | padding-top: 48px;
399 | }
400 | #file-menu {
401 | height: 32px;
402 | }
403 | .output {
404 | background: white;
405 | max-height: 300px;
406 | overflow: auto;
407 | }
408 | #editor-container {
409 | padding-bottom: 40px;
410 | }
411 | .module-bar {
412 | position: fixed;
413 | background: #e0ebf5;
414 | left: 0;
415 | right: 0;
416 | bottom: 0;
417 | z-index: 10;
418 | height: 42px;
419 | padding: 0;
420 | overflow: hidden;
421 | text-align: center;
422 | }
423 | .module-bar * {
424 | display: inline-block;
425 | width: 25%;
426 | font-size: 1.1em;
427 | padding: 8px 0;
428 | }
429 | div[horizontal-slide], div[vertical-slide] {
430 | display: none;
431 | }
432 | }
433 | /* Table of contents */
434 | .toc {
435 | display: none;
436 | position: fixed;
437 | z-index: 200;
438 | font-size: 1.3em;
439 | top: 48px;
440 | bottom: 0;
441 | right: 0;
442 | width: 500px;
443 | background: #e0ebf5;
444 | color: black;
445 | overflow-y: auto;
446 | padding: 0;
447 | margin: 0;
448 | border-left: 4px solid #e0ebf5;
449 | border-bottom: 4px solid #e0ebf5;
450 | -moz-user-select: none;
451 | -webkit-user-select: none;
452 | -ms-user-select: none;
453 | user-select: none;
454 | }
455 | .click-catcher {
456 | position: fixed;
457 | z-index: -100;
458 | top:0;
459 | bottom: 0;
460 | left:0;
461 | right: 10; /* avoid covering the TOC scroller */
462 | background: rgba(0, 0, 0, 0);
463 | }
464 | .toc * {
465 | margin: 0;
466 | padding: 0;
467 | font-size: 0.95em;
468 | display: block;
469 | }
470 | .toc span, .toc a {
471 | padding: 4px;
472 | }
473 | .toc-module {
474 | color: #375eab;
475 | background: #e0ebf5;
476 | }
477 | .toc-lesson {
478 | background: #fafafa;
479 | color: #333;
480 | margin: 1px 0;
481 | cursor: pointer;
482 | }
483 | .toc-page {
484 | background: #fff;
485 | color: #333;
486 | padding-left: 4px;
487 | display: list-item;
488 | }
489 | .toc-lesson.active .toc-page {
490 | display: list-item;
491 | }
492 | .toc-page.active {
493 | color: #375eab;
494 | font-weight: bold;
495 | }
496 | @media (max-width: 600px) {
497 | .toc {
498 | position: absolute;
499 | left: 0;
500 | right: 0;
501 | bottom: 0;
502 | width: 100%;
503 | border: none;
504 | }
505 | .toc ul {
506 | width: 100%;
507 | }
508 | .click-catcher {
509 | display: none;
510 | }
511 | }
512 |
--------------------------------------------------------------------------------
/static/lib/angular-ui.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * AngularUI - The companion suite for AngularJS
3 | * @version v0.4.0 - 2013-02-15
4 | * @link http://angular-ui.github.com
5 | * @license MIT License, http://www.opensource.org/licenses/MIT
6 | */
7 | angular.module("ui.config",[]).value("ui.config",{}),angular.module("ui.filters",["ui.config"]),angular.module("ui.directives",["ui.config"]),angular.module("ui",["ui.filters","ui.directives","ui.config"]),angular.module("ui.directives").directive("uiAnimate",["ui.config","$timeout",function(e,t){var n={};return angular.isString(e.animate)?n["class"]=e.animate:e.animate&&(n=e.animate),{restrict:"A",link:function(e,r,i){var s={};i.uiAnimate&&(s=e.$eval(i.uiAnimate),angular.isString(s)&&(s={"class":s})),s=angular.extend({"class":"ui-animate"},n,s),r.addClass(s["class"]),t(function(){r.removeClass(s["class"])},20,!1)}}}]),angular.module("ui.directives").directive("uiCalendar",["ui.config","$parse",function(e,t){return e.uiCalendar=e.uiCalendar||{},{require:"ngModel",restrict:"A",link:function(t,n,r,i){function a(){t.calendar=n.html("");var i=t.calendar.fullCalendar("getView");i&&(i=i.name);var o,u={defaultView:i,eventSources:s};r.uiCalendar?o=t.$eval(r.uiCalendar):o={},angular.extend(u,e.uiCalendar,o),t.calendar.fullCalendar(u)}var s=t.$eval(r.ngModel),o=0,u=function(){var e=t.$eval(r.equalsTracker);return o=0,angular.forEach(s,function(e,t){angular.isArray(e)&&(o+=e.length)}),angular.isNumber(e)?o+s.length+e:o+s.length};a(),t.$watch(u,function(e,t){a()})}}}]),angular.module("ui.directives").directive("uiCodemirror",["ui.config","$timeout",function(e,t){"use strict";var n=["cursorActivity","viewportChange","gutterClick","focus","blur","scroll","update"];return{restrict:"A",require:"ngModel",link:function(r,i,s,o){var u,a,f,l,c;if(i[0].type!=="textarea")throw new Error("uiCodemirror3 can only be applied to a textarea element");u=e.codemirror||{},a=angular.extend({},u,r.$eval(s.uiCodemirror)),f=function(e){return function(t,n){var i=t.getValue();i!==o.$viewValue&&(o.$setViewValue(i),r.$apply()),typeof e=="function"&&e(t,n)}},l=function(){c=CodeMirror.fromTextArea(i[0],a),c.on("change",f(a.onChange));for(var e=0,u=n.length,l;e0),r.toggleClass(o.neg,n<0),r.toggleClass(o.zero,n===0),e===""?r.text(""):r.text(t(n,o.symbol)),!0},s.$render=function(){a=s.$viewValue,r.val(a),u(a)}}}}]),angular.module("ui.directives").directive("uiDate",["ui.config",function(e){"use strict";var t;return t={},angular.isObject(e.date)&&angular.extend(t,e.date),{require:"?ngModel",link:function(t,n,r,i){var s=function(){return angular.extend({},e.date,t.$eval(r.uiDate))},o=function(){var e=s();if(i){var r=function(){t.$apply(function(){var e=n.datepicker("getDate");n.datepicker("setDate",n.val()),i.$setViewValue(e),n.blur()})};if(e.onSelect){var o=e.onSelect;e.onSelect=function(e,n){r(),t.$apply(function(){o(e,n)})}}else e.onSelect=r;n.bind("change",r),i.$render=function(){var e=i.$viewValue;if(angular.isDefined(e)&&e!==null&&!angular.isDate(e))throw new Error("ng-Model value must be a Date object - currently it is a "+typeof e+" - use ui-date-format to convert it from a string");n.datepicker("setDate",e)}}n.datepicker("destroy"),n.datepicker(e),i&&i.$render()};t.$watch(s,o,!0)}}}]).directive("uiDateFormat",["ui.config",function(e){var t={require:"ngModel",link:function(t,n,r,i){var s=r.uiDateFormat||e.dateFormat;s?(i.$formatters.push(function(e){if(angular.isString(e))return $.datepicker.parseDate(s,e)}),i.$parsers.push(function(e){if(e)return $.datepicker.formatDate(s,e)})):(i.$formatters.push(function(e){if(angular.isString(e))return new Date(e)}),i.$parsers.push(function(e){if(e)return e.toISOString()}))}};return t}]),angular.module("ui.directives").directive("uiEvent",["$parse",function(e){return function(t,n,r){var i=t.$eval(r.uiEvent);angular.forEach(i,function(r,i){var s=e(r);n.bind(i,function(e){var n=Array.prototype.slice.call(arguments);n=n.splice(1),t.$apply(function(){s(t,{$event:e,$params:n})})})})}}]),angular.module("ui.directives").directive("uiIf",[function(){return{transclude:"element",priority:1e3,terminal:!0,restrict:"A",compile:function(e,t,n){return function(e,t,r){var i,s;e.$watch(r.uiIf,function(r){i&&(i.remove(),i=undefined),s&&(s.$destroy(),s=undefined),r&&(s=e.$new(),n(s,function(e){i=e,t.after(e)}))})}}}}]),angular.module("ui.directives").directive("uiJq",["ui.config","$timeout",function(t,n){return{restrict:"A",compile:function(r,i){if(!angular.isFunction(r[i.uiJq]))throw new Error('ui-jq: The "'+i.uiJq+'" function does not exist');var s=t.jq&&t.jq[i.uiJq];return function(t,r,i){function u(){n(function(){r[i.uiJq].apply(r,o)},0,!1)}var o=[];i.uiOptions?(o=t.$eval("["+i.uiOptions+"]"),angular.isObject(s)&&angular.isObject(o[0])&&(o[0]=angular.extend({},s,o[0]))):s&&(o=[s]),i.ngModel&&r.is("select,input,textarea")&&r.on("change",function(){r.trigger("input")}),i.uiRefresh&&t.$watch(i.uiRefresh,function(e){u()}),u()}}}}]),angular.module("ui.directives").factory("keypressHelper",["$parse",function(t){var n={8:"backspace",9:"tab",13:"enter",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"delete"},r=function(e){return e.charAt(0).toUpperCase()+e.slice(1)};return function(e,i,s,o){var u,a=[];u=i.$eval(o["ui"+r(e)]),angular.forEach(u,function(e,n){var r,i;i=t(e),angular.forEach(n.split(" "),function(e){r={expression:i,keys:{}},angular.forEach(e.split("-"),function(e){r.keys[e]=!0}),a.push(r)})}),s.bind(e,function(t){var r=t.metaKey||t.altKey,s=t.ctrlKey,o=t.shiftKey,u=t.keyCode;e==="keypress"&&!o&&u>=97&&u<=122&&(u-=32),angular.forEach(a,function(e){var u=e.keys[n[t.keyCode]]||e.keys[t.keyCode.toString()]||!1,a=e.keys.alt||!1,f=e.keys.ctrl||!1,l=e.keys.shift||!1;u&&a==r&&f==s&&l==o&&i.$apply(function(){e.expression(i,{$event:t})})})})}}]),angular.module("ui.directives").directive("uiKeydown",["keypressHelper",function(e){return{link:function(t,n,r){e("keydown",t,n,r)}}}]),angular.module("ui.directives").directive("uiKeypress",["keypressHelper",function(e){return{link:function(t,n,r){e("keypress",t,n,r)}}}]),angular.module("ui.directives").directive("uiKeyup",["keypressHelper",function(e){return{link:function(t,n,r){e("keyup",t,n,r)}}}]),function(){function t(e,t,n,r){angular.forEach(t.split(" "),function(t){var i={type:"map-"+t};google.maps.event.addListener(n,t,function(t){r.triggerHandler(angular.extend({},i,t)),e.$$phase||e.$apply()})})}function n(n,r){e.directive(n,[function(){return{restrict:"A",link:function(e,i,s){e.$watch(s[n],function(n){t(e,r,n,i)})}}}])}var e=angular.module("ui.directives");e.directive("uiMap",["ui.config","$parse",function(e,n){var r="bounds_changed center_changed click dblclick drag dragend dragstart heading_changed idle maptypeid_changed mousemove mouseout mouseover projection_changed resize rightclick tilesloaded tilt_changed zoom_changed",i=e.map||{};return{restrict:"A",link:function(e,s,o){var u=angular.extend({},i,e.$eval(o.uiOptions)),a=new google.maps.Map(s[0],u),f=n(o.uiMap);f.assign(e,a),t(e,r,a,s)}}}]),e.directive("uiMapInfoWindow",["ui.config","$parse","$compile",function(e,n,r){var i="closeclick content_change domready position_changed zindex_changed",s=e.mapInfoWindow||{};return{link:function(e,o,u){var a=angular.extend({},s,e.$eval(u.uiOptions));a.content=o[0];var f=n(u.uiMapInfoWindow),l=f(e);l||(l=new google.maps.InfoWindow(a),f.assign(e,l)),t(e,i,l,o),o.replaceWith("
");var c=l.open;l.open=function(n,i,s,u,a,f){r(o.contents())(e),c.call(l,n,i,s,u,a,f)}}}}]),n("uiMapMarker","animation_changed click clickable_changed cursor_changed dblclick drag dragend draggable_changed dragstart flat_changed icon_changed mousedown mouseout mouseover mouseup position_changed rightclick shadow_changed shape_changed title_changed visible_changed zindex_changed"),n("uiMapPolyline","click dblclick mousedown mousemove mouseout mouseover mouseup rightclick"),n("uiMapPolygon","click dblclick mousedown mousemove mouseout mouseover mouseup rightclick"),n("uiMapRectangle","bounds_changed click dblclick mousedown mousemove mouseout mouseover mouseup rightclick"),n("uiMapCircle","center_changed click dblclick mousedown mousemove mouseout mouseover mouseup radius_changed rightclick"),n("uiMapGroundOverlay","click dblclick")}(),angular.module("ui.directives").directive("uiMask",[function(){return{require:"ngModel",link:function(e,t,n,r){r.$render=function(){var i=r.$viewValue||"";t.val(i),t.mask(e.$eval(n.uiMask))},r.$parsers.push(function(e){var n=t.isMaskValid()||angular.isUndefined(t.isMaskValid())&&t.val().length>0;return r.$setValidity("mask",n),n?e:undefined}),t.bind("keyup",function(){e.$apply(function(){r.$setViewValue(t.mask())})})}}}]),angular.module("ui.directives").directive("uiReset",["ui.config",function(e){var t=null;return e.reset!==undefined&&(t=e.reset),{require:"ngModel",link:function(e,n,r,i){var s;s=angular.element(' '),n.wrap(' ').after(s),s.bind("click",function(n){n.preventDefault(),e.$apply(function(){r.uiReset?i.$setViewValue(e.$eval(r.uiReset)):i.$setViewValue(t),i.$render()})})}}}]),angular.module("ui.directives").directive("uiRoute",["$location","$parse",function(e,t){return{restrict:"AC",compile:function(n,r){var i;if(r.uiRoute)i="uiRoute";else if(r.ngHref)i="ngHref";else{if(!r.href)throw new Error("uiRoute missing a route or href property on "+n[0]);i="href"}return function(n,r,s){function a(t){(hash=t.indexOf("#"))>-1&&(t=t.substr(hash+1)),u=function(){o(n,e.path().indexOf(t)>-1)},u()}function f(t){(hash=t.indexOf("#"))>-1&&(t=t.substr(hash+1)),u=function(){var i=new RegExp("^"+t+"$",["i"]);o(n,i.test(e.path()))},u()}var o=t(s.ngModel||s.routeModel||"$uiRoute").assign,u=angular.noop;switch(i){case"uiRoute":s.uiRoute?f(s.uiRoute):s.$observe("uiRoute",f);break;case"ngHref":s.ngHref?a(s.ngHref):s.$observe("ngHref",a);break;case"href":a(s.href)}n.$on("$routeChangeSuccess",function(){u()})}}}}]),angular.module("ui.directives").directive("uiScrollfix",["$window",function(e){"use strict";return{link:function(t,n,r){var i=n.offset().top;r.uiScrollfix?r.uiScrollfix.charAt(0)==="-"?r.uiScrollfix=i-r.uiScrollfix.substr(1):r.uiScrollfix.charAt(0)==="+"&&(r.uiScrollfix=i+parseFloat(r.uiScrollfix.substr(1))):r.uiScrollfix=i,angular.element(e).on("scroll.ui-scrollfix",function(){var t;if(angular.isDefined(e.pageYOffset))t=e.pageYOffset;else{var i=document.compatMode&&document.compatMode!=="BackCompat"?document.documentElement:document.body;t=i.scrollTop}!n.hasClass("ui-scrollfix")&&t>r.uiScrollfix?n.addClass("ui-scrollfix"):n.hasClass("ui-scrollfix")&&t'+t+""):e.replace(new RegExp(t,"gi"),'$& ')):e}}),angular.module("ui.filters").filter("inflector",function(){function e(e){return e.replace(/^([a-z])|\s+([a-z])/g,function(e){return e.toUpperCase()})}function t(e,t){return e.replace(/[A-Z]/g,function(e){return t+e})}var n={humanize:function(n){return e(t(n," ").split("_").join(" "))},underscore:function(e){return e.substr(0,1).toLowerCase()+t(e.substr(1),"_").toLowerCase().split(" ").join("_")},variable:function(t){return t=t.substr(0,1).toLowerCase()+e(t.split("_").join(" ")).substr(1).split(" ").join(""),t}};return function(e,t,r){return t!==!1&&angular.isString(e)?(t=t||"humanize",n[t](e)):e}}),angular.module("ui.filters").filter("unique",function(){return function(e,t){if(t===!1)return e;if((t||angular.isUndefined(t))&&angular.isArray(e)){var n={},r=[],i=function(e){return angular.isObject(e)&&angular.isString(t)?e[t]:e};angular.forEach(e,function(e){var t,n=!1;for(var s=0;s