and others
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/vendor/golang.org/x/tour/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 // import "golang.org/x/tour/tree"
6 |
7 | import (
8 | "fmt"
9 | "math/rand"
10 | )
11 |
12 | // A Tree is a binary tree with integer values.
13 | type Tree struct {
14 | Left *Tree
15 | Value int
16 | Right *Tree
17 | }
18 |
19 | // New returns a new, random binary tree holding the values k, 2k, ..., 10k.
20 | func New(k int) *Tree {
21 | var t *Tree
22 | for _, v := range rand.Perm(10) {
23 | t = insert(t, (1+v)*k)
24 | }
25 | return t
26 | }
27 |
28 | func insert(t *Tree, v int) *Tree {
29 | if t == nil {
30 | return &Tree{nil, v, nil}
31 | }
32 | if v < t.Value {
33 | t.Left = insert(t.Left, v)
34 | } else {
35 | t.Right = insert(t.Right, v)
36 | }
37 | return t
38 | }
39 |
40 | func (t *Tree) String() string {
41 | if t == nil {
42 | return "()"
43 | }
44 | s := ""
45 | if t.Left != nil {
46 | s += t.Left.String() + " "
47 | }
48 | s += fmt.Sprint(t.Value)
49 | if t.Right != nil {
50 | s += " " + t.Right.String()
51 | }
52 | return "(" + s + ")"
53 | }
54 |
--------------------------------------------------------------------------------
/template/action.tmpl:
--------------------------------------------------------------------------------
1 | {{/*{
2 | This is the action template.
3 | It determines how the formatting actions are rendered.
4 | */}}
5 |
6 | {{define "section"}}
7 | {{.Title}}
8 | {{range .Elem}}{{elem $.Template .}}{{end}}
9 | {{end}}
10 |
11 | {{define "list"}}
12 |
13 | {{range .Bullet}}
14 | {{style .}}
15 | {{end}}
16 |
17 | {{end}}
18 |
19 | {{define "text"}}
20 | {{if .Pre}}
21 | {{range .Lines}}{{.}}{{end}}
22 | {{else}}
23 |
24 | {{range $i, $l := .Lines}}{{if $i}}{{template "newline"}}
25 | {{end}}{{style $l}}{{end}}
26 |
27 | {{end}}
28 | {{end}}
29 |
30 | {{define "code"}}
31 | {{if .Play}}
32 | {{/* playable code is not displayed in the slides */}}
33 | {{else}}
34 | {{.Text}}
35 | {{end}}
36 | {{end}}
37 |
38 | {{define "image"}}
39 |
40 | {{end}}
41 |
42 | {{define "link"}}
43 | {{style .Label}}
44 | {{end}}
45 |
46 | {{define "html"}}{{.HTML}}{{end}}
47 |
48 | {{define "newline"}}
49 | {{/* No automatic line break. Paragraphs are free-form. */}}
50 | {{end}}
51 |
--------------------------------------------------------------------------------
/tools/map.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Copyright 2011 The Go Authors. All rights reserved.
4 | # Use of this source code is governed by a BSD-style
5 | # license that can be found in the LICENSE file.
6 |
7 | # This code parses mapping.old and finds a correspondance from the old
8 | # urls (e.g. #42) to the corresponding path (e.g. /concurrency/3).
9 |
10 | function findURL {
11 | title="$1"
12 | file=$(grep -l "* $title\$" *.article)
13 | if [[ -z $file ]]
14 | then
15 | echo "undefined"
16 | return 1
17 | fi
18 | titles=$(grep "^* " $file | awk '{print NR, $0}')
19 | page=$(echo "$titles" | grep "* $title\$" | awk '{print $1}')
20 | if [[ $(echo "$page" | wc -l) -gt "1" ]]
21 | then
22 | echo "multiple matches found for $title; find 'CHOOSE BETWEEN' in the output" 1>&2
23 | page="CHOOSE BETWEEN $page"
24 | fi
25 |
26 | page=$(echo $page)
27 | lesson=$(echo "$file" | rev | cut -c 9- | rev)
28 | echo "'/$lesson/$page'"
29 | return 0
30 | }
31 |
32 | mapping=`cat mapping.old`
33 |
34 | pushd ../content
35 | echo "$mapping" | while read page; do
36 | num=$(echo "$page" | awk '{print $1}')
37 | title=$(echo "$page" | sed "s/[0-9]* //")
38 | url=$(findURL "$title")
39 | echo " '#$num': $url, // $title"
40 | done
41 | popd > /dev/null
42 |
--------------------------------------------------------------------------------
/vendor/golang.org/x/tour/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 // import "golang.org/x/tour/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 |
--------------------------------------------------------------------------------
/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 | https://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 | you can subscribe to the feed at
29 |
30 | https://github.com/golang/tour/commits/master.atom
31 |
32 | Finally, if you have any questions about the Tour or Go,
33 | please mail golang-nuts@googlegroups.com.
34 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/Go-zh/tools v0.0.0-20190316121337-122bff284a43 h1:bff/hNAs+FfxlS7uF24XRobZddjrixOZ/3KHzdkk4Kg=
2 | github.com/Go-zh/tools v0.0.0-20190316121337-122bff284a43/go.mod h1:7MEc479yUY4J0kMFx3boEBDkOrX643VQw62tuUPXejY=
3 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
4 | golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
5 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
6 | golang.org/x/net v0.0.0-20190313220215-9f648a60d977 h1:actzWV6iWn3GLqN8dZjzsB+CLt+gaV2+wsxroxiQI8I=
7 | golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
8 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
9 | golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
10 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
11 | golang.org/x/tools v0.0.0-20190312164927-7b79afddac43/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
12 | golang.org/x/tools v0.0.0-20190315214010-f0bfdbff1f9c/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
13 | golang.org/x/tour v0.0.0-20190313211959-7de0bd1d74c6 h1:Lc7Ee4UuWUQKX4mkhJHgYIKyIzTmhEk8PZnvZ73gWuM=
14 | golang.org/x/tour v0.0.0-20190313211959-7de0bd1d74c6/go.mod h1:qMugOFWX59KzC8Nx7f2uvXxKxAqJfi1J6ZUHAWKnrRA=
15 |
--------------------------------------------------------------------------------
/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 | "github.com/Go-zh/tools/imports"
17 | )
18 |
19 | func init() {
20 | http.HandleFunc("/fmt", fmtHandler)
21 | }
22 |
23 | type fmtResponse struct {
24 | Body string
25 | Error string
26 | }
27 |
28 | func fmtHandler(w http.ResponseWriter, r *http.Request) {
29 | resp := new(fmtResponse)
30 | var body string
31 | var err error
32 | if r.FormValue("imports") == "true" {
33 | var b []byte
34 | b, err = imports.Process("prog.go", []byte(r.FormValue("body")), nil)
35 | body = string(b)
36 | } else {
37 | body, err = gofmt(r.FormValue("body"))
38 | }
39 | if err != nil {
40 | resp.Error = err.Error()
41 | } else {
42 | resp.Body = body
43 | }
44 | json.NewEncoder(w).Encode(resp)
45 | }
46 |
47 | func gofmt(body string) (string, error) {
48 | fset := token.NewFileSet()
49 | f, err := parser.ParseFile(fset, "prog.go", body, parser.ParseComments)
50 | if err != nil {
51 | return "", err
52 | }
53 | ast.SortImports(fset, f)
54 | var buf bytes.Buffer
55 | config := &printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
56 | err = config.Fprint(&buf, fset, f)
57 | if err != nil {
58 | return "", err
59 | }
60 | return buf.String(), nil
61 | }
62 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | "github.com/Go-zh/tour/tree"
13 | )
14 |
15 | func walkImpl(t *tree.Tree, ch chan int) {
16 | if t == nil {
17 | return
18 | }
19 | walkImpl(t.Left, ch)
20 | ch <- t.Value
21 | walkImpl(t.Right, ch)
22 | }
23 |
24 | // Walk walks the tree t sending all values
25 | // from the tree to the channel ch.
26 | func Walk(t *tree.Tree, ch chan int) {
27 | walkImpl(t, ch)
28 | // Need to close the channel here
29 | close(ch)
30 | }
31 |
32 | // Same determines whether the trees
33 | // t1 and t2 contain the same values.
34 | // NOTE: The implementation leaks goroutines when trees are different.
35 | // See binarytrees_quit.go for a better solution.
36 | func Same(t1, t2 *tree.Tree) bool {
37 | w1, w2 := make(chan int), make(chan int)
38 |
39 | go Walk(t1, w1)
40 | go Walk(t2, w2)
41 |
42 | for {
43 | v1, ok1 := <-w1
44 | v2, ok2 := <-w2
45 | if !ok1 || !ok2 {
46 | return ok1 == ok2
47 | }
48 | if v1 != v2 {
49 | return false
50 | }
51 | }
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 |
--------------------------------------------------------------------------------
/solutions/binarytrees_quit.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 (
10 | "fmt"
11 |
12 | "github.com/Go-zh/tour/tree"
13 | )
14 |
15 | func walkImpl(t *tree.Tree, ch, quit chan int) {
16 | if t == nil {
17 | return
18 | }
19 | walkImpl(t.Left, ch, quit)
20 | select {
21 | case ch <- t.Value:
22 | // Value successfully sent.
23 | case <-quit:
24 | return
25 | }
26 | walkImpl(t.Right, ch, quit)
27 | }
28 |
29 | // Walk walks the tree t sending all values
30 | // from the tree to the channel ch.
31 | func Walk(t *tree.Tree, ch, quit chan int) {
32 | walkImpl(t, ch, quit)
33 | close(ch)
34 | }
35 |
36 | // Same determines whether the trees
37 | // t1 and t2 contain the same values.
38 | func Same(t1, t2 *tree.Tree) bool {
39 | w1, w2 := make(chan int), make(chan int)
40 | quit := make(chan int)
41 | defer close(quit)
42 |
43 | go Walk(t1, w1, quit)
44 | go Walk(t2, w2, quit)
45 |
46 | for {
47 | v1, ok1 := <-w1
48 | v2, ok2 := <-w2
49 | if !ok1 || !ok2 {
50 | return ok1 == ok2
51 | }
52 | if v1 != v2 {
53 | return false
54 | }
55 | }
56 | }
57 |
58 | func main() {
59 | fmt.Print("tree.New(1) == tree.New(1): ")
60 | if Same(tree.New(1), tree.New(1)) {
61 | fmt.Println("PASSED")
62 | } else {
63 | fmt.Println("FAILED")
64 | }
65 |
66 | fmt.Print("tree.New(1) != tree.New(2): ")
67 | if !Same(tree.New(1), tree.New(2)) {
68 | fmt.Println("PASSED")
69 | } else {
70 | fmt.Println("FAILED")
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/tools/mapping.old:
--------------------------------------------------------------------------------
1 | 1 Hello, 世界
2 | 2 Go local
3 | 3 Packages
4 | 4 Imports
5 | 5 Exported names
6 | 6 Functions
7 | 7 Functions continued
8 | 8 Multiple results
9 | 9 Named results
10 | 10 Variables
11 | 11 Variables with initializers
12 | 12 Short variable declarations
13 | 13 Basic types
14 | 14 Type conversions
15 | 15 Constants
16 | 16 Numeric Constants
17 | 17 For
18 | 18 For continued
19 | 19 For is Go's "while"
20 | 20 Forever
21 | 21 If
22 | 22 If with a short statement
23 | 23 If and else
24 | 24 Exercise: Loops and Functions
25 | 25 Structs
26 | 26 Struct Fields
27 | 27 Pointers
28 | 28 Struct Literals
29 | 29 The new function
30 | 30 Arrays
31 | 31 Slices
32 | 32 Slicing slices
33 | 33 Making slices
34 | 34 Nil slices
35 | 35 Range
36 | 36 Range continued
37 | 37 Exercise: Slices
38 | 38 Maps
39 | 39 Map literals
40 | 40 Map literals continued
41 | 41 Mutating Maps
42 | 42 Exercise: Maps
43 | 43 Function values
44 | 44 Function closures
45 | 45 Exercise: Fibonacci closure
46 | 46 Switch
47 | 47 Switch evaluation order
48 | 48 Switch with no condition
49 | 49 Advanced Exercise: Complex cube roots
50 | 50 Methods and Interfaces
51 | 51 Methods
52 | 52 Methods continued
53 | 53 Methods with pointer receivers
54 | 54 Interfaces
55 | 55 Interfaces are satisfied implicitly
56 | 56 Errors
57 | 57 Exercise: Errors
58 | 58 Web servers
59 | 59 Exercise: HTTP Handlers
60 | 60 Images
61 | 61 Exercise: Images
62 | 62 Exercise: Rot13 Reader
63 | 63 Concurrency
64 | 64 Goroutines
65 | 65 Channels
66 | 66 Buffered Channels
67 | 67 Range and Close
68 | 68 Select
69 | 69 Default Selection
70 | 70 Exercise: Equivalent Binary Trees
71 | 71 Exercise: Equivalent Binary Trees
72 | 72 Exercise: Web Crawler
73 | 73 Where to Go from here...
74 |
--------------------------------------------------------------------------------
/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).hashPrefix('!');
30 | }
31 | ]).
32 |
33 | // handle mapping from old paths (#42) to the new organization.
34 | run(function($rootScope, $location, mapping) {
35 | $rootScope.$on( "$locationChangeStart", function(event, next) {
36 | var url = document.createElement('a');
37 | url.href = next;
38 | if (url.pathname != '/' || url.hash == '') {
39 | return;
40 | }
41 | $location.hash('');
42 | var m = mapping[url.hash];
43 | if (m === undefined) {
44 | console.log('unknown url, redirecting home');
45 | $location.path('/welcome/1');
46 | return;
47 | }
48 | $location.path(m);
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/static/lib/codemirror/README.md:
--------------------------------------------------------------------------------
1 | # CodeMirror
2 | [](https://travis-ci.org/codemirror/CodeMirror)
3 | [](https://www.npmjs.org/package/codemirror)
4 | [](https://gitter.im/codemirror/CodeMirror)
5 | [Funding status: ](https://marijnhaverbeke.nl/fund/)
6 |
7 | CodeMirror is a versatile text editor implemented in JavaScript for
8 | the browser. It is specialized for editing code, and comes with over
9 | 100 language modes and various addons that implement more advanced
10 | editing functionality.
11 |
12 | A rich programming API and a CSS theming system are available for
13 | customizing CodeMirror to fit your application, and extending it with
14 | new functionality.
15 |
16 | You can find more information (and the
17 | [manual](http://codemirror.net/doc/manual.html)) on the [project
18 | page](http://codemirror.net). For questions and discussion, use the
19 | [discussion forum](https://discuss.codemirror.net/).
20 |
21 | See
22 | [CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md)
23 | for contributing guidelines.
24 |
25 | The CodeMirror community aims to be welcoming to everybody. We use the
26 | [Contributor Covenant
27 | (1.1)](http://contributor-covenant.org/version/1/1/0/) as our code of
28 | conduct.
29 |
30 | ### Quickstart
31 |
32 | To build the project, make sure you have Node.js installed (at least version 6)
33 | and then `npm install`. To run, just open `index.html` in your
34 | browser (you don't need to run a webserver). Run the tests with `npm test`.
35 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Go
2 |
3 | Go is an open source project.
4 |
5 | It is the work of hundreds of contributors. We appreciate your help!
6 |
7 | ## Filing issues
8 |
9 | When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
10 |
11 | 1. What version of Go are you using (`go version`)?
12 | 2. What operating system and processor architecture are you using?
13 | 3. What did you do?
14 | 4. What did you expect to see?
15 | 5. What did you see instead?
16 |
17 | General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
18 | The gophers there will answer or ask you to file an issue if you've tripped over a bug.
19 |
20 | ## Verifying changes during development
21 |
22 | In order to verify changes to the slides or code examples while developing
23 | locally compile with your local toolchain:
24 |
25 | $ go install golang.org/x/tour/gotour
26 | $ $GOPATH/bin/gotour
27 |
28 | ## Running the App Engine version locally
29 |
30 | To view the App Engine version of the slides while developing locally, install
31 | the [Go App Engine SDK](https://cloud.google.com/appengine/downloads?hl=en)
32 | and then:
33 |
34 | $ cd $GOPATH/src/golang.org/x/tour
35 | $ $SDK_PATH/dev_appserver.py .
36 |
37 | The App Engine version runs code examples against the service at play.golang.org.
38 | To verify changes to the code examples you must use your local toolchain to compile
39 | and run `gotour` locally.
40 |
41 | ## Contributing code
42 |
43 | Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
44 | before sending patches.
45 |
46 | Unless otherwise noted, the Go source files are distributed under
47 | the BSD-style license found in the LICENSE file.
48 |
--------------------------------------------------------------------------------
/content/content_test.go:
--------------------------------------------------------------------------------
1 | // Go语言的作者2016年版权所有
2 | // 此源代码的使用受BSD风格的约束
3 | // 可以在LICENSE文件中找到的许可证
4 |
5 | package content
6 |
7 | import (
8 | "bytes"
9 | "errors"
10 | "fmt"
11 | "io/ioutil"
12 | "os"
13 | "os/exec"
14 | "path/filepath"
15 | "strings"
16 | "testing"
17 | )
18 |
19 | // 测试内容文件中的所有.go文件是否构建并执行(不检查输出正确性)
20 | // 不构建包含字符串 "// +build no-build" 的文件
21 | // 不执行包含字符串 "// +build no-run" 的文件
22 | func TestContent(t *testing.T) {
23 | scratch, err := ioutil.TempDir("", "tour-content-test")
24 | if err != nil {
25 | t.Fatal(err)
26 | }
27 | defer os.RemoveAll(scratch)
28 |
29 | err = filepath.Walk(".", func(path string, fi os.FileInfo, err error) error {
30 | if filepath.Ext(path) != ".go" {
31 | return nil
32 | }
33 | if filepath.Base(path) == "content_test.go" {
34 | return nil
35 | }
36 | if err := testSnippet(t, path, scratch); err != nil {
37 | t.Errorf("%v: %v", path, err)
38 | }
39 | return nil
40 | })
41 | if err != nil {
42 | t.Error(err)
43 | }
44 | }
45 |
46 | func testSnippet(t *testing.T, path, scratch string) error {
47 | b, err := ioutil.ReadFile(path)
48 | if err != nil {
49 | return err
50 | }
51 |
52 | build := string(bytes.SplitN(b, []byte{'\n'}, 2)[0])
53 | if !strings.HasPrefix(build, "// +build ") {
54 | return errors.New("first line is not a +build comment")
55 | }
56 | if !strings.Contains(build, "OMIT") {
57 | return errors.New(`+build comment does not contain "OMIT"`)
58 | }
59 |
60 | if strings.Contains(build, "no-build") {
61 | return nil
62 | }
63 | bin := filepath.Join(scratch, filepath.Base(path)+".exe")
64 | out, err := exec.Command("go", "build", "-o", bin, path).CombinedOutput()
65 | if err != nil {
66 | return fmt.Errorf("build error: %v\noutput:\n%s", err, out)
67 | }
68 | defer os.Remove(bin)
69 |
70 | if strings.Contains(build, "no-run") {
71 | return nil
72 | }
73 | out, err = exec.Command(bin).CombinedOutput()
74 | if err != nil {
75 | return fmt.Errorf("%v\nOutput:\n%s", err, out)
76 | }
77 | return nil
78 | }
79 |
--------------------------------------------------------------------------------
/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("https://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 | "https://golang.org/": &fakeResult{
56 | "The Go Programming Language",
57 | []string{
58 | "https://golang.org/pkg/",
59 | "https://golang.org/cmd/",
60 | },
61 | },
62 | "https://golang.org/pkg/": &fakeResult{
63 | "Packages",
64 | []string{
65 | "https://golang.org/",
66 | "https://golang.org/cmd/",
67 | "https://golang.org/pkg/fmt/",
68 | "https://golang.org/pkg/os/",
69 | },
70 | },
71 | "https://golang.org/pkg/fmt/": &fakeResult{
72 | "Package fmt",
73 | []string{
74 | "https://golang.org/",
75 | "https://golang.org/pkg/",
76 | },
77 | },
78 | "https://golang.org/pkg/os/": &fakeResult{
79 | "Package os",
80 | []string{
81 | "https://golang.org/",
82 | "https://golang.org/pkg/",
83 | },
84 | },
85 | }
86 |
--------------------------------------------------------------------------------
/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 | package main
6 |
7 | import (
8 | "bufio"
9 | "bytes"
10 | "io"
11 | "log"
12 | "net/http"
13 | "os"
14 |
15 | _ "github.com/Go-zh/tools/playground"
16 | )
17 |
18 | func gaeMain() {
19 | prepContent = gaePrepContent
20 | socketAddr = gaeSocketAddr
21 |
22 | if err := initTour(".", "HTTPTransport"); err != nil {
23 | log.Fatal(err)
24 | }
25 |
26 | http.Handle("/", hstsHandler(rootHandler))
27 | http.Handle("/lesson/", hstsHandler(lessonHandler))
28 |
29 | registerStatic(".")
30 |
31 | port := os.Getenv("PORT")
32 | if port == "" {
33 | port = "8080"
34 | }
35 | log.Fatal(http.ListenAndServe(":"+port, nil))
36 | }
37 |
38 | // gaePrepContent returns a Reader that produces the content from the given
39 | // Reader, but strips the prefix "#appengine: " from each line. It also drops
40 | // any non-blank like that follows a series of 1 or more lines with the prefix.
41 | func gaePrepContent(in io.Reader) io.Reader {
42 | var prefix = []byte("#appengine: ")
43 | out, w := io.Pipe()
44 | go func() {
45 | r := bufio.NewReader(in)
46 | drop := false
47 | for {
48 | b, err := r.ReadBytes('\n')
49 | if err != nil && err != io.EOF {
50 | w.CloseWithError(err)
51 | return
52 | }
53 | if bytes.HasPrefix(b, prefix) {
54 | b = b[len(prefix):]
55 | drop = true
56 | } else if drop {
57 | if len(b) > 1 {
58 | b = nil
59 | }
60 | drop = false
61 | }
62 | if len(b) > 0 {
63 | w.Write(b)
64 | }
65 | if err == io.EOF {
66 | w.Close()
67 | return
68 | }
69 | }
70 | }()
71 | return out
72 | }
73 |
74 | // gaeSocketAddr returns the WebSocket handler address.
75 | // The App Engine version does not provide a WebSocket handler.
76 | func gaeSocketAddr() string { return "" }
77 |
78 | // hstsHandler wraps an http.HandlerFunc such that it sets the HSTS header.
79 | func hstsHandler(fn http.HandlerFunc) http.Handler {
80 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
81 | w.Header().Set("Strict-Transport-Security", "max-age=31536000; preload")
82 | fn(w, r)
83 | })
84 | }
85 |
--------------------------------------------------------------------------------
/template/index.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Go 语言之旅
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
Go 指南
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
53 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/partials/editor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
<
10 |
{{curPage}}/{{toc.lessons[lessonId].Pages.length}}
11 |
>
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/content/flowcontrol.article:
--------------------------------------------------------------------------------
1 | 流程控制语句:for、if、else、switch 和 defer
2 | 学习如何使用条件、循环、分支和推迟语句来控制代码的流程。
3 |
4 | Go 作者组编写,Go-zh 小组翻译。
5 | https://go-zh.org
6 |
7 | * for
8 |
9 | Go 只有一种循环结构:`for` 循环。
10 |
11 | 基本的 `for` 循环由三部分组成,它们用分号隔开:
12 |
13 | - 初始化语句:在第一次迭代前执行
14 | - 条件表达式:在每次迭代前求值
15 | - 后置语句:在每次迭代的结尾执行
16 |
17 | 初始化语句通常为一句短变量声明,该变量声明仅在 `for` 语句的作用域中可见。
18 |
19 | 一旦条件表达式的布尔值为 `false`,循环迭代就会终止。
20 |
21 | *注意*:和 C、Java、JavaScript 之类的语言不同,Go 的 for 语句后面的三个构成部分外没有小括号,
22 | 大括号 `{`}` 则是必须的。
23 |
24 | .play flowcontrol/for.go
25 |
26 | * for(续)
27 |
28 | 初始化语句和后置语句是可选的。
29 |
30 | .play flowcontrol/for-continued.go
31 |
32 | * for 是 Go 中的 “while”
33 |
34 | 此时你可以去掉分号,因为 C 的 `while` 在 Go 中叫做 `for`。
35 |
36 | .play flowcontrol/for-is-gos-while.go
37 |
38 | * 无限循环
39 |
40 | 如果省略循环条件,该循环就不会结束,因此无限循环可以写得很紧凑。
41 |
42 | .play flowcontrol/forever.go
43 |
44 | * if
45 |
46 | Go 的 `if` 语句与 `for` 循环类似,表达式外无需小括号 `(`)` ,而大括号 `{`}` 则是必须的。
47 |
48 | .play flowcontrol/if.go
49 |
50 | * if 的简短语句
51 |
52 | 同 `for` 一样, `if` 语句可以在条件表达式前执行一个简单的语句。
53 |
54 | 该语句声明的变量作用域仅在 `if` 之内。
55 |
56 | (在最后的 `return` 语句处使用 `v` 看看。)
57 |
58 | .play flowcontrol/if-with-a-short-statement.go
59 |
60 | * if 和 else
61 |
62 | 在 `if` 的简短语句中声明的变量同样可以在任何对应的 `else` 块中使用。
63 |
64 | (在 `main` 的 `fmt.Println` 调用开始前,两次对 `pow` 的调用均已执行并返回其各自的结果。)
65 |
66 | .play flowcontrol/if-and-else.go
67 |
68 | * 练习:循环与函数
69 |
70 | 为了练习函数与循环,我们来实现一个平方根函数:用牛顿法实现平方根函数。
71 |
72 | 计算机通常使用循环来计算 x 的平方根。从某个猜测的值 z 开始,我们可以根据 z² 与 x 的近似度来调整 z,产生一个更好的猜测:
73 |
74 | z -= (z*z - x) / (2*z)
75 |
76 | 重复调整的过程,猜测的结果会越来越精确,得到的答案也会尽可能接近实际的平方根。
77 |
78 | 在提供的 `func`Sqrt` 中实现它。无论输入是什么,对 z 的一个恰当的猜测为 1。
79 | 要开始,请重复计算 10 次并随之打印每次的 z 值。观察对于不同的值 x(1、2、3 ...),
80 | 你得到的答案是如何逼近结果的,猜测提升的速度有多快。
81 |
82 | 提示:用类型转换或浮点数语法来声明并初始化一个浮点数值:
83 |
84 | z := 1.0
85 | z := float64(1)
86 |
87 | 然后,修改循环条件,使得当值停止改变(或改变非常小)的时候退出循环。观察迭代次数大于还是小于 10。
88 | 尝试改变 z 的初始猜测,如 x 或 x/2。你的函数结果与标准库中的 [[https://go-zh.org/pkg/math/#Sqrt][math.Sqrt]] 接近吗?
89 |
90 | (*注:* 如果你对该算法的细节感兴趣,上面的 z² − x 是 z² 到它所要到达的值(即 x)的距离,
91 | 除以的 2z 为 z² 的导数,我们通过 z² 的变化速度来改变 z 的调整量。
92 | 这种通用方法叫做[[https://zh.wikipedia.org/wiki/%E7%89%9B%E9%A1%BF%E6%B3%95][牛顿法]]。
93 | 它对很多函数,特别是平方根而言非常有效。)
94 |
95 | .play flowcontrol/exercise-loops-and-functions.go
96 |
97 | * switch
98 |
99 | `switch` 是编写一连串 `if`-`else` 语句的简便方法。它运行第一个值等于条件表达式的 case 语句。
100 |
101 | Go 的 switch 语句类似于 C、C++、Java、JavaScript 和 PHP 中的,不过 Go 只运行选定的 case,而非之后所有的 case。
102 | 实际上,Go 自动提供了在这些语言中每个 case 后面所需的 `break` 语句。
103 | 除非以 `fallthrough` 语句结束,否则分支会自动终止。
104 | Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。
105 |
106 | .play flowcontrol/switch.go
107 |
108 | * switch 的求值顺序
109 |
110 | switch 的 case 语句从上到下顺次执行,直到匹配成功时停止。
111 |
112 | (例如,
113 |
114 | switch i {
115 | case 0:
116 | case f():
117 | }
118 |
119 | 在 `i==0` 时 `f` 不会被调用。)
120 |
121 | #appengine: *注意:* Go 练习场中的时间总是从 2009-11-10 23:00:00 UTC 开始,该值的意义留给读者去发现。
122 |
123 | .play flowcontrol/switch-evaluation-order.go
124 |
125 | * 没有条件的 switch
126 |
127 | 没有条件的 switch 同 `switch`true` 一样。
128 |
129 | 这种形式能将一长串 if-then-else 写得更加清晰。
130 |
131 | .play flowcontrol/switch-with-no-condition.go
132 |
133 | * defer
134 |
135 | defer 语句会将函数推迟到外层函数返回之后执行。
136 |
137 | 推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
138 |
139 | .play flowcontrol/defer.go
140 |
141 | * defer 栈
142 |
143 | 推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
144 |
145 | 更多关于 defer 语句的信息,请阅读[[http://blog.go-zh.org/defer-panic-and-recover][此博文]]。
146 |
147 | .play flowcontrol/defer-multi.go
148 |
149 | * 恭喜!
150 |
151 | 你已经完成了本课程!
152 |
153 | 你可以返回[[/list][模块]]列表看看接下来学习什么,也可以继续[[javascript:click('.next-page')][后面的课程]]。
154 |
--------------------------------------------------------------------------------
/content/welcome.article:
--------------------------------------------------------------------------------
1 | 欢迎!
2 | 学习使用本指南:包括如何在不同的课程间切换以及运行代码。
3 |
4 | Go 作者组编写,Go-zh 小组翻译。
5 | https://go-zh.org
6 |
7 | * Hello, 世界
8 |
9 | 欢迎来到 [[https://go-zh.org/][Go 编程语言]]指南。
10 |
11 | 点击页面左上角的 [[javascript:highlight(".logo")][Go 指南]]可以访问本指南的模块列表。
12 |
13 | 你可以随时点击页面右上角的[[javascript:highlightAndClick(".nav")][菜单]]来浏览内容列表。
14 |
15 | 本指南由一系列幻灯片贯穿始终,其中有一些练习需要你来完成。
16 |
17 | 你可以
18 |
19 | - 按[[javascript:highlight(".prev-page")]["上一页"]] 或 `PageUp` 键跳转到上一页,
20 | - 按[[javascript:highlight(".next-page")]["下一页"]] 或 `PageDown` 键跳转到下一页。
21 |
22 | 该指南可以进行交互。点击“[[javascript:highlightAndClick("#run")][运行]]”按钮(或按 `Shift+Enter`)可以在
23 | #appengine: 远程服务器上
24 | 你的电脑上
25 | 编译并执行程序。运行结果会显示在代码下面。
26 |
27 | 本指南中的示例程序展示了 Go 的各个方面,它们可以成为你积累经验的开始。
28 |
29 | 编辑程序并再次执行它。
30 |
31 | 注意当你点击[[javascript:highlightAndClick("#format")][格式化]](快捷键: `Ctrl-Enter`)时,
32 | 编辑器中的文本会被 [[https://go-zh.org/cmd/gofmt/][gofmt]] 工具格式化。
33 | 你可以点击[[javascript:highlightAndClick(".syntax-checkbox")][语法]]开启或关闭语法高亮。
34 |
35 | 如果你准备好了,请点击页面底部的[[javascript:highlightAndClick(".next-page")][右箭头]]或按 `PageDown` 键继续。
36 |
37 | .play welcome/hello.go
38 |
39 | * Go 本地化
40 |
41 | 本指南也有其它语言的版本:
42 |
43 | - [[https://go-tour-br.appspot.com/][Brazilian Portuguese — Português do Brasil]]
44 | - [[https://go-tour-ca.appspot.com/][Catalan — Català]]
45 | - [[https://tour.go-zh.org/][Simplified Chinese — 中文(简体)]]
46 | - [[https://go-tour-zh-tw.appspot.com/][Traditional Chinese — 中文(繁體)]]
47 | - [[https://go-tour-cz.appspot.com/][Czech — Česky]]
48 | - [[https://tour.golang.org/][English]]
49 | - [[https://go-tour-fr.appspot.com/][French — Français]]
50 | - [[https://go-tour-de.appspot.com/][German — Deutsch]]
51 | - [[https://go-tour-he.appspot.com/][Hebrew — עִבְרִית]]
52 | - [[https://go-tour-id2.appspot.com/][Indonesian — Bahasa Indonesia]]
53 | - [[https://go-tour-ita.appspot.com/][Italian — Italiano]]
54 | - [[https://go-tour-jp.appspot.com/][Japanese — 日本語]]
55 | - [[https://go-tour-kr.appspot.com/][Korean — 한국어]]
56 | - [[https://go-tour-ro.appspot.com/][Romanian — Română]]
57 | - [[https://go-tour-ru-ru.appspot.com/][Russian - Русский]]
58 | - [[https://gotour-es.appspot.com/][Spanish — Español]]
59 | - [[https://go-tour-th.appspot.com/][Thai - ภาษาไทย]]
60 | - [[https://go-tour-turkish.appspot.com/][Turkish - Türkçe]]
61 | - [[https://go-tour-ua.appspot.com/][Ukrainian — Українська]]
62 | - [[https://go-tour-uz.appspot.com/][Uzbek — Ўзбекча]]
63 |
64 | 点击[[javascript:highlightAndClick(".next-page")][“下一页”]]按钮或者按 `PageDown` 键继续。
65 |
66 | #appengine: * Go 离线版教程
67 | #appengine:
68 | #appengine: 本指南也可作为独立的程序使用,这样你无需访问互联网就能运行它。
69 | #appengine:
70 | #appengine: 独立的 tour 更快,它会在你自己的机器上构建并运行代码示例。
71 | #appengine:
72 | #appengine: 要在本地安装并运行此教程的中文版,请在命令行执行:
73 | #appengine:
74 | #appengine: go get -u github.com/Go-zh/tour
75 | #appengine: tour
76 |
77 | #appengine: 该程序会打开一个浏览器并显示你本地版本的教程。
78 | #appengine:
79 | #appengine: 当然,你也可以继续在线学习本教程。
80 | #appengine:
81 | #appengine: * Go 练习场
82 | #appengine:
83 | #appengine: 本指南构建在 [[https://play.golang.org/][Go 练习场]]上,它是一个运行在
84 | #appengine: [[https://golang.org/][golang.org]] 服务器上的一个 Web 服务。
85 | #appengine:
86 | #appengine: 该服务会接收一个 Go 程序,然后在沙盒中编译、链接并运行它,最后返回输出。
87 | #appengine:
88 | #appengine: 在练习场中运行的程序有一些限制:
89 | #appengine:
90 | #appengine: - 练习场中的时间始于 2009-11-10 23:00:00 UTC(此日期的含义留给读者自己去发现)。
91 | #appengine: 赋予程序确定的输出能让缓存程序更加容易。
92 | #appengine:
93 | #appengine: - 程序的执行时间、CPU 和内存的使用同样也有限制,并且程序不能访问外部网络中的主机。
94 | #appengine:
95 | #appengine: 练习场使用最新发布的 Go 的稳定版本。
96 | #appengine:
97 | #appengine: 参阅 [[https://blog.go-zh.org/playground][Go 练习场的内部机制]]了解更多信息。
98 |
99 | #appengine: .play welcome/sandbox.go
100 |
101 | * 恭喜!
102 |
103 | 你已经完成了本指南的第一部分!
104 |
105 | 现在点击[[javascript:highlightAndClick(".logo")][模块]]列表看看接下来要学些什么。
106 | 你也可以继续[[javascript:click('.next-page')][后面的课程]]。
107 |
--------------------------------------------------------------------------------
/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 https://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("https://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 | "https://golang.org/": &fakeResult{
108 | "The Go Programming Language",
109 | []string{
110 | "https://golang.org/pkg/",
111 | "https://golang.org/cmd/",
112 | },
113 | },
114 | "https://golang.org/pkg/": &fakeResult{
115 | "Packages",
116 | []string{
117 | "https://golang.org/",
118 | "https://golang.org/cmd/",
119 | "https://golang.org/pkg/fmt/",
120 | "https://golang.org/pkg/os/",
121 | },
122 | },
123 | "https://golang.org/pkg/fmt/": &fakeResult{
124 | "Package fmt",
125 | []string{
126 | "https://golang.org/",
127 | "https://golang.org/pkg/",
128 | },
129 | },
130 | "https://golang.org/pkg/os/": &fakeResult{
131 | "Package os",
132 | []string{
133 | "https://golang.org/",
134 | "https://golang.org/pkg/",
135 | },
136 | },
137 | }
138 |
--------------------------------------------------------------------------------
/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 | if (val) storage.set(file().Hash, val);
26 | });
27 | });
28 |
29 | $scope.toc = toc;
30 | $scope.lessonId = $routeParams.lessonId;
31 | $scope.curPage = parseInt($routeParams.pageNumber);
32 | $scope.curFile = 0;
33 | $scope.job = null;
34 |
35 | $scope.nextPageClick = function(event) {
36 | event.preventDefault();
37 | $scope.nextPage();
38 | };
39 | $scope.prevPageClick = function(event) {
40 | event.preventDefault();
41 | $scope.prevPage();
42 | };
43 | $scope.nextPage = function() {
44 | $scope.gotoPage($scope.curPage + 1);
45 | };
46 | $scope.prevPage = function() {
47 | $scope.gotoPage($scope.curPage - 1);
48 | };
49 | $scope.gotoPage = function(page) {
50 | $scope.kill();
51 | var l = $routeParams.lessonId;
52 | if (page >= 1 && page <= lessons[$scope.lessonId].Pages.length) {
53 | $scope.curPage = page;
54 | } else {
55 | l = (page < 1) ? toc.prevLesson(l) : toc.nextLesson(l);
56 | if (l === '') { // If there's not previous or next
57 | $location.path('/list');
58 | return;
59 | }
60 | page = (page < 1) ? lessons[l].Pages.length : 1;
61 | }
62 | $location.path('/' + l + '/' + page);
63 | $scope.openFile($scope.curFile);
64 | analytics.trackView();
65 | };
66 | $scope.openFile = function(file) {
67 | $scope.curFile = file;
68 | editor.paint();
69 | };
70 |
71 | function log(mode, text) {
72 | $('.output.active').html('' + text + ' ');
73 | }
74 |
75 | function clearOutput() {
76 | $('.output.active').html('');
77 | }
78 |
79 | function file() {
80 | return lessons[$scope.lessonId].Pages[$scope.curPage - 1].Files[$scope.curFile];
81 | }
82 |
83 | $scope.run = function() {
84 | log('info', i18n.l('waiting'));
85 | var f = file();
86 | $scope.job = run(f.Content, $('.output.active > pre')[0], {
87 | path: f.Name
88 | }, function() {
89 | $scope.job = null;
90 | $scope.$apply();
91 | });
92 | };
93 |
94 | $scope.kill = function() {
95 | if ($scope.job !== null) $scope.job.Kill();
96 | };
97 |
98 | $scope.format = function() {
99 | log('info', i18n.l('waiting'));
100 | fmt(file().Content, editor.imports).then(
101 | function(data) {
102 | if (data.data.Error !== '') {
103 | log('stderr', data.data.Error);
104 | return;
105 | }
106 | clearOutput();
107 | file().Content = data.data.Body;
108 | },
109 | function(error) {
110 | log('stderr', error);
111 | });
112 | };
113 |
114 | $scope.reset = function() {
115 | file().Content = file().OrigContent;
116 | };
117 | }
118 | ]);
119 |
--------------------------------------------------------------------------------
/content/basics.article:
--------------------------------------------------------------------------------
1 | 包、变量和函数
2 | 学习 Go 程序的基本结构。
3 |
4 | Go 作者组编写,Go-zh 小组翻译。
5 | https://go-zh.org
6 |
7 | * 包
8 |
9 | 每个 Go 程序都是由包构成的。
10 |
11 | 程序从 `main` 包开始运行。
12 |
13 | 本程序通过导入路径 `"fmt"` 和 `"math/rand"` 来使用这两个包。
14 |
15 | 按照约定,包名与导入路径的最后一个元素一致。例如,`"math/rand"` 包中的源码均以 `package`rand` 语句开始。
16 |
17 | #appengine: *注意:* 此程序的运行环境是固定的,因此 `rand.Intn` 总是会返回相同的数字。
18 | #appengine:
19 | #appengine: (要得到不同的数字,需为生成器提供不同的种子数,参见
20 | #appengine: [[https://go-zh.org/pkg/math/rand/#Seed][`rand.Seed`]]。
21 | #appengine: 练习场中的时间为常量,因此你需要用其它的值作为种子数。)
22 |
23 | .play basics/packages.go
24 |
25 | * 导入
26 |
27 | 此代码用圆括号组合了导入,这是“分组”形式的导入语句。
28 |
29 | 当然你也可以编写多个导入语句,例如:
30 |
31 | import "fmt"
32 | import "math"
33 |
34 | 不过使用分组导入语句是更好的形式。
35 |
36 | .play basics/imports.go
37 |
38 | * 导出名
39 |
40 | 在 Go 中,如果一个名字以大写字母开头,那么它就是已导出的。例如,`Pizza` 就是个已导出名,`Pi` 也同样,它导出自 `math` 包。
41 |
42 | `pizza` 和 `pi` 并未以大写字母开头,所以它们是未导出的。
43 |
44 | 在导入一个包时,你只能引用其中已导出的名字。任何“未导出”的名字在该包外均无法访问。
45 |
46 | 执行代码,观察错误输出。
47 |
48 | 然后将 `math.pi` 改名为 `math.Pi` 再试着执行一次。
49 |
50 | .play basics/exported-names.go
51 |
52 | * 函数
53 |
54 | 函数可以没有参数或接受多个参数。
55 |
56 | 在本例中,`add` 接受两个 `int` 类型的参数。
57 |
58 | 注意类型在变量名 *之后*。
59 |
60 | (参考 [[http://blog.go-zh.org/gos-declaration-syntax][这篇关于 Go 语法声明的文章]]了解这种类型声明形式出现的原因。)
61 |
62 | .play basics/functions.go
63 |
64 | * 函数(续)
65 |
66 | 当连续两个或多个函数的已命名形参类型相同时,除最后一个类型以外,其它都可以省略。
67 |
68 | 在本例中,
69 |
70 | x int, y int
71 |
72 | 被缩写为
73 |
74 | x, y int
75 |
76 | .play basics/functions-continued.go
77 |
78 | * 多值返回
79 |
80 | 函数可以返回任意数量的返回值。
81 |
82 | `swap` 函数返回了两个字符串。
83 |
84 | .play basics/multiple-results.go
85 |
86 | * 命名返回值
87 |
88 | Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。
89 |
90 | 返回值的名称应当具有一定的意义,它可以作为文档使用。
91 |
92 | 没有参数的 `return` 语句返回已命名的返回值。也就是 `直接` 返回。
93 |
94 | 直接返回语句应当仅用在下面这样的短函数中。在长的函数中它们会影响代码的可读性。
95 |
96 | .play basics/named-results.go
97 |
98 | * 变量
99 |
100 | `var` 语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。
101 |
102 | 就像在这个例子中看到的一样,`var` 语句可以出现在包或函数级别。
103 |
104 | .play basics/variables.go
105 |
106 | * 变量的初始化
107 |
108 | 变量声明可以包含初始值,每个变量对应一个。
109 |
110 | 如果初始化值已存在,则可以省略类型;变量会从初始值中获得类型。
111 |
112 | .play basics/variables-with-initializers.go
113 |
114 | * 短变量声明
115 |
116 | 在函数中,简洁赋值语句 `:=` 可在类型明确的地方代替 `var` 声明。
117 |
118 | 函数外的每个语句都必须以关键字开始(`var`, `func` 等等),因此 `:=` 结构不能在函数外使用。
119 |
120 | .play basics/short-variable-declarations.go
121 |
122 | * 基本类型
123 |
124 | Go 的基本类型有
125 |
126 | bool
127 |
128 | string
129 |
130 | int int8 int16 int32 int64
131 | uint uint8 uint16 uint32 uint64 uintptr
132 |
133 | byte // uint8 的别名
134 |
135 | rune // int32 的别名
136 | // 表示一个 Unicode 码点
137 |
138 | float32 float64
139 |
140 | complex64 complex128
141 |
142 | 本例展示了几种类型的变量。
143 | 同导入语句一样,变量声明也可以“分组”成一个语法块。
144 |
145 | `int`, `uint` 和 `uintptr` 在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽。
146 | 当你需要一个整数值时应使用 `int` 类型,除非你有特殊的理由使用固定大小或无符号的整数类型。
147 |
148 | .play basics/basic-types.go
149 |
150 | * 零值
151 |
152 | 没有明确初始值的变量声明会被赋予它们的 *零值*。
153 |
154 | 零值是:
155 |
156 | - 数值类型为 `0`,
157 | - 布尔类型为 `false`,
158 | - 字符串为 `""`(空字符串)。
159 |
160 | .play basics/zero.go
161 |
162 | * 类型转换
163 |
164 | 表达式 `T(v)` 将值 `v` 转换为类型 `T`。
165 |
166 | 一些关于数值的转换:
167 |
168 | var i int = 42
169 | var f float64 = float64(i)
170 | var u uint = uint(f)
171 |
172 | 或者,更加简单的形式:
173 |
174 | i := 42
175 | f := float64(i)
176 | u := uint(f)
177 |
178 | 与 C 不同的是,Go 在不同类型的项之间赋值时需要显式转换。试着移除例子中 `float64` 或 `uint` 的转换看看会发生什么。
179 |
180 | .play basics/type-conversions.go
181 |
182 | * 类型推导
183 |
184 | 在声明一个变量而不指定其类型时(即使用不带类型的 `:=` 语法或 `var`=` 表达式语法),变量的类型由右值推导得出。
185 |
186 | 当右值声明了类型时,新变量的类型与其相同:
187 |
188 | var i int
189 | j := i // j 也是一个 int
190 |
191 | 不过当右边包含未指明类型的数值常量时,新变量的类型就可能是 `int`, `float64` 或 `complex128` 了,这取决于常量的精度:
192 |
193 | i := 42 // int
194 | f := 3.142 // float64
195 | g := 0.867 + 0.5i // complex128
196 |
197 | 尝试修改示例代码中 `v` 的初始值,并观察它是如何影响类型的。
198 |
199 | .play basics/type-inference.go
200 |
201 | * 常量
202 |
203 | 常量的声明与变量类似,只不过是使用 `const` 关键字。
204 |
205 | 常量可以是字符、字符串、布尔值或数值。
206 |
207 | 常量不能用 `:=` 语法声明。
208 |
209 | .play basics/constants.go
210 |
211 | * 数值常量
212 |
213 | 数值常量是高精度的 *值*。
214 |
215 | 一个未指定类型的常量由上下文来决定其类型。
216 |
217 | 再尝试一下输出 `needInt(Big)` 吧。
218 |
219 | (`int` 类型最大可以存储一个 64 位的整数,有时会更小。)
220 |
221 | (`int` 可以存放最大64位的整数,根据平台不同有时会更少。)
222 |
223 | .play basics/numeric-constants.go
224 |
225 | * 恭喜!
226 |
227 | 你已经完成了本课程!
228 |
229 | 你可以返回[[/list][模块]]列表看看接下来要学什么,或者继续[[javascript:click('.next-page')][后面的课程]]。
230 |
--------------------------------------------------------------------------------
/content/concurrency.article:
--------------------------------------------------------------------------------
1 | 并发
2 | Go 将并发结构作为核心语言的一部分提供。本节课程通过一些示例介绍并展示了它们的用法。
3 |
4 | Go 作者组编写,Go-zh 小组翻译。
5 | https://go-zh.org
6 |
7 | * Go 程
8 |
9 | Go 程(goroutine)是由 Go 运行时管理的轻量级线程。
10 |
11 | go f(x, y, z)
12 |
13 | 会启动一个新的 Go 程并执行
14 |
15 | f(x, y, z)
16 |
17 | `f`, `x`, `y` 和 `z` 的求值发生在当前的 Go 程中,而 `f` 的执行发生在新的 Go 程中。
18 |
19 | Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。[[https://go-zh.org/pkg/sync/][`sync`]] 包提供了这种能力,不过在 Go 中并不经常用到,因为还有其它的办法(见下一页)。
20 |
21 | .play concurrency/goroutines.go
22 |
23 | * 信道
24 |
25 | 信道是带有类型的管道,你可以通过它用信道操作符 `<-` 来发送或者接收值。
26 |
27 | ch <- v // 将 v 发送至信道 ch。
28 | v := <-ch // 从 ch 接收值并赋予 v。
29 |
30 | (“箭头”就是数据流的方向。)
31 |
32 | 和映射与切片一样,信道在使用前必须创建:
33 |
34 | ch := make(chan int)
35 |
36 | 默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。
37 |
38 | 以下示例对切片中的数进行求和,将任务分配给两个 Go 程。一旦两个 Go 程完成了它们的计算,它就能算出最终的结果。
39 |
40 | .play concurrency/channels.go
41 |
42 | * 带缓冲的信道
43 |
44 | 信道可以是 _带缓冲的_。将缓冲长度作为第二个参数提供给 `make` 来初始化一个带缓冲的信道:
45 |
46 | ch := make(chan int, 100)
47 |
48 | 仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。
49 |
50 | 修改示例填满缓冲区,然后看看会发生什么。
51 |
52 | .play concurrency/buffered-channels.go
53 |
54 | * range 和 close
55 |
56 | 发送者可通过 `close` 关闭一个信道来表示没有需要发送的值了。接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,那么在执行完
57 |
58 | v, ok := <-ch
59 |
60 | 此时 `ok` 会被设置为 `false`。
61 |
62 | 循环 `for`i`:=`range`c` 会不断从信道接收值,直到它被关闭。
63 |
64 | *注意:* 只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序恐慌(panic)。
65 |
66 | *还要注意:* 信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有需要发送的值时才有必要关闭,例如终止一个 `range` 循环。
67 |
68 | .play concurrency/range-and-close.go
69 |
70 | * select 语句
71 |
72 | `select` 语句使一个 Go 程可以等待多个通信操作。
73 |
74 | `select` 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。
75 |
76 | .play concurrency/select.go
77 |
78 | * 默认选择
79 |
80 | 当 `select` 中的其它分支都没有准备好时,`default` 分支就会执行。
81 |
82 | 为了在尝试发送或者接收时不发生阻塞,可使用 `default` 分支:
83 |
84 | select {
85 | case i := <-c:
86 | // 使用 i
87 | default:
88 | // 从 c 中接收会阻塞时执行
89 | }
90 |
91 | .play concurrency/default-selection.go
92 |
93 | * 练习:等价二叉查找树
94 |
95 | 不同二叉树的叶节点上可以保存相同的值序列。例如,以下两个二叉树都保存了序列 `1,1,2,3,5,8,13`。
96 |
97 | .image /content/img/tree.png
98 |
99 | 在大多数语言中,检查两个二叉树是否保存了相同序列的函数都相当复杂。
100 | 我们将使用 Go 的并发和信道来编写一个简单的解法。
101 |
102 | 本例使用了 `tree` 包,它定义了类型:
103 |
104 | type Tree struct {
105 | Left *Tree
106 | Value int
107 | Right *Tree
108 | }
109 |
110 | 点击[[javascript:click('.next-page')][下一页]]继续。
111 |
112 | * 练习:等价二叉查找树
113 |
114 | *1.* 实现 `Walk` 函数。
115 |
116 | *2.* 测试 `Walk` 函数。
117 |
118 | 函数 `tree.New(k)` 用于构造一个随机结构的已排序二叉查找树,它保存了值 `k`, `2k`, `3k`, ..., `10k`。
119 |
120 | 创建一个新的信道 `ch` 并且对其进行步进:
121 |
122 | go Walk(tree.New(1), ch)
123 |
124 | 然后从信道中读取并打印 10 个值。应当是数字 `1,`2,`3,`...,`10`。
125 |
126 | *3.* 用 `Walk` 实现 `Same` 函数来检测 `t1` 和 `t2` 是否存储了相同的值。
127 |
128 | *4.* 测试 `Same` 函数。
129 |
130 | `Same(tree.New(1),`tree.New(1))` 应当返回 `true`,而 `Same(tree.New(1),`tree.New(2))` 应当返回 `false`。
131 |
132 | `Tree` 的文档可在[[https://godoc.org/golang.org/x/tour/tree#Tree][这里]]找到。
133 |
134 | .play concurrency/exercise-equivalent-binary-trees.go
135 |
136 | * sync.Mutex
137 |
138 | 我们已经看到信道非常适合在各个 Go 程间进行通信。
139 |
140 | 但是如果我们并不需要通信呢?比如说,若我们只是想保证每次只有一个 Go 程能够访问一个共享的变量,从而避免冲突?
141 |
142 | 这里涉及的概念叫做 *互斥(mutual*exclusion)* ,我们通常使用 *互斥锁(Mutex)* 这一数据结构来提供这种机制。
143 |
144 | Go 标准库中提供了 [[https://go-zh.org/pkg/sync/#Mutex][`sync.Mutex`]] 互斥锁类型及其两个方法:
145 |
146 | - `Lock`
147 | - `Unlock`
148 |
149 | 我们可以通过在代码前调用 `Lock` 方法,在代码后调用 `Unlock` 方法来保证一段代码的互斥执行。参见 `Inc` 方法。
150 |
151 | 我们也可以用 `defer` 语句来保证互斥锁一定会被解锁。参见 `Value` 方法。
152 |
153 | .play concurrency/mutex-counter.go
154 |
155 | * 练习:Web 爬虫
156 |
157 | 在这个练习中,我们将会使用 Go 的并发特性来并行化一个 Web 爬虫。
158 |
159 | 修改 `Crawl` 函数来并行地抓取 URL,并且保证不重复。
160 |
161 | _提示_:你可以用一个 map 来缓存已经获取的 URL,但是要注意 map 本身并不是并发安全的!
162 |
163 | .play concurrency/exercise-web-crawler.go
164 |
165 | * 接下来去哪?
166 |
167 | #appengine: 你可以从[[https://go-zh.org/doc/install/][安装 Go]] 开始。
168 |
169 | #appengine: 一旦安装了 Go,Go
170 | Go
171 | [[https://go-zh.org/doc/][文档]]是一个极好的
172 | #appengine: 应当继续阅读的内容。
173 | 开始。
174 | 它包含了参考、指南、视频等等更多资料。
175 |
176 | 了解如何组织 Go 代码并在其上工作,参阅[[https://www.youtube.com/watch?v=XCsL89YtqCs][此视频]],或者阅读[[https://go-zh.org/doc/code.html][如何编写 Go 代码]]。
177 |
178 | 如果你需要标准库方面的帮助,请参考[[https://go-zh.org/pkg/][包手册]]。如果是语言本身的帮助,阅读[[https://go-zh.org/ref/spec][语言规范]]是件令人愉快的事情。
179 |
180 | 进一步探索 Go 的并发模型,参阅 [[https://www.youtube.com/watch?v=f6kdp27TYZs][Go 并发模型]]([[https://talks.go-zh.org/2012/concurrency.slide][幻灯片]])以及[[https://www.youtube.com/watch?v=QDDwwePbDtw][深入 Go 并发模型]]([[https://talks.go-zh.org/2013/advconc.slide][幻灯片]])并阅读[[https://go-zh.org/doc/codewalk/sharemem/][通过通信共享内存]]的代码之旅。
181 |
182 | 想要开始编写 Web 应用,请参阅[[https://vimeo.com/53221558][一个简单的编程环境]]([[https://talks.go-zh.org/2012/simple.slide][幻灯片]])并阅读[[https://go-zh.org/doc/articles/wiki/][编写 Web 应用]]的指南。
183 |
184 | [[https://go-zh.org/doc/codewalk/functions/][函数:Go 中的一等公民]]展示了有趣的函数类型。
185 |
186 | [[https://blog.go-zh.org/][Go 博客]]有着众多关于 Go 的文章和信息。
187 |
188 | [[https://www.mikespook.com/tag/golang/][mikespook 的博客]]中有大量中文的关于 Go 的文章和翻译。
189 |
190 | 开源电子书 [[https://github.com/astaxie/build-web-application-with-golang][Go Web 编程]]和 [[https://github.com/Unknwon/the-way-to-go_ZH_CN][Go 入门指南]]能够帮助你更加深入的了解和学习 Go 语言。
191 |
192 | 访问 [[https://go-zh.org][go-zh.org]] 了解更多内容。
193 |
--------------------------------------------------------------------------------
/content/moretypes.article:
--------------------------------------------------------------------------------
1 | 更多类型:struct、slice 和映射
2 | 学习如何基于现有类型定义新的类型:本节课涵盖了结构体、数组、切片和映射。
3 |
4 | Go 作者组编写,Go-zh 小组翻译。
5 | https://go-zh.org
6 |
7 | * 指针
8 |
9 | Go 拥有指针。指针保存了值的内存地址。
10 |
11 | 类型 `*T` 是指向 `T` 类型值的指针。其零值为 `nil`。
12 |
13 | var p *int
14 |
15 | `&` 操作符会生成一个指向其操作数的指针。
16 |
17 | i := 42
18 | p = &i
19 |
20 | `*` 操作符表示指针指向的底层值。
21 |
22 | fmt.Println(*p) // 通过指针 p 读取 i
23 | *p = 21 // 通过指针 p 设置 i
24 |
25 | 这也就是通常所说的“间接引用”或“重定向”。
26 |
27 | 与 C 不同,Go 没有指针运算。
28 |
29 | .play moretypes/pointers.go
30 |
31 | * 结构体
32 |
33 | 一个结构体(`struct`)就是一组字段(field)。
34 |
35 | .play moretypes/structs.go
36 |
37 | * 结构体字段
38 |
39 | 结构体字段使用点号来访问。
40 |
41 | .play moretypes/struct-fields.go
42 |
43 | * 结构体指针
44 |
45 | 结构体字段可以通过结构体指针来访问。
46 |
47 | 如果我们有一个指向结构体的指针 `p`,那么可以通过 `(*p).X` 来访问其字段 `X`。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 `p.X` 就可以。
48 |
49 | .play moretypes/struct-pointers.go
50 |
51 | * 结构体文法
52 |
53 | 结构体文法通过直接列出字段的值来新分配一个结构体。
54 |
55 | 使用 `Name:` 语法可以仅列出部分字段。(字段名的顺序无关。)
56 |
57 | 特殊的前缀 `&` 返回一个指向结构体的指针。
58 |
59 | .play moretypes/struct-literals.go
60 |
61 |
62 | * 数组
63 |
64 | 类型 `[n]T` 表示拥有 `n` 个 `T` 类型的值的数组。
65 |
66 | 表达式
67 |
68 | var a [10]int
69 |
70 | 会将变量 `a` 声明为拥有 10 个整数的数组。
71 |
72 | 数组的长度是其类型的一部分,因此数组不能改变大小。这看起来是个限制,不过没关系,Go 提供了更加便利的方式来使用数组。
73 |
74 | .play moretypes/array.go
75 |
76 |
77 | * 切片
78 |
79 | 每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
80 |
81 | 类型 `[]T` 表示一个元素类型为 `T` 的切片。
82 |
83 | 切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
84 |
85 | a[low : high]
86 |
87 | 它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
88 |
89 | 以下表达式创建了一个切片,它包含 `a` 中下标从 1 到 3 的元素:
90 |
91 | a[1:4]
92 |
93 | .play moretypes/slices.go
94 |
95 |
96 | * 切片就像数组的引用
97 |
98 | 切片并不存储任何数据,它只是描述了底层数组中的一段。
99 |
100 | 更改切片的元素会修改其底层数组中对应的元素。
101 |
102 | 与它共享底层数组的切片都会观测到这些修改。
103 |
104 | .play moretypes/slices-pointers.go
105 |
106 |
107 | * 切片文法
108 |
109 | 切片文法类似于没有长度的数组文法。
110 |
111 | 这是一个数组文法:
112 |
113 | [3]bool{true, true, false}
114 |
115 | 下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片:
116 |
117 | []bool{true, true, false}
118 |
119 | .play moretypes/slice-literals.go
120 |
121 |
122 | * 切片的默认行为
123 |
124 | 在进行切片时,你可以利用它的默认行为来忽略上下界。
125 |
126 | 切片下界的默认值为 `0`,上界则是该切片的长度。
127 |
128 | 对于数组
129 |
130 | var a [10]int
131 |
132 | 来说,以下切片是等价的:
133 |
134 | a[0:10]
135 | a[:10]
136 | a[0:]
137 | a[:]
138 |
139 | .play moretypes/slice-bounds.go
140 |
141 |
142 | * 切片的长度与容量
143 |
144 | 切片拥有 *长度* 和 *容量*。
145 |
146 | 切片的长度就是它所包含的元素个数。
147 |
148 | 切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
149 |
150 | 切片 `s` 的长度和容量可通过表达式 `len(s)` 和 `cap(s)` 来获取。
151 |
152 | 只要具有足够的容量,你就可以通过重新切片来扩展一个切片。请试着修改示例程序中的某个切片操作,使其长度超过容量(即将它扩展到超出其容量范围),看看会发生什么。
153 |
154 | .play moretypes/slice-len-cap.go
155 |
156 | * nil 切片
157 |
158 | 切片的零值是 `nil`。
159 |
160 | nil 切片的长度和容量为 0 且没有底层数组。
161 |
162 | .play moretypes/nil-slices.go
163 |
164 |
165 | * 用 make 创建切片
166 |
167 | 切片可以用内建函数 `make` 来创建,这也是你创建动态数组的方式。
168 |
169 | `make` 函数会分配一个元素为零值的数组并返回一个引用了它的切片:
170 |
171 | a := make([]int, 5) // len(a)=5
172 |
173 | 要指定它的容量,需向 `make` 传入第三个参数:
174 |
175 | b := make([]int, 0, 5) // len(b)=0, cap(b)=5
176 |
177 | b = b[:cap(b)] // len(b)=5, cap(b)=5
178 | b = b[1:] // len(b)=4, cap(b)=4
179 |
180 | .play moretypes/making-slices.go
181 |
182 | * 切片的切片
183 |
184 | 切片可包含任何类型,甚至包括其它的切片。
185 |
186 | .play moretypes/slices-of-slice.go
187 |
188 |
189 | * 向切片追加元素
190 |
191 | 为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 `append` 函数。内建函数的[[https://go-zh.org/pkg/builtin/#append][文档]]对此函数有详细的介绍。
192 |
193 | func append(s []T, vs ...T) []T
194 |
195 | `append` 的第一个参数 `s` 是一个元素类型为 `T` 的切片,其余类型为 `T` 的值将会追加到该切片的末尾。
196 |
197 | `append` 的结果是一个包含原切片所有元素加上新添加元素的切片。
198 |
199 | 当 `s` 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
200 |
201 | (要了解关于切片的更多内容,请阅读文章 [[https://blog.go-zh.org/go-slices-usage-and-internals][Go 切片:用法和本质]]。)
202 |
203 | .play moretypes/append.go
204 |
205 |
206 | * Range
207 |
208 | `for` 循环的 `range` 形式可遍历切片或映射。
209 |
210 | 当使用 `for` 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
211 |
212 | .play moretypes/range.go
213 |
214 | * range(续)
215 |
216 | 可以将下标或值赋予 `_` 来忽略它。
217 |
218 | for i, _ := range pow
219 | for _, value := range pow
220 |
221 | 若你只需要索引,忽略第二个变量即可。
222 |
223 | for i := range pow
224 |
225 | .play moretypes/range-continued.go
226 |
227 | * 练习:切片
228 |
229 | 实现 `Pic`。它应当返回一个长度为 `dy` 的切片,其中每个元素是一个长度为 `dx`,元素类型为 `uint8` 的切片。当你运行此程序时,它会将每个整数解释为灰度值(好吧,其实是蓝度值)并显示它所对应的图像。
230 |
231 | 图像的选择由你来定。几个有趣的函数包括 `(x+y)/2`, `x*y`, `x^y`, `x*log(y)` 和 `x%(y+1)`。
232 |
233 | (提示:需要使用循环来分配 `[][]uint8` 中的每个 `[]uint8`;请使用 `uint8(intValue)` 在类型之间转换;你可能会用到 `math` 包中的函数。)
234 |
235 | .play moretypes/exercise-slices.go
236 |
237 | * 映射 (`map`)
238 |
239 | 映射将键映射到值。
240 |
241 | 映射的零值为 `nil` 。`nil` 映射既没有键,也不能添加键。
242 |
243 | `make` 函数会返回给定类型的映射,并将其初始化备用。
244 |
245 | .play moretypes/maps.go
246 |
247 | * 映射的文法
248 |
249 | 映射的文法与结构体相似,不过必须有键名。
250 |
251 | .play moretypes/map-literals.go
252 |
253 | * 映射的文法(续)
254 |
255 | 若顶级类型只是一个类型名,你可以在文法的元素中省略它。
256 |
257 | .play moretypes/map-literals-continued.go
258 |
259 | * 修改映射
260 |
261 | 在映射 `m` 中插入或修改元素:
262 |
263 | m[key] = elem
264 |
265 | 获取元素:
266 |
267 | elem = m[key]
268 |
269 | 删除元素:
270 |
271 | delete(m, key)
272 |
273 | 通过双赋值检测某个键是否存在:
274 |
275 | elem, ok = m[key]
276 |
277 | 若 `key` 在 `m` 中,`ok` 为 `true` ;否则,`ok` 为 `false`。
278 |
279 | 若 `key` 不在映射中,那么 `elem` 是该映射元素类型的零值。
280 |
281 | 同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。
282 |
283 | *注* :若 `elem` 或 `ok` 还未声明,你可以使用短变量声明:
284 |
285 | elem, ok := m[key]
286 |
287 | .play moretypes/mutating-maps.go
288 |
289 | * 练习:映射
290 |
291 | 实现 `WordCount`。它应当返回一个映射,其中包含字符串 `s` 中每个“单词”的个数。函数 `wc.Test` 会对此函数执行一系列测试用例,并输出成功还是失败。
292 |
293 | 你会发现 [[https://go-zh.org/pkg/strings/#Fields][strings.Fields]] 很有帮助。
294 |
295 | .play moretypes/exercise-maps.go
296 |
297 | * 函数值
298 |
299 | 函数也是值。它们可以像其它值一样传递。
300 |
301 | 函数值可以用作函数的参数或返回值。
302 |
303 | .play moretypes/function-values.go
304 |
305 | * 函数的闭包
306 |
307 | Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。
308 |
309 | 例如,函数 `adder` 返回一个闭包。每个闭包都被绑定在其各自的 `sum` 变量上。
310 |
311 | .play moretypes/function-closures.go
312 |
313 | * 练习:斐波纳契闭包
314 |
315 | 让我们用函数做些好玩的事情。
316 |
317 | 实现一个 `fibonacci` 函数,它返回一个函数(闭包),该闭包返回一个[[https://zh.wikipedia.org/wiki/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97][斐波纳契数列]] `(0, 1, 1, 2, 3, 5, ...)`。
318 |
319 | .play moretypes/exercise-fibonacci-closure.go
320 |
321 | * 恭喜!
322 |
323 | 你已经完成了本课程!
324 |
325 | 你可以返回[[/list][模块列表]]看看接下来学习什么,或者继续[[javascript:click('.next-page')][后面的课程]]。
326 |
--------------------------------------------------------------------------------
/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 提供了并发的特性。
这一部分概览了 goroutine 和 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 | 'submit-feedback': '汇报页面上的问题',
50 |
51 | // GitHub issue template: update repo and messaging when translating.
52 | 'github-repo': 'github.com/Go-zh/tour',
53 | 'issue-title': '[简单描述一下要汇报的问题]',
54 | 'issue-message': '请修改上方的标题来简要描述要汇报的问题,并把详细的内容写在这里,如果可能的话请附上源代码',
55 | 'context': '上下文',
56 | }).
57 |
58 | // Config for codemirror plugin
59 | value('ui.config', {
60 | codemirror: {
61 | mode: 'text/x-go',
62 | matchBrackets: true,
63 | lineNumbers: true,
64 | autofocus: true,
65 | indentWithTabs: true,
66 | indentUnit: 4,
67 | tabSize: 4,
68 | lineWrapping: true,
69 | extraKeys: {
70 | 'Shift-Enter': function() {
71 | $('#run').click();
72 | },
73 | 'Ctrl-Enter': function() {
74 | $('#format').click();
75 | },
76 | 'PageDown': function() {
77 | return false;
78 | },
79 | 'PageUp': function() {
80 | return false;
81 | },
82 | },
83 | // TODO: is there a better way to do this?
84 | // AngularJS values can't depend on factories.
85 | onChange: function() {
86 | if (window.codeChanged !== null) window.codeChanged();
87 | }
88 | }
89 | }).
90 |
91 | // mapping from the old paths (#42) to the new organization.
92 | // The values have been generated with the map.sh script in the tools directory.
93 | value('mapping', {
94 | '#1': '/welcome/1', // Hello, 世界
95 | '#2': '/welcome/2', // Go local
96 | '#3': '/basics/1', // Packages
97 | '#4': '/basics/2', // Imports
98 | '#5': '/basics/3', // Exported names
99 | '#6': '/basics/4', // Functions
100 | '#7': '/basics/5', // Functions continued
101 | '#8': '/basics/6', // Multiple results
102 | '#9': undefined, // Named results
103 | '#10': '/basics/8', // Variables
104 | '#11': '/basics/9', // Variables with initializers
105 | '#12': '/basics/10', // Short variable declarations
106 | '#13': '/basics/11', // Basic types
107 | '#14': '/basics/13', // Type conversions
108 | '#15': '/basics/15', // Constants
109 | '#16': '/basics/16', // Numeric Constants
110 | '#17': '/flowcontrol/1', // For
111 | '#18': '/flowcontrol/2', // For continued
112 | '#19': '/flowcontrol/3', // For is Go's "while"
113 | '#20': '/flowcontrol/4', // Forever
114 | '#21': '/flowcontrol/5', // If
115 | '#22': '/flowcontrol/6', // If with a short statement
116 | '#23': '/flowcontrol/7', // If and else
117 | '#24': '/flowcontrol/8', // Exercise: Loops and Functions
118 | '#25': '/moretypes/2', // Structs
119 | '#26': '/moretypes/3', // Struct Fields
120 | '#27': '/moretypes/1', // Pointers
121 | '#28': '/moretypes/5', // Struct Literals
122 | '#29': undefined, // The new function
123 | '#30': '/moretypes/6', // Arrays
124 | '#31': '/moretypes/7', // Slices
125 | '#32': '/moretypes/8', // Slicing slices
126 | '#33': '/moretypes/9', // Making slices
127 | '#34': '/moretypes/10', // Nil slices
128 | '#35': '/moretypes/12', // Range
129 | '#36': '/moretypes/13', // Range continued
130 | '#37': '/moretypes/14', // Exercise: Slices
131 | '#38': '/moretypes/15', // Maps
132 | '#39': '/moretypes/16', // Map literals
133 | '#40': '/moretypes/17', // Map literals continued
134 | '#41': '/moretypes/18', // Mutating Maps
135 | '#42': '/moretypes/19', // Exercise: Maps
136 | '#43': '/moretypes/20', // Function values
137 | '#44': '/moretypes/21', // Function closures
138 | '#45': '/moretypes/22', // Exercise: Fibonacci closure
139 | '#46': '/flowcontrol/9', // Switch
140 | '#47': '/flowcontrol/10', // Switch evaluation order
141 | '#48': '/flowcontrol/11', // Switch with no condition
142 | '#49': undefined, // Advanced Exercise: Complex cube roots
143 | '#50': undefined, // Methods and Interfaces
144 | '#51': '/methods/1', // Methods
145 | '#52': '/methods/2', // Methods continued
146 | '#53': '/methods/3', // Methods with pointer receivers
147 | '#54': '/methods/4', // Interfaces
148 | '#55': '/methods/5', // Interfaces are satisfied implicitly
149 | '#56': '/methods/8', // Errors
150 | '#57': '/methods/9', // Exercise: Errors
151 | '#58': '/methods/13', // Web servers
152 | '#59': '/methods/14', // Exercise: HTTP Handlers
153 | '#60': '/methods/15', // Images
154 | '#61': '/methods/16', // Exercise: Images
155 | '#62': undefined, // Exercise: Rot13 Reader
156 | '#63': undefined, // Concurrency
157 | '#64': '/concurrency/1', // Goroutines
158 | '#65': '/concurrency/2', // Channels
159 | '#66': '/concurrency/3', // Buffered Channels
160 | '#67': '/concurrency/4', // Range and Close
161 | '#68': '/concurrency/5', // Select
162 | '#69': '/concurrency/6', // Default Selection
163 | '#70': '/concurrency/7', // Exercise: Equivalent Binary Trees
164 | '#71': '/concurrency/8', // Exercise: Equivalent Binary Trees
165 | '#72': '/concurrency/9', // Exercise: Web Crawler
166 | '#73': '/concurrency/10', // Where to Go from here...
167 | });
168 |
--------------------------------------------------------------------------------
/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 | package main
6 |
7 | import (
8 | "flag"
9 | "fmt"
10 | "go/build"
11 | "io"
12 | "log"
13 | "net"
14 | "net/http"
15 | "net/url"
16 | "os"
17 | "os/exec"
18 | "path/filepath"
19 | "runtime"
20 | "strings"
21 | "time"
22 |
23 | "github.com/Go-zh/tools/playground/socket"
24 |
25 | // Imports so that go build/install automatically installs them.
26 | // For Go 1.5 and above, will use our vendored copy.
27 | _ "golang.org/x/tour/pic"
28 | _ "golang.org/x/tour/tree"
29 | _ "golang.org/x/tour/wc"
30 | )
31 |
32 | const (
33 | basePkg = "github.com/Go-zh/tour"
34 | socketPath = "/socket"
35 | )
36 |
37 | var (
38 | httpListen = flag.String("http", "127.0.0.1:3999", "host:port to listen on")
39 | openBrowser = flag.Bool("openbrowser", true, "open browser automatically")
40 | )
41 |
42 | var (
43 | // GOPATH containing the tour packages
44 | gopath = os.Getenv("GOPATH")
45 |
46 | httpAddr string
47 | )
48 |
49 | // isRoot reports whether path is the root directory of the tour tree.
50 | // To be the root, it must have content and template subdirectories.
51 | func isRoot(path string) bool {
52 | _, err := os.Stat(filepath.Join(path, "content", "welcome.article"))
53 | if err == nil {
54 | _, err = os.Stat(filepath.Join(path, "template", "index.tmpl"))
55 | }
56 | return err == nil
57 | }
58 |
59 | func findRoot() (string, error) {
60 | ctx := build.Default
61 | p, err := ctx.Import(basePkg, "", build.FindOnly)
62 | if err == nil && isRoot(p.Dir) {
63 | return p.Dir, nil
64 | }
65 | tourRoot := filepath.Join(runtime.GOROOT(), "misc", "tour")
66 | ctx.GOPATH = tourRoot
67 | p, err = ctx.Import(basePkg, "", build.FindOnly)
68 | if err == nil && isRoot(tourRoot) {
69 | gopath = tourRoot
70 | return tourRoot, nil
71 | }
72 | return "", fmt.Errorf("could not find go-tour content; check $GOROOT and $GOPATH")
73 | }
74 |
75 | func main() {
76 | flag.Parse()
77 |
78 | if os.Getenv("GAE_ENV") == "standard" {
79 | log.Println("running in App Engine Standard mode")
80 | gaeMain()
81 | return
82 | }
83 |
84 | // find and serve the go tour files
85 | root, err := findRoot()
86 | if err != nil {
87 | log.Fatalf("Couldn't find tour files: %v", err)
88 | }
89 |
90 | log.Println("Serving content from", root)
91 |
92 | host, port, err := net.SplitHostPort(*httpListen)
93 | if err != nil {
94 | log.Fatal(err)
95 | }
96 | if host == "" {
97 | host = "localhost"
98 | }
99 | if host != "127.0.0.1" && host != "localhost" {
100 | log.Print(localhostWarning)
101 | }
102 | httpAddr = host + ":" + port
103 |
104 | if err := initTour(root, "SocketTransport"); err != nil {
105 | log.Fatal(err)
106 | }
107 |
108 | http.HandleFunc("/", rootHandler)
109 | http.HandleFunc("/lesson/", lessonHandler)
110 |
111 | origin := &url.URL{Scheme: "http", Host: host + ":" + port}
112 | http.Handle(socketPath, socket.NewHandler(origin))
113 |
114 | registerStatic(root)
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 | // registerStatic registers handlers to serve static content
128 | // from the directory root.
129 | func registerStatic(root string) {
130 | // Keep these static file handlers in sync with app.yaml.
131 | http.Handle("/favicon.ico", http.FileServer(http.Dir(filepath.Join(root, "static", "img"))))
132 | static := http.FileServer(http.Dir(root))
133 | http.Handle("/content/img/", static)
134 | http.Handle("/static/", static)
135 | }
136 |
137 | // rootHandler returns a handler for all the requests except the ones for lessons.
138 | func rootHandler(w http.ResponseWriter, r *http.Request) {
139 | if err := renderUI(w); err != nil {
140 | log.Println(err)
141 | }
142 | }
143 |
144 | // lessonHandler handler the HTTP requests for lessons.
145 | func lessonHandler(w http.ResponseWriter, r *http.Request) {
146 | lesson := strings.TrimPrefix(r.URL.Path, "/lesson/")
147 | if err := writeLesson(lesson, w); err != nil {
148 | if err == lessonNotFound {
149 | http.NotFound(w, r)
150 | } else {
151 | log.Println(err)
152 | }
153 | }
154 | }
155 |
156 | const localhostWarning = `
157 | WARNING! WARNING! WARNING!
158 |
159 | The tour server appears to be listening on an address that is
160 | not localhost and is configured to run code snippets locally.
161 | Anyone with access to this address and port will have access
162 | to this machine as the user running gotour.
163 |
164 | If you don't understand this message, hit Control-C to terminate this process.
165 |
166 | WARNING! WARNING! WARNING!
167 | `
168 |
169 | type response struct {
170 | Output string `json:"output"`
171 | Errors string `json:"compile_errors"`
172 | }
173 |
174 | func init() {
175 | socket.Environ = environ
176 | }
177 |
178 | // environ returns the original execution environment with GOPATH
179 | // replaced (or added) with the value of the global var gopath.
180 | func environ() (env []string) {
181 | for _, v := range os.Environ() {
182 | if !strings.HasPrefix(v, "GOPATH=") {
183 | env = append(env, v)
184 | }
185 | }
186 | env = append(env, "GOPATH="+gopath)
187 | return
188 | }
189 |
190 | // waitServer waits some time for the http Server to start
191 | // serving url. The return value reports whether it starts.
192 | func waitServer(url string) bool {
193 | tries := 20
194 | for tries > 0 {
195 | resp, err := http.Get(url)
196 | if err == nil {
197 | resp.Body.Close()
198 | return true
199 | }
200 | time.Sleep(100 * time.Millisecond)
201 | tries--
202 | }
203 | return false
204 | }
205 |
206 | // startBrowser tries to open the URL in a browser, and returns
207 | // whether it succeed.
208 | func startBrowser(url string) bool {
209 | // try to start the browser
210 | var args []string
211 | switch runtime.GOOS {
212 | case "darwin":
213 | args = []string{"open"}
214 | case "windows":
215 | args = []string{"cmd", "/c", "start"}
216 | default:
217 | args = []string{"xdg-open"}
218 | }
219 | cmd := exec.Command(args[0], append(args[1:], url)...)
220 | return cmd.Start() == nil
221 | }
222 |
223 | // prepContent for the local tour simply returns the content as-is.
224 | var prepContent = func(r io.Reader) io.Reader { return r }
225 |
226 | // socketAddr returns the WebSocket handler address.
227 | var socketAddr = func() string { return "ws://" + httpAddr + socketPath }
228 |
--------------------------------------------------------------------------------
/static/lib/codemirror/mode/go/go.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: http://codemirror.net/LICENSE
3 |
4 | (function(mod) {
5 | if (typeof exports == "object" && typeof module == "object") // CommonJS
6 | mod(require("../../lib/codemirror"));
7 | else if (typeof define == "function" && define.amd) // AMD
8 | define(["../../lib/codemirror"], mod);
9 | else // Plain browser env
10 | mod(CodeMirror);
11 | })(function(CodeMirror) {
12 | "use strict";
13 |
14 | CodeMirror.defineMode("go", function(config) {
15 | var indentUnit = config.indentUnit;
16 |
17 | var keywords = {
18 | "break":true, "case":true, "chan":true, "const":true, "continue":true,
19 | "default":true, "defer":true, "else":true, "fallthrough":true, "for":true,
20 | "func":true, "go":true, "goto":true, "if":true, "import":true,
21 | "interface":true, "map":true, "package":true, "range":true, "return":true,
22 | "select":true, "struct":true, "switch":true, "type":true, "var":true,
23 | "bool":true, "byte":true, "complex64":true, "complex128":true,
24 | "float32":true, "float64":true, "int8":true, "int16":true, "int32":true,
25 | "int64":true, "string":true, "uint8":true, "uint16":true, "uint32":true,
26 | "uint64":true, "int":true, "uint":true, "uintptr":true, "error": true,
27 | "rune":true
28 | };
29 |
30 | var atoms = {
31 | "true":true, "false":true, "iota":true, "nil":true, "append":true,
32 | "cap":true, "close":true, "complex":true, "copy":true, "delete":true, "imag":true,
33 | "len":true, "make":true, "new":true, "panic":true, "print":true,
34 | "println":true, "real":true, "recover":true
35 | };
36 |
37 | var isOperatorChar = /[+\-*&^%:=<>!|\/]/;
38 |
39 | var curPunc;
40 |
41 | function tokenBase(stream, state) {
42 | var ch = stream.next();
43 | if (ch == '"' || ch == "'" || ch == "`") {
44 | state.tokenize = tokenString(ch);
45 | return state.tokenize(stream, state);
46 | }
47 | if (/[\d\.]/.test(ch)) {
48 | if (ch == ".") {
49 | stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/);
50 | } else if (ch == "0") {
51 | stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/);
52 | } else {
53 | stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/);
54 | }
55 | return "number";
56 | }
57 | if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
58 | curPunc = ch;
59 | return null;
60 | }
61 | if (ch == "/") {
62 | if (stream.eat("*")) {
63 | state.tokenize = tokenComment;
64 | return tokenComment(stream, state);
65 | }
66 | if (stream.eat("/")) {
67 | stream.skipToEnd();
68 | return "comment";
69 | }
70 | }
71 | if (isOperatorChar.test(ch)) {
72 | stream.eatWhile(isOperatorChar);
73 | return "operator";
74 | }
75 | stream.eatWhile(/[\w\$_\xa1-\uffff]/);
76 | var cur = stream.current();
77 | if (keywords.propertyIsEnumerable(cur)) {
78 | if (cur == "case" || cur == "default") curPunc = "case";
79 | return "keyword";
80 | }
81 | if (atoms.propertyIsEnumerable(cur)) return "atom";
82 | return "variable";
83 | }
84 |
85 | function tokenString(quote) {
86 | return function(stream, state) {
87 | var escaped = false, next, end = false;
88 | while ((next = stream.next()) != null) {
89 | if (next == quote && !escaped) {end = true; break;}
90 | escaped = !escaped && quote != "`" && next == "\\";
91 | }
92 | if (end || !(escaped || quote == "`"))
93 | state.tokenize = tokenBase;
94 | return "string";
95 | };
96 | }
97 |
98 | function tokenComment(stream, state) {
99 | var maybeEnd = false, ch;
100 | while (ch = stream.next()) {
101 | if (ch == "/" && maybeEnd) {
102 | state.tokenize = tokenBase;
103 | break;
104 | }
105 | maybeEnd = (ch == "*");
106 | }
107 | return "comment";
108 | }
109 |
110 | function Context(indented, column, type, align, prev) {
111 | this.indented = indented;
112 | this.column = column;
113 | this.type = type;
114 | this.align = align;
115 | this.prev = prev;
116 | }
117 | function pushContext(state, col, type) {
118 | return state.context = new Context(state.indented, col, type, null, state.context);
119 | }
120 | function popContext(state) {
121 | if (!state.context.prev) return;
122 | var t = state.context.type;
123 | if (t == ")" || t == "]" || t == "}")
124 | state.indented = state.context.indented;
125 | return state.context = state.context.prev;
126 | }
127 |
128 | // Interface
129 |
130 | return {
131 | startState: function(basecolumn) {
132 | return {
133 | tokenize: null,
134 | context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
135 | indented: 0,
136 | startOfLine: true
137 | };
138 | },
139 |
140 | token: function(stream, state) {
141 | var ctx = state.context;
142 | if (stream.sol()) {
143 | if (ctx.align == null) ctx.align = false;
144 | state.indented = stream.indentation();
145 | state.startOfLine = true;
146 | if (ctx.type == "case") ctx.type = "}";
147 | }
148 | if (stream.eatSpace()) return null;
149 | curPunc = null;
150 | var style = (state.tokenize || tokenBase)(stream, state);
151 | if (style == "comment") return style;
152 | if (ctx.align == null) ctx.align = true;
153 |
154 | if (curPunc == "{") pushContext(state, stream.column(), "}");
155 | else if (curPunc == "[") pushContext(state, stream.column(), "]");
156 | else if (curPunc == "(") pushContext(state, stream.column(), ")");
157 | else if (curPunc == "case") ctx.type = "case";
158 | else if (curPunc == "}" && ctx.type == "}") ctx = popContext(state);
159 | else if (curPunc == ctx.type) popContext(state);
160 | state.startOfLine = false;
161 | return style;
162 | },
163 |
164 | indent: function(state, textAfter) {
165 | if (state.tokenize != tokenBase && state.tokenize != null) return 0;
166 | var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
167 | if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) {
168 | state.context.type = "}";
169 | return ctx.indented;
170 | }
171 | var closing = firstChar == ctx.type;
172 | if (ctx.align) return ctx.column + (closing ? 0 : 1);
173 | else return ctx.indented + (closing ? 0 : indentUnit);
174 | },
175 |
176 | electricChars: "{}):",
177 | closeBrackets: "()[]{}''\"\"``",
178 | fold: "brace",
179 | blockCommentStart: "/*",
180 | blockCommentEnd: "*/",
181 | lineComment: "//"
182 | };
183 | });
184 |
185 | CodeMirror.defineMIME("text/x-go", "go");
186 |
187 | });
188 |
--------------------------------------------------------------------------------
/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 // import "github.com/Go-zh/tour"
6 |
7 | import (
8 | "bytes"
9 | "compress/gzip"
10 | "crypto/sha1"
11 | "encoding/base64"
12 | "encoding/json"
13 | "fmt"
14 | "html/template"
15 | "io"
16 | "io/ioutil"
17 | "net/http"
18 | "os"
19 | "path/filepath"
20 | "strings"
21 | "time"
22 |
23 | "github.com/Go-zh/tools/godoc/static"
24 | "github.com/Go-zh/tools/present"
25 | )
26 |
27 | var (
28 | uiContent []byte
29 | lessons = make(map[string][]byte)
30 | lessonNotFound = fmt.Errorf("lesson not found")
31 | )
32 |
33 | // initTour loads tour.article and the relevant HTML templates from the given
34 | // tour root, and renders the template to the tourContent global variable.
35 | func initTour(root, transport string) error {
36 | // Make sure playground is enabled before rendering.
37 | present.PlayEnabled = true
38 |
39 | // Set up templates.
40 | action := filepath.Join(root, "template", "action.tmpl")
41 | tmpl, err := present.Template().ParseFiles(action)
42 | if err != nil {
43 | return fmt.Errorf("parse templates: %v", err)
44 | }
45 |
46 | // Init lessons.
47 | contentPath := filepath.Join(root, "content")
48 | if err := initLessons(tmpl, contentPath); err != nil {
49 | return fmt.Errorf("init lessons: %v", err)
50 | }
51 |
52 | // Init UI
53 | index := filepath.Join(root, "template", "index.tmpl")
54 | ui, err := template.ParseFiles(index)
55 | if err != nil {
56 | return fmt.Errorf("parse index.tmpl: %v", err)
57 | }
58 | buf := new(bytes.Buffer)
59 |
60 | data := struct {
61 | SocketAddr string
62 | Transport template.JS
63 | }{socketAddr(), template.JS(transport)}
64 |
65 | if err := ui.Execute(buf, data); err != nil {
66 | return fmt.Errorf("render UI: %v", err)
67 | }
68 | uiContent = buf.Bytes()
69 |
70 | return initScript(root)
71 | }
72 |
73 | // initLessonss finds all the lessons in the passed directory, renders them,
74 | // using the given template and saves the content in the lessons map.
75 | func initLessons(tmpl *template.Template, content string) error {
76 | dir, err := os.Open(content)
77 | if err != nil {
78 | return err
79 | }
80 | files, err := dir.Readdirnames(0)
81 | if err != nil {
82 | return err
83 | }
84 | for _, f := range files {
85 | if filepath.Ext(f) != ".article" {
86 | continue
87 | }
88 | content, err := parseLesson(tmpl, filepath.Join(content, f))
89 | if err != nil {
90 | return fmt.Errorf("parsing %v: %v", f, err)
91 | }
92 | name := strings.TrimSuffix(f, ".article")
93 | lessons[name] = content
94 | }
95 | return nil
96 | }
97 |
98 | // File defines the JSON form of a code file in a page.
99 | type File struct {
100 | Name string
101 | Content string
102 | Hash string
103 | }
104 |
105 | // Page defines the JSON form of a tour lesson page.
106 | type Page struct {
107 | Title string
108 | Content string
109 | Files []File
110 | }
111 |
112 | // Lesson defines the JSON form of a tour lesson.
113 | type Lesson struct {
114 | Title string
115 | Description string
116 | Pages []Page
117 | }
118 |
119 | // parseLesson parses and returns a lesson content given its name and
120 | // the template to render it.
121 | func parseLesson(tmpl *template.Template, path string) ([]byte, error) {
122 | f, err := os.Open(path)
123 | if err != nil {
124 | return nil, err
125 | }
126 | defer f.Close()
127 | doc, err := present.Parse(prepContent(f), path, 0)
128 | if err != nil {
129 | return nil, err
130 | }
131 |
132 | lesson := Lesson{
133 | doc.Title,
134 | doc.Subtitle,
135 | make([]Page, len(doc.Sections)),
136 | }
137 |
138 | for i, sec := range doc.Sections {
139 | p := &lesson.Pages[i]
140 | w := new(bytes.Buffer)
141 | if err := sec.Render(w, tmpl); err != nil {
142 | return nil, fmt.Errorf("render section: %v", err)
143 | }
144 | p.Title = sec.Title
145 | p.Content = w.String()
146 | codes := findPlayCode(sec)
147 | p.Files = make([]File, len(codes))
148 | for i, c := range codes {
149 | f := &p.Files[i]
150 | f.Name = c.FileName
151 | f.Content = string(c.Raw)
152 | hash := sha1.Sum(c.Raw)
153 | f.Hash = base64.StdEncoding.EncodeToString(hash[:])
154 | }
155 | }
156 |
157 | w := new(bytes.Buffer)
158 | if err := json.NewEncoder(w).Encode(lesson); err != nil {
159 | return nil, fmt.Errorf("encode lesson: %v", err)
160 | }
161 | return w.Bytes(), nil
162 | }
163 |
164 | // findPlayCode returns a slide with all the Code elements in the given
165 | // Elem with Play set to true.
166 | func findPlayCode(e present.Elem) []*present.Code {
167 | var r []*present.Code
168 | switch v := e.(type) {
169 | case present.Code:
170 | if v.Play {
171 | r = append(r, &v)
172 | }
173 | case present.Section:
174 | for _, s := range v.Elem {
175 | r = append(r, findPlayCode(s)...)
176 | }
177 | }
178 | return r
179 | }
180 |
181 | // writeLesson writes the tour content to the provided Writer.
182 | func writeLesson(name string, w io.Writer) error {
183 | if uiContent == nil {
184 | panic("writeLesson called before successful initTour")
185 | }
186 | if len(name) == 0 {
187 | return writeAllLessons(w)
188 | }
189 | l, ok := lessons[name]
190 | if !ok {
191 | return lessonNotFound
192 | }
193 | _, err := w.Write(l)
194 | return err
195 | }
196 |
197 | func writeAllLessons(w io.Writer) error {
198 | if _, err := fmt.Fprint(w, "{"); err != nil {
199 | return err
200 | }
201 | nLessons := len(lessons)
202 | for k, v := range lessons {
203 | if _, err := fmt.Fprintf(w, "%q:%s", k, v); err != nil {
204 | return err
205 | }
206 | nLessons--
207 | if nLessons != 0 {
208 | if _, err := fmt.Fprint(w, ","); err != nil {
209 | return err
210 | }
211 | }
212 | }
213 | _, err := fmt.Fprint(w, "}")
214 | return err
215 | }
216 |
217 | // renderUI writes the tour UI to the provided Writer.
218 | func renderUI(w io.Writer) error {
219 | if uiContent == nil {
220 | panic("renderUI called before successful initTour")
221 | }
222 | _, err := w.Write(uiContent)
223 | return err
224 | }
225 |
226 | // nocode returns true if the provided Section contains
227 | // no Code elements with Play enabled.
228 | func nocode(s present.Section) bool {
229 | for _, e := range s.Elem {
230 | if c, ok := e.(present.Code); ok && c.Play {
231 | return false
232 | }
233 | }
234 | return true
235 | }
236 |
237 | // initScript concatenates all the javascript files needed to render
238 | // the tour UI and serves the result on /script.js.
239 | func initScript(root string) error {
240 | modTime := time.Now()
241 | b := new(bytes.Buffer)
242 |
243 | content, ok := static.Files["playground.js"]
244 | if !ok {
245 | return fmt.Errorf("playground.js not found in static files")
246 | }
247 | b.WriteString(content)
248 |
249 | // Keep this list in dependency order
250 | files := []string{
251 | "static/lib/jquery.min.js",
252 | "static/lib/jquery-ui.min.js",
253 | "static/lib/angular.min.js",
254 | "static/lib/codemirror/lib/codemirror.js",
255 | "static/lib/codemirror/mode/go/go.js",
256 | "static/lib/angular-ui.min.js",
257 | "static/js/app.js",
258 | "static/js/controllers.js",
259 | "static/js/directives.js",
260 | "static/js/services.js",
261 | "static/js/values.js",
262 | }
263 |
264 | for _, file := range files {
265 | f, err := ioutil.ReadFile(filepath.Join(root, file))
266 | if err != nil {
267 | return fmt.Errorf("couldn't open %v: %v", file, err)
268 | }
269 | _, err = b.Write(f)
270 | if err != nil {
271 | return fmt.Errorf("error concatenating %v: %v", file, err)
272 | }
273 | }
274 |
275 | var gzBuf bytes.Buffer
276 | gz, err := gzip.NewWriterLevel(&gzBuf, gzip.BestCompression)
277 | if err != nil {
278 | return err
279 | }
280 | gz.Write(b.Bytes())
281 | gz.Close()
282 |
283 | http.HandleFunc("/script.js", func(w http.ResponseWriter, r *http.Request) {
284 | w.Header().Set("Content-type", "application/javascript")
285 | // Set expiration time in one week.
286 | w.Header().Set("Cache-control", "max-age=604800")
287 | if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
288 | http.ServeContent(w, r, "", modTime, bytes.NewReader(b.Bytes()))
289 | } else {
290 | w.Header().Set("Content-Encoding", "gzip")
291 | http.ServeContent(w, r, "", modTime, bytes.NewReader(gzBuf.Bytes()))
292 | }
293 | })
294 |
295 | return nil
296 | }
297 |
--------------------------------------------------------------------------------
/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, done) {
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 | if (write.Kind == 'end') done();
49 | };
50 | };
51 | return function(code, output, options, done) {
52 | // PlaygroundOutput is defined in playground.js which is prepended
53 | // to the generated script.js in gotour/tour.go.
54 | // The next line removes the jshint warning.
55 | // global PlaygroundOutput
56 | return win.transport.Run(code, writeInterceptor(PlaygroundOutput(output), done), options);
57 | };
58 | }
59 | ]).
60 |
61 | // Formatting code
62 | factory('fmt', ['$http',
63 | function($http) {
64 | return function(body, imports) {
65 | var params = $.param({
66 | 'body': body,
67 | 'imports': imports,
68 | });
69 | var headers = {
70 | 'Content-Type': 'application/x-www-form-urlencoded'
71 | };
72 | return $http.post('/fmt', params, {
73 | headers: headers
74 | });
75 | };
76 | }
77 | ]).
78 |
79 | // Local storage, persistent to page refreshing.
80 | factory('storage', ['$window',
81 | function(win) {
82 | try {
83 | // This will raise an exception if cookies are disabled.
84 | win.localStorage = win.localStorage;
85 | return {
86 | get: function(key) {
87 | return win.localStorage.getItem(key);
88 | },
89 | set: function(key, val) {
90 | win.localStorage.setItem(key, val);
91 | }
92 | };
93 | } catch (e) {
94 | return {
95 | get: function() {
96 | return null;
97 | },
98 | set: function() {}
99 | };
100 | }
101 | }
102 | ]).
103 |
104 | // Editor context service, kept through the whole app.
105 | factory('editor', ['$window', 'storage',
106 | function(win, storage) {
107 | var ctx = {
108 | imports: storage.get('imports') === 'true',
109 | toggleImports: function() {
110 | ctx.imports = !ctx.imports;
111 | storage.set('imports', ctx.imports);
112 | },
113 | syntax: storage.get('syntax') === 'true',
114 | toggleSyntax: function() {
115 | ctx.syntax = !ctx.syntax;
116 | storage.set('syntax', ctx.syntax);
117 | ctx.paint();
118 | },
119 | paint: function() {
120 | var mode = ctx.syntax && 'text/x-go' || 'text/x-go-comment';
121 | // Wait for codemirror to start.
122 | var set = function() {
123 | if ($('.CodeMirror').length > 0) {
124 | var cm = $('.CodeMirror')[0].CodeMirror;
125 | if (cm.getOption('mode') == mode) {
126 | cm.refresh();
127 | return;
128 | }
129 | cm.setOption('mode', mode);
130 | }
131 | win.setTimeout(set, 10);
132 | };
133 | set();
134 | },
135 | highlight: function(line, message) {
136 | $('.CodeMirror-code > div:nth-child(' + line + ')')
137 | .addClass('line-error').attr('title', message);
138 | },
139 | onChange: function() {
140 | $('.line-error').removeClass('line-error').attr('title', null);
141 | }
142 | };
143 | // Set in the window so the onChange function in the codemirror config
144 | // can call it.
145 | win.codeChanged = ctx.onChange;
146 | return ctx;
147 | }
148 | ]).
149 |
150 | // Table of contents management and navigation
151 | factory('toc', ['$http', '$q', '$log', 'tableOfContents', 'storage',
152 | function($http, $q, $log, tableOfContents, storage) {
153 | var modules = tableOfContents;
154 |
155 | var lessons = {};
156 |
157 | var prevLesson = function(id) {
158 | var mod = lessons[id].module;
159 | var idx = mod.lessons.indexOf(id);
160 | if (idx < 0) return '';
161 | if (idx > 0) return mod.lessons[idx - 1];
162 |
163 | idx = modules.indexOf(mod);
164 | if (idx <= 0) return '';
165 | mod = modules[idx - 1];
166 | return mod.lessons[mod.lessons.length - 1];
167 | };
168 |
169 | var nextLesson = function(id) {
170 | var mod = lessons[id].module;
171 | var idx = mod.lessons.indexOf(id);
172 | if (idx < 0) return '';
173 | if (idx + 1 < mod.lessons.length) return mod.lessons[idx + 1];
174 |
175 | idx = modules.indexOf(mod);
176 | if (idx < 0 || modules.length <= idx + 1) return '';
177 | mod = modules[idx + 1];
178 | return mod.lessons[0];
179 | };
180 |
181 | $http.get('/lesson/').then(
182 | function(data) {
183 | lessons = data.data;
184 | for (var m = 0; m < modules.length; m++) {
185 | var module = modules[m];
186 | module.lesson = {};
187 | for (var l = 0; l < modules[m].lessons.length; l++) {
188 | var lessonName = module.lessons[l];
189 | var lesson = lessons[lessonName];
190 | lesson.module = module;
191 | module.lesson[lessonName] = lesson;
192 |
193 | // replace file contents with locally stored copies.
194 | for (var p = 0; p < lesson.Pages.length; p++) {
195 | var page = lesson.Pages[p];
196 | for (var f = 0; f < page.Files.length; f++) {
197 | page.Files[f].OrigContent = page.Files[f].Content;
198 | var val = storage.get(page.Files[f].Hash);
199 | if (val !== null) {
200 | page.Files[f].Content = val;
201 | }
202 | }
203 | }
204 | }
205 | }
206 | moduleQ.resolve(modules);
207 | lessonQ.resolve(lessons);
208 | },
209 | function(error) {
210 | $log.error('error loading lessons : ', error);
211 | moduleQ.reject(error);
212 | lessonQ.reject(error);
213 | }
214 | );
215 |
216 | var moduleQ = $q.defer();
217 | var lessonQ = $q.defer();
218 |
219 | return {
220 | modules: moduleQ.promise,
221 | lessons: lessonQ.promise,
222 | prevLesson: prevLesson,
223 | nextLesson: nextLesson
224 | };
225 | }
226 | ]);
227 |
--------------------------------------------------------------------------------
/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.which || evt.keyCode;
17 | if (key == 33 && !evt.ctrlKey) {
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.which || evt.keyCode;
31 | if (key == 34 && !evt.ctrlKey) {
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 | // imports-checkbox activates and deactivates
52 | directive('importsCheckbox', ['editor',
53 | function(editor) {
54 | return function(scope, elm) {
55 | elm.click(function() {
56 | editor.toggleImports();
57 | scope.$digest();
58 | });
59 | scope.editor = editor;
60 | };
61 | }
62 | ]).
63 |
64 | // syntax-checkbox activates and deactivates
65 | directive('syntaxCheckbox', ['editor',
66 | function(editor) {
67 | return function(scope, elm) {
68 | elm.click(function() {
69 | editor.toggleSyntax();
70 | scope.$digest();
71 | });
72 | scope.editor = editor;
73 | };
74 | }
75 | ]).
76 |
77 | // verticalSlide creates a sliding separator between the left and right elements.
78 | // e.g.:
79 | //
80 | //
81 | //
82 | directive('verticalSlide', ['editor',
83 | function(editor) {
84 | return function(scope, elm, attrs) {
85 | var moveTo = function(x) {
86 | if (x < 0) {
87 | x = 0;
88 | }
89 | if (x > $(window).width()) {
90 | x = $(window).width();
91 | }
92 | elm.css('left', x);
93 | $(attrs.left).width(x);
94 | $(attrs.right).offset({
95 | left: x
96 | });
97 | editor.x = x;
98 | };
99 |
100 | elm.draggable({
101 | axis: 'x',
102 | drag: function(event) {
103 | moveTo(event.clientX);
104 | return true;
105 | },
106 | containment: 'parent',
107 | });
108 |
109 | if (editor.x !== undefined) {
110 | moveTo(editor.x);
111 | }
112 | };
113 | }
114 | ]).
115 |
116 | // horizontalSlide creates a sliding separator between the top and bottom elements.
117 | //
118 | //
119 | // Some content
120 | directive('horizontalSlide', ['editor',
121 | function(editor) {
122 | return function(scope, elm, attrs) {
123 | var moveTo = function(y) {
124 | var top = $(attrs.top).offset().top;
125 | if (y < top) {
126 | y = top;
127 | }
128 | elm.css('top', y - top);
129 | $(attrs.top).height(y - top);
130 | $(attrs.bottom).offset({
131 | top: y,
132 | height: 0
133 | });
134 | editor.y = y;
135 | };
136 | elm.draggable({
137 | axis: 'y',
138 | drag: function(event) {
139 | moveTo(event.clientY);
140 | return true;
141 | },
142 | containment: 'parent',
143 | });
144 |
145 | if (editor.y !== undefined) {
146 | moveTo(editor.y);
147 | }
148 | };
149 | }
150 | ]).
151 |
152 | directive('tableOfContentsButton', ['i18n', function(i18n) {
153 | var speed = 250;
154 | return {
155 | restrict: 'A',
156 | templateUrl: '/static/partials/toc-button.html',
157 | link: function(scope, elm, attrs) {
158 | scope.tocMessage = i18n.l('toc');
159 | elm.on('click', function() {
160 | var toc = $(attrs.tableOfContentsButton);
161 | // hide all non active lessons before displaying the toc.
162 | var visible = toc.css('display') != 'none';
163 | if (!visible) {
164 | toc.find('.toc-lesson:not(.active) .toc-page').hide();
165 | toc.find('.toc-lesson.active .toc-page').show();
166 | }
167 | toc.toggle('slide', {
168 | direction: 'right'
169 | }, speed);
170 |
171 | // if fullscreen hide the rest of the content when showing the atoc.
172 | var fullScreen = toc.width() == $(window).width();
173 | if (fullScreen) $('#editor-container')[visible ? 'show' : 'hide']();
174 | });
175 | }
176 | };
177 | }]).
178 |
179 | // side bar with dynamic table of contents
180 | directive('tableOfContents', ['$routeParams', 'toc',
181 | function($routeParams, toc) {
182 | var speed = 250;
183 | return {
184 | restrict: 'A',
185 | templateUrl: '/static/partials/toc.html',
186 | link: function(scope, elm) {
187 | scope.toc = toc;
188 | scope.params = $routeParams;
189 |
190 | scope.toggleLesson = function(id) {
191 | var l = $('#toc-l-' + id + ' .toc-page');
192 | l[l.css('display') == 'none' ? 'slideDown' : 'slideUp']();
193 | };
194 |
195 | scope.$watch(function() {
196 | return scope.params.lessonId + scope.params.lessonId;
197 | }, function() {
198 | $('.toc-lesson:not(#toc-l-' + scope.params.lessonId + ') .toc-page').slideUp(speed);
199 | });
200 |
201 | scope.hideTOC = function(fullScreenOnly) {
202 | var fullScreen = elm.find('.toc').width() == $(window).width();
203 | if (fullScreenOnly && !fullScreen) {
204 | return;
205 | }
206 | $('.toc').toggle('slide', {
207 | direction: 'right'
208 | }, speed);
209 | $('#editor-container').show();
210 | };
211 | }
212 | };
213 | }
214 | ]).
215 |
216 | directive('feedbackButton', ['i18n', function(i18n) {
217 | return {
218 | restrict: 'A',
219 | templateUrl: '/static/partials/feedback-button.html',
220 | link: function(scope, elm, attrs) {
221 | scope.feedbackMessage = i18n.l('submit-feedback');
222 |
223 | elm.on('click', function() {
224 | var context = window.location.pathname === '/list'
225 | ? '/list'
226 | : '/' + scope.params.lessonId + '/' + scope.params.pageNumber;
227 | context = window.location.protocol + '//' + window.location.host + context;
228 | var title = i18n.l('issue-title');
229 | var body = i18n.l('context') + ': '+ context + '\n\n'+ i18n.l('issue-message');
230 | var url = 'https://' + i18n.l('github-repo') + '/issues/new'
231 | + '?title=' + encodeURIComponent(title)
232 | + '&body=' + encodeURIComponent(body);
233 | window.open(url);
234 | });
235 | }
236 | };
237 | }]);
238 |
--------------------------------------------------------------------------------
/content/methods.article:
--------------------------------------------------------------------------------
1 | 方法和接口
2 | 本节课包含了方法和接口,可以用这种构造来定义对象及其行为。
3 |
4 | Go 作者组编写,Go-zh 小组翻译。
5 | https://go-zh.org
6 |
7 | * 方法
8 |
9 | Go 没有类。不过你可以为结构体类型定义方法。
10 |
11 | 方法就是一类带特殊的 *接收者* 参数的函数。
12 |
13 | 方法接收者在它自己的参数列表内,位于 `func` 关键字和方法名之间。
14 |
15 | 在此例中,`Abs` 方法拥有一个名为 `v`,类型为 `Vertex` 的接收者。
16 |
17 | .play methods/methods.go
18 |
19 | * 方法即函数
20 |
21 | 记住:方法只是个带接收者参数的函数。
22 |
23 | 现在这个 `Abs` 的写法就是个正常的函数,功能并没有什么变化。
24 |
25 | .play methods/methods-funcs.go
26 |
27 | * 方法(续)
28 |
29 | 你也可以为非结构体类型声明方法。
30 |
31 | 在此例中,我们看到了一个带 `Abs` 方法的数值类型 `MyFloat`。
32 |
33 | 你只能为在同一包内定义的类型的接收者声明方法,而不能为其它包内定义的类型(包括 `int` 之类的内建类型)的接收者声明方法。
34 |
35 | (译注:就是接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法。)
36 |
37 | .play methods/methods-continued.go
38 |
39 | * 指针接收者
40 |
41 | 你可以为指针接收者声明方法。
42 |
43 | 这意味着对于某类型 `T`,接收者的类型可以用 `*T` 的文法。(此外,`T` 不能是像 `*int` 这样的指针。)
44 |
45 | 例如,这里为 `*Vertex` 定义了 `Scale` 方法。
46 |
47 | 指针接收者的方法可以修改接收者指向的值(就像 `Scale` 在这做的)。由于方法经常需要修改它的接收者,指针接收者比值接收者更常用。
48 |
49 | 试着移除第 16 行 `Scale` 函数声明中的 `*`,观察此程序的行为如何变化。
50 |
51 | 若使用值接收者,那么 `Scale` 方法会对原始 `Vertex` 值的副本进行操作。(对于函数的其它参数也是如此。)`Scale` 方法必须用指针接受者来更改 `main` 函数中声明的 `Vertex` 的值。
52 |
53 | .play methods/methods-pointers.go
54 |
55 | * 指针与函数
56 |
57 | 现在我们要把 `Abs` 和 `Scale` 方法重写为函数。
58 |
59 | 同样,我们先试着移除掉第 16 的 `*`。你能看出为什么程序的行为改变了吗?要怎样做才能让该示例顺利通过编译?
60 |
61 | (若你不确定,继续往下看。)
62 |
63 | .play methods/methods-pointers-explained.go
64 |
65 | * 方法与指针重定向
66 |
67 | 比较前两个程序,你大概会注意到带指针参数的函数必须接受一个指针:
68 |
69 | var v Vertex
70 | ScaleFunc(v, 5) // 编译错误!
71 | ScaleFunc(&v, 5) // OK
72 |
73 | 而以指针为接收者的方法被调用时,接收者既能为值又能为指针:
74 |
75 | var v Vertex
76 | v.Scale(5) // OK
77 | p := &v
78 | p.Scale(10) // OK
79 |
80 | 对于语句 `v.Scale(5)`,即便 `v` 是个值而非指针,带指针接收者的方法也能被直接调用。
81 | 也就是说,由于 `Scale` 方法有一个指针接收者,为方便起见,Go 会将语句 `v.Scale(5)` 解释为 `(&v).Scale(5)`。
82 |
83 | .play methods/indirection.go
84 |
85 | * 方法与指针重定向(续)
86 |
87 | 同样的事情也发生在相反的方向。
88 |
89 | 接受一个值作为参数的函数必须接受一个指定类型的值:
90 |
91 | var v Vertex
92 | fmt.Println(AbsFunc(v)) // OK
93 | fmt.Println(AbsFunc(&v)) // 编译错误!
94 |
95 | 而以值为接收者的方法被调用时,接收者既能为值又能为指针:
96 |
97 | var v Vertex
98 | fmt.Println(v.Abs()) // OK
99 | p := &v
100 | fmt.Println(p.Abs()) // OK
101 |
102 | 这种情况下,方法调用 `p.Abs()` 会被解释为 `(*p).Abs()`。
103 |
104 | .play methods/indirection-values.go
105 |
106 | * 选择值或指针作为接收者
107 |
108 | 使用指针接收者的原因有二:
109 |
110 | 首先,方法能够修改其接收者指向的值。
111 |
112 | 其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效。
113 |
114 | 在本例中,`Scale` 和 `Abs` 接收者的类型为 `*Vertex`,即便 `Abs` 并不需要修改其接收者。
115 |
116 | 通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用。(我们会在接下来几页中明白为什么。)
117 |
118 | .play methods/methods-with-pointer-receivers.go
119 |
120 | * 接口
121 |
122 | *接口类型* 是由一组方法签名定义的集合。
123 |
124 | 接口类型的变量可以保存任何实现了这些方法的值。
125 |
126 | *注意:* 示例代码的 22 行存在一个错误。由于 `Abs` 方法只为 `*Vertex` (指针类型)定义,因此 `Vertex`(值类型)并未实现 `Abser`。
127 |
128 | .play methods/interfaces.go
129 |
130 | * 接口与隐式实现
131 |
132 | 类型通过实现一个接口的所有方法来实现该接口。既然无需专门显式声明,也就没有“implements”关键字。
133 |
134 | 隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备。
135 |
136 | 因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。
137 |
138 | .play methods/interfaces-are-satisfied-implicitly.go
139 |
140 | * 接口值
141 |
142 | 接口也是值。它们可以像其它值一样传递。
143 |
144 | 接口值可以用作函数的参数或返回值。
145 |
146 | 在内部,接口值可以看做包含值和具体类型的元组:
147 |
148 | (value, type)
149 |
150 | 接口值保存了一个具体底层类型的具体值。
151 |
152 | 接口值调用方法时会执行其底层类型的同名方法。
153 |
154 | .play methods/interface-values.go
155 |
156 | * 底层值为 nil 的接口值
157 |
158 | 即便接口内的具体值为 nil,方法仍然会被 nil 接收者调用。
159 |
160 | 在一些语言中,这会触发一个空指针异常,但在 Go 中通常会写一些方法来优雅地处理它(如本例中的 `M` 方法)。
161 |
162 | *注意:* 保存了 nil 具体值的接口其自身并不为 nil。
163 |
164 | .play methods/interface-values-with-nil.go
165 |
166 | * nil 接口值
167 |
168 | nil 接口值既不保存值也不保存具体类型。
169 |
170 | 为 nil 接口调用方法会产生运行时错误,因为接口的元组内并未包含能够指明该调用哪个 *具体* 方法的类型。
171 |
172 | .play methods/nil-interface-values.go
173 |
174 | * 空接口
175 |
176 | 指定了零个方法的接口值被称为 *空接口:*
177 |
178 | interface{}
179 |
180 | 空接口可保存任何类型的值。(因为每个类型都至少实现了零个方法。)
181 |
182 | 空接口被用来处理未知类型的值。例如,`fmt.Print` 可接受类型为 `interface{}` 的任意数量的参数。
183 |
184 | .play methods/empty-interface.go
185 |
186 | * 类型断言
187 |
188 | *类型断言* 提供了访问接口值底层具体值的方式。
189 |
190 | t := i.(T)
191 |
192 | 该语句断言接口值 `i` 保存了具体类型 `T`,并将其底层类型为 `T` 的值赋予变量 `t`。
193 |
194 | 若 `i` 并未保存 `T` 类型的值,该语句就会触发一个panic。
195 |
196 | 为了 *判断* 一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。
197 |
198 | t, ok := i.(T)
199 |
200 | 若 `i` 保存了一个 `T`,那么 `t` 将会是其底层值,而 `ok` 为 `true`。
201 |
202 | 否则,`ok` 将为 `false` 而 `t` 将为 `T` 类型的零值,程序并不会产生panic。
203 |
204 | 请注意这种语法和读取一个映射时的相同之处。
205 |
206 | .play methods/type-assertions.go
207 |
208 | * 类型选择
209 |
210 | *类型选择* 是一种按顺序从几个类型断言中选择分支的结构。
211 |
212 | 类型选择与一般的 switch 语句相似,不过类型选择中的 case 为类型(而非值),
213 | 它们针对给定接口值所存储的值的类型进行比较。
214 |
215 | switch v := i.(type) {
216 | case T:
217 | // v 的类型为 T
218 | case S:
219 | // v 的类型为 S
220 | default:
221 | // 没有匹配,v 与 i 的类型相同
222 | }
223 |
224 | 类型选择中的声明与类型断言 `i.(T)` 的语法相同,只是具体类型 `T` 被替换成了关键字 `type`。
225 |
226 | 此选择语句判断接口值 `i` 保存的值类型是 `T` 还是 `S`。在 `T` 或 `S` 的情况下,变量 `v` 会分别按 `T` 或 `S` 类型保存 `i` 拥有的值。在默认(即没有匹配)的情况下,变量 `v` 与 `i` 的接口类型和值相同。
227 |
228 | .play methods/type-switches.go
229 |
230 | * Stringer
231 |
232 | [[https://go-zh.org/pkg/fmt/][`fmt`]] 包中定义的 [[https://go-zh.org/pkg/fmt/#Stringer][`Stringer`]] 是最普遍的接口之一。
233 |
234 | type Stringer interface {
235 | String() string
236 | }
237 |
238 | `Stringer` 是一个可以用字符串描述自己的类型。`fmt` 包(还有很多包)都通过此接口来打印值。
239 |
240 | .play methods/stringer.go
241 |
242 | * 练习:Stringer
243 |
244 | 通过让 `IPAddr` 类型实现 `fmt.Stringer` 来打印点号分隔的地址。
245 |
246 | 例如,`IPAddr{1,`2,`3,`4}` 应当打印为 `"1.2.3.4"`。
247 |
248 | .play methods/exercise-stringer.go
249 |
250 | * 错误
251 |
252 | Go 程序使用 `error` 值来表示错误状态。
253 |
254 | 与 `fmt.Stringer` 类似,`error` 类型是一个内建接口:
255 |
256 | type error interface {
257 | Error() string
258 | }
259 |
260 | (与 `fmt.Stringer` 类似,`fmt` 包也会根据对 `error` 的实现来打印值。)
261 |
262 | 通常函数会返回一个 `error` 值,调用它的代码应当判断这个错误是否等于 `nil` 来进行错误处理。
263 |
264 | i, err := strconv.Atoi("42")
265 | if err != nil {
266 | fmt.Printf("couldn't convert number: %v\n", err)
267 | return
268 | }
269 | fmt.Println("Converted integer:", i)
270 |
271 | `error` 为 nil 时表示成功;非 nil 的 `error` 表示失败。
272 |
273 | .play methods/errors.go
274 |
275 | * 练习:错误
276 |
277 | 从[[/flowcontrol/8][之前的练习]]中复制 `Sqrt` 函数,修改它使其返回 `error` 值。
278 |
279 | `Sqrt` 接受到一个负数时,应当返回一个非 nil 的错误值。复数同样也不被支持。
280 |
281 | 创建一个新的类型
282 |
283 | type ErrNegativeSqrt float64
284 |
285 | 并为其实现
286 |
287 | func (e ErrNegativeSqrt) Error() string
288 |
289 | 方法使其拥有 `error` 值,通过 `ErrNegativeSqrt(-2).Error()` 调用该方法应返回 `"cannot`Sqrt`negative`number:`-2"`。
290 |
291 | *注意:* 在 `Error` 方法内调用 `fmt.Sprint(e)` 会让程序陷入死循环。可以通过先转换 `e`
292 | 来避免这个问题:`fmt.Sprint(float64(e))`。这是为什么呢?
293 |
294 | 修改 `Sqrt` 函数,使其接受一个负数时,返回 `ErrNegativeSqrt` 值。
295 |
296 | .play methods/exercise-errors.go
297 |
298 | * Reader
299 |
300 | `io` 包指定了 `io.Reader` 接口,它表示从数据流的末尾进行读取。
301 |
302 | Go 标准库包含了该接口的[[https://go-zh.org/search?q=Read#Global][许多实现]],包括文件、网络连接、压缩和加密等等。
303 |
304 | `io.Reader` 接口有一个 `Read` 方法:
305 |
306 | func (T) Read(b []byte) (n int, err error)
307 |
308 | `Read` 用数据填充给定的字节切片并返回填充的字节数和错误值。在遇到数据流的结尾时,它会返回一个 `io.EOF` 错误。
309 |
310 | 示例代码创建了一个 [[https://go-zh.org/pkg/strings/#Reader][`strings.Reader`]] 并以每次 8 字节的速度读取它的输出。
311 |
312 | .play methods/reader.go
313 |
314 | * 练习:Reader
315 |
316 | 实现一个 `Reader` 类型,它产生一个 ASCII 字符 `'A'` 的无限流。
317 |
318 | .play methods/exercise-reader.go
319 |
320 | * 练习:rot13Reader
321 |
322 | 有种常见的模式是一个 [[https://go-zh.org/pkg/io/#Reader][`io.Reader`]] 包装另一个 `io.Reader`,然后通过某种方式修改其数据流。
323 |
324 | 例如,[[https://go-zh.org/pkg/compress/gzip/#NewReader][`gzip.NewReader`]] 函数接受一个 `io.Reader`(已压缩的数据流)并返回一个同样实现了 `io.Reader` 的 `*gzip.Reader`(解压后的数据流)。
325 |
326 | 编写一个实现了 `io.Reader` 并从另一个 `io.Reader` 中读取数据的 `rot13Reader`,通过应用 [[http://en.wikipedia.org/wiki/ROT13][rot13]] 代换密码对数据流进行修改。
327 |
328 | `rot13Reader` 类型已经提供。实现 `Read` 方法以满足 `io.Reader`。
329 |
330 | .play methods/exercise-rot-reader.go
331 |
332 | * 图像
333 |
334 | [[https://go-zh.org/pkg/image/#Image][`image`]] 包定义了 `Image` 接口:
335 |
336 | package image
337 |
338 | type Image interface {
339 | ColorModel() color.Model
340 | Bounds() Rectangle
341 | At(x, y int) color.Color
342 | }
343 |
344 | *注意:* `Bounds` 方法的返回值 `Rectangle` 实际上是一个 [[https://go-zh.org/pkg/image/#Rectangle][`image.Rectangle`]],它在 `image` 包中声明。
345 |
346 | (请参阅[[https://go-zh.org/pkg/image/#Image][文档]]了解全部信息。)
347 |
348 | `color.Color` 和 `color.Model` 类型也是接口,但是通常因为直接使用预定义的实现 `image.RGBA` 和 `image.RGBAModel` 而被忽视了。这些接口和类型由 [[https://go-zh.org/pkg/image/color/][`image/color`]] 包定义。
349 |
350 | .play methods/images.go
351 |
352 | * 练习:图像
353 |
354 | 还记得之前编写的[[/moretypes/18][图片生成器]] 吗?我们再来编写另外一个,不过这次它将会返回一个 `image.Image` 的实现而非一个数据切片。
355 |
356 | 定义你自己的 `Image` 类型,实现[[https://go-zh.org/pkg/image/#Image][必要的方法]]并调用 `pic.ShowImage`。
357 |
358 | `Bounds` 应当返回一个 `image.Rectangle` ,例如 `image.Rect(0,`0,`w,`h)`。
359 |
360 | `ColorModel` 应当返回 `color.RGBAModel`。
361 |
362 | `At` 应当返回一个颜色。上一个图片生成器的值 `v` 对应于此次的 `color.RGBA{v,`v,`255,`255}`。
363 |
364 | .play methods/exercise-images.go
365 |
366 | * 恭喜!
367 |
368 | 你已经完成了本课程!
369 |
370 | 你可以返回[[/list][模块]]列表看看接下来学习什么,或者继续[[javascript:click('.next-page')][后面的课程]]。
371 |
--------------------------------------------------------------------------------
/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 | color: black;
8 | }
9 |
10 | /* PADDING */
11 |
12 | .CodeMirror-lines {
13 | padding: 4px 0; /* Vertical padding around content */
14 | }
15 | .CodeMirror pre {
16 | padding: 0 4px; /* Horizontal padding of content */
17 | }
18 |
19 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
20 | background-color: white; /* The little square between H and V scrollbars */
21 | }
22 |
23 | /* GUTTER */
24 |
25 | .CodeMirror-gutters {
26 | border-right: 1px solid #ddd;
27 | background-color: #f7f7f7;
28 | white-space: nowrap;
29 | }
30 | .CodeMirror-linenumbers {}
31 | .CodeMirror-linenumber {
32 | padding: 0 3px 0 5px;
33 | min-width: 20px;
34 | text-align: right;
35 | color: #999;
36 | white-space: nowrap;
37 | }
38 |
39 | .CodeMirror-guttermarker { color: black; }
40 | .CodeMirror-guttermarker-subtle { color: #999; }
41 |
42 | /* CURSOR */
43 |
44 | .CodeMirror-cursor {
45 | border-left: 1px solid black;
46 | border-right: none;
47 | width: 0;
48 | }
49 | /* Shown when moving in bi-directional text */
50 | .CodeMirror div.CodeMirror-secondarycursor {
51 | border-left: 1px solid silver;
52 | }
53 | .cm-fat-cursor .CodeMirror-cursor {
54 | width: auto;
55 | border: 0 !important;
56 | background: #7e7;
57 | }
58 | .cm-fat-cursor div.CodeMirror-cursors {
59 | z-index: 1;
60 | }
61 |
62 | .cm-animate-fat-cursor {
63 | width: auto;
64 | border: 0;
65 | -webkit-animation: blink 1.06s steps(1) infinite;
66 | -moz-animation: blink 1.06s steps(1) infinite;
67 | animation: blink 1.06s steps(1) infinite;
68 | background-color: #7e7;
69 | }
70 | @-moz-keyframes blink {
71 | 0% {}
72 | 50% { background-color: transparent; }
73 | 100% {}
74 | }
75 | @-webkit-keyframes blink {
76 | 0% {}
77 | 50% { background-color: transparent; }
78 | 100% {}
79 | }
80 | @keyframes blink {
81 | 0% {}
82 | 50% { background-color: transparent; }
83 | 100% {}
84 | }
85 |
86 | /* Can style cursor different in overwrite (non-insert) mode */
87 | .CodeMirror-overwrite .CodeMirror-cursor {}
88 |
89 | .cm-tab { display: inline-block; text-decoration: inherit; }
90 |
91 | .CodeMirror-rulers {
92 | position: absolute;
93 | left: 0; right: 0; top: -50px; bottom: -20px;
94 | overflow: hidden;
95 | }
96 | .CodeMirror-ruler {
97 | border-left: 1px solid #ccc;
98 | top: 0; bottom: 0;
99 | position: absolute;
100 | }
101 |
102 | /* DEFAULT THEME */
103 |
104 | .cm-s-default .cm-header {color: blue;}
105 | .cm-s-default .cm-quote {color: #090;}
106 | .cm-negative {color: #d44;}
107 | .cm-positive {color: #292;}
108 | .cm-header, .cm-strong {font-weight: bold;}
109 | .cm-em {font-style: italic;}
110 | .cm-link {text-decoration: underline;}
111 | .cm-strikethrough {text-decoration: line-through;}
112 |
113 | .cm-s-default .cm-keyword {color: #708;}
114 | .cm-s-default .cm-atom {color: #219;}
115 | .cm-s-default .cm-number {color: #164;}
116 | .cm-s-default .cm-def {color: #00f;}
117 | .cm-s-default .cm-variable,
118 | .cm-s-default .cm-punctuation,
119 | .cm-s-default .cm-property,
120 | .cm-s-default .cm-operator {}
121 | .cm-s-default .cm-variable-2 {color: #05a;}
122 | .cm-s-default .cm-variable-3 {color: #085;}
123 | .cm-s-default .cm-comment {color: #a50;}
124 | .cm-s-default .cm-string {color: #a11;}
125 | .cm-s-default .cm-string-2 {color: #f50;}
126 | .cm-s-default .cm-meta {color: #555;}
127 | .cm-s-default .cm-qualifier {color: #555;}
128 | .cm-s-default .cm-builtin {color: #30a;}
129 | .cm-s-default .cm-bracket {color: #997;}
130 | .cm-s-default .cm-tag {color: #170;}
131 | .cm-s-default .cm-attribute {color: #00c;}
132 | .cm-s-default .cm-hr {color: #999;}
133 | .cm-s-default .cm-link {color: #00c;}
134 |
135 | .cm-s-default .cm-error {color: #f00;}
136 | .cm-invalidchar {color: #f00;}
137 |
138 | .CodeMirror-composing { border-bottom: 2px solid; }
139 |
140 | /* Default styles for common addons */
141 |
142 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
143 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
144 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
145 | .CodeMirror-activeline-background {background: #e8f2ff;}
146 |
147 | /* STOP */
148 |
149 | /* The rest of this file contains styles related to the mechanics of
150 | the editor. You probably shouldn't touch them. */
151 |
152 | .CodeMirror {
153 | position: relative;
154 | overflow: hidden;
155 | background: white;
156 | }
157 |
158 | .CodeMirror-scroll {
159 | overflow: scroll !important; /* Things will break if this is overridden */
160 | /* 30px is the magic margin used to hide the element's real scrollbars */
161 | /* See overflow: hidden in .CodeMirror */
162 | margin-bottom: -30px; margin-right: -30px;
163 | padding-bottom: 30px;
164 | height: 100%;
165 | outline: none; /* Prevent dragging from highlighting the element */
166 | position: relative;
167 | }
168 | .CodeMirror-sizer {
169 | position: relative;
170 | border-right: 30px solid transparent;
171 | }
172 |
173 | /* The fake, visible scrollbars. Used to force redraw during scrolling
174 | before actual scrolling happens, thus preventing shaking and
175 | flickering artifacts. */
176 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
177 | position: absolute;
178 | z-index: 6;
179 | display: none;
180 | }
181 | .CodeMirror-vscrollbar {
182 | right: 0; top: 0;
183 | overflow-x: hidden;
184 | overflow-y: scroll;
185 | }
186 | .CodeMirror-hscrollbar {
187 | bottom: 0; left: 0;
188 | overflow-y: hidden;
189 | overflow-x: scroll;
190 | }
191 | .CodeMirror-scrollbar-filler {
192 | right: 0; bottom: 0;
193 | }
194 | .CodeMirror-gutter-filler {
195 | left: 0; bottom: 0;
196 | }
197 |
198 | .CodeMirror-gutters {
199 | position: absolute; left: 0; top: 0;
200 | min-height: 100%;
201 | z-index: 3;
202 | }
203 | .CodeMirror-gutter {
204 | white-space: normal;
205 | height: 100%;
206 | display: inline-block;
207 | vertical-align: top;
208 | margin-bottom: -30px;
209 | }
210 | .CodeMirror-gutter-wrapper {
211 | position: absolute;
212 | z-index: 4;
213 | background: none !important;
214 | border: none !important;
215 | }
216 | .CodeMirror-gutter-background {
217 | position: absolute;
218 | top: 0; bottom: 0;
219 | z-index: 4;
220 | }
221 | .CodeMirror-gutter-elt {
222 | position: absolute;
223 | cursor: default;
224 | z-index: 4;
225 | }
226 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent }
227 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
228 |
229 | .CodeMirror-lines {
230 | cursor: text;
231 | min-height: 1px; /* prevents collapsing before first draw */
232 | }
233 | .CodeMirror pre {
234 | /* Reset some styles that the rest of the page might have set */
235 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
236 | border-width: 0;
237 | background: transparent;
238 | font-family: inherit;
239 | font-size: inherit;
240 | margin: 0;
241 | white-space: pre;
242 | word-wrap: normal;
243 | line-height: inherit;
244 | color: inherit;
245 | z-index: 2;
246 | position: relative;
247 | overflow: visible;
248 | -webkit-tap-highlight-color: transparent;
249 | -webkit-font-variant-ligatures: contextual;
250 | font-variant-ligatures: contextual;
251 | }
252 | .CodeMirror-wrap pre {
253 | word-wrap: break-word;
254 | white-space: pre-wrap;
255 | word-break: normal;
256 | }
257 |
258 | .CodeMirror-linebackground {
259 | position: absolute;
260 | left: 0; right: 0; top: 0; bottom: 0;
261 | z-index: 0;
262 | }
263 |
264 | .CodeMirror-linewidget {
265 | position: relative;
266 | z-index: 2;
267 | overflow: auto;
268 | }
269 |
270 | .CodeMirror-widget {}
271 |
272 | .CodeMirror-rtl pre { direction: rtl; }
273 |
274 | .CodeMirror-code {
275 | outline: none;
276 | }
277 |
278 | /* Force content-box sizing for the elements where we expect it */
279 | .CodeMirror-scroll,
280 | .CodeMirror-sizer,
281 | .CodeMirror-gutter,
282 | .CodeMirror-gutters,
283 | .CodeMirror-linenumber {
284 | -moz-box-sizing: content-box;
285 | box-sizing: content-box;
286 | }
287 |
288 | .CodeMirror-measure {
289 | position: absolute;
290 | width: 100%;
291 | height: 0;
292 | overflow: hidden;
293 | visibility: hidden;
294 | }
295 |
296 | .CodeMirror-cursor {
297 | position: absolute;
298 | pointer-events: none;
299 | }
300 | .CodeMirror-measure pre { position: static; }
301 |
302 | div.CodeMirror-cursors {
303 | visibility: hidden;
304 | position: relative;
305 | z-index: 3;
306 | }
307 | div.CodeMirror-dragcursors {
308 | visibility: visible;
309 | }
310 |
311 | .CodeMirror-focused div.CodeMirror-cursors {
312 | visibility: visible;
313 | }
314 |
315 | .CodeMirror-selected { background: #d9d9d9; }
316 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
317 | .CodeMirror-crosshair { cursor: crosshair; }
318 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
319 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
320 |
321 | .cm-searching {
322 | background: #ffa;
323 | background: rgba(255, 255, 0, .4);
324 | }
325 |
326 | /* Used to force a border model for a node */
327 | .cm-force-border { padding-right: .1px; }
328 |
329 | @media print {
330 | /* Hide the cursor when printing */
331 | .CodeMirror div.CodeMirror-cursors {
332 | visibility: hidden;
333 | }
334 | }
335 |
336 | /* See issue #2901 */
337 | .cm-tab-wrap-hack:after { content: ''; }
338 |
339 | /* Help users use markselection to safely style text background */
340 | span.CodeMirror-selectedtext { background: none; }
341 |
--------------------------------------------------------------------------------
/static/lib/codemirror/AUTHORS:
--------------------------------------------------------------------------------
1 | List of CodeMirror contributors. Updated before every release.
2 |
3 | 4r2r
4 | Aaron Brooks
5 | Abdelouahab
6 | Abe Fettig
7 | Adam Ahmed
8 | Adam King
9 | Adam Particka
10 | adanlobato
11 | Adán Lobato
12 | Adrian Aichner
13 | Adrian Heine
14 | Adrien Bertrand
15 | aeroson
16 | Ahmad Amireh
17 | Ahmad M. Zawawi
18 | ahoward
19 | Akeksandr Motsjonov
20 | Alasdair Smith
21 | Alberto González Palomo
22 | Alberto Pose
23 | Albert Xing
24 | Alexander Pavlov
25 | Alexander Schepanovski
26 | Alexander Shvets
27 | Alexander Solovyov
28 | Alexandre Bique
29 | alexey-k
30 | Alex Piggott
31 | Aliaksei Chapyzhenka
32 | Allen Sarkisyan
33 | Ami Fischman
34 | Amin Shali
35 | Amin Ullah Khan
36 | amshali@google.com
37 | Amsul
38 | amuntean
39 | Amy
40 | Ananya Sen
41 | anaran
42 | AndersMad
43 | Anders Nawroth
44 | Anderson Mesquita
45 | Anders Wåglund
46 | Andrea G
47 | Andreas Reischuck
48 | Andres Taylor
49 | Andre von Houck
50 | Andrew Cheng
51 | Andrey Fedorov
52 | Andrey Klyuchnikov
53 | Andrey Lushnikov
54 | Andrey Shchekin
55 | Andy Joslin
56 | Andy Kimball
57 | Andy Li
58 | Angelo
59 | angelozerr
60 | angelo.zerr@gmail.com
61 | Ankit
62 | Ankit Ahuja
63 | Ansel Santosa
64 | Anthony Dugois
65 | anthonygego
66 | Anthony Gégo
67 | Anthony Grimes
68 | Anton Kovalyov
69 | Apollo Zhu
70 | AQNOUCH Mohammed
71 | areos
72 | Arnab Bose
73 | Arthur Müller
74 | Arun Narasani
75 | as3boyan
76 | atelierbram
77 | AtomicPages LLC
78 | Atul Bhouraskar
79 | Aurelian Oancea
80 | Axel Lewenhaupt
81 | Barret Rennie
82 | Basarat Ali Syed
83 | Bastian Müller
84 | belhaj
85 | Bem Jones-Bey
86 | benbro
87 | Beni Cherniavsky-Paskin
88 | Benjamin DeCoste
89 | Ben Keen
90 | Ben Miller
91 | Ben Mosher
92 | Bernhard Sirlinger
93 | Bert Chang
94 | Bharad
95 | BigBlueHat
96 | Billy Moon
97 | binny
98 | B Krishna Chaitanya
99 | Blaine G
100 | blukat29
101 | boomyjee
102 | borawjm
103 | Brad Metcalf
104 | Brandon Frohs
105 | Brandon Wamboldt
106 | Brett Zamir
107 | Brian Grinstead
108 | Brian Sletten
109 | brrd
110 | Bruce Mitchener
111 | Bryan Massoth
112 | Caitlin Potter
113 | Calin Barbat
114 | callodacity
115 | Camilo Roca
116 | Chad Jolly
117 | Chandra Sekhar Pydi
118 | Charles Skelton
119 | Cheah Chu Yeow
120 | Chris Coyier
121 | Chris Ford
122 | Chris Granger
123 | Chris Houseknecht
124 | Chris Lohfink
125 | Chris Morgan
126 | Chris Smith
127 | Christian Oyarzun
128 | Christian Petrov
129 | Christopher Brown
130 | Christopher Kramer
131 | Christopher Mitchell
132 | Christopher Pfohl
133 | Chunliang Lyu
134 | ciaranj
135 | CodeAnimal
136 | coderaiser
137 | Cole R Lawrence
138 | ComFreek
139 | Curtis Gagliardi
140 | dagsta
141 | daines
142 | Dale Jung
143 | Dan Bentley
144 | Dan Heberden
145 | Daniel, Dao Quang Minh
146 | Daniele Di Sarli
147 | Daniel Faust
148 | Daniel Huigens
149 | Daniel Kesler
150 | Daniel KJ
151 | Daniel Neel
152 | Daniel Parnell
153 | Danny Yoo
154 | darealshinji
155 | Darius Roberts
156 | Dave Brondsema
157 | Dave Myers
158 | David Barnett
159 | David H. Bronke
160 | David Mignot
161 | David Pathakjee
162 | David Vázquez
163 | David Whittington
164 | deebugger
165 | Deep Thought
166 | Devin Abbott
167 | Devon Carew
168 | Dick Choi
169 | dignifiedquire
170 | Dimage Sapelkin
171 | Dmitry Kiselyov
172 | domagoj412
173 | Dominator008
174 | Domizio Demichelis
175 | Doug Wikle
176 | Drew Bratcher
177 | Drew Hintz
178 | Drew Khoury
179 | Drini Cami
180 | Dror BG
181 | duralog
182 | eborden
183 | edsharp
184 | ekhaled
185 | Elisée
186 | Emmanuel Schanzer
187 | Enam Mijbah Noor
188 | Eric Allam
189 | Erik Welander
190 | eustas
191 | Fabien Dubosson
192 | Fabien O'Carroll
193 | Fabio Zendhi Nagao
194 | Faiza Alsaied
195 | Fauntleroy
196 | fbuchinger
197 | feizhang365
198 | Felipe Lalanne
199 | Felix Raab
200 | ficristo
201 | Filip Noetzel
202 | Filip Stollár
203 | flack
204 | Florian Felten
205 | ForbesLindesay
206 | Forbes Lindesay
207 | Ford_Lawnmower
208 | Forrest Oliphant
209 | Frank Wiegand
210 | Gabriel Gheorghian
211 | Gabriel Horner
212 | Gabriel Nahmias
213 | galambalazs
214 | Gary Sheng
215 | Gautam Mehta
216 | Gavin Douglas
217 | gekkoe
218 | Geordie Hall
219 | geowarin
220 | Gerard Braad
221 | Gergely Hegykozi
222 | Giovanni Calò
223 | Glebov Boris
224 | Glenn Jorde
225 | Glenn Ruehle
226 | Golevka
227 | Google Inc.
228 | Gordon Smith
229 | Grant Skinner
230 | greengiant
231 | Gregory Koberger
232 | Grzegorz Mazur
233 | Guillaume Massé
234 | Guillaume Massé
235 | guraga
236 | Gustavo Rodrigues
237 | Hakan Tunc
238 | Hans Engel
239 | Hardest
240 | Harshvardhan Gupta
241 | Hasan Karahan
242 | Hector Oswaldo Caballero
243 | Hendrik Wallbaum
244 | Herculano Campos
245 | Hiroyuki Makino
246 | hitsthings
247 | Hocdoc
248 | Hugues Malphettes
249 | Ian Beck
250 | Ian Dickinson
251 | Ian Wehrman
252 | Ian Wetherbee
253 | Ice White
254 | ICHIKAWA, Yuji
255 | idleberg
256 | ilvalle
257 | Ingo Richter
258 | Irakli Gozalishvili
259 | Ivan Kurnosov
260 | Ivoah
261 | Jacob Lee
262 | Jake Peyser
263 | Jakob Miland
264 | Jakub Vrana
265 | Jakub Vrána
266 | James Campos
267 | James Howard
268 | James Thorne
269 | Jamie Hill
270 | Jamie Morris
271 | Jan Jongboom
272 | jankeromnes
273 | Jan Keromnes
274 | Jan Odvarko
275 | Jan Schär
276 | Jan T. Sott
277 | Jared Dean
278 | Jared Forsyth
279 | Jared Jacobs
280 | Jason
281 | Jason Barnabe
282 | Jason Grout
283 | Jason Johnston
284 | Jason San Jose
285 | Jason Siefken
286 | Jaydeep Solanki
287 | Jean Boussier
288 | Jeff Blaisdell
289 | Jeff Jenkins
290 | jeffkenton
291 | Jeff Pickhardt
292 | jem (graphite)
293 | Jeremy Parmenter
294 | Jim
295 | Jim Avery
296 | JobJob
297 | jochenberger
298 | Jochen Berger
299 | Joel Einbinder
300 | joelpinheiro
301 | Johan Ask
302 | John Connor
303 | John-David Dalton
304 | John Engler
305 | John Lees-Miller
306 | John Snelson
307 | John Van Der Loo
308 | Jon Ander Peñalba
309 | Jonas Döbertin
310 | Jonathan Malmaud
311 | Jon Gacnik
312 | jongalloway
313 | Jon Malmaud
314 | Jon Sangster
315 | Joost-Wim Boekesteijn
316 | Joseph Pecoraro
317 | Josh Barnes
318 | Josh Cohen
319 | Josh Soref
320 | Joshua Newman
321 | Josh Watzman
322 | jots
323 | jsoojeon
324 | ju1ius
325 | Juan Benavides Romero
326 | Jucovschi Constantin
327 | Juho Vuori
328 | Julien Rebetez
329 | Justin Andresen
330 | Justin Hileman
331 | jwallers@gmail.com
332 | kaniga
333 | karevn
334 | Kayur Patel
335 | Kazuhito Hokamura
336 | Ken Newman
337 | ken restivo
338 | Ken Rockot
339 | Kevin Earls
340 | Kevin Muret
341 | Kevin Sawicki
342 | Kevin Ushey
343 | Klaus Silveira
344 | Koh Zi Han, Cliff
345 | komakino
346 | Konstantin Lopuhin
347 | koops
348 | Kris Ciccarello
349 | ks-ifware
350 | kubelsmieci
351 | KwanEsq
352 | Kyle Kelley
353 | Lanfei
354 | Lanny
355 | Laszlo Vidacs
356 | leaf corcoran
357 | Leonid Khachaturov
358 | Leon Sorokin
359 | Leonya Khachaturov
360 | Liam Newman
361 | Libo Cannici
362 | LloydMilligan
363 | LM
364 | lochel
365 | Lorenzo Stoakes
366 | Luca Fabbri
367 | Luciano Longo
368 | Lu Fangjian
369 | Luke Browning
370 | Luke Granger-Brown
371 | Luke Stagner
372 | lynschinzer
373 | M1cha
374 | Madhura Jayaratne
375 | Maksim Lin
376 | Maksym Taran
377 | Malay Majithia
378 | Manideep
379 | Manuel Rego Casasnovas
380 | Marat Dreizin
381 | Marcel Gerber
382 | Marcelo Camargo
383 | Marco Aurélio
384 | Marco Munizaga
385 | Marcus Bointon
386 | Marek Rudnicki
387 | Marijn Haverbeke
388 | Mário Gonçalves
389 | Mario Pietsch
390 | Mark Anderson
391 | Mark Lentczner
392 | Marko Bonaci
393 | Mark Peace
394 | Markus Bordihn
395 | Martin Balek
396 | Martín Gaitán
397 | Martin Hasoň
398 | Martin Hunt
399 | Martin Laine
400 | Martin Zagora
401 | Mason Malone
402 | Mateusz Paprocki
403 | Mathias Bynens
404 | mats cronqvist
405 | Matt Gaide
406 | Matthew Bauer
407 | Matthew Beale
408 | matthewhayes
409 | Matthew Rathbone
410 | Matthias Bussonnier
411 | Matthias BUSSONNIER
412 | Matt McDonald
413 | Matt Pass
414 | Matt Sacks
415 | mauricio
416 | Maximilian Hils
417 | Maxim Kraev
418 | Max Kirsch
419 | Max Schaefer
420 | Max Xiantu
421 | mbarkhau
422 | McBrainy
423 | mce2
424 | melpon
425 | Metatheos
426 | Micah Dubinko
427 | Michael
428 | Michael Goderbauer
429 | Michael Grey
430 | Michael Kaminsky
431 | Michael Lehenbauer
432 | Michael Zhou
433 | Michal Dorner
434 | Mighty Guava
435 | Miguel Castillo
436 | mihailik
437 | Mike
438 | Mike Brevoort
439 | Mike Diaz
440 | Mike Ivanov
441 | Mike Kadin
442 | Mike Kobit
443 | MinRK
444 | Miraculix87
445 | misfo
446 | mkaminsky11
447 | mloginov
448 | Moritz Schwörer
449 | mps
450 | ms
451 | mtaran-google
452 | Mu-An Chiou
453 | Narciso Jaramillo
454 | Nathan Williams
455 | ndr
456 | nerbert
457 | nextrevision
458 | ngn
459 | nguillaumin
460 | Ng Zhi An
461 | Nicholas Bollweg
462 | Nicholas Bollweg (Nick)
463 | Nick Kreeger
464 | Nick Small
465 | Nicolò Ribaudo
466 | Niels van Groningen
467 | nightwing
468 | Nikita Beloglazov
469 | Nikita Vasilyev
470 | Nikolay Kostov
471 | nilp0inter
472 | Nisarg Jhaveri
473 | nlwillia
474 | noragrossman
475 | Norman Rzepka
476 | Oreoluwa Onatemowo
477 | Oskar Segersvärd
478 | pablo
479 | pabloferz
480 | Pablo Zubieta
481 | Page
482 | Panupong Pasupat
483 | paris
484 | Paris
485 | Paris Kasidiaris
486 | Patil Arpith
487 | Patrick Stoica
488 | Patrick Strawderman
489 | Paul Garvin
490 | Paul Ivanov
491 | Paul Masson
492 | Pavel
493 | Pavel Feldman
494 | Pavel Petržela
495 | Pavel Strashkin
496 | Paweł Bartkiewicz
497 | peteguhl
498 | peter
499 | Peter Flynn
500 | peterkroon
501 | Peter Kroon
502 | Philipp A
503 | Philip Stadermann
504 | Pierre Gerold
505 | Piët Delport
506 | Pontus Melke
507 | prasanthj
508 | Prasanth J
509 | Prayag Verma
510 | Radek Piórkowski
511 | Rahul
512 | Rahul Anand
513 | ramwin1
514 | Randall Mason
515 | Randy Burden
516 | Randy Edmunds
517 | Rasmus Erik Voel Jensen
518 | ray ratchup
519 | Ray Ratchup
520 | Remi Nyborg
521 | Renaud Durlin
522 | Richard Denton
523 | Richard van der Meer
524 | Richard Z.H. Wang
525 | Rishi Goomar
526 | Robert Crossfield
527 | Roberto Abdelkader Martínez Pérez
528 | robertop23
529 | Robert Plummer
530 | Rrandom
531 | Rrrandom
532 | Ruslan Osmanov
533 | Ryan Petrello
534 | Ryan Prior
535 | sabaca
536 | Sam Lee
537 | Samuel Ainsworth
538 | Sam Wilson
539 | sandeepshetty
540 | Sander AKA Redsandro
541 | Sander Verweij
542 | santec
543 | Sascha Peilicke
544 | satamas
545 | satchmorun
546 | sathyamoorthi
547 | Saul Costa
548 | S. Chris Colbert
549 | SCLINIC\jdecker
550 | Scott Aikin
551 | Scott Goodhew
552 | Sebastian Zaha
553 | Sergey Goder
554 | Sergey Tselovalnikov
555 | Se-Won Kim
556 | shaund
557 | shaun gilchrist
558 | Shawn A
559 | Shea Bunge
560 | sheopory
561 | Shiv Deepak
562 | Shmuel Englard
563 | Shubham Jain
564 | Siamak Mokhtari
565 | silverwind
566 | sinkuu
567 | snasa
568 | soliton4
569 | sonson
570 | spastorelli
571 | srajanpaliwal
572 | Stanislav Oaserele
573 | Stas Kobzar
574 | Stefan Borsje
575 | Steffen Beyer
576 | Steffen Bruchmann
577 | Stephen Lavelle
578 | Steve Champagne
579 | Steve Hoover
580 | Steve O'Hara
581 | stoskov
582 | Stu Kennedy
583 | Sungho Kim
584 | sverweij
585 | Taha Jahangir
586 | takamori
587 | Tako Schotanus
588 | Takuji Shimokawa
589 | Tarmil
590 | TDaglis
591 | tel
592 | tfjgeorge
593 | Thaddee Tyl
594 | thanasis
595 | TheHowl
596 | themrmax
597 | think
598 | Thomas Dvornik
599 | Thomas Kluyver
600 | Thomas Schmid
601 | Tim Alby
602 | Tim Baumann
603 | Timothy Farrell
604 | Timothy Gu
605 | Timothy Hatcher
606 | TobiasBg
607 | Todd Berman
608 | Tomas-A
609 | Tomas Varaneckas
610 | Tom Erik Støwer
611 | Tom Klancer
612 | Tom MacWright
613 | Tony Jian
614 | Travis Heppe
615 | Triangle717
616 | Tristan Tarrant
617 | TSUYUSATO Kitsune
618 | twifkak
619 | VapidWorx
620 | Vestimir Markov
621 | vf
622 | Victor Bocharsky
623 | Vincent Woo
624 | Volker Mische
625 | Weiyan Shao
626 | wenli
627 | Wes Cossick
628 | Wesley Wiser
629 | Will Binns-Smith
630 | Will Dean
631 | William Jamieson
632 | William Stein
633 | Willy
634 | Wojtek Ptak
635 | Wu Cheng-Han
636 | Xavier Mendez
637 | Yassin N. Hassan
638 | YNH Webdev
639 | Yunchi Luo
640 | Yuvi Panda
641 | Zac Anger
642 | Zachary Dremann
643 | Zeno Rocha
644 | Zhang Hao
645 | Ziv
646 | zziuni
647 | 魏鹏刚
648 |
--------------------------------------------------------------------------------
/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: #375eab;
20 | text-decoration: none;
21 | }
22 | a.logo, .toc a {
23 | color: inherit;
24 | }
25 | h1, h2, h3, h4 {
26 | color: #333;
27 | line-height: 32px;
28 | margin: 0;
29 | }
30 | pre, code {
31 | font-family: 'Inconsolata', monospace;
32 | border-radius: 4px;
33 | color: #333;
34 | background-color: #fafafa;
35 | }
36 | pre {
37 | padding: 10px;
38 | }
39 | code {
40 | padding: 2px;
41 | }
42 | .left {
43 | display: block;
44 | float: left;
45 | margin-right: 10px;
46 | }
47 | .right {
48 | display: block;
49 | float: right;
50 | margin-left: 10px;
51 | }
52 | .bar {
53 | display: block;
54 | overflow: hidden;
55 | -moz-user-select: none;
56 | -webkit-user-select: none;
57 | -ms-user-select: none;
58 | user-select: none;
59 | }
60 | .wrapper {
61 | position: fixed;
62 | overflow: auto;
63 | top: 48px;
64 | bottom: 0;
65 | left: 0;
66 | right: 0;
67 | }
68 | .container {
69 | max-width: 800px;
70 | width: 90%;
71 | margin: 0 auto 36px auto;
72 | padding: 16px 5%;
73 | background: #ffffff;
74 | }
75 | .container a {
76 | color: #375eab;
77 | }
78 | .relative-content {
79 | display: block;
80 | position: relative;
81 | height: 100%;
82 | }
83 | .highlight {
84 | background: #b5533b !important;
85 | color: yellow !important;
86 | }
87 | .hidden {
88 | display: none;
89 | }
90 | p {
91 | margin: 16px 0;
92 | }
93 | li {
94 | margin: 8px 0;
95 | }
96 | ul {
97 | list-style: none;
98 | margin: 0;
99 | padding-left: 32px;
100 | }
101 | /* Navigation bars */
102 | .top-bar {
103 | position: fixed;
104 | left: 0;
105 | right: 0;
106 | top: 0;
107 | z-index: 1000;
108 | font-size: 1.4em;
109 | padding: 8px 24px;
110 | line-height: 32px;
111 | color: #222;
112 | background: #E0EBF5;
113 | }
114 | .nav {
115 | float: right;
116 | padding: 2px;
117 | height: 25px;
118 | width: 25px;
119 | margin-left: 10px;
120 | cursor: pointer;
121 | fill: #375eab;
122 | }
123 | .nav:hover {
124 | fill: #ffffff;
125 | }
126 | /* Module list */
127 | .page-header {
128 | font-size: 1.2em;
129 | line-height: 32px;
130 | margin: 32px 0;
131 | }
132 | @media (max-width: 515px) {
133 | .page-header {
134 | font-size: 0.75em;
135 | }
136 | }
137 | .module {
138 | margin: 32px 0;
139 | }
140 | .module-title {
141 | font-size: 1.3em;
142 | font-weight: bold;
143 | color: #333;
144 | margin: 0;
145 | }
146 | .lesson {
147 | background: #E0EBF5;
148 | padding: 8px 16px;
149 | margin: 16px 0;
150 | position: relative;
151 | }
152 | .lesson-title {
153 | display: inline-block;
154 | font-size: 1.2em;
155 | font-weight: bold;
156 | margin: 16px 0 0 0;
157 | padding-right: 48px;
158 | }
159 | /* Lesson viewer */
160 | .slide-content {
161 | padding: 16px;
162 | background: #fff;
163 | }
164 | .module-bar {
165 | font-size: 1.5em;
166 | padding: 8px 0;
167 | text-align: center;
168 | line-height: 24px;
169 | font-size: 24px;
170 | }
171 | .module-bar a {
172 | color: #375eab;
173 | position: relative;
174 | font-weight: bold;
175 | margin: 5px;
176 | }
177 | .menu-button {
178 | display: inline-block;
179 | text-decoration: none;
180 | cursor: pointer;
181 | font-size: 0.9em;
182 | border-radius: 2px;
183 | background-color: #E0EBF5;
184 | border: 1px solid rgba(0, 0, 0, 0.1);
185 | margin: 2px;
186 | height: 24px;
187 | padding: 1px 8px;
188 | line-height: 24px;
189 | color: #444;
190 | -moz-user-select: none;
191 | -webkit-user-select: none;
192 | -ms-user-select: none;
193 | user-select: none;
194 | }
195 | .menu-button:hover:not(.active) {
196 | border: 1px solid #C6C6C6;
197 | background-color: #fafafa;
198 | }
199 | .menu-button.active {
200 | background: #fff;
201 | }
202 | .menu-button[imports-checkbox]:after {
203 | content: ' - 关';
204 | }
205 | .menu-button[imports-checkbox].active:after {
206 | content: ' - 开';
207 | }
208 | .menu-button[syntax-checkbox]:after {
209 | content:' - 关';
210 | }
211 | .menu-button[syntax-checkbox].active:after {
212 | content:' - 开';
213 | }
214 | #file-menu .menu-button {
215 | float: right;
216 | }
217 | a#run, a#kill {
218 | background-color: #375eab;
219 | color: #fff;
220 | width: 40px;
221 | text-align: center;
222 | }
223 | #run:hover:not(:active), #kill:hover:not(:active) {
224 | background-color: #fff;
225 | color: #375eab;
226 | }
227 | .output:not(.active) {
228 | display: none;
229 | }
230 | .output > pre {
231 | font-family: 'Inconsolata', monospace;
232 | background: #fafafa;
233 | margin: 0;
234 | }
235 | .output .system {
236 | color: #888;
237 | }
238 | .output .stderr {
239 | color: #D00A0A;
240 | }
241 | .output-menu .menu-button {
242 | float: left;
243 | }
244 | .output-menu, #file-menu {
245 | background: #fafafa;
246 | }
247 | #explorer {
248 | height: 32px;
249 | padding-left: 30px;
250 | background: #fafafa;
251 | }
252 | #explorer .imports-checkbox {
253 | float: right;
254 | }
255 | #explorer .menu-button.active {
256 | cursor: default;
257 | }
258 | #explorer .syntax-checkbox {
259 | float: right;
260 | }
261 | /* CodeMirror */
262 | #file-editor {
263 | background: #FFFFD8;
264 | overflow: auto;
265 | }
266 | #file-editor > textarea {
267 | display: none;
268 | }
269 | #file-editor .CodeMirror {
270 | height: 100%;
271 | background: #FFFFD8;
272 | }
273 | #file-editor .CodeMirror-lines, #file-editor .CodeMirror-gutters {
274 | background: #FFFFD8;
275 | font-family: 'Inconsolata', monospace;
276 | line-height: 1.2em;
277 | }
278 | .CodeMirror-code > .line-error {
279 | background: #FF8080;
280 | }
281 | .CodeMirror-code > .line-error .CodeMirror-linenumber {
282 | color: #FF5555;
283 | font-weight: bolder;
284 | }
285 | #file-editor .CodeMirror-gutters {
286 | width: 32px;
287 | }
288 | @media (min-width: 601px) {
289 | #editor-container {
290 | position: fixed;
291 | top: 48px;
292 | left: 0px;
293 | right: 0px;
294 | bottom: 0px;
295 | overflow: hidden;
296 | background: #fff;
297 | }
298 | #left-side {
299 | position: absolute;
300 | top: 0;
301 | bottom: 0;
302 | left: 0;
303 | width: 50%;
304 | overflow: hidden;
305 | }
306 | .output {
307 | background-image: url(/static/img/gopher.png);
308 | background-repeat: no-repeat;
309 | background-position: bottom;
310 | background-color: #fff;
311 | }
312 | div[vertical-slide] {
313 | position: absolute;
314 | top: 0px;
315 | bottom: 0px;
316 | width: 5px;
317 | background: #e0ebf5;
318 | left: 50%;
319 | right: 50%;
320 | z-index: 100;
321 | cursor: move;
322 | }
323 | #right-side {
324 | position: absolute;
325 | top: 0;
326 | bottom: 0;
327 | right: 0;
328 | left: 50%;
329 | background: #fff;
330 | }
331 | .slide-content {
332 | position: absolute;
333 | left: 0;
334 | right: 0;
335 | top: 0;
336 | bottom: 30px;
337 | overflow: auto;
338 | }
339 | .module-bar {
340 | position: absolute;
341 | left: 0;
342 | right: 0;
343 | bottom: 0;
344 | padding: 4px 0;
345 | margin: 0;
346 | }
347 | #top-part {
348 | position: absolute;
349 | left: 0;
350 | right: 0;
351 | top: 0;
352 | bottom: 33%;
353 | background: #e0ebf5;
354 | }
355 | #file-editor {
356 | position: absolute;
357 | left: 0;
358 | right: 0;
359 | top: 0;
360 | bottom: 0;
361 | }
362 | div[horizontal-slide] {
363 | position: absolute;
364 | left: 0;
365 | right: 0;
366 | bottom: 33%;
367 | height: 5px;
368 | background: #e0ebf5;
369 | z-index: 100;
370 | cursor: move;
371 | }
372 | #bottom-part {
373 | position: absolute;
374 | left: 0;
375 | right: 0;
376 | bottom: 0;
377 | top: 67%;
378 | min-height: 100px;
379 | z-index: 50;
380 | }
381 | #explorer {
382 | position: absolute;
383 | top: 0;
384 | left: 0;
385 | right: 0;
386 | }
387 | #explorer + div {
388 | top: 32px;
389 | }
390 | #file-menu {
391 | position: absolute;
392 | top: 0;
393 | right: 0;
394 | left: 0;
395 | background: #fafafa;
396 | }
397 | .output {
398 | position: absolute;
399 | top: 34px;
400 | bottom: 0;
401 | left: 0;
402 | right: 0;
403 | margin: 0;
404 | padding: 0;
405 | overflow: auto;
406 | }
407 | }
408 | @media (max-width: 600px) {
409 | #top-part {
410 | border: 1px solid #ccc;
411 | }
412 | #left-side {
413 | background: #e0ebf5;
414 | }
415 | #right-side {
416 | padding-top: 48px;
417 | }
418 | #file-menu {
419 | height: 32px;
420 | }
421 | .output {
422 | background: white;
423 | max-height: 300px;
424 | overflow: auto;
425 | }
426 | #editor-container {
427 | padding-bottom: 40px;
428 | }
429 | .module-bar {
430 | position: fixed;
431 | background: #e0ebf5;
432 | left: 0;
433 | right: 0;
434 | bottom: 0;
435 | z-index: 10;
436 | height: 42px;
437 | padding: 0;
438 | overflow: hidden;
439 | text-align: center;
440 | }
441 | .module-bar * {
442 | display: inline-block;
443 | width: 25%;
444 | font-size: 1.1em;
445 | padding: 8px 0;
446 | }
447 | div[horizontal-slide], div[vertical-slide] {
448 | display: none;
449 | }
450 | }
451 | /* Table of contents */
452 | .toc {
453 | display: none;
454 | position: fixed;
455 | z-index: 200;
456 | font-size: 1.3em;
457 | top: 48px;
458 | bottom: 0;
459 | right: 0;
460 | width: 500px;
461 | background: #e0ebf5;
462 | color: black;
463 | overflow-y: auto;
464 | padding: 0;
465 | margin: 0;
466 | border-left: 4px solid #e0ebf5;
467 | border-bottom: 4px solid #e0ebf5;
468 | -moz-user-select: none;
469 | -webkit-user-select: none;
470 | -ms-user-select: none;
471 | user-select: none;
472 | }
473 | .click-catcher {
474 | position: fixed;
475 | z-index: -100;
476 | top: 0;
477 | bottom: 0;
478 | left: 0;
479 | right: 10px; /* avoid covering the TOC scroller */
480 | background: rgba(0, 0, 0, 0);
481 | }
482 | .toc * {
483 | margin: 0;
484 | padding: 0;
485 | font-size: 0.95em;
486 | display: block;
487 | }
488 | .toc span, .toc a {
489 | padding: 4px;
490 | }
491 | .toc-module {
492 | color: #375eab;
493 | background: #e0ebf5;
494 | }
495 | .toc-lesson {
496 | background: #fafafa;
497 | color: #333;
498 | margin: 1px 0;
499 | cursor: pointer;
500 | }
501 | .toc-page {
502 | background: #fff;
503 | color: #333;
504 | padding-left: 4px;
505 | display: list-item;
506 | }
507 | .toc-lesson.active .toc-page {
508 | display: list-item;
509 | }
510 | .toc-page.active {
511 | color: #375eab;
512 | font-weight: bold;
513 | }
514 | @media (max-width: 600px) {
515 | .toc {
516 | position: absolute;
517 | left: 0;
518 | right: 0;
519 | bottom: 0;
520 | width: 100%;
521 | border: none;
522 | }
523 | .toc ul {
524 | width: 100%;
525 | }
526 | .click-catcher {
527 | display: none;
528 | }
529 | }
530 |
--------------------------------------------------------------------------------