Sorry, we couldn't find that! Check out the home page?
+{{ template "footer" }}
+
+
+
diff --git a/templates/example.tmpl b/templates/example.tmpl
index 49d2f040..29b185c3 100644
--- a/templates/example.tmpl
+++ b/templates/example.tmpl
@@ -2,7 +2,7 @@
- Go by Example 中文版: {{.Name}}
+ Go by Example: {{.Name}}
-
+ by Mark McGranaghan and Eli Bendersky | source | license
{{end}}
diff --git a/templates/index.tmpl b/templates/index.tmpl
index ba1b5053..520f68bc 100644
--- a/templates/index.tmpl
+++ b/templates/index.tmpl
@@ -2,18 +2,27 @@
- Go by Example 中文版
+ Go by Example
- Go 是一门被设计用来构建简单、高效、可信赖软件的开源程序设计语言。
+ Go is an
+ open source programming language designed for
+ building simple, fast, and reliable software.
+ Please read the official
+ documentation
+ to learn a bit about Go code, tools packages,
+ and modules.
- Go by Example 是对 Go 基于实践的介绍,包含一系列带有注释说明的示例程序。查看第一个例子或者浏览下面的完整列表吧。
+ Go by Example is a hands-on introduction
+ to Go using annotated example programs. Check out
+ the first example or
+ browse the full list below.
diff --git a/templates/site.css b/templates/site.css
index 3457fa0a..f74c733c 100644
--- a/templates/site.css
+++ b/templates/site.css
@@ -1,4 +1,4 @@
-/* CSS reset: http://meyerweb.com/eric/tools/css/reset/ */
+/* CSS reset: https://meyerweb.com/eric/tools/css/reset/ */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
@@ -19,7 +19,7 @@ time, mark, audio, video {
font: inherit;
vertical-align: baseline;
}
-article, aside, details, figcaption, feigure,
+article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
@@ -29,16 +29,6 @@ body {
ol, ul {
list-style: none;
}
-li {
- padding: 2px;
-}
-a {
- text-decoration : none;
- border-bottom: #777 1px solid;
-}
-a:hover{
- border-bottom: #000 2px solid;
-}
blockquote, q {
quotes: none;
}
@@ -60,7 +50,7 @@ body {
color: #252519;
}
em {
- font-weight: bold;
+ font-style: italic;
}
a, a:visited {
color: #261a3b;
@@ -90,14 +80,15 @@ p.next {
}
p.footer {
color: grey;
+ font-size: 75%;
}
p.footer a, p.footer a:visited {
color: grey;
}
div#intro {
- width: 450px;
- min-width: 450px;
- max-width: 450px;
+ width: 420px;
+ min-width: 420px;
+ max-width: 420px;
margin-left: auto;
margin-right: auto;
margin-bottom: 120px;
diff --git a/tools/build b/tools/build
index 4f669021..1aca25b1 100755
--- a/tools/build
+++ b/tools/build
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
set -e
diff --git a/tools/build-loop b/tools/build-loop
index 53847018..02d70d57 100755
--- a/tools/build-loop
+++ b/tools/build-loop
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
TRAPPING=0
trap "{ echo finishing; TRAPPING=1; }" SIGINT
diff --git a/tools/format b/tools/format
index 457b13fe..c1045257 100755
--- a/tools/format
+++ b/tools/format
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
set -eo pipefail
diff --git a/tools/generate b/tools/generate
index af897e5b..be41375a 100755
--- a/tools/generate
+++ b/tools/generate
@@ -1,3 +1,3 @@
-#!/bin/bash
+#!/usr/bin/env bash
exec go run tools/generate.go $@
diff --git a/tools/generate.go b/tools/generate.go
index 8d8b9441..82e20476 100644
--- a/tools/generate.go
+++ b/tools/generate.go
@@ -7,16 +7,15 @@ import (
"io"
"net/http"
"os"
- "os/exec"
"path/filepath"
"regexp"
"strings"
"text/template"
- "github.com/alecthomas/chroma"
- "github.com/alecthomas/chroma/formatters/html"
- "github.com/alecthomas/chroma/lexers"
- "github.com/alecthomas/chroma/styles"
+ "github.com/alecthomas/chroma/v2"
+ "github.com/alecthomas/chroma/v2/formatters/html"
+ "github.com/alecthomas/chroma/v2/lexers"
+ "github.com/alecthomas/chroma/v2/styles"
"github.com/russross/blackfriday/v2"
)
@@ -36,6 +35,11 @@ func check(err error) {
}
}
+func isDir(path string) bool {
+ fileStat, _ := os.Stat(path)
+ return fileStat.IsDir()
+}
+
func ensureDir(dir string) {
err := os.MkdirAll(dir, 0755)
check(err)
@@ -48,25 +52,6 @@ func copyFile(src, dst string) {
check(err)
}
-func pipe(bin string, arg []string, src string) []byte {
- cmd := exec.Command(bin, arg...)
- in, err := cmd.StdinPipe()
- check(err)
- out, err := cmd.StdoutPipe()
- check(err)
- err = cmd.Start()
- check(err)
- _, err = in.Write([]byte(src))
- check(err)
- err = in.Close()
- check(err)
- bytes, err := io.ReadAll(out)
- check(err)
- err = cmd.Wait()
- check(err)
- return bytes
-}
-
func sha1Sum(s string) string {
h := sha1.New()
h.Write([]byte(s))
@@ -110,8 +95,8 @@ func debug(msg string) {
}
}
-var docsPat = regexp.MustCompile("^\\s*(\\/\\/|#)\\s")
-var dashPat = regexp.MustCompile("\\-+")
+var docsPat = regexp.MustCompile(`^(\s*(\/\/|#)\s|\s*\/\/$)`)
+var dashPat = regexp.MustCompile(`\-+`)
// Seg is a segment of an example
type Seg struct {
@@ -160,7 +145,6 @@ func parseSegs(sourcePath string) ([]*Seg, string) {
lines = append(lines, strings.Replace(line, "\t", " ", -1))
source = append(source, line)
}
- filecontent := strings.Join(source, "\n")
segs := []*Seg{}
lastSeen := ""
for _, line := range lines {
@@ -190,7 +174,12 @@ func parseSegs(sourcePath string) ([]*Seg, string) {
newSeg := Seg{Docs: "", Code: line}
segs = append(segs, &newSeg)
} else {
- segs[len(segs)-1].Code = segs[len(segs)-1].Code + "\n" + line
+ lastSeg := segs[len(segs)-1]
+ if len(lastSeg.Code) == 0 {
+ lastSeg.Code = line
+ } else {
+ lastSeg.Code = lastSeg.Code + "\n" + line
+ }
}
debug("CODE: " + line)
lastSeen = "code"
@@ -201,11 +190,10 @@ func parseSegs(sourcePath string) ([]*Seg, string) {
seg.CodeLeading = (i < (len(segs) - 1))
seg.CodeRun = strings.Contains(seg.Code, "package main")
}
- return segs, filecontent
+ return segs, strings.Join(source, "\n")
}
func chromaFormat(code, filePath string) string {
-
lexer := lexers.Get(filePath)
if lexer == nil {
lexer = lexers.Fallback
@@ -265,14 +253,7 @@ func parseExamples() []*Example {
if verbose() {
fmt.Printf("Processing %s [%d/%d]\n", exampleName, i+1, len(exampleNames))
}
- exampleNameDisplay := exampleName
- names := strings.Split(exampleName, "->")
- exampleName = names[0]
- exampleNameDisplay = names[0]
- if len(names) > 1 && strings.Trim(names[1], " ") != "" {
- exampleNameDisplay = names[1]
- }
- example := Example{Name: exampleNameDisplay}
+ example := Example{Name: exampleName}
exampleID := strings.ToLower(exampleName)
exampleID = strings.Replace(exampleID, " ", "-", -1)
exampleID = strings.Replace(exampleID, "/", "-", -1)
@@ -282,14 +263,16 @@ func parseExamples() []*Example {
example.Segs = make([][]*Seg, 0)
sourcePaths := mustGlob("examples/" + exampleID + "/*")
for _, sourcePath := range sourcePaths {
- if strings.HasSuffix(sourcePath, ".hash") {
- example.GoCodeHash, example.URLHash = parseHashFile(sourcePath)
- } else {
- sourceSegs, filecontents := parseAndRenderSegs(sourcePath)
- if filecontents != "" {
- example.GoCode = filecontents
+ if !isDir(sourcePath) {
+ if strings.HasSuffix(sourcePath, ".hash") {
+ example.GoCodeHash, example.URLHash = parseHashFile(sourcePath)
+ } else {
+ sourceSegs, filecontents := parseAndRenderSegs(sourcePath)
+ if filecontents != "" {
+ example.GoCode = filecontents
+ }
+ example.Segs = append(example.Segs, sourceSegs)
}
- example.Segs = append(example.Segs, sourceSegs)
}
}
newCodeHash := sha1Sum(example.GoCode)
@@ -314,14 +297,12 @@ func renderIndex(examples []*Example) {
fmt.Println("Rendering index")
}
indexTmpl := template.New("index")
- _, err := indexTmpl.Parse(mustReadFile("templates/footer.tmpl"))
- check(err)
- _, err = indexTmpl.Parse(mustReadFile("templates/index.tmpl"))
- check(err)
+ template.Must(indexTmpl.Parse(mustReadFile("templates/footer.tmpl")))
+ template.Must(indexTmpl.Parse(mustReadFile("templates/index.tmpl")))
indexF, err := os.Create(siteDir + "/index.html")
check(err)
- err = indexTmpl.Execute(indexF, examples)
- check(err)
+ defer indexF.Close()
+ check(indexTmpl.Execute(indexF, examples))
}
func renderExamples(examples []*Example) {
@@ -329,17 +310,29 @@ func renderExamples(examples []*Example) {
fmt.Println("Rendering examples")
}
exampleTmpl := template.New("example")
- _, err := exampleTmpl.Parse(mustReadFile("templates/footer.tmpl"))
- check(err)
- _, err = exampleTmpl.Parse(mustReadFile("templates/example.tmpl"))
- check(err)
+ template.Must(exampleTmpl.Parse(mustReadFile("templates/footer.tmpl")))
+ template.Must(exampleTmpl.Parse(mustReadFile("templates/example.tmpl")))
for _, example := range examples {
- exampleF, err := os.Create(siteDir + "/" + example.ID + ".html")
+ exampleF, err := os.Create(siteDir + "/" + example.ID)
check(err)
- exampleTmpl.Execute(exampleF, example)
+ defer exampleF.Close()
+ check(exampleTmpl.Execute(exampleF, example))
}
}
+func render404() {
+ if verbose() {
+ fmt.Println("Rendering 404")
+ }
+ tmpl := template.New("404")
+ template.Must(tmpl.Parse(mustReadFile("templates/footer.tmpl")))
+ template.Must(tmpl.Parse(mustReadFile("templates/404.tmpl")))
+ file, err := os.Create(siteDir + "/404.html")
+ check(err)
+ defer file.Close()
+ check(tmpl.Execute(file, ""))
+}
+
func main() {
if len(os.Args) > 1 {
siteDir = os.Args[1]
@@ -349,12 +342,12 @@ func main() {
copyFile("templates/site.css", siteDir+"/site.css")
copyFile("templates/site.js", siteDir+"/site.js")
copyFile("templates/favicon.ico", siteDir+"/favicon.ico")
- copyFile("templates/404.html", siteDir+"/404.html")
copyFile("templates/play.png", siteDir+"/play.png")
copyFile("templates/clipboard.png", siteDir+"/clipboard.png")
examples := parseExamples()
renderIndex(examples)
renderExamples(examples)
+ render404()
}
var SimpleShellOutputLexer = chroma.MustNewLexer(
@@ -364,30 +357,32 @@ var SimpleShellOutputLexer = chroma.MustNewLexer(
Filenames: []string{"*.sh"},
MimeTypes: []string{},
},
- chroma.Rules{
- "root": {
- // $ or > triggers the start of prompt formatting
- {`^\$`, chroma.GenericPrompt, chroma.Push("prompt")},
- {`^>`, chroma.GenericPrompt, chroma.Push("prompt")},
-
- // empty lines are just text
- {`^$\n`, chroma.Text, nil},
-
- // otherwise its all output
- {`[^\n]+$\n?`, chroma.GenericOutput, nil},
- },
- "prompt": {
- // when we find newline, do output formatting rules
- {`\n`, chroma.Text, chroma.Push("output")},
- // otherwise its all text
- {`[^\n]+$`, chroma.Text, nil},
- },
- "output": {
- // sometimes there isn't output so we go right back to prompt
- {`^\$`, chroma.GenericPrompt, chroma.Pop(1)},
- {`^>`, chroma.GenericPrompt, chroma.Pop(1)},
- // otherwise its all output
- {`[^\n]+$\n?`, chroma.GenericOutput, nil},
- },
+ func() chroma.Rules {
+ return chroma.Rules{
+ "root": {
+ // $ or > triggers the start of prompt formatting
+ {`^\$`, chroma.GenericPrompt, chroma.Push("prompt")},
+ {`^>`, chroma.GenericPrompt, chroma.Push("prompt")},
+
+ // empty lines are just text
+ {`^$\n`, chroma.Text, nil},
+
+ // otherwise its all output
+ {`[^\n]+$\n?`, chroma.GenericOutput, nil},
+ },
+ "prompt": {
+ // when we find newline, do output formatting rules
+ {`\n`, chroma.Text, chroma.Push("output")},
+ // otherwise its all text
+ {`[^\n]+$`, chroma.Text, nil},
+ },
+ "output": {
+ // sometimes there isn't output so we go right back to prompt
+ {`^\$`, chroma.GenericPrompt, chroma.Pop(1)},
+ {`^>`, chroma.GenericPrompt, chroma.Pop(1)},
+ // otherwise its all output
+ {`[^\n]+$\n?`, chroma.GenericOutput, nil},
+ },
+ }
},
)
diff --git a/tools/measure b/tools/measure
index f62c5783..39d583b5 100755
--- a/tools/measure
+++ b/tools/measure
@@ -1,3 +1,3 @@
-#!/bin/bash
+#!/usr/bin/env bash
exec go run tools/measure.go
diff --git a/tools/measure.go b/tools/measure.go
index 7c9d6beb..04c1a30b 100644
--- a/tools/measure.go
+++ b/tools/measure.go
@@ -21,6 +21,11 @@ func readLines(path string) []string {
return strings.Split(string(srcBytes), "\n")
}
+func isDir(path string) bool {
+ fileStat, _ := os.Stat(path)
+ return fileStat.IsDir()
+}
+
var commentPat = regexp.MustCompile("\\s*\\/\\/")
func main() {
@@ -29,15 +34,17 @@ func main() {
foundLongFile := false
for _, sourcePath := range sourcePaths {
foundLongLine := false
- lines := readLines(sourcePath)
- for i, line := range lines {
- // Convert tabs to spaces before measuring, so we get an accurate measure
- // of how long the output will end up being.
- line := strings.Replace(line, "\t", " ", -1)
- if !foundLongLine && !commentPat.MatchString(line) && (utf8.RuneCountInString(line) > 58) {
- fmt.Printf("measure: %s:%d\n", sourcePath, i+1)
- foundLongLine = true
- foundLongFile = true
+ if !isDir(sourcePath) {
+ lines := readLines(sourcePath)
+ for i, line := range lines {
+ // Convert tabs to spaces before measuring, so we get an accurate measure
+ // of how long the output will end up being.
+ line := strings.Replace(line, "\t", " ", -1)
+ if !foundLongLine && !commentPat.MatchString(line) && (utf8.RuneCountInString(line) > 58) {
+ fmt.Printf("measure: %s:%d\n", sourcePath, i+1)
+ foundLongLine = true
+ foundLongFile = true
+ }
}
}
}
diff --git a/tools/serve b/tools/serve
index baf68d12..1d4b594b 100755
--- a/tools/serve
+++ b/tools/serve
@@ -1,3 +1,3 @@
-#!/bin/bash
+#!/usr/bin/env bash
exec go run tools/serve.go
diff --git a/tools/serve.go b/tools/serve.go
index 57ec3fef..b31a6b0f 100644
--- a/tools/serve.go
+++ b/tools/serve.go
@@ -3,28 +3,11 @@ package main
import (
"fmt"
"net/http"
- "os"
)
-type HTMLDir struct {
- d http.Dir
-}
-
-func (d HTMLDir) Open(name string) (http.File, error) {
- f, err := d.d.Open(name)
- if os.IsNotExist(err) {
- if f, err := d.d.Open(name + ".html"); err == nil {
- return f, nil
- }
- }
- return f, err
-}
-
func main() {
port := "8000"
publicDir := "public"
fmt.Printf("Serving Go by Example at http://127.0.0.1:%s\n", port)
- fs := http.FileServer(HTMLDir{http.Dir(publicDir)})
- http.Handle("/", http.StripPrefix("/", fs))
- http.ListenAndServe(":"+port, nil)
+ http.ListenAndServe(":"+port, http.FileServer(http.Dir(publicDir)))
}
diff --git a/tools/test b/tools/test
index c0c9c551..c3805156 100755
--- a/tools/test
+++ b/tools/test
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
# Sanity testing of the examples.
diff --git a/tools/upload b/tools/upload
index 5ca34efb..c00a2ab4 100755
--- a/tools/upload
+++ b/tools/upload
@@ -1,3 +1,3 @@
-#!/bin/bash
+#!/usr/bin/env bash
exec go run tools/upload.go -region us-east-1 -bucket gobyexample.com