German (Deutsch) translation by Nikol Angelowa (you can also view the original English article)
Go ist eine netzwerkorientierte Programmiersprache, die von Google entwickelt wurde und das Schreiben netzwerkbezogener Programme vereinfacht. Mit den vielen großartigen Bibliotheken, aus denen Sie auswählen können, ist es ein Kinderspiel, eine Webanwendung zum Laufen zu bringen.
In diesem Tutorial werde ich ein Content Management System (CMS) mit Go und einigen Hilfsbibliotheken erstellen. Dieses CMS verwendet die Site-Datenstruktur, wie im ersten Lernprogramm Erstellen eines CMS: Struktur und Stil beschrieben.
Entwicklungs-Setup mit Go
Der einfachste Weg, die Programmiersprache go auf einem Mac zu installieren, ist Homebrew. Wenn Sie Homebrew noch nicht installiert haben, zeigt Ihnen das Tutorial Homebrew Demystified: OS Xs Ultimate Package Manager, wie. Befolgen Sie für andere Plattformen einfach die Anweisungen auf der Go-Download-Seite.
Geben Sie in einem Terminal Folgendes ein:
1 |
brew install go
|
Erstellen Sie in Ihrem Home-Verzeichnis das Verzeichnis go. In der Go-Sprache werden alle heruntergeladenen Bibliotheken dort gespeichert. Fügen Sie Ihrer .bashrc-Datei und / oder .zshrc-Datei diese Zeile hinzu:
1 |
export GOPATH="/Users/<your user name>/go" |
Wenn Sie fish verwenden, fügen Sie dies Ihrer config.fish-Datei hinzu:
1 |
set -xg GOPATH "/Users/<your user name>/go" |
Als nächstes müssen Sie die Bibliotheken installieren. Die goWeb-Bibliothek stellt das Webserver-Framework bereit, die amber-Bibliothek gibt den Jade-äquivalenten HTML-Präprozessor und BlackFriday übersetzt Markdown in den richtigen HTML-Code. Außerdem verwende ich die Handlebars-Bibliothek für Vorlagen. Um diese Bibliotheken zu installieren, müssen Sie Folgendes in das Projektverzeichnis eingeben:
1 |
go get github.com/hoisie/web |
2 |
go get github.com/eknkc/amber |
3 |
go get github.com/russross/blackfriday |
4 |
go get github.com/murz/go-handlebars/handlebars |
Nachdem sich Go und die erforderlichen Bibliotheken auf Ihrem System befinden, besteht der nächste Schritt darin, mit dem Codieren zu beginnen. Die goPress-Bibliothek enthält fünf Dateien, während das Hauptprogramm eine Datei zum Aufrufen der Bibliotheksfunktionen ist.
goPress.go-Bibliotheksdatei
Ich wollte, dass der Server so schnell wie möglich ist. Um dies zu erreichen, befindet sich alles, was wiederverwendbar ist, im Speicher. Daher behält eine globale Variable alle Assets und die Homepage bei. Sie könnten den Server mit allen im Speicher befindlichen Assets entwerfen, dies würde jedoch dazu führen, dass der Speicher auf großen Websites aufgebläht wird. Da die Homepage die am häufigsten geladene Seite sein sollte, wird sie auch gespeichert.
Erstellen Sie mithilfe der im letzten Lernprogramm eingerichteten Dateistruktur ein neues Verzeichnis im src-Verzeichnis mit dem Namen goPress. Hier werden alle goPress-Bibliotheksdateien abgelegt. Die erste Datei ist goPress.go. Erstellen Sie die Datei und geben Sie den folgenden Code ein.
1 |
package goPress |
2 |
|
3 |
//
|
4 |
// Package: goPress
|
5 |
//
|
6 |
// Description: This package is for the goPress CMS
|
7 |
// written in the go programming
|
8 |
// language made by Google. This package
|
9 |
// defines everything for a full
|
10 |
// CMS.
|
11 |
//
|
12 |
|
13 |
//
|
14 |
// Import the libraries we use for this program.
|
15 |
//
|
16 |
import ( |
17 |
"encoding/json"
|
18 |
"github.com/hoisie/web"
|
19 |
"io/ioutil"
|
20 |
"log"
|
21 |
"os"
|
22 |
"strings"
|
23 |
)
|
24 |
|
25 |
//
|
26 |
// Define a structure to contain all the information
|
27 |
// important to the CMS. Capticalized
|
28 |
// variables within the structure is imported
|
29 |
// and exported.
|
30 |
//
|
31 |
type goPressData struct { |
32 |
CurrentLayout string |
33 |
CurrentStyling string |
34 |
ServerAddress string |
35 |
SiteTitle string |
36 |
Sitebase string |
37 |
TemplatBase string |
38 |
CapatchaWidth int |
39 |
CapatchaHeight int |
40 |
Cache bool |
41 |
MainBase string |
42 |
content map[string]string |
43 |
layoutBase string |
44 |
mainpg string |
45 |
postbase string |
46 |
scripts string |
47 |
stylesheet string |
48 |
stylingBase string |
49 |
template string |
50 |
}
|
51 |
|
52 |
var SiteData = new(goPressData) |
53 |
var ServerParamFile string = "server.json" |
Die package
-Anweisung oben teilt dem Compiler mit, dass diese Datei Teil einer Paketbibliothek ist, und gibt den Namen der Bibliothek an. Jede Datei in diesem Verzeichnis muss diese oben haben, um Teil der Datei zu sein.
Als Nächstes importieren Sie alle Bibliotheken, auf die in dieser Datei verwiesen wird. Wenn Sie eine Bibliothek auflisten und nicht verwenden, beschwert sich der Compiler. Dies hilft, Ihren Code sauber und ordentlich zu halten.
Nach dem Laden der Bibliotheken definiere ich die verschiedenen Datenstrukturen und globalen Variablen, die das CMS verwenden wird. Diese Globals sind bibliotheksglobal. Außerhalb der Bibliothek können Variablen, die mit einem Großbuchstaben beginnen, referenziert werden, wenn der Bibliotheksname dies festlegt.
Fügen Sie als Nächstes diese Funktion derselben Datei hinzu:
1 |
//
|
2 |
// Function: GetGlobals
|
3 |
//
|
4 |
// Description: This function is used to create the
|
5 |
// global variables initialize the
|
6 |
// global variables.
|
7 |
//
|
8 |
// Inputs:
|
9 |
//
|
10 |
func GetGlobals() { |
11 |
//
|
12 |
// Load the Server Parameters from a file.
|
13 |
//
|
14 |
LoadServerParameters() |
15 |
|
16 |
//
|
17 |
// Setup the basic paths to everything.
|
18 |
//
|
19 |
SiteData.layoutBase = SiteData.TemplatBase + "layouts/" |
20 |
SiteData.stylingBase = SiteData.TemplatBase + "styling/" |
21 |
SiteData.postbase = SiteData.Sitebase + "posts/" |
22 |
|
23 |
//
|
24 |
// Create the content array that will hold the site
|
25 |
// fragments. Set the title now.
|
26 |
//
|
27 |
SiteData.content = make(map[string]string) |
28 |
SiteData.content["title"] = SiteData.SiteTitle |
29 |
|
30 |
//
|
31 |
// Log that the data is being loaded.
|
32 |
//
|
33 |
log.Println("Loading data for site: " + SiteData.SiteTitle) |
34 |
|
35 |
//
|
36 |
// Get all the basic information that is generic and
|
37 |
// in the styles and layout directories.
|
38 |
// These will then be over written if a new default
|
39 |
// in the site area is found. This gives
|
40 |
// the flexibility to load defaults from a directory
|
41 |
// without having to make sure that all
|
42 |
// the necessary ones are loaded.
|
43 |
//
|
44 |
|
45 |
//
|
46 |
// Get the 404 page contents
|
47 |
//
|
48 |
SiteData.content["404"] = GetPageContents(SiteData.stylingBase + SiteData.CurrentStyling + "/404") |
49 |
|
50 |
//
|
51 |
// Get the sidebar contents
|
52 |
//
|
53 |
SiteData.content["sidebar"] = GetPageContents(SiteData.stylingBase + SiteData.CurrentStyling + "/sidebar") |
54 |
|
55 |
//
|
56 |
// Get the footer contents
|
57 |
//
|
58 |
SiteData.content["footer"] = GetPageContents(SiteData.stylingBase + SiteData.CurrentStyling + "/footer") |
59 |
|
60 |
//
|
61 |
// Get the template contents
|
62 |
//
|
63 |
SiteData.template = GetPageContents(SiteData.layoutBase + SiteData.CurrentLayout + "/template") |
64 |
|
65 |
//
|
66 |
// Get the header contents
|
67 |
//
|
68 |
SiteData.content["header"] = GetPageContents(SiteData.stylingBase + SiteData.CurrentStyling + "/header") |
69 |
|
70 |
//
|
71 |
// Get the main page contents
|
72 |
//
|
73 |
SiteData.mainpg = GetPageContents(SiteData.Sitebase + "pages/" + "main") |
74 |
|
75 |
//
|
76 |
// The following will load page parts from the
|
77 |
// "parts" directory for the site. These might
|
78 |
// overload those already defined or add new stuff
|
79 |
// that the users site templates
|
80 |
// will need.
|
81 |
//
|
82 |
partsdir := SiteData.Sitebase + "parts/" |
83 |
|
84 |
//
|
85 |
// Read the directory.
|
86 |
//
|
87 |
fileList, err := ioutil.ReadDir(partsdir) |
88 |
if err != nil { |
89 |
//
|
90 |
// Error reading the directory.
|
91 |
//
|
92 |
log.Printf("Error reading directory: %s\n", partsdir) |
93 |
} else { |
94 |
//
|
95 |
// Get the number of items in the directory list.
|
96 |
//
|
97 |
count := len(fileList) |
98 |
|
99 |
//
|
100 |
// Loop through each directory element.
|
101 |
//
|
102 |
for i := 0; i < count; i++ { |
103 |
if !fileList[i].IsDir() { |
104 |
//
|
105 |
// It is a file. Read it and add to the
|
106 |
// scripts variable.
|
107 |
//
|
108 |
filename := fileList[i].Name() |
109 |
parts := strings.Split(filename, ".") |
110 |
if filename != ".DS_Store" { |
111 |
SiteData.content[parts[0]] = LoadFile(partsdir + filename) |
112 |
}
|
113 |
}
|
114 |
}
|
115 |
}
|
116 |
|
117 |
//
|
118 |
// Clear out the global variables not set.
|
119 |
//
|
120 |
SiteData.scripts = "" |
121 |
SiteData.stylesheet = "" |
122 |
}
|
Die GetGlobals
-Funktion lädt alle global gespeicherten Informationen für die Site. Eine auf dem Dateinamen basierende Hash-Map (ohne Erweiterung) speichert die Daten aus der Serverdatei, dem Layoutverzeichnis und dem Stilverzeichnis. Dann wird alles im Verzeichnis site/parts
in dieselbe Struktur gestellt. Auf diese Weise muss der Benutzer keine Datei im Verzeichnis site/parts
in das Verzeichnis site/parts einfügen, wenn die Site nur die im Thema angegebenen Standardeinstellungen verwenden möchte.
Fügen Sie in derselben Datei die folgenden Funktionen hinzu:
1 |
//
|
2 |
// Function: SaveServerParameters
|
3 |
//
|
4 |
// Description: This function is for saving the
|
5 |
// authorization secret for DropBox.
|
6 |
//
|
7 |
// Inputs:
|
8 |
//
|
9 |
func SaveServerParameters() { |
10 |
if wfile, err := os.Create(ServerParamFile); err == nil { |
11 |
enc := json.NewEncoder(wfile) |
12 |
enc.Encode(&SiteData) |
13 |
wfile.Close() |
14 |
} else { |
15 |
log.Println("Writing Server file denied.") |
16 |
}
|
17 |
}
|
18 |
|
19 |
//
|
20 |
// Function: LoadServerParameters
|
21 |
//
|
22 |
// Description: This function is used to load the
|
23 |
// parameters for this server.
|
24 |
//
|
25 |
// Inputs:
|
26 |
//
|
27 |
func LoadServerParameters() { |
28 |
if wfile, err := os.Open(ServerParamFile); err == nil { |
29 |
enc := json.NewDecoder(wfile) |
30 |
enc.Decode(&SiteData) |
31 |
wfile.Close() |
32 |
log.Println("Read the " + ServerParamFile + " server parameter file. Site Title is: " + SiteData.SiteTitle) |
33 |
} else { |
34 |
log.Println("No Server File found.") |
35 |
}
|
36 |
}
|
Dies sind die Hilfsfunktionen SaveServerParameters() und LoadServerParameters(). Diese Funktionen speichern und laden die verschiedenen Servereinstellungen in die Datei server.json
.
Die nächsten Funktionen dienen zum Erstellen von Routen und unseren Standardrouten. Fügen Sie diese Funktionen derselben Datei hinzu:
1 |
//
|
2 |
// Function: DefaultRoutes
|
3 |
//
|
4 |
// Description: This function sets the default
|
5 |
// routes for a CMS.
|
6 |
//
|
7 |
// Inputs:
|
8 |
//
|
9 |
func DefaultRoutes() { |
10 |
SetGetRoute("/", Mainpage) |
11 |
SetGetRoute("/sitemap.xml", SiteMap) |
12 |
SetGetRoute("/stylesheets.css", GetStylesheets) |
13 |
SetGetRoute("/scripts.js", GetScripts) |
14 |
SetGetRoute("/theme/images/(.*)", LoadThemeImage) |
15 |
SetGetRoute("/(favicon.ico)", ImagesLoad) |
16 |
SetGetRoute("/images/(.*)", ImagesLoad) |
17 |
SetGetRoute("/posts/([a-zA-Z0-9]*)/([a-zA-Z0-9]*)", PostIndex) |
18 |
SetGetRoute("/posts/([a-zA-Z0-9]*)/([a-zA-Z0-9]*)/(.*)", PostPages) |
19 |
SetGetRoute("/(.*)", TopPages) |
20 |
}
|
21 |
|
22 |
//
|
23 |
// Function: SetGetRoute
|
24 |
//
|
25 |
// Description: This function gives an easy access
|
26 |
// to the web variable setup in this
|
27 |
// library.
|
28 |
//
|
29 |
// Inputs:
|
30 |
// route Route to setup
|
31 |
// handler Function to run that route.
|
32 |
//
|
33 |
func SetGetRoute(route string, handler interface{}) { |
34 |
web.Get(route, handler) |
35 |
}
|
36 |
|
37 |
//
|
38 |
// Function: StartServer
|
39 |
//
|
40 |
// Description: This function is for starting the web
|
41 |
// server using the SiteData
|
42 |
// configuration.
|
43 |
//
|
44 |
// Inputs:
|
45 |
//
|
46 |
func StartServer(serverAddress string) { |
47 |
web.Run(serverAddress) |
48 |
}
|
Die Funktion DefaultRoutes()
erstellt die Standardrouten für unser CMS. Die Funktionen für diese Routen befinden sich in den anderen Bibliotheksdateien. Die SetGetRoute()
erstellt jede Route. Dies ist einfach ein Wrapper über die goWeb-Bibliotheksfunktion, der einen regulären Ausdruck verwendet, um das Format der Route zu definieren, und eine Funktion, die ausgeführt wird, wenn dieser Ausdruck wahr ist. Wenn Sie jemals das Sinatra-Framework für Ruby oder das Express-Framework für Node.js verwendet haben, sind Sie mit diesem Setup vertraut.
Die Erstellungsreihenfolge für die Routen ist wichtig. Wenn die erste Route einen regulären Ausdruck enthält, der mit allem übereinstimmt, ist der Rest der Routen nicht verfügbar. Die erste Route würde sie alle fangen. Daher habe ich zuerst die spezifischsten Routen und zuletzt die allgemeineren Routen definiert.
Die Funktion StartServer()
startet den Webserver. Es ruft die goWeb-Funktion Run()
auf, die die Adresse für den Server übernimmt.
Im gesamten Code nutze ich die Funktion log.PrintLn()
. Dadurch wird die mit einem Datums- und Zeitstempel versehene Nachricht an die Konsole gedruckt. Dies eignet sich hervorragend zum Debuggen, wird aber auch für die Verkehrsanalyse verwendet.
PagesPosts.go-Bibliotheksdatei
Erstellen Sie als Nächstes die Datei PagesPosts.go im selben Verzeichnis. Diese Datei enthält den gesamten Code für die Arbeit mit Seiten und Beitragstypen. Eine Seite ist einfach eine Webseite. Ein Beitrag ist alles, was im Laufe der Zeit erstellt wurde: Nachrichtenbeiträge, Blogbeiträge, Tutorials usw. Fügen Sie in dieser Datei den folgenden Code hinzu:
1 |
package goPress |
2 |
|
3 |
import ( |
4 |
"bytes"
|
5 |
"encoding/json"
|
6 |
"github.com/eknkc/amber"
|
7 |
"github.com/hoisie/web"
|
8 |
"github.com/murz/go-handlebars/handlebars"
|
9 |
"github.com/russross/blackfriday"
|
10 |
"io/ioutil"
|
11 |
"log"
|
12 |
"os"
|
13 |
"strings"
|
14 |
"time"
|
15 |
)
|
Wie in der Datei goPress.go beginnt sie mit der package
-Deklaration und der Liste der zu importierenden Bibliotheken. Diese Datei verwendet jede Bibliothek, die wir für go heruntergeladen haben.
1 |
//
|
2 |
// Function: Mainpage
|
3 |
//
|
4 |
// Description: This function is used to generate
|
5 |
// and display the main page for the
|
6 |
// web site. This function will guide
|
7 |
// the user to setup the DropBox
|
8 |
// account if this is the first time
|
9 |
// being ran or the dropbox
|
10 |
// authorization secret gets zeroed.
|
11 |
//
|
12 |
// Inputs:
|
13 |
// ctx Contents from the request
|
14 |
//
|
15 |
func Mainpage(ctx *web.Context) string { |
16 |
//
|
17 |
// Render the main page.
|
18 |
//
|
19 |
page := RenderPageContents(ctx, SiteData.mainpg, SiteData.Sitebase+"pages/main") |
20 |
|
21 |
return page |
22 |
}
|
Die Funktion Mainpage()
zeigt die Startseite der Site an. Es ist einfach ein Wrapper für die Funktion RenderPageContents()
, die die zu rendernde Hauptindexseite angibt. RenderPageContents()
erledigt die ganze eigentliche Arbeit.
1 |
//
|
2 |
// Function: SiteMap
|
3 |
//
|
4 |
// Description: This function is to give a site map
|
5 |
// to requesters.
|
6 |
//
|
7 |
// Inputs:
|
8 |
// ctx Contents from the request
|
9 |
//
|
10 |
func SiteMap(ctx *web.Context) string { |
11 |
var contents string |
12 |
|
13 |
wfile, err := os.Open(SiteData.Sitebase + "sitemap.xml") |
14 |
if err == nil { |
15 |
bcontents, _ := ioutil.ReadAll(wfile) |
16 |
contents = string(bcontents) |
17 |
wfile.Close() |
18 |
}
|
19 |
return contents |
20 |
}
|
Die SiteMap()
-Funktion gibt die Sitemap an den Anforderer weiter. Die Informationen werden aus der Datei sitemap.xml
oben im Site-Verzeichnis abgerufen.
1 |
//
|
2 |
// Function: PostPages
|
3 |
//
|
4 |
// Description: This function generates the needed
|
5 |
// post page.
|
6 |
//
|
7 |
// Inputs:
|
8 |
// ctx What the browser sends
|
9 |
// posttype The type of post
|
10 |
// postname The name of the post
|
11 |
// type instance
|
12 |
// val The name of the post
|
13 |
// page to display
|
14 |
//
|
15 |
func PostPages(ctx *web.Context, posttype string, postname string, val string) string { |
16 |
//
|
17 |
// Get the page contents and process it.
|
18 |
//
|
19 |
pgloc := SiteData.postbase + posttype + "/" + postname + "/" + val |
20 |
return RenderPageContents(ctx, GetPageContents(pgloc), pgloc) |
21 |
}
|
Die Funktion PostPages()
zeigt den richtigen angeforderten Beitrag an. Dies führt wiederum nur den Aufruf der Funktion RenderPageContents()
ein, die die gesamte Hauptarbeit erledigt.
1 |
//
|
2 |
// Function: PostIndex
|
3 |
//
|
4 |
// Description: This function generates the needed post index.
|
5 |
//
|
6 |
// Inputs:
|
7 |
// ctx What the browser sends
|
8 |
// posttype The type of post
|
9 |
// postname The name of the post type instance
|
10 |
//
|
11 |
func PostIndex(ctx *web.Context, posttype string, postname string) string { |
12 |
//
|
13 |
// Get the page contents and process it.
|
14 |
//
|
15 |
pgloc := SiteData.postbase + posttype + "/" + postname + "/index" |
16 |
return RenderPageContents(ctx, GetPageContents(pgloc), pgloc) |
17 |
}
|
Die Funktion PostIndex()
fasst die Informationen für einen Post-Index zusammen und gibt sie an die Funktion RenderPageContents()
weiter.
1 |
//
|
2 |
// Function: topPages
|
3 |
//
|
4 |
// Description: This function will generate a
|
5 |
// "static" top level page that is not
|
6 |
// a post page.
|
7 |
//
|
8 |
// Inputs:
|
9 |
// val The name of the top level page
|
10 |
//
|
11 |
func TopPages(ctx *web.Context, val string) string { |
12 |
//
|
13 |
// Look for the markdown of the page.
|
14 |
//
|
15 |
pgloc := SiteData.Sitebase + "pages/" + val |
16 |
return RenderPageContents(ctx, GetPageContents(pgloc), pgloc) |
17 |
}
|
Die Funktion topPages()
richtet die Funktion RenderPageContents()
für eine Standardseite ein. Alle Seiten befinden sich im Verzeichnis pages/
.
1 |
//
|
2 |
// Function: GetPageContents
|
3 |
//
|
4 |
// Description: This function is used to retrieve
|
5 |
// the page contents. It will first look
|
6 |
// for a markdown page, then for a html
|
7 |
// page, and then it looks for an amber
|
8 |
// page.
|
9 |
//
|
10 |
// Inputs:
|
11 |
// filename The name of the file
|
12 |
//
|
13 |
func GetPageContents(filename string) string { |
14 |
//
|
15 |
// Assume the page can not be found.
|
16 |
//
|
17 |
contents := SiteData.content["404"] |
18 |
|
19 |
//
|
20 |
// Let's look for a markdown version first.
|
21 |
//
|
22 |
wfile, err := os.Open(filename + ".md") |
23 |
if err == nil { |
24 |
bcontents, _ := ioutil.ReadAll(wfile) |
25 |
wfile.Close() |
26 |
contents = string(blackfriday.MarkdownCommon(bcontents)) |
27 |
//
|
28 |
// Double quotes were turned into “ and
|
29 |
// ”. Turn them back. Without this, and
|
30 |
// Handlebar macros will be broken.
|
31 |
//
|
32 |
contents = strings.Replace(contents, "“", "\"", -1) |
33 |
contents = strings.Replace(contents, "”", "\"", -1) |
34 |
} else { |
35 |
//
|
36 |
// It must be an html. Look for that.
|
37 |
//
|
38 |
wfile, err = os.Open(filename + ".html") |
39 |
if err == nil { |
40 |
bcontents, _ := ioutil.ReadAll(wfile) |
41 |
contents = string(bcontents) |
42 |
wfile.Close() |
43 |
} else { |
44 |
//
|
45 |
// It must be an amber. Look for that.
|
46 |
//
|
47 |
wfile, err = os.Open(filename + ".amber") |
48 |
if err == nil { |
49 |
wfile.Close() |
50 |
template, err2 := amber.CompileFile(filename+".amber", amber.Options{true, false}) |
51 |
if err2 != nil { |
52 |
//
|
53 |
// Bad amber file.
|
54 |
|
55 |
log.Println("Amber file bad: " + filename) |
56 |
} else { |
57 |
//
|
58 |
// Put the default site info.
|
59 |
//
|
60 |
pgData := SiteData.content |
61 |
|
62 |
//
|
63 |
// read in that pages specific data
|
64 |
// to be added to the rest
|
65 |
// of the data. It is stored at the
|
66 |
// same place, but in a json
|
67 |
// file.
|
68 |
//
|
69 |
if wfile, err := os.Open(filename + ".json"); err == nil { |
70 |
//
|
71 |
// Load the json file of extra
|
72 |
// data for this page. This could
|
73 |
// override the standard data as
|
74 |
// well.
|
75 |
//
|
76 |
enc := json.NewDecoder(wfile) |
77 |
enc.Decode(&pgData) |
78 |
wfile.Close() |
79 |
} else { |
80 |
log.Println("The page: " + filename + " did not have a json file.") |
81 |
}
|
82 |
|
83 |
pgData["PageName"] = filename |
84 |
|
85 |
//
|
86 |
// The amber source compiles okay.
|
87 |
// Run the template and return
|
88 |
// the results.
|
89 |
//
|
90 |
var b bytes.Buffer |
91 |
template.Execute(&b, pgData) |
92 |
contents = b.String() |
93 |
}
|
94 |
} else { |
95 |
//
|
96 |
// A file could not be found.
|
97 |
//
|
98 |
log.Println("Could not find file: " + filename) |
99 |
}
|
100 |
}
|
101 |
}
|
102 |
|
103 |
//
|
104 |
// Return the file contains obtained.
|
105 |
//
|
106 |
return contents |
107 |
}
|
Die Funktion GetPageContents()
lädt den Inhalt aller Seiten / Beiträge. Zunächst wird der nicht gefundene 404
-Seiteninhalt aus der globalen Datenstruktur geladen. Die Funktion sucht dann zuerst nach einer Markdown-Datei, dann nach einer HTML-Datei und dann nach einer Amber-Datei. Als Nächstes konvertiert die Routine alle Markdown- und Amber-Inhalte in HTML. Der Amber-Datei sind möglicherweise Daten in einer JSON-Datei zugeordnet. Diese Datendatei wird auch für die Verarbeitung der Amber-Datei geladen.
Die Markdown-Verarbeitung in Blackfriday hat Konsequenzen für den Lenkerprozessor. Der Blackfriday-Markdown für den HTML-Prozessor ändert alle doppelten Anführungszeichen in das HTML-Escape-Äquivalent (“
und ”
). Da dies für das Rendern nicht zu 100% erforderlich ist, habe ich die Änderung anschließend rückgängig gemacht. Dadurch bleiben alle Lenker-Makros, die doppelte Anführungszeichen verwenden, funktionsfähig.
Wenn Sie weitere Dateiformattypen wünschen, fügen Sie diese einfach hier hinzu. Diese Routine lädt jeden Inhaltstyp.
1 |
//
|
2 |
// Function: RenderPageContents
|
3 |
//
|
4 |
// Description: This function is used to process
|
5 |
// and render the contents of a page.
|
6 |
// It can be the main page, or a post
|
7 |
// page, or any page. It accepts the
|
8 |
// input as the contents for the page
|
9 |
// template, run the page template
|
10 |
// with it, process all shortcodes and
|
11 |
// embedded codes, and return the
|
12 |
// results.
|
13 |
//
|
14 |
// Inputs:
|
15 |
// ctx The calling context
|
16 |
// contents The pages main contents.
|
17 |
// filename The name of the file the
|
18 |
// contents was taken from.
|
19 |
//
|
20 |
func RenderPageContents(ctx *web.Context, contents string, filename string) string { |
21 |
//
|
22 |
// Set the header information
|
23 |
//
|
24 |
SetStandardHeader(ctx) |
25 |
|
26 |
//
|
27 |
// Put the default site info.
|
28 |
//
|
29 |
pgData := SiteData.content |
30 |
|
31 |
//
|
32 |
// Add data specific to this page.
|
33 |
//
|
34 |
pgData["content"] = contents |
35 |
|
36 |
//
|
37 |
// read in that pages specific data to be added to
|
38 |
// the rest of the data. It is stored at the same
|
39 |
// place, but in a json file.
|
40 |
//
|
41 |
if wfile, err := os.Open(filename + ".json"); err == nil { |
42 |
//
|
43 |
// Load the json file of extra data for this
|
44 |
// page. This could override the standard data as
|
45 |
// well.
|
46 |
//
|
47 |
enc := json.NewDecoder(wfile) |
48 |
enc.Decode(&pgData) |
49 |
wfile.Close() |
50 |
} else { |
51 |
log.Println("The page: " + filename + " did not have a json file.") |
52 |
}
|
53 |
|
54 |
//
|
55 |
// Set the Page Name data field.
|
56 |
//
|
57 |
pgData["PageName"] = filename |
58 |
|
59 |
//
|
60 |
// Register the helpers.
|
61 |
//
|
62 |
// NOTICE: All helpers can not have spaces in the
|
63 |
// parameter. Therefore, all of these
|
64 |
// helpers assume a "-" is a space. It gets
|
65 |
// translated to a space before using.
|
66 |
//
|
67 |
// Helper: save
|
68 |
//
|
69 |
// Description: This helper allows you do define
|
70 |
// macros for expanding inside the
|
71 |
// template. You give it a name,
|
72 |
// "|", and text to expand into.
|
73 |
// Currently, all spaces have to be
|
74 |
// "-".
|
75 |
//
|
76 |
handlebars.RegisterHelper("save", func(params ...interface{}) string { |
77 |
if text, ok := params[0].(string); ok { |
78 |
parts := strings.Split(text, "|") |
79 |
content := strings.Replace(parts[1], "-", " ", -1) |
80 |
pgData[parts[0]] = content |
81 |
return content |
82 |
}
|
83 |
return "" |
84 |
})
|
85 |
|
86 |
//
|
87 |
// The format has to use these sets of constants:
|
88 |
// Stdlongmonth = "January"
|
89 |
// Stdmonth = "Jan"
|
90 |
// Stdnummonth = "1"
|
91 |
// Stdzeromonth = "01"
|
92 |
// Stdlongweekday = "Monday"
|
93 |
// Stdweekday = "Mon"
|
94 |
// Stdday = "2"
|
95 |
// Stdunderday = "_2"
|
96 |
// Stdzeroday = "02"
|
97 |
// Stdhour = "15"
|
98 |
// stdHour12 = "3"
|
99 |
// stdZeroHour12 = "03"
|
100 |
// Stdminute = "4"
|
101 |
// Stdzerominute = "04"
|
102 |
// Stdsecond = "5"
|
103 |
// Stdzerosecond = "05"
|
104 |
// Stdlongyear = "2006"
|
105 |
// Stdyear = "06"
|
106 |
// Stdpm = "Pm"
|
107 |
// Stdpm = "Pm"
|
108 |
// Stdtz = "Mst"
|
109 |
//
|
110 |
// Helper: date
|
111 |
//
|
112 |
// Description: This helper prints the current
|
113 |
// date/time in the format
|
114 |
// given. Please refer to the above
|
115 |
// chart for proper format codes.
|
116 |
// EX: 07/20/2015 is "01/02/2006"
|
117 |
//
|
118 |
handlebars.RegisterHelper("date", func(params ...interface{}) string { |
119 |
if format, ok := params[0].(string); ok { |
120 |
format = strings.Replace(format, "-", " ", -1) |
121 |
tnow := time.Now() |
122 |
return tnow.Format(format) |
123 |
}
|
124 |
return "" |
125 |
})
|
126 |
|
127 |
//
|
128 |
// Render the current for the first pass.
|
129 |
//
|
130 |
page := handlebars.Render(SiteData.template, pgData) |
131 |
|
132 |
//
|
133 |
// Process any shortcodes on the page.
|
134 |
//
|
135 |
page1 := ProcessShortCodes(page) |
136 |
|
137 |
//
|
138 |
// Render new content from Short Code and filters.
|
139 |
//
|
140 |
page2 := handlebars.Render(page1, pgData) |
141 |
|
142 |
//
|
143 |
// Return the results.
|
144 |
//
|
145 |
return page2} |
RenderPageContents()
ist die Hauptfunktion zum Erstellen einer Webseite. Nachdem der Standardheader für die Antwort festgelegt wurde, erstellt diese Routine eine Datenstruktur und füllt sie mit dem Standardinhalt, dem Seiteninhalt und einer zugehörigen JSON-Datei für die Seite. Der Templater "Lenker" verwendet die Datenstruktur, um die gesamte Seite zu rendern.
Als nächstes definiert die Routine alle Hilfsfunktionen des Lenkers. Derzeit gibt es zwei: save
Helper und date
Helper. Wenn Sie weitere Hilfsfunktionen wünschen, können Sie diese hier zu Ihrem Projekt hinzufügen.
Der save
-Helfer verwendet zwei Parameter: einen Namen, der durch ein |
vom Inhalt getrennt ist. Da die Hilfsparameter für den Lenker keine Leerzeichen enthalten dürfen, verwenden die Parameter ein -
anstelle eines Leerzeichens. Auf diese Weise können Sie pro Seite Vorlagenvariablen im Kontext der Seite erstellen. Beispielsweise platziert das Makro {{save site|Custom-Computer-Tools}}
Custom Computer Tools
an der Definitionsstelle und an einer anderen Stelle auf der Seite mit {{site}}
.
Der date
-Helfer verwendet eine Formatzeichenfolge und erstellt das richtige Datum gemäß dieser Formatzeichenfolge. Beispielsweise erzeugt das Makro {{date January-2,-2006}}
an diesem Tag den October 13, 2015
.
Der Handlebars-Templater verarbeitet die Seite zweimal: bevor Shortcodes gerendert werden, falls sich Shortcodes in der Vorlagenerweiterung befinden, und nach dem Ausführen der Shortcodes, falls ein Shortcode Handlebars-Vorlagenaktionen hinzufügt. Am Ende gibt die Funktion den vollständigen HTML-Inhalt für die angeforderte Seite zurück.
1 |
//
|
2 |
// Function: SetStandardHeader
|
3 |
//
|
4 |
// Description: This function is used as a one place
|
5 |
// for setting the standard
|
6 |
// header information.
|
7 |
//
|
8 |
// Inputs:
|
9 |
//
|
10 |
func SetStandardHeader(ctx *web.Context) { |
11 |
//
|
12 |
// Set caching for the item
|
13 |
//
|
14 |
ctx.SetHeader("Cache-Control", "public", false) |
15 |
|
16 |
//
|
17 |
// Set the maximum age to one month (30 days)
|
18 |
//
|
19 |
ctx.SetHeader("Cache-Control", "max-age=2592000", false) |
20 |
|
21 |
//
|
22 |
// Set the name to gpPress for the server type.
|
23 |
//
|
24 |
ctx.SetHeader("Server", "goPress - a CMS written in go from Custom Computer Tools: http://customct.com.", true) |
25 |
}
|
Die Funktion SetStandardHeader()
legt alle benutzerdefinierten Headerelemente in der Antwort fest. Hier legen Sie die Serverinformationen und alle Caching-Steuerelemente fest.
Images.go-Bibliotheksdatei
Die nächste Datei, an der gearbeitet werden muss, ist die Datei Images.go und alle Funktionen, die zum Senden eines Bildes an den Browser erforderlich sind. Da dies ein vollständiger Webserver sein wird, muss er sich mit den Binärdaten des Sendens eines Bildes befassen. Erstellen Sie die Datei Images.go und fügen Sie diesen Code ein:
1 |
package goPress |
2 |
|
3 |
import ( |
4 |
"github.com/hoisie/web"
|
5 |
"io"
|
6 |
"io/ioutil"
|
7 |
"log"
|
8 |
"math/big"
|
9 |
"os"
|
10 |
"path/filepath"
|
11 |
)
|
12 |
|
13 |
//
|
14 |
// Function: ImagesLoad
|
15 |
//
|
16 |
// Description: This function is called to upload an image for the
|
17 |
// images directory.
|
18 |
//
|
19 |
// Inputs:
|
20 |
// val Name of the image with relative path
|
21 |
//
|
22 |
func ImagesLoad(ctx *web.Context, val string) { |
23 |
LoadImage(ctx, SiteData.Sitebase+"images/"+val) |
24 |
}
|
25 |
|
26 |
//
|
27 |
// Function: LoadThemeImage
|
28 |
//
|
29 |
// Description: This function loads images from the theme's directory.
|
30 |
//
|
31 |
// Inputs
|
32 |
// image Name of the image file to load
|
33 |
//
|
34 |
func LoadThemeImage(ctx *web.Context, image string) { |
35 |
LoadImage(ctx, SiteData.stylingBase+SiteData.CurrentStyling+"/images/"+image) |
36 |
}
|
Diese Bibliotheksdatei beginnt genau wie die anderen: eine Paketdeklaration und die Bibliotheksdeklarationen. Die ImagesLoad()
-Funktion und die LoadThemeImage()
-Funktion richten einen Aufruf der LoadImage()
-Funktion ein, um die eigentliche Arbeit zu erledigen. Diese Funktionen ermöglichen das Laden von Bildern aus dem Site-Verzeichnis oder aus dem aktuellen Themenverzeichnis.
1 |
//
|
2 |
// Function: LoadImage
|
3 |
//
|
4 |
// Description: This function does the work of
|
5 |
// loading an image file and passing it
|
6 |
// on.
|
7 |
//
|
8 |
// Inputs:
|
9 |
//
|
10 |
func LoadImage(ctx *web.Context, val string) { |
11 |
//
|
12 |
// Get the file extension.
|
13 |
//
|
14 |
fileExt := filepath.Ext(val) |
15 |
|
16 |
//
|
17 |
// Set the http header based on the file type.
|
18 |
//
|
19 |
SetStandardHeader(ctx) |
20 |
ctx.ContentType(fileExt) |
21 |
if fileExt == ".svg" { |
22 |
//
|
23 |
// This is a text based file. Read it and send to the browser.
|
24 |
//
|
25 |
wfile, err := os.Open(val) |
26 |
if err == nil { |
27 |
bcontents, _ := ioutil.ReadAll(wfile) |
28 |
wfile.Close() |
29 |
ctx.WriteString(string(bcontents)) |
30 |
}
|
31 |
} else { |
32 |
//
|
33 |
// This is a binary based file. Read it and sent the contents to the browser.
|
34 |
//
|
35 |
fi, err := os.Open(val) |
36 |
|
37 |
//
|
38 |
// Set the size of the binary coming down the pipe. Chrome has to have this value
|
39 |
// one larger than real.
|
40 |
//
|
41 |
finfo, _ := os.Stat(val) |
42 |
i := big.NewInt(finfo.Size()) |
43 |
ctx.SetHeader("Accept-Ranges", "bytes", true) |
44 |
ctx.SetHeader("Content-Length", i.String(), true) |
45 |
|
46 |
if err != nil { |
47 |
log.Println(err) |
48 |
return
|
49 |
}
|
50 |
defer fi.Close() |
51 |
|
52 |
//
|
53 |
// Create a buffer to contain the image data. Binary images usually get
|
54 |
// very big.
|
55 |
//
|
56 |
buf := make([]byte, 1024) |
57 |
|
58 |
//
|
59 |
// Go through the binary file 1K at a time and send to the browser.
|
60 |
//
|
61 |
for { |
62 |
//
|
63 |
// Read a buffer full.
|
64 |
//
|
65 |
n, err := fi.Read(buf) |
66 |
if err != nil && err != io.EOF { |
67 |
log.Println(err) |
68 |
break
|
69 |
}
|
70 |
|
71 |
//
|
72 |
// If nothing was read, then exit.
|
73 |
//
|
74 |
if n == 0 { |
75 |
break
|
76 |
}
|
77 |
|
78 |
//
|
79 |
// Write the binary buffer to the browser.
|
80 |
//
|
81 |
n2, err := ctx.Write(buf[:n]) |
82 |
if err != nil { |
83 |
log.Println(err) |
84 |
break
|
85 |
} else if n2 != n { |
86 |
log.Println("Error in sending " + val + " to the browser. Amount read does not equal the amount sent.") |
87 |
break
|
88 |
}
|
89 |
}
|
90 |
}
|
91 |
}
|
Die Funktion LoadImage()
überprüft den Bildtyp. Wenn es sich um eine SVG-Datei handelt, wird sie als einfacher Text geladen. Angenommen, alle anderen Dateitypen sind Binärdateien, werden sie von der Routine sorgfältiger geladen. Es werden Binärdateien in 1K-Blöcken hochgeladen.
StyleSheetScripts.go-Bibliotheksdatei
Die nächste Datei dient zum Laden von CSS und JavaScript. Da unser Build-Skript das gesamte CSS und JavaScript zu einer einzigen Datei kompiliert, sind diese Funktionen sehr einfach. Erstellen Sie die Datei StyleSheetScripts.go und fügen Sie die folgenden Zeilen hinzu:
1 |
package goPress |
2 |
|
3 |
import ( |
4 |
"github.com/hoisie/web"
|
5 |
"io/ioutil"
|
6 |
"log"
|
7 |
"os"
|
8 |
)
|
9 |
|
10 |
//
|
11 |
// Function: GetStylesheets
|
12 |
//
|
13 |
// Description: This function is used to produce
|
14 |
// the stylesheet to the user.
|
15 |
//
|
16 |
// Inputs:
|
17 |
//
|
18 |
func GetStylesheets(ctx *web.Context) string { |
19 |
//
|
20 |
// See if we have already loaded them or not. If so,
|
21 |
// just return the pre-loaded stylesheet.
|
22 |
//
|
23 |
ctx.SetHeader("Content-Type", "text/css", true) |
24 |
SetStandardHeader(ctx) |
25 |
tmp := "" |
26 |
if SiteData.stylesheet == "" { |
27 |
tmp = LoadFile(SiteData.Sitebase + "css/final/final.css") |
28 |
//
|
29 |
// If we are testing, we do not want the server
|
30 |
// to cache the stylesheets. Therefore, if
|
31 |
// cache is true, cache them. Otherwise,
|
32 |
// do not.
|
33 |
//
|
34 |
if SiteData.Cache == true { |
35 |
SiteData.stylesheet = tmp |
36 |
}
|
37 |
} else { |
38 |
//
|
39 |
// We have a cached style sheet. Send it to the
|
40 |
// browser.
|
41 |
//
|
42 |
tmp = SiteData.stylesheet |
43 |
}
|
44 |
|
45 |
//
|
46 |
// Return the stylesheet.
|
47 |
//
|
48 |
return tmp |
49 |
}
|
50 |
|
51 |
//
|
52 |
// Function: GetScripts
|
53 |
//
|
54 |
// Description: This function is to load JavaScripts
|
55 |
// to the browser. This will
|
56 |
// actually load all the JavaScript
|
57 |
// files into one compressed file
|
58 |
// for uploading to the browser.
|
59 |
//
|
60 |
// Inputs:
|
61 |
//
|
62 |
func GetScripts(ctx *web.Context) string { |
63 |
//
|
64 |
// See if we have already loaded them or not. If so,
|
65 |
// just return the pre-loaded scripts.
|
66 |
//
|
67 |
ctx.SetHeader("Content-Type", "text/javascript", true) |
68 |
SetStandardHeader(ctx) |
69 |
tmp := "" |
70 |
if SiteData.scripts == "" { |
71 |
tmp = LoadFile(SiteData.Sitebase + "js/final/final.js") |
72 |
|
73 |
//
|
74 |
// If we are testing, we do not want the server
|
75 |
// to cache the scripts. Therefore, if cache is
|
76 |
// true, cache them. Otherwise, do not.
|
77 |
//
|
78 |
if SiteData.Cache == true { |
79 |
SiteData.scripts = tmp |
80 |
}
|
81 |
} else { |
82 |
//
|
83 |
// We have a cached style sheet. Send it to the
|
84 |
// browser.
|
85 |
//
|
86 |
tmp = SiteData.scripts |
87 |
}
|
88 |
|
89 |
//
|
90 |
// Return the resulting compiled stylesheet.
|
91 |
//
|
92 |
return tmp |
93 |
}
|
94 |
|
95 |
//
|
96 |
// Function: LoadFile
|
97 |
//
|
98 |
// Description: This function if for loading
|
99 |
// individual file contents.
|
100 |
//
|
101 |
// Inputs
|
102 |
// file name of the file to be
|
103 |
// loaded
|
104 |
//
|
105 |
func LoadFile(file string) string { |
106 |
ret := "" |
107 |
log.Println("Loading file: " + file) |
108 |
wfile, err := os.Open(file) |
109 |
if err == nil { |
110 |
bcontents, err := ioutil.ReadAll(wfile) |
111 |
err = err |
112 |
ret = string(bcontents) |
113 |
wfile.Close() |
114 |
} else { |
115 |
//
|
116 |
// Could not read the file.
|
117 |
//
|
118 |
log.Println("Could not read: " + file) |
119 |
}
|
120 |
return ret |
121 |
}
|
Diese Datei hat drei Funktionen. Die Funktion GetStylesheets()
lädt die kompilierte CSS-Datei. Die Funktion GetScripts()
lädt die kompilierte JavaScript-Datei. Wenn das Cache-Flag gesetzt ist, werden diese beiden Funktionen den Inhalt zwischenspeichern. Ich schalte das Cache-Flag während des Testens aus. Die LoadFile()
-Funktion ist eine einfache Funktion zum Laden von Dateien zum Abrufen des Dateiinhalts.
Shortcodes.go-Bibliotheksdatei
Ich wollte zwar einen schnellen Server, aber auch viel Flexibilität. Um die Flexibilität zu erreichen, gibt es zwei verschiedene Arten von Makroerweiterungen: direkte Lenkererweiterung und Shortcode-Erweiterung.
Der Unterschied besteht darin, dass die Lenkererweiterung eine einfache Erweiterung mit geringer Logik ist, während die Shortcode-Erweiterung alles ist, was Sie in das System programmieren können: Herunterladen von Informationen von einer externen Site, Verarbeiten von Informationen mit einem externen Programm oder so ziemlich alles.
Erstellen Sie die Datei Shortcodes.go und platzieren Sie diese darin:
1 |
package goPress |
2 |
|
3 |
//
|
4 |
// Library: Shortcodes
|
5 |
//
|
6 |
// Description: This library gives the functionality
|
7 |
// of shortcodes in the CMS. A shortcode
|
8 |
// runs a function with specified
|
9 |
// argument and a surrounded contents.
|
10 |
// The function should process the
|
11 |
// contents according to the arguments
|
12 |
// and return a string for placement
|
13 |
// into the page. Shortcodes are
|
14 |
// recursively processed, therefore you
|
15 |
// can have different shortcodes inside
|
16 |
// of other shortcodes. You can not
|
17 |
// have the same shortcode inside of
|
18 |
// itself.
|
19 |
//
|
20 |
import ( |
21 |
"bytes"
|
22 |
"log"
|
23 |
"regexp"
|
24 |
)
|
25 |
|
26 |
//
|
27 |
// Type: ShortcodeFunction
|
28 |
//
|
29 |
// Description: This type defines a function that
|
30 |
// implements a shortcode. The function
|
31 |
// should receive two strings and return
|
32 |
// a string.
|
33 |
//
|
34 |
type ShortcodeFunction func(string, string) string |
35 |
|
36 |
//
|
37 |
// Library Variables:
|
38 |
//
|
39 |
// shortcodeStack This array of functions
|
40 |
// holds all of the
|
41 |
// shortcodes usable by the
|
42 |
// CMS. You add shortcodes
|
43 |
// using the AddShortCode
|
44 |
// function.
|
45 |
//
|
46 |
var ( |
47 |
shortcodeStack map[string]ShortcodeFunction |
48 |
)
|
49 |
|
50 |
//
|
51 |
// Library Function:
|
52 |
//
|
53 |
// init This function is called upon
|
54 |
// library use to initialize any
|
55 |
// variables used for the
|
56 |
// library before anyone can
|
57 |
// make a call to a library
|
58 |
// function.
|
59 |
//
|
60 |
|
61 |
func init() { |
62 |
shortcodeStack = make(map[string]ShortcodeFunction) |
63 |
}
|
Diese Datei beginnt wie alle anderen mit einer Deklaration des verwendeten Pakets und der verwendeten Bibliotheken. Bei der Definition eines speziellen ShortcodeFunction
-Variablentyps, einer Bibliotheksvariablen und einer init()
-Funktion ist dies jedoch schnell anders. Bibliotheksvariablen werden nur von einer Funktion der Bibliothek gesehen. Diese Bibliotheksvariable, shortcodeStack
, ist eine Zuordnung von Zeichenfolgen zu einer Funktion.
Mit den Funktionen der Bibliothek init()
können Sie Code ausführen, bevor andere Aufrufe an die Bibliothek erfolgen. Hier initialisiere ich die shortcodeStack
-Datenstruktur für die Liste der Shortcodes.
1 |
//
|
2 |
// Function: AddShortCode
|
3 |
//
|
4 |
// Description: This function adds a new shortcode to
|
5 |
// be used.
|
6 |
//
|
7 |
// Inputs
|
8 |
// name Name of the shortcode
|
9 |
// funct function to process the shortcode
|
10 |
//
|
11 |
func AddShortCode(name string, funct ShortcodeFunction) { |
12 |
shortcodeStack[name] = funct |
13 |
}
|
Mit der Funktion AddShortCode()
können Sie eine Funktion zum Verarbeiten eines Shortcodes für alle Shortcodes in die Bibliotheksvariable laden.
1 |
//
|
2 |
// Function: ProcessShortCodes
|
3 |
//
|
4 |
// Description: This function takes in a string,
|
5 |
// searches for shortcodes, process the
|
6 |
// shortcode, put the results into the
|
7 |
// string, and then return the fully
|
8 |
// processed string.
|
9 |
//
|
10 |
// Inputs
|
11 |
// page String with possible shortcodes
|
12 |
//
|
13 |
func ProcessShortCodes(page string) string { |
14 |
//
|
15 |
// Create a work buffer.
|
16 |
//
|
17 |
var buff bytes.Buffer |
18 |
|
19 |
//
|
20 |
// Search for shortcodes. We first capture a
|
21 |
// shortcode.
|
22 |
//
|
23 |
r, err := regexp.Compile(`\-\[([^\]]*)\]\-`) |
24 |
if err == nil { |
25 |
match := r.FindString(page) |
26 |
if match != "" { |
27 |
//
|
28 |
// Get the indexes to the matching area.
|
29 |
//
|
30 |
index := r.FindStringIndex(page) |
31 |
|
32 |
//
|
33 |
// Save all the text before the shortcode
|
34 |
// into the buffer.
|
35 |
//
|
36 |
buff.WriteString(page[0:index[0]]) |
37 |
|
38 |
//
|
39 |
// Get everything that is left after the
|
40 |
// shortcode.
|
41 |
//
|
42 |
remander := page[index[1]:] |
43 |
|
44 |
//
|
45 |
// Separate the strings out and setup
|
46 |
// variables for their contents.
|
47 |
//
|
48 |
submatch := r.FindStringSubmatch(match) |
49 |
name := "" |
50 |
contents := "" |
51 |
args := "" |
52 |
|
53 |
//
|
54 |
// Get the name of the shortcode and separate
|
55 |
// the extra arguments.
|
56 |
//
|
57 |
r3, err3 := regexp.Compile(`(\w+)(.*)`) |
58 |
if err3 == nil { |
59 |
submatch2 := r3.FindStringSubmatch(submatch[1]) |
60 |
|
61 |
//
|
62 |
// The first submatch is the name of the
|
63 |
// shortcode.
|
64 |
//
|
65 |
name = submatch2[1] |
66 |
|
67 |
//
|
68 |
// The second submatch, if any, are the
|
69 |
// arguments for the shortcode.
|
70 |
//
|
71 |
args = submatch2[2] |
72 |
} else { |
73 |
//
|
74 |
// Something happened to the internal
|
75 |
// matching.
|
76 |
//
|
77 |
name = submatch[1] |
78 |
args = "" |
79 |
}
|
80 |
|
81 |
//
|
82 |
// Find the end of the shortcode.
|
83 |
//
|
84 |
final := "\\-\\[\\/" + name + "\\]\\-" |
85 |
r2, err2 := regexp.Compile(final) |
86 |
if err2 == nil { |
87 |
index2 := r2.FindStringIndex(remander) |
88 |
if index2 != nil { |
89 |
//
|
90 |
// Get the contents and what is left
|
91 |
// over after the closing of the
|
92 |
// shortcode.
|
93 |
//
|
94 |
contents = remander[:index2[0]] |
95 |
remander2 := remander[index2[1]:] |
96 |
|
97 |
//
|
98 |
// If it is a real shortcode, then
|
99 |
// run it!
|
100 |
//
|
101 |
if shortcodeStack[name] != nil { |
102 |
//
|
103 |
// See if there is any shortcodes
|
104 |
// inside the contents area.
|
105 |
//
|
106 |
contents = ProcessShortCodes(contents) |
107 |
|
108 |
//
|
109 |
// Run the shortcode and add it's
|
110 |
// result to the buffer.
|
111 |
//
|
112 |
buff.WriteString(shortcodeStack[name](args, contents)) |
113 |
}
|
114 |
|
115 |
//
|
116 |
// Process any remaining shortcodes.
|
117 |
//
|
118 |
buff.WriteString(ProcessShortCodes(remander2)) |
119 |
|
120 |
} else { |
121 |
//
|
122 |
// We have a bad shortcode
|
123 |
// definition. All shortcodes have
|
124 |
// to be closed. Therefore,
|
125 |
// simply do not process anything and
|
126 |
// tell the logs.
|
127 |
//
|
128 |
log.Println("Bad Shortcode definition. It was not closed. Name: " + name) |
129 |
buff.WriteString(page[index[0]:index[1]]) |
130 |
buff.WriteString(ProcessShortCodes(remander)) |
131 |
}
|
132 |
} else { |
133 |
//
|
134 |
// There was an error in the regular
|
135 |
// expression for closing the shortcode.
|
136 |
//
|
137 |
log.Println("The closing shortcode's regexp did not work!") |
138 |
}
|
139 |
} else { |
140 |
//
|
141 |
// No shortcodes, just copy the page to the
|
142 |
// buffer.
|
143 |
//
|
144 |
buff.WriteString(page) |
145 |
}
|
146 |
} else { |
147 |
//
|
148 |
// If the Regular Expression is invalid, tell the
|
149 |
// world!
|
150 |
//
|
151 |
log.Println("RegEx: Invalid expression.") |
152 |
}
|
153 |
|
154 |
//
|
155 |
// Return the resulting buffer.
|
156 |
//
|
157 |
return buff.String() |
158 |
}
|
Die ProcessShortCodes()
-Funktion verwendet eine Zeichenfolge, die den Inhalt der Webseite darstellt, und sucht nach allen darin enthaltenen Shortcodes. Wenn Sie also einen Shortcode namens box
haben, fügen Sie ihn in folgendem Format in Ihre Webseite ein:
1 |
-[box args="some items"]- |
2 |
<p>This should be inside the box.</p> |
3 |
-[/box]- |
Alles nach dem Leerzeichen im Shortcode-Öffner sind die Argumente für die Verarbeitung des Shortcodes. Die Formatierung der Argumente hängt von der zu verarbeitenden Shortcode-Funktion ab.
Alle Funktionscodes müssen einen abschließenden Funktionscode haben. Was sich im öffnenden und schließenden Shortcode befindet, ist der Vorgang für Shortcodes, bevor er an die Shortcode-Verarbeitungsfunktion gesendet wird. Ich verwende das -[]-
, um einen Shortcode zu definieren, damit die Inline-JavaScript-Indizierung nicht als Shortcode verwechselt wird.
1 |
//
|
2 |
// Function: ShortcodeBox
|
3 |
//
|
4 |
// Description: This shortcode is used to put the
|
5 |
// surrounded HTML in a box div.
|
6 |
//
|
7 |
// Inputs:
|
8 |
// parms The parameters used by the
|
9 |
// shortcode
|
10 |
// context The HTML enclosed by the opening
|
11 |
// and closing shortcodes.
|
12 |
//
|
13 |
func ShortcodeBox(parms string, context string) string { |
14 |
return ("<div class='box'>" + context + "</div>") |
15 |
}
|
16 |
|
17 |
//
|
18 |
// Function: ShortcodeColumn1
|
19 |
//
|
20 |
// Description: This shortcode is used to put the
|
21 |
// surrounded HTML in the first column.
|
22 |
//
|
23 |
// Inputs:
|
24 |
// parms The parameters used by the
|
25 |
// shortcode
|
26 |
// context The HTML enclosed by the opening
|
27 |
// and closing shortcodes.
|
28 |
//
|
29 |
func ShortcodeColumn1(parms string, context string) string { |
30 |
return ("<div class='col1'>" + context + "</div>") |
31 |
}
|
32 |
|
33 |
//
|
34 |
// Function: ShortcodeColumn2
|
35 |
//
|
36 |
// Description: This shortcode is used to put the
|
37 |
// surrounded HTML in the second column.
|
38 |
//
|
39 |
// Inputs:
|
40 |
// parms The parameters used by the
|
41 |
// shortcode
|
42 |
// context The HTML enclosed by the opening
|
43 |
// and closing shortcodes.
|
44 |
//
|
45 |
func ShortcodeColumn2(parms string, context string) string { |
46 |
return ("<div class='col2'>" + context + "</div>") |
47 |
}
|
48 |
|
49 |
//
|
50 |
// Function: ShortcodePHP
|
51 |
//
|
52 |
// Description: This shortcode is for surrounding a
|
53 |
// code block and formatting it's look
|
54 |
// and feel properly. This one is for a
|
55 |
// PHP code block.
|
56 |
//
|
57 |
// Inputs:
|
58 |
// parms The parameters used by the
|
59 |
// shortcode
|
60 |
// context The HTML enclosed by the opening
|
61 |
// and closing shortcodes.
|
62 |
//
|
63 |
func ShortcodePHP(params string, context string) string { |
64 |
return ("<div class='showcode'><pre type='syntaxhighlighter' class='brush: php'>" + context + "</pre></div>") |
65 |
}
|
66 |
|
67 |
//
|
68 |
// Function: ShortcodeJS
|
69 |
//
|
70 |
// Description: This shortcode is for surrounding a
|
71 |
// code block and formatting it's look
|
72 |
// and feel properly. This one is for a
|
73 |
// JavaScript code block.
|
74 |
//
|
75 |
// Inputs:
|
76 |
// parms The parameters used by the
|
77 |
// shortcode
|
78 |
// context The HTML enclosed by the opening
|
79 |
// and closing shortcodes.
|
80 |
//
|
81 |
func ShortcodeJS(params string, context string) string { |
82 |
return ("<div class='showcode'><pre type='syntaxhighlighter' class='brush: javascript'>" + context + "</pre></div>") |
83 |
}
|
84 |
|
85 |
//
|
86 |
// Function: ShortcodeHTML
|
87 |
//
|
88 |
// Description: This shortcode is for surrounding a
|
89 |
// code block and formatting it's look
|
90 |
// and feel properly. This one is for a
|
91 |
// HTML code block.
|
92 |
//
|
93 |
// Inputs:
|
94 |
// parms The parameters used by the
|
95 |
// shortcode
|
96 |
// context The HTML enclosed by the opening
|
97 |
// and closing shortcodes.
|
98 |
//
|
99 |
func ShortcodeHTML(params string, context string) string { |
100 |
return ("<div class='showcode'><pre type='syntaxhighlighter' class='brush: html'>" + context + "</pre></div>") |
101 |
}
|
102 |
|
103 |
//
|
104 |
// Function: ShortcodeCSS
|
105 |
//
|
106 |
// Description: This shortcode is for surrounding a
|
107 |
// code block and formatting it's look
|
108 |
// and feel properly. This one is for a
|
109 |
// CSS code block.
|
110 |
//
|
111 |
// Inputs:
|
112 |
// parms The parameters used by the
|
113 |
// shortcode
|
114 |
// context The HTML enclosed by the opening
|
115 |
// and closing shortcodes.
|
116 |
//
|
117 |
func ShortcodeCSS(params string, context string) string { |
118 |
return ("<div class='showcode'><pre type='syntaxhighlighter' class='brush: css'>" + context + "</pre></div>") |
119 |
}
|
120 |
|
121 |
//
|
122 |
// Function: LoadDefaultShortcodes
|
123 |
//
|
124 |
// Description: This function is used to load in all
|
125 |
// the default shortcodes.
|
126 |
//
|
127 |
// Inputs:
|
128 |
//
|
129 |
func LoadDefaultShortcodes() { |
130 |
AddShortCode("Box", ShortcodeBox) |
131 |
AddShortCode("Column1", ShortcodeColumn1) |
132 |
AddShortCode("Column2", ShortcodeColumn2) |
133 |
AddShortCode("php", ShortcodePHP) |
134 |
AddShortCode("javascript", ShortcodeJS) |
135 |
AddShortCode("html", ShortcodeHTML) |
136 |
AddShortCode("css", ShortcodeCSS) |
137 |
}
|
Der letzte Codeabschnitt definiert sieben einfache Shortcodes und fügt sie mit der Funktion LoadDefaultShortcodes()
dem Shortcode-Array hinzu. Wenn Sie eine andere Funktionalität wünschen, müssen Sie diesen Code nur ändern und er wird überall auf Ihrer Website aktualisiert.
goPressServer.go Hauptprogrammdatei
Die letzte zu erstellende Datei ist die Hauptprogrammdatei. Erstellen Sie oben im Entwicklungsverzeichnis die Datei goPressServer.go und platzieren Sie diese Informationen:
1 |
package main |
2 |
|
3 |
import ( |
4 |
"./src/goPress"
|
5 |
)
|
6 |
|
7 |
//
|
8 |
// Function: main
|
9 |
//
|
10 |
// Description: This is the main function that is
|
11 |
// called whenever the program is
|
12 |
// executed. It will load the globals,
|
13 |
// set the different routes, and
|
14 |
// start the server.
|
15 |
//
|
16 |
// Inputs:
|
17 |
//
|
18 |
func main() { |
19 |
//
|
20 |
// Load the default Shortcodes.
|
21 |
//
|
22 |
goPress.LoadDefaultShortcodes() |
23 |
|
24 |
//
|
25 |
// Load all global variables.
|
26 |
//
|
27 |
goPress.GetGlobals() |
28 |
|
29 |
//
|
30 |
// Setup the Default routes.
|
31 |
//
|
32 |
goPress.DefaultRoutes() |
33 |
|
34 |
//
|
35 |
// Run the web server
|
36 |
//
|
37 |
goPress.StartServer(goPress.SiteData.ServerAddress) |
38 |
}
|
Die main()
-Funktion ist die Routine, die beim Ausführen des Programms aufgerufen wird. Zuerst werden die Shortcodes eingerichtet, globale Variablen geladen, die Standardrouten festgelegt und dann der Server gestartet.
Kompilieren und Ausführen
Um das gesamte Programm zu kompilieren, wechseln Sie in das oberste Verzeichnis, in dem sich die Datei goPressServer.go befindet, und geben Sie Folgendes ein:
1 |
go build goPressServer.go |
Wenn alle Dateien vorhanden sind, sollte sie auf den Mac- und Linux-Systemen mit goPressServer
und unter Windows mit goPressServer.exe
kompiliert werden.



Wenn Sie das Programm in einem Terminal ausführen, werden die Protokollanweisungen mit Datum und Uhrzeit wie oben angezeigt.



Wenn Sie Ihren Browser unter der Adresse des Servers öffnen, wird die Startseite angezeigt. Sie sehen den Beispiel-Shortcode und die zwei verschiedenen verwendeten Lenker-Hilfsfunktionen. Sie haben jetzt Ihren eigenen Webserver!
Wie Sie sehen, habe ich die Startseite geändert und dem ursprünglichen Site-Design im Tutorial Erstellen eines CMS: Struktur drei weitere Seiten hinzugefügt. Ich habe auch die JavaScript-Bibliothek Syntax Highlighter im Verzeichnis site/js/
hinzugefügt, um den Code auf der Webseite mithilfe des Shortcodes anzuzeigen.
Alle diese Änderungen dienen dazu, den Lenker und die Shortcode-Verarbeitung zu demonstrieren. Da Syntax Highlighter jedoch nicht gut mit der Komprimierung funktioniert, habe ich die JavaScript-Komprimierung aus der Gulp-Datei entfernt. Alle Änderungen befinden sich in der Download-Datei dieses Tutorials.
Es gibt einen neuen Kurs, Go Fundamentals for Building Web Servers, der eine großartige Einführung in die Go-Sprache und deren Programmierung bietet.
Abschluss
Nachdem Sie nun wissen, wie Sie einen einfachen, aber leistungsstarken Webserver mit der Sprache go erstellen, ist es Zeit für Sie, zu experimentieren. Erstellen Sie neue Seiten, Beiträge, einbettbare Teile und Shortcodes. Diese einfache Plattform ist weitaus schneller als die Verwendung von WordPress und liegt ganz in Ihrer Hand. Erzählen Sie mir in den Kommentaren unten von Ihrem Server.