@ -0,0 +1,2 @@ |
|||||
|
webInput |
||||
|
webOutput |
@ -0,0 +1,104 @@ |
|||||
|
# Timmy [![Go Report Card](https://goreportcard.com/badge/github.com/arnaucode/timmy)](https://goreportcard.com/report/github.com/arnaucode/timmy) |
||||
|
|
||||
|
web templating engine for static websites, written in Go lang |
||||
|
|
||||
|
|
||||
|
![timmy](https://raw.githubusercontent.com/arnaucode/timmy/master/timmy.png "timmy") |
||||
|
|
||||
|
|
||||
|
## Example |
||||
|
|
||||
|
- Simple project structure example: |
||||
|
|
||||
|
``` |
||||
|
webInput/ |
||||
|
index.html |
||||
|
templates/ |
||||
|
userTemplate.html |
||||
|
userTemplate.json |
||||
|
``` |
||||
|
|
||||
|
- Set the html file: |
||||
|
|
||||
|
```html |
||||
|
<!DOCTYPE html> |
||||
|
<html> |
||||
|
<body> |
||||
|
|
||||
|
<h1>My First Heading</h1> |
||||
|
|
||||
|
<p>My first paragraph.</p> |
||||
|
|
||||
|
<timmy-template html="templates/userTemplate.html" data="templates/userTemplate.json"></timmy-template> |
||||
|
|
||||
|
</body> |
||||
|
</html> |
||||
|
|
||||
|
``` |
||||
|
|
||||
|
|
||||
|
- Set the template file: |
||||
|
|
||||
|
```html |
||||
|
<div class="class1"> |
||||
|
<div class="class2">{{username}}</div> |
||||
|
<div class="class2">{{description}}</div> |
||||
|
<div class="class2">{{phone}}</div> |
||||
|
</div> |
||||
|
``` |
||||
|
|
||||
|
- Set the template data file: |
||||
|
|
||||
|
```json |
||||
|
[{ |
||||
|
"username": "Michaela Doe", |
||||
|
"description": "Hi, I'm here to code", |
||||
|
"phone": "456456456" |
||||
|
}, |
||||
|
{ |
||||
|
"username": "John Doe", |
||||
|
"description": "Hi, I'm here", |
||||
|
"phone": "123456789" |
||||
|
}, |
||||
|
{ |
||||
|
"username": "Myself", |
||||
|
"description": "How are you", |
||||
|
"phone": "no phone" |
||||
|
} |
||||
|
] |
||||
|
``` |
||||
|
|
||||
|
- Execute Timmy |
||||
|
|
||||
|
``` |
||||
|
./timmy |
||||
|
``` |
||||
|
|
||||
|
- Output: |
||||
|
|
||||
|
```html |
||||
|
<!DOCTYPE html> |
||||
|
<html> |
||||
|
|
||||
|
<body> |
||||
|
<h1>My First Heading</h1> |
||||
|
<p>My first paragraph.</p> |
||||
|
<div class="class1"> |
||||
|
<div class="class2">Michaela Doe</div> |
||||
|
<div class="class2">Hi, I'm here to code</div> |
||||
|
<div class="class2">456456456</div> |
||||
|
</div> |
||||
|
<div class="class1"> |
||||
|
<div class="class2">John Doe</div> |
||||
|
<div class="class2">Hi, I'm here</div> |
||||
|
<div class="class2">123456789</div> |
||||
|
</div> |
||||
|
<div class="class1"> |
||||
|
<div class="class2">Myself</div> |
||||
|
<div class="class2">How are you</div> |
||||
|
<div class="class2">no phone</div> |
||||
|
</div> |
||||
|
</body> |
||||
|
|
||||
|
</html> |
||||
|
``` |
@ -0,0 +1,57 @@ |
|||||
|
package main |
||||
|
|
||||
|
import "fmt" |
||||
|
|
||||
|
//Color struct, defines the color
|
||||
|
type Color struct{} |
||||
|
|
||||
|
var c Color |
||||
|
|
||||
|
//DarkGray color
|
||||
|
func (c Color) DarkGray(t string) { |
||||
|
fmt.Print("\x1b[30;1m") //dark gray
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
||||
|
|
||||
|
//Red color
|
||||
|
func (c Color) Red(t string) { |
||||
|
fmt.Print("\x1b[31;1m") //red
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
||||
|
|
||||
|
//Green color
|
||||
|
func (c Color) Green(t string) { |
||||
|
fmt.Print("\x1b[32;1m") //green
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
||||
|
|
||||
|
//Yellow color
|
||||
|
func (c Color) Yellow(t string) { |
||||
|
fmt.Print("\x1b[33;1m") //yellow
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
||||
|
|
||||
|
//Blue color
|
||||
|
func (c Color) Blue(t string) { |
||||
|
fmt.Print("\x1b[34;1m") //blue
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
||||
|
|
||||
|
//Purple color
|
||||
|
func (c Color) Purple(t string) { |
||||
|
fmt.Print("\x1b[35;1m") //purple
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
||||
|
|
||||
|
//Cyan color
|
||||
|
func (c Color) Cyan(t string) { |
||||
|
fmt.Print("\x1b[36;1m") //cyan
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
package main |
||||
|
|
||||
|
func check(e error) { |
||||
|
if e != nil { |
||||
|
panic(e) |
||||
|
} |
||||
|
} |
@ -0,0 +1,107 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"bufio" |
||||
|
"encoding/json" |
||||
|
"io/ioutil" |
||||
|
"os" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
//dataEntry is the map used to create the array of maps, where the templatejson data is stored
|
||||
|
type dataEntry map[string]string |
||||
|
|
||||
|
func readFile(folderPath string, filename string) string { |
||||
|
dat, err := ioutil.ReadFile(folderPath + "/" + filename) |
||||
|
check(err) |
||||
|
return string(dat) |
||||
|
} |
||||
|
|
||||
|
func getDataFromJson(path string) []dataEntry { |
||||
|
var entries []dataEntry |
||||
|
file, err := ioutil.ReadFile(path) |
||||
|
check(err) |
||||
|
content := string(file) |
||||
|
var rawEntries []*json.RawMessage |
||||
|
json.Unmarshal([]byte(content), &rawEntries) |
||||
|
for i := 0; i < len(rawEntries); i++ { |
||||
|
rawEntryMarshaled, err := json.Marshal(rawEntries[i]) |
||||
|
check(err) |
||||
|
var newDataEntry map[string]string |
||||
|
json.Unmarshal(rawEntryMarshaled, &newDataEntry) |
||||
|
entries = append(entries, newDataEntry) |
||||
|
} |
||||
|
|
||||
|
return entries |
||||
|
} |
||||
|
|
||||
|
func generateFromTemplateAndData(templateContent string, entries []dataEntry) string { |
||||
|
|
||||
|
var newContent string |
||||
|
|
||||
|
for i := 0; i < len(entries); i++ { |
||||
|
var entryContent string |
||||
|
entryContent = templateContent |
||||
|
//first, get the map keys
|
||||
|
var keys []string |
||||
|
for key, _ := range entries[i] { |
||||
|
keys = append(keys, key) |
||||
|
} |
||||
|
//now, replace the keys with the values
|
||||
|
for j := 0; j < len(keys); j++ { |
||||
|
entryContent = strings.Replace(entryContent, "{{"+keys[j]+"}}", entries[i][keys[j]], -1) |
||||
|
} |
||||
|
|
||||
|
newContent = newContent + entryContent |
||||
|
} |
||||
|
return newContent |
||||
|
} |
||||
|
func getTemplateParameters(line string) (string, string) { |
||||
|
var templatePath string |
||||
|
var data string |
||||
|
line = strings.Replace(line, "<timmy-template ", "", -1) |
||||
|
line = strings.Replace(line, "></timmy-template>", "", -1) |
||||
|
attributes := strings.Split(line, " ") |
||||
|
//fmt.Println(attributes)
|
||||
|
for i := 0; i < len(attributes); i++ { |
||||
|
attSplitted := strings.Split(attributes[i], "=") |
||||
|
if attSplitted[0] == "html" { |
||||
|
templatePath = strings.Replace(attSplitted[1], `"`, "", -1) |
||||
|
} |
||||
|
if attSplitted[0] == "data" { |
||||
|
data = strings.Replace(attSplitted[1], `"`, "", -1) |
||||
|
} |
||||
|
} |
||||
|
return templatePath, data |
||||
|
} |
||||
|
|
||||
|
func useTemplate(templatePath string, dataPath string) string { |
||||
|
templateContent := readFile(rawFolderPath, templatePath) |
||||
|
entries := getDataFromJson(rawFolderPath + "/" + dataPath) |
||||
|
generated := generateFromTemplateAndData(templateContent, entries) |
||||
|
return generated |
||||
|
} |
||||
|
|
||||
|
func putTemplates(folderPath string, filename string) string { |
||||
|
var fileContent string |
||||
|
f, err := os.Open(folderPath + "/" + filename) |
||||
|
check(err) |
||||
|
scanner := bufio.NewScanner(f) |
||||
|
lineCount := 1 |
||||
|
for scanner.Scan() { |
||||
|
currentLine := scanner.Text() |
||||
|
if strings.Contains(currentLine, "<timmy-template") && strings.Contains(currentLine, "</timmy-template>") { |
||||
|
templatePath, data := getTemplateParameters(currentLine) |
||||
|
fileContent = fileContent + useTemplate(templatePath, data) |
||||
|
} else { |
||||
|
fileContent = fileContent + currentLine |
||||
|
} |
||||
|
lineCount++ |
||||
|
} |
||||
|
return fileContent |
||||
|
} |
||||
|
|
||||
|
func writeFile(path string, newContent string) { |
||||
|
err := ioutil.WriteFile(path, []byte(newContent), 0644) |
||||
|
check(err) |
||||
|
} |
@ -0,0 +1,39 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"io/ioutil" |
||||
|
"os" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
const rawFolderPath = "./webInput" |
||||
|
const newFolderPath = "./webOutput" |
||||
|
|
||||
|
func parseDir(folderPath string, newDir string) { |
||||
|
files, _ := ioutil.ReadDir(folderPath) |
||||
|
for _, f := range files { |
||||
|
fileNameSplitted := strings.Split(f.Name(), ".") |
||||
|
extension := fileNameSplitted[len(fileNameSplitted)-1] |
||||
|
if extension == "html" { |
||||
|
fileContent := putTemplates(folderPath, f.Name()) |
||||
|
writeFile(newDir+"/"+f.Name(), fileContent) |
||||
|
} else if extension == "css" { |
||||
|
fileContent := readFile(folderPath, f.Name()) |
||||
|
writeFile(newDir+"/"+f.Name(), fileContent) |
||||
|
} |
||||
|
if len(fileNameSplitted) == 1 { |
||||
|
newDir := newDir + "/" + f.Name() |
||||
|
oldDir := rawFolderPath + "/" + f.Name() |
||||
|
if _, err := os.Stat(newDir); os.IsNotExist(err) { |
||||
|
_ = os.Mkdir(newDir, 0700) |
||||
|
} |
||||
|
parseDir(oldDir, newDir) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
func main() { |
||||
|
c.Green("getting files from /webInput") |
||||
|
c.Green("templating") |
||||
|
parseDir(rawFolderPath, newFolderPath) |
||||
|
c.Green("webpage finished, wiles at /webOutput") |
||||
|
} |