├── .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 | menu -------------------------------------------------------------------------------- /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 |
2 | {{title}} 3 |

{{description}}

4 |
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 |
13 | {{m.lesson[l].Title}} 14 |

{{m.lesson[l].Description}}

15 |
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 | [![Build Status](https://secure.travis-ci.org/marijnh/CodeMirror.png?branch=master)](http://travis-ci.org/marijnh/CodeMirror) 3 | [![NPM version](https://badge.fury.io/js/codemirror.png)](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 | 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 | 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 | 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 |
18 | 19 |
20 |
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 | {{f.Name}} 19 | 语法高亮 20 |
21 | 22 |
23 | 24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 |
33 | 34 |
35 |
36 | 37 |
38 | 运行 39 | 格式化 40 | 重置 41 |
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