@ -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") |
|||
} |