...
This commit is contained in:
38
aiprompts/instructions/instruction_handlers.md
Normal file
38
aiprompts/instructions/instruction_handlers.md
Normal file
@@ -0,0 +1,38 @@
|
||||
|
||||
## handler factor
|
||||
|
||||
a handler factory has a function to register handlers
|
||||
|
||||
each handler has a name (what the actor is called)
|
||||
|
||||
|
||||
each handler represents an actor with its actions
|
||||
to understand more about heroscript which is the way how we can actor and actions see @instructions/knowledge/1_heroscript.md
|
||||
|
||||
and each handler has as function to translate heroscript to the implementation
|
||||
|
||||
the handler calls the required implementation (can be in one or more packages)
|
||||
|
||||
the handler has a play method which uses @pkg/heroscript/playbook to process heroscript and call the required implementation
|
||||
|
||||
create a folder in @pkg/heroscript/handlers which will have all the knowledge how to go from heroscript to implementation
|
||||
|
||||
|
||||
## telnet server
|
||||
|
||||
we need a generic telnet server which takes a handler factory as input
|
||||
|
||||
the telnet server is very basic, it get's messages
|
||||
each message is a heroscript
|
||||
|
||||
when an empty line is sent that means its the end of a heroscript message
|
||||
|
||||
the telnet server needs to be authenticated using a special heroscript message
|
||||
|
||||
!!auth secret:'secret123'
|
||||
|
||||
as long as that authentication has not been done it will not process any heroscript
|
||||
|
||||
the processing of heroscript happens by means of calling the handler factory
|
||||
|
||||
there can be more than one secret on the telnet server
|
435
aiprompts/instructions/instructions1.md
Normal file
435
aiprompts/instructions/instructions1.md
Normal file
@@ -0,0 +1,435 @@
|
||||
|
||||
create a golang project
|
||||
|
||||
there will be multiple
|
||||
|
||||
- modules
|
||||
- one is for installers
|
||||
- one is for a fiber web server with a web ui, swagger UI and opeapi rest interface (v3.1.0 swagger)
|
||||
- a generic redis server
|
||||
|
||||
- on the fiber webserver create multiple endpoints nicely structures as separate directories underneith the module
|
||||
- executor (for executing commands, results in jobs)
|
||||
- package manager (on basis of apt, brew, scoop)
|
||||
- create an openapi interface for each of those v3.1.0
|
||||
- integrate in generic way the goswagger interface so people can use the rest interface from web
|
||||
|
||||
- create a main server which connects to all the modules
|
||||
|
||||
|
||||
### code for the redis server
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/redcon"
|
||||
)
|
||||
|
||||
// entry represents a stored value. For strings, value is stored as a string.
|
||||
// For hashes, value is stored as a map[string]string.
|
||||
type entry struct {
|
||||
value interface{}
|
||||
expiration time.Time // zero means no expiration
|
||||
}
|
||||
|
||||
// Server holds the in-memory datastore and provides thread-safe access.
|
||||
type Server struct {
|
||||
mu sync.RWMutex
|
||||
data map[string]*entry
|
||||
}
|
||||
|
||||
// NewServer creates a new server instance and starts a cleanup goroutine.
|
||||
func NewServer() *Server {
|
||||
s := &Server{
|
||||
data: make(map[string]*entry),
|
||||
}
|
||||
go s.cleanupExpiredKeys()
|
||||
return s
|
||||
}
|
||||
|
||||
// cleanupExpiredKeys periodically removes expired keys.
|
||||
func (s *Server) cleanupExpiredKeys() {
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
now := time.Now()
|
||||
s.mu.Lock()
|
||||
for k, ent := range s.data {
|
||||
if !ent.expiration.IsZero() && now.After(ent.expiration) {
|
||||
delete(s.data, k)
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// set stores a key with a value and an optional expiration duration.
|
||||
func (s *Server) set(key string, value interface{}, duration time.Duration) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
var exp time.Time
|
||||
if duration > 0 {
|
||||
exp = time.Now().Add(duration)
|
||||
}
|
||||
s.data[key] = &entry{
|
||||
value: value,
|
||||
expiration: exp,
|
||||
}
|
||||
}
|
||||
|
||||
// get retrieves the value for a key if it exists and is not expired.
|
||||
func (s *Server) get(key string) (interface{}, bool) {
|
||||
s.mu.RLock()
|
||||
ent, ok := s.data[key]
|
||||
s.mu.RUnlock()
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if !ent.expiration.IsZero() && time.Now().After(ent.expiration) {
|
||||
// Key has expired; remove it.
|
||||
s.mu.Lock()
|
||||
delete(s.data, key)
|
||||
s.mu.Unlock()
|
||||
return nil, false
|
||||
}
|
||||
return ent.value, true
|
||||
}
|
||||
|
||||
// del deletes a key and returns 1 if the key was present.
|
||||
func (s *Server) del(key string) int {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if _, ok := s.data[key]; ok {
|
||||
delete(s.data, key)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// keys returns all keys matching the given pattern.
|
||||
// For simplicity, only "*" is fully supported.
|
||||
func (s *Server) keys(pattern string) []string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
var result []string
|
||||
// Simple pattern matching: if pattern is "*", return all nonexpired keys.
|
||||
if pattern == "*" {
|
||||
for k, ent := range s.data {
|
||||
if !ent.expiration.IsZero() && time.Now().After(ent.expiration) {
|
||||
continue
|
||||
}
|
||||
result = append(result, k)
|
||||
}
|
||||
} else {
|
||||
// For any other pattern, do a simple substring match.
|
||||
for k, ent := range s.data {
|
||||
if !ent.expiration.IsZero() && time.Now().After(ent.expiration) {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(k, pattern) {
|
||||
result = append(result, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// getHash retrieves the hash map stored at key.
|
||||
func (s *Server) getHash(key string) (map[string]string, bool) {
|
||||
v, ok := s.get(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
hash, ok := v.(map[string]string)
|
||||
return hash, ok
|
||||
}
|
||||
|
||||
// hset sets a field in the hash stored at key. It returns 1 if the field is new.
|
||||
func (s *Server) hset(key, field, value string) int {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
var hash map[string]string
|
||||
ent, exists := s.data[key]
|
||||
if exists {
|
||||
if !ent.expiration.IsZero() && time.Now().After(ent.expiration) {
|
||||
// expired; recreate a new hash.
|
||||
hash = make(map[string]string)
|
||||
s.data[key] = &entry{value: hash}
|
||||
} else {
|
||||
var ok bool
|
||||
hash, ok = ent.value.(map[string]string)
|
||||
if !ok {
|
||||
// Overwrite if the key holds a non-hash value.
|
||||
hash = make(map[string]string)
|
||||
s.data[key] = &entry{value: hash}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hash = make(map[string]string)
|
||||
s.data[key] = &entry{value: hash}
|
||||
}
|
||||
_, fieldExists := hash[field]
|
||||
hash[field] = value
|
||||
if fieldExists {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// hget retrieves the value of a field in the hash stored at key.
|
||||
func (s *Server) hget(key, field string) (string, bool) {
|
||||
hash, ok := s.getHash(key)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
val, exists := hash[field]
|
||||
return val, exists
|
||||
}
|
||||
|
||||
// hdel deletes one or more fields from the hash stored at key.
|
||||
// Returns the number of fields that were removed.
|
||||
func (s *Server) hdel(key string, fields []string) int {
|
||||
hash, ok := s.getHash(key)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
count := 0
|
||||
for _, field := range fields {
|
||||
if _, exists := hash[field]; exists {
|
||||
delete(hash, field)
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// hkeys returns all field names in the hash stored at key.
|
||||
func (s *Server) hkeys(key string) []string {
|
||||
hash, ok := s.getHash(key)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var keys []string
|
||||
for field := range hash {
|
||||
keys = append(keys, field)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// hlen returns the number of fields in the hash stored at key.
|
||||
func (s *Server) hlen(key string) int {
|
||||
hash, ok := s.getHash(key)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return len(hash)
|
||||
}
|
||||
|
||||
// incr increments the integer value stored at key by one.
|
||||
// If the key does not exist, it is set to 0 before performing the operation.
|
||||
func (s *Server) incr(key string) (int64, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
var current int64
|
||||
ent, exists := s.data[key]
|
||||
if exists {
|
||||
if !ent.expiration.IsZero() && time.Now().After(ent.expiration) {
|
||||
current = 0
|
||||
} else {
|
||||
switch v := ent.value.(type) {
|
||||
case string:
|
||||
var err error
|
||||
current, err = strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case int:
|
||||
current = int64(v)
|
||||
case int64:
|
||||
current = v
|
||||
default:
|
||||
return 0, fmt.Errorf("value is not an integer")
|
||||
}
|
||||
}
|
||||
}
|
||||
current++
|
||||
// Store the new value as a string.
|
||||
s.data[key] = &entry{
|
||||
value: strconv.FormatInt(current, 10),
|
||||
}
|
||||
return current, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
server := NewServer()
|
||||
log.Println("Starting Redis-like server on :6379")
|
||||
err := redcon.ListenAndServe(":6379",
|
||||
func(conn redcon.Conn, cmd redcon.Command) {
|
||||
// Every command is expected to have at least one argument (the command name).
|
||||
if len(cmd.Args) == 0 {
|
||||
conn.WriteError("ERR empty command")
|
||||
return
|
||||
}
|
||||
command := strings.ToLower(string(cmd.Args[0]))
|
||||
switch command {
|
||||
case "ping":
|
||||
conn.WriteString("PONG")
|
||||
case "set":
|
||||
// Usage: SET key value [EX seconds]
|
||||
if len(cmd.Args) < 3 {
|
||||
conn.WriteError("ERR wrong number of arguments for 'set' command")
|
||||
return
|
||||
}
|
||||
key := string(cmd.Args[1])
|
||||
value := string(cmd.Args[2])
|
||||
duration := time.Duration(0)
|
||||
// Check for an expiration option (only EX is supported here).
|
||||
if len(cmd.Args) > 3 {
|
||||
if strings.ToLower(string(cmd.Args[3])) == "ex" && len(cmd.Args) > 4 {
|
||||
seconds, err := strconv.Atoi(string(cmd.Args[4]))
|
||||
if err != nil {
|
||||
conn.WriteError("ERR invalid expire time")
|
||||
return
|
||||
}
|
||||
duration = time.Duration(seconds) * time.Second
|
||||
}
|
||||
}
|
||||
server.set(key, value, duration)
|
||||
conn.WriteString("OK")
|
||||
case "get":
|
||||
if len(cmd.Args) < 2 {
|
||||
conn.WriteError("ERR wrong number of arguments for 'get' command")
|
||||
return
|
||||
}
|
||||
key := string(cmd.Args[1])
|
||||
v, ok := server.get(key)
|
||||
if !ok {
|
||||
conn.WriteNull()
|
||||
return
|
||||
}
|
||||
// Only string type is returned by GET.
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
conn.WriteBulkString(val)
|
||||
default:
|
||||
conn.WriteError("WRONGTYPE Operation against a key holding the wrong kind of value")
|
||||
}
|
||||
case "del":
|
||||
if len(cmd.Args) < 2 {
|
||||
conn.WriteError("ERR wrong number of arguments for 'del' command")
|
||||
return
|
||||
}
|
||||
count := 0
|
||||
for i := 1; i < len(cmd.Args); i++ {
|
||||
key := string(cmd.Args[i])
|
||||
count += server.del(key)
|
||||
}
|
||||
conn.WriteInt(count)
|
||||
case "keys":
|
||||
if len(cmd.Args) < 2 {
|
||||
conn.WriteError("ERR wrong number of arguments for 'keys' command")
|
||||
return
|
||||
}
|
||||
pattern := string(cmd.Args[1])
|
||||
keys := server.keys(pattern)
|
||||
res := make([][]byte, len(keys))
|
||||
for i, k := range keys {
|
||||
res[i] = []byte(k)
|
||||
}
|
||||
conn.WriteArray(res)
|
||||
case "hset":
|
||||
// Usage: HSET key field value
|
||||
if len(cmd.Args) < 4 {
|
||||
conn.WriteError("ERR wrong number of arguments for 'hset' command")
|
||||
return
|
||||
}
|
||||
key := string(cmd.Args[1])
|
||||
field := string(cmd.Args[2])
|
||||
value := string(cmd.Args[3])
|
||||
added := server.hset(key, field, value)
|
||||
conn.WriteInt(added)
|
||||
case "hget":
|
||||
// Usage: HGET key field
|
||||
if len(cmd.Args) < 3 {
|
||||
conn.WriteError("ERR wrong number of arguments for 'hget' command")
|
||||
return
|
||||
}
|
||||
key := string(cmd.Args[1])
|
||||
field := string(cmd.Args[2])
|
||||
v, ok := server.hget(key, field)
|
||||
if !ok {
|
||||
conn.WriteNull()
|
||||
return
|
||||
}
|
||||
conn.WriteBulkString(v)
|
||||
case "hdel":
|
||||
// Usage: HDEL key field [field ...]
|
||||
if len(cmd.Args) < 3 {
|
||||
conn.WriteError("ERR wrong number of arguments for 'hdel' command")
|
||||
return
|
||||
}
|
||||
key := string(cmd.Args[1])
|
||||
fields := make([]string, 0, len(cmd.Args)-2)
|
||||
for i := 2; i < len(cmd.Args); i++ {
|
||||
fields = append(fields, string(cmd.Args[i]))
|
||||
}
|
||||
removed := server.hdel(key, fields)
|
||||
conn.WriteInt(removed)
|
||||
case "hkeys":
|
||||
// Usage: HKEYS key
|
||||
if len(cmd.Args) < 2 {
|
||||
conn.WriteError("ERR wrong number of arguments for 'hkeys' command")
|
||||
return
|
||||
}
|
||||
key := string(cmd.Args[1])
|
||||
fields := server.hkeys(key)
|
||||
res := make([][]byte, len(fields))
|
||||
for i, field := range fields {
|
||||
res[i] = []byte(field)
|
||||
}
|
||||
conn.WriteArray(res)
|
||||
case "hlen":
|
||||
// Usage: HLEN key
|
||||
if len(cmd.Args) < 2 {
|
||||
conn.WriteError("ERR wrong number of arguments for 'hlen' command")
|
||||
return
|
||||
}
|
||||
key := string(cmd.Args[1])
|
||||
length := server.hlen(key)
|
||||
conn.WriteInt(length)
|
||||
case "incr":
|
||||
if len(cmd.Args) < 2 {
|
||||
conn.WriteError("ERR wrong number of arguments for 'incr' command")
|
||||
return
|
||||
}
|
||||
key := string(cmd.Args[1])
|
||||
newVal, err := server.incr(key)
|
||||
if err != nil {
|
||||
conn.WriteError("ERR " + err.Error())
|
||||
return
|
||||
}
|
||||
conn.WriteInt64(newVal)
|
||||
default:
|
||||
conn.WriteError("ERR unknown command '" + command + "'")
|
||||
}
|
||||
},
|
||||
// Accept connection: always allow.
|
||||
func(conn redcon.Conn) bool { return true },
|
||||
// On connection close.
|
||||
func(conn redcon.Conn, err error) {},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
test above code, test with a redis client it works
|
78
aiprompts/instructions/instructions_doctree.md
Normal file
78
aiprompts/instructions/instructions_doctree.md
Normal file
@@ -0,0 +1,78 @@
|
||||
there is a module called doctree
|
||||
|
||||
the metadata info of doctree is stored in a redis server
|
||||
|
||||
there is the concept of collection
|
||||
|
||||
a collection has markdown pages
|
||||
each page has a unique name in the collection
|
||||
|
||||
a collection has files which can be a sstd file or image
|
||||
each file has a unique name
|
||||
|
||||
the names are lower_cased and all non ascci chars are removed, use the namefix function as used in internal/tools/name_fix_test.go
|
||||
|
||||
its a struct called DocTree
|
||||
which has as argument a path and a name which is also namefixed
|
||||
|
||||
the init walks over the path and finds all files and .md files
|
||||
|
||||
we remember the relative position of each file and markdown page in a hset
|
||||
|
||||
hset is:
|
||||
|
||||
- key: collections:$name
|
||||
- hkey: pagename.md (always namefixed)
|
||||
- hkey: imagename.png ... or any other extension for files (always namefixed)
|
||||
- val: the relative position in the doctree location
|
||||
|
||||
use redisclient to internal redis to store this
|
||||
|
||||
create following methods on doctree
|
||||
|
||||
- Scan (scan the collection again, remove hset and repopulate)
|
||||
- PageGet get page from a name (do namefix inside method) return the markdown
|
||||
- PageGetHtml same as PageGet but make html
|
||||
- FileGetUrl the url which can then be used in static webserver for downloading this content
|
||||
- PageGetPath relative path in the collection
|
||||
- Info (name & path)
|
||||
|
||||
in PageGet implement a simple include function which is done as !!include name:'pagename' this needs to include page as mentioned in this collection
|
||||
if !!include name:'othercollection:pagename' then pagename comes from other collection do namefix to find
|
||||
|
||||
|
||||
## Objects
|
||||
|
||||
#### DocTree
|
||||
|
||||
- has add, get, delete, list functions in relation to underlying Collection
|
||||
|
||||
### Collection
|
||||
|
||||
- has get/set/delete/list for pages
|
||||
- has get/set/delete/list for files
|
||||
|
||||
namefix used everywhere to make sure
|
||||
|
||||
- in get for page we do the include which an get other pages
|
||||
|
||||
## special functions
|
||||
|
||||
### Include
|
||||
|
||||
```
|
||||
!!include collectionname:'pagename'
|
||||
!!include collectionname:'pagename.md'
|
||||
!!include 'pagename'
|
||||
!!include collectionname:pagename
|
||||
!!include collectionname:pagename.md
|
||||
|
||||
```
|
||||
|
||||
the include needs to parse the following
|
||||
|
||||
note:
|
||||
|
||||
- pages can have .md or not, check if given if not add
|
||||
- all is namefixed on collection and page level
|
||||
- there can be '' around the name but this is optional
|
9
aiprompts/instructions/instructions_handler_client.md
Normal file
9
aiprompts/instructions/instructions_handler_client.md
Normal file
@@ -0,0 +1,9 @@
|
||||
create a client for processmanager over telnet in @pkg/handlerfactory/clients
|
||||
|
||||
this is over tcp or socket
|
||||
|
||||
make a factory in clients where we say to which server we want to connect and then make methods per action which have right parameters which then create the heroscript which is sent to the server
|
||||
|
||||
we always expect json back
|
||||
|
||||
the json gets parsed and returned
|
0
aiprompts/instructions/instructions_herojobs.md
Normal file
0
aiprompts/instructions/instructions_herojobs.md
Normal file
28
aiprompts/instructions/instructions_imap.md
Normal file
28
aiprompts/instructions/instructions_imap.md
Normal file
@@ -0,0 +1,28 @@
|
||||
create a pkg/imapserver lib which uses:
|
||||
|
||||
https://github.com/foxcpp/go-imap
|
||||
|
||||
the mails are in redis
|
||||
|
||||
the model for mail is in @pkg/mail/model.go
|
||||
|
||||
## the mails are in redis based on following code, learn from it
|
||||
|
||||
cmd/redis_mail_feeder/main.go
|
||||
|
||||
the redis keys are
|
||||
|
||||
- mail:in:$account:$folder:$uid
|
||||
|
||||
the json is the mail model
|
||||
|
||||
see @instructions_imap_feeder.md for details
|
||||
|
||||
## imap server is using the redis as backedn
|
||||
|
||||
- based on what the feeder put in
|
||||
|
||||
there is no no login/passwd, anything is fine, any authentication is fine,
|
||||
ignore if user specifies it, try to support any login/passwd/authentication method just accept everything
|
||||
|
||||
|
23
aiprompts/instructions/instructions_imap_feeder.md
Normal file
23
aiprompts/instructions/instructions_imap_feeder.md
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
## populator of imap
|
||||
|
||||
in @/cmd/
|
||||
create a new command called redis_mail_feeder
|
||||
|
||||
this feeder creates 100 mails in different folders and stores them in redis as datastor
|
||||
|
||||
the mail model is in @pkg/mail/model.go
|
||||
|
||||
@uid is epoch in seconds + an incrementing number based on of there was already a mail with the same uid before, so we just increment the number until we get a unique uid (is string(epoch)+string(incrementing number))
|
||||
|
||||
the mails are stored in
|
||||
|
||||
and stores mail in mail:in:$account:$folder:$uid
|
||||
|
||||
id is the blake192 from the json serialization
|
||||
|
||||
- account is random over pol & jan
|
||||
- folders chose then random can be upto 3 levels deep
|
||||
|
||||
make random emails, 100x in well chosen folder
|
||||
|
4
aiprompts/instructions/instructions_mcp.md
Normal file
4
aiprompts/instructions/instructions_mcp.md
Normal file
@@ -0,0 +1,4 @@
|
||||
the @pkg/mcpopenapi/cmd/mcpopenapi works very welll
|
||||
|
||||
we want to implement
|
||||
|
236
aiprompts/instructions/instructions_openapi_generation.md
Normal file
236
aiprompts/instructions/instructions_openapi_generation.md
Normal file
@@ -0,0 +1,236 @@
|
||||
# OpenAPI Generation Instructions
|
||||
|
||||
## Overview
|
||||
|
||||
The OpenAPI package in `pkg/openapi` provides functionality to generate server code from OpenAPI specifications. This document explains how to use this package to generate and host multiple APIs under a single server with Swagger UI integration.
|
||||
|
||||
## Implementation Status
|
||||
|
||||
We have successfully implemented:
|
||||
|
||||
1. A proper test in `pkg/openapi/examples` that generates code from OpenAPI specifications
|
||||
2. Code generation for two example APIs:
|
||||
- `petstoreapi` (from `petstore.yaml`)
|
||||
- `actionsapi` (from `actions.yaml`)
|
||||
3. A webserver that hosts multiple generated APIs
|
||||
4. Swagger UI integration for API documentation
|
||||
5. A home page with links to the APIs and their documentation
|
||||
|
||||
All APIs are hosted under `$serverurl:$port/api` with a clean navigation structure.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
pkg/openapi/
|
||||
├── examples/
|
||||
│ ├── actions.yaml # OpenAPI spec for Actions API
|
||||
│ ├── actionsapi/ # Generated code for Actions API
|
||||
│ ├── main.go # Main server implementation
|
||||
│ ├── petstore.yaml # OpenAPI spec for Petstore API
|
||||
│ ├── petstoreapi/ # Generated code for Petstore API
|
||||
│ ├── README.md # Documentation for examples
|
||||
│ ├── run_test.sh # Script to run tests and server
|
||||
│ └── test/ # Tests for OpenAPI generation
|
||||
├── generator.go # Server code generator
|
||||
├── parser.go # OpenAPI spec parser
|
||||
├── example.go # Example usage
|
||||
└── templates/ # Code generation templates
|
||||
└── server.tmpl # Server template
|
||||
```
|
||||
|
||||
## How to Use
|
||||
|
||||
### Running the Example
|
||||
|
||||
To run the example implementation:
|
||||
|
||||
1. Navigate to the examples directory:
|
||||
```bash
|
||||
cd pkg/openapi/examples
|
||||
```
|
||||
|
||||
2. Run the test script:
|
||||
```bash
|
||||
./run_test.sh
|
||||
```
|
||||
|
||||
3. Access the APIs:
|
||||
- API Home: http://localhost:9091/api
|
||||
- Petstore API: http://localhost:9091/api/petstore
|
||||
- Petstore API Documentation: http://localhost:9091/api/swagger/petstore
|
||||
- Actions API: http://localhost:9091/api/actions
|
||||
- Actions API Documentation: http://localhost:9091/api/swagger/actions
|
||||
|
||||
### Generating Code from Your Own OpenAPI Spec
|
||||
|
||||
To generate code from your own OpenAPI specification:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/openapi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Parse the OpenAPI specification
|
||||
spec, err := openapi.ParseFromFile("your-api.yaml")
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to parse OpenAPI specification: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create a server generator
|
||||
generator := openapi.NewServerGenerator(spec)
|
||||
|
||||
// Generate server code
|
||||
serverCode := generator.GenerateServerCode()
|
||||
|
||||
// Write the server code to a file
|
||||
outputPath := "generated-server.go"
|
||||
err = os.WriteFile(outputPath, []byte(serverCode), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to write server code: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Generated server code in %s\n", outputPath)
|
||||
}
|
||||
```
|
||||
|
||||
### Hosting Multiple APIs
|
||||
|
||||
To host multiple APIs under a single server:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/openapi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create the main server
|
||||
app := fiber.New()
|
||||
|
||||
// Setup API routes
|
||||
app.Get("/api", func(c *fiber.Ctx) error {
|
||||
return c.SendString("API Home Page")
|
||||
})
|
||||
|
||||
// Mount the first API
|
||||
spec1, _ := openapi.ParseFromFile("api1.yaml")
|
||||
generator1 := openapi.NewServerGenerator(spec1)
|
||||
apiServer1 := generator1.GenerateServer()
|
||||
app.Mount("/api/api1", apiServer1)
|
||||
|
||||
// Mount the second API
|
||||
spec2, _ := openapi.ParseFromFile("api2.yaml")
|
||||
generator2 := openapi.NewServerGenerator(spec2)
|
||||
apiServer2 := generator2.GenerateServer()
|
||||
app.Mount("/api/api2", apiServer2)
|
||||
|
||||
// Start the server
|
||||
app.Listen(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
### Adding Swagger UI
|
||||
|
||||
To add Swagger UI for API documentation:
|
||||
|
||||
```go
|
||||
// Serve OpenAPI specs
|
||||
app.Static("/api/api1/openapi.yaml", "api1.yaml")
|
||||
app.Static("/api/api2/openapi.yaml", "api2.yaml")
|
||||
|
||||
// API1 Swagger UI
|
||||
app.Get("/api/swagger/api1", func(c *fiber.Ctx) error {
|
||||
return c.SendString(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>API1 - Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui.css" />
|
||||
<style>
|
||||
html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; }
|
||||
*, *:before, *:after { box-sizing: inherit; }
|
||||
body { margin: 0; background: #fafafa; }
|
||||
.swagger-ui .topbar { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-bundle.js"></script>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-standalone-preset.js"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "/api/api1/openapi.yaml",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
});
|
||||
window.ui = ui;
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
})
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### OpenAPI Parsing
|
||||
|
||||
The package can parse OpenAPI 3.0 and 3.1 specifications from files or byte slices.
|
||||
|
||||
### Code Generation
|
||||
|
||||
The package generates Fiber server code with mock implementations based on examples in the OpenAPI spec.
|
||||
|
||||
### Mock Implementations
|
||||
|
||||
Mock implementations are created using examples from the OpenAPI spec, making it easy to test APIs without writing any code.
|
||||
|
||||
### Multiple API Hosting
|
||||
|
||||
The package supports hosting multiple APIs under a single server, with each API mounted at a different path.
|
||||
|
||||
### Swagger UI Integration
|
||||
|
||||
The package includes Swagger UI integration for API documentation, making it easy to explore and test APIs.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Organize Your Code**: Keep your OpenAPI specs, generated code, and server implementation in separate directories.
|
||||
|
||||
2. **Use Examples**: Include examples in your OpenAPI spec to generate better mock implementations.
|
||||
|
||||
3. **Test Your APIs**: Write tests to verify that your APIs work as expected.
|
||||
|
||||
4. **Document Your APIs**: Use Swagger UI to document your APIs and make them easier to use.
|
||||
|
||||
5. **Use Version Control**: Keep your OpenAPI specs and generated code in version control to track changes.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Parse Error**: If you get a parse error, check that your OpenAPI spec is valid. You can use tools like [Swagger Editor](https://editor.swagger.io/) to validate your spec.
|
||||
|
||||
- **Generation Error**: If code generation fails, check that your OpenAPI spec includes all required fields and that examples are properly formatted.
|
||||
|
||||
- **Server Error**: If the server fails to start, check that the port is not already in use and that all required dependencies are installed.
|
||||
|
23
aiprompts/instructions/instructions_process_manager.md
Normal file
23
aiprompts/instructions/instructions_process_manager.md
Normal file
@@ -0,0 +1,23 @@
|
||||
in @pkg/system/stats
|
||||
create a factory which is called StatsManager
|
||||
|
||||
then each method in the different files is a method on that StatsManager
|
||||
|
||||
then on StatsManager make a connection to a redis server and have this connection as property
|
||||
|
||||
then have a dict on the StatsManager called Expiration which in seconds defines per type of info how long we will cache
|
||||
|
||||
then on each method cache the info in redis on a well chosen key, if someone asks the info and its out of expiration then we send message to the goroutine which fetches the info (see further) for the next request, in other words we will stil give the info we had in cache but next request would then have the new info if it got fetched in time
|
||||
|
||||
make a goroutine which is doing the updates, only when info is asked for it will request it,
|
||||
have an internal queue which tells this go-routine which info to ask for, NO info can be asked in parallel its one after the other
|
||||
|
||||
when an external consumer asks for the info it always comes from the cache
|
||||
|
||||
when the system starts up, the goroutine will do a first fetch, so in background initial info is loaded
|
||||
|
||||
when a request is asked from external consumer and info is not there yet, the method wll keep on polling redis this info is there (block wait), this redis will only be filled in once the goroutine fetches it
|
||||
|
||||
there is a generic timeout of 1 min
|
||||
|
||||
put a debug flag (bool) on the StatsManager if that one is set then the request for stats is always direct, not waiting for cache and no go-routine used
|
100
aiprompts/instructions/instructions_processmanager.md
Normal file
100
aiprompts/instructions/instructions_processmanager.md
Normal file
@@ -0,0 +1,100 @@
|
||||
|
||||
|
||||
create a process manager
|
||||
|
||||
which keeps separate process under control, measures the used cpu, memory
|
||||
|
||||
add possibilities to list, create, delete
|
||||
|
||||
we can talk to the process manager over local unix domain socket using a telnet session
|
||||
|
||||
## authentication
|
||||
|
||||
the telnet server says:
|
||||
|
||||
** Welcome: you are not authenticated, provide secret.
|
||||
|
||||
then we pass the secret which was passed when we started the process manager
|
||||
|
||||
once authenticated it says
|
||||
|
||||
** Welcome: you are authenticated.
|
||||
|
||||
now we can send heroscripts to it (see @pkg/playbook for how to parse that)
|
||||
|
||||
## actions can be sent over telnet
|
||||
|
||||
just send heroscript statements
|
||||
|
||||
everytime a new !! goes or # as comment we execute the previous heroscripts
|
||||
|
||||
## we make handlers
|
||||
|
||||
using the playbook: @pkg/playbook
|
||||
|
||||
this checks which commands are sent and this then calls the corresponding handler and instructs the processmanager
|
||||
|
||||
## start
|
||||
|
||||
heroscript
|
||||
|
||||
```bash
|
||||
!!process.start name:'processname' command:'command\n which can be multiline' log:true
|
||||
deadline:30 cron:'0 0 * * *' jobid:'e42'
|
||||
|
||||
```
|
||||
|
||||
|
||||
## list
|
||||
|
||||
|
||||
heroscript
|
||||
|
||||
```bash
|
||||
!!process.list format:json
|
||||
|
||||
```
|
||||
|
||||
lists the processes and returns as json
|
||||
|
||||
when telnet protocol needs to return its always as
|
||||
|
||||
**RESULT** e42
|
||||
... here is the result in chosen format
|
||||
**ENDRESULT**
|
||||
|
||||
if jobid specified on the heroscript action then its shown behind **RESULT** if not then its empty
|
||||
|
||||
## delete
|
||||
|
||||
```bash
|
||||
!!process.delete name:'processname'
|
||||
|
||||
```
|
||||
|
||||
## status
|
||||
|
||||
```bash
|
||||
!!process.status name:'processname' format:json
|
||||
|
||||
```
|
||||
|
||||
shows mem usage, cpu usage, status e.g. running ...
|
||||
|
||||
## restart, stop, start
|
||||
|
||||
do same as status but then for these
|
||||
|
||||
## log
|
||||
|
||||
|
||||
```bash
|
||||
!!process.log name:'processname' format:json limit:100
|
||||
|
||||
```
|
||||
|
||||
returns the last 100 lines of the log
|
||||
|
||||
if not format then just the log itself
|
||||
|
||||
|
17
aiprompts/instructions/instructions_smtp.md
Normal file
17
aiprompts/instructions/instructions_smtp.md
Normal file
@@ -0,0 +1,17 @@
|
||||
create a pkg/smtp lib based on
|
||||
https://github.com/emersion/go-smtp/tree/master
|
||||
|
||||
each mail coming in need to be converted to unicode text
|
||||
and stored as json with
|
||||
|
||||
from
|
||||
to
|
||||
subject
|
||||
message
|
||||
attachments []Attachment
|
||||
|
||||
Attachment = encoded binary
|
||||
|
||||
into the local redis as hset and in a queue called mail:out which has has the unique id of the mssage
|
||||
hset is mail:out:$unid -> the json
|
||||
|
9
aiprompts/instructions/instructions_systats.md
Normal file
9
aiprompts/instructions/instructions_systats.md
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
in @pkg/sysstats
|
||||
|
||||
create a factory to SysStats
|
||||
|
||||
which has following methods
|
||||
|
||||
- memory
|
||||
- cpu % used
|
23
aiprompts/instructions/instructions_vlang.md
Normal file
23
aiprompts/instructions/instructions_vlang.md
Normal file
@@ -0,0 +1,23 @@
|
||||
in @pkg/lang
|
||||
|
||||
create a vlangprocessor struct which will have some functions
|
||||
|
||||
first function is get_spect(path)
|
||||
|
||||
which walks over the path -recursive and finds all .v files
|
||||
then it will process each of these files
|
||||
|
||||
in each file we will look for public structs and public methods on those structs
|
||||
|
||||
then return a script which only has
|
||||
|
||||
the Struct...
|
||||
and then the methods on the structs
|
||||
|
||||
BUT NO CODE INSIDE THE METHODS
|
||||
|
||||
basically the returned codeis just Structs and Methods without the code
|
||||
|
||||
documentation is maintained
|
||||
|
||||
test on /Users/despiegk/code/github/freeflowuniverse/herolib/lib/circles/core
|
172
aiprompts/instructions/instructions_webdav.md
Normal file
172
aiprompts/instructions/instructions_webdav.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# WebDAV Server Implementation
|
||||
|
||||
This document describes the WebDAV server implementation for HeroLauncher.
|
||||
|
||||
## Overview
|
||||
|
||||
The WebDAV server provides a way to access and manage files through the WebDAV protocol, which allows for remote file management over HTTP/HTTPS. This implementation uses the Go standard library's WebDAV package from `golang.org/x/net/webdav`.
|
||||
|
||||
The server supports both HTTP and HTTPS connections, basic authentication, and includes comprehensive debug logging for troubleshooting.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The WebDAV server is implemented in the `pkg/webdavserver` package. The server can be configured with various options including:
|
||||
|
||||
- Host and port to listen on
|
||||
- Base path for the WebDAV endpoint
|
||||
- File system path to serve files from
|
||||
- Read and write timeouts
|
||||
- Debug mode for verbose logging
|
||||
- Basic authentication with username/password
|
||||
- HTTPS support with TLS certificate and key files
|
||||
|
||||
## Usage
|
||||
|
||||
### Starting the WebDAV Server
|
||||
|
||||
To start the WebDAV server, use the `cmd/webdavserver/main.go` command:
|
||||
|
||||
```bash
|
||||
go run cmd/webdavserver/main.go [options]
|
||||
```
|
||||
|
||||
Available options:
|
||||
|
||||
- `-host`: Host address to bind to (default: "0.0.0.0")
|
||||
- `-port`: Port to listen on (default: 9999)
|
||||
- `-base-path`: Base URL path for WebDAV (default: "/")
|
||||
- `-fs`: File system path to serve (default: system temp directory + "/heroagent")
|
||||
- `-debug`: Enable debug mode with verbose logging (default: false)
|
||||
- `-auth`: Enable basic authentication (default: false)
|
||||
- `-username`: Username for basic authentication (default: "admin")
|
||||
- `-password`: Password for basic authentication (default: "1234")
|
||||
- `-https`: Enable HTTPS (default: false)
|
||||
- `-cert`: Path to TLS certificate file (optional if auto-generation is enabled)
|
||||
- `-key`: Path to TLS key file (optional if auto-generation is enabled)
|
||||
- `-auto-gen-certs`: Auto-generate certificates if they don't exist (default: true)
|
||||
- `-cert-validity`: Validity period in days for auto-generated certificates (default: 365)
|
||||
- `-cert-org`: Organization name for auto-generated certificates (default: "HeroLauncher WebDAV Server")
|
||||
|
||||
### Connecting to WebDAV from macOS
|
||||
|
||||
A bash script is provided to easily connect to the WebDAV server from macOS:
|
||||
|
||||
```bash
|
||||
./scripts/open_webdav_osx.sh [options]
|
||||
```
|
||||
|
||||
Available options:
|
||||
|
||||
- `-h, --host`: WebDAV server hostname (default: "localhost")
|
||||
- `-p, --port`: WebDAV server port (default: 9999)
|
||||
- `-path, --path-prefix`: Path prefix for WebDAV URL (default: "")
|
||||
- `-s, --https`: Use HTTPS instead of HTTP (default: false)
|
||||
- `-u, --username`: Username for authentication
|
||||
- `-pw, --password`: Password for authentication
|
||||
- `--help`: Show help message
|
||||
|
||||
## API
|
||||
|
||||
### Server Configuration
|
||||
|
||||
```go
|
||||
// Config holds the configuration for the WebDAV server
|
||||
type Config struct {
|
||||
Host string
|
||||
Port int
|
||||
BasePath string
|
||||
FileSystem string
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
DebugMode bool
|
||||
UseAuth bool
|
||||
Username string
|
||||
Password string
|
||||
UseHTTPS bool
|
||||
CertFile string
|
||||
KeyFile string
|
||||
AutoGenerateCerts bool
|
||||
CertValidityDays int
|
||||
CertOrganization string
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default configuration
|
||||
func DefaultConfig() Config
|
||||
```
|
||||
|
||||
### Server Methods
|
||||
|
||||
```go
|
||||
// NewServer creates a new WebDAV server
|
||||
func NewServer(config Config) (*Server, error)
|
||||
|
||||
// Start starts the WebDAV server
|
||||
func (s *Server) Start() error
|
||||
|
||||
// Stop stops the WebDAV server
|
||||
func (s *Server) Stop() error
|
||||
```
|
||||
|
||||
## Integration with HeroLauncher
|
||||
|
||||
The WebDAV server can be integrated with the main HeroLauncher application by adding it to the server initialization in `cmd/server/main.go`.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
The WebDAV server uses the following directory structure:
|
||||
|
||||
```
|
||||
<parent-of-fs>/
|
||||
├── <fs-dir>/ # WebDAV files served to clients (specified by -fs)
|
||||
└── certificates/ # TLS certificates for HTTPS
|
||||
```
|
||||
|
||||
Where certificates are stored in a `certificates` directory next to the filesystem directory specified with the `-fs` parameter.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Basic authentication is supported but disabled by default
|
||||
- HTTPS is supported but disabled by default
|
||||
- The server can automatically generate self-signed certificates if needed
|
||||
- For production use, always enable authentication and HTTPS
|
||||
- Use strong passwords and properly signed certificates for production
|
||||
- Be careful about which directories you expose through WebDAV
|
||||
- Consider implementing IP-based access restrictions for additional security
|
||||
|
||||
## Debugging
|
||||
|
||||
When troubleshooting WebDAV connections, the debug mode can be enabled with the `-debug` flag. This will provide detailed logging of:
|
||||
|
||||
- All incoming requests
|
||||
- Request headers
|
||||
- Client information
|
||||
- Authentication attempts
|
||||
- WebDAV operations
|
||||
|
||||
Debug logs are prefixed with `[WebDAV DEBUG]` for easy filtering.
|
||||
|
||||
## Examples
|
||||
|
||||
### Starting a secure WebDAV server with auto-generated certificates
|
||||
|
||||
```bash
|
||||
go run cmd/webdavserver/main.go -auth -username myuser -password mypass -https -fs /path/to/files -debug
|
||||
```
|
||||
|
||||
### Starting a secure WebDAV server with existing certificates
|
||||
|
||||
```bash
|
||||
go run cmd/webdavserver/main.go -auth -username myuser -password mypass -https -cert /path/to/cert.pem -key /path/to/key.pem -fs /path/to/files -debug -auto-gen-certs=false
|
||||
```
|
||||
|
||||
### Connecting from macOS with authentication
|
||||
|
||||
```bash
|
||||
./scripts/open_webdav_osx.sh -s -u myuser -pw mypass
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [WebDAV Protocol (RFC 4918)](https://tools.ietf.org/html/rfc4918)
|
||||
- [Go WebDAV Package](https://pkg.go.dev/golang.org/x/net/webdav)
|
||||
- [TLS in Go](https://pkg.go.dev/crypto/tls)
|
10
aiprompts/instructions/isnttuction_handlers2.md
Normal file
10
aiprompts/instructions/isnttuction_handlers2.md
Normal file
@@ -0,0 +1,10 @@
|
||||
in pkg/handlerfactory
|
||||
create a handler for the process manager
|
||||
the code of process manager is in pkg/processmanager/processmanager.go
|
||||
|
||||
make a directory per handler and call processmanager underneith handlerfactory
|
||||
|
||||
inspiration how to do it comes from pkg/handlerfactory/cmd/vmhandler/vm_handler.go
|
||||
|
||||
|
||||
how to use heroscript is in pkg/heroscript
|
78
aiprompts/instructions/knowledge/1_heroscript.md
Normal file
78
aiprompts/instructions/knowledge/1_heroscript.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# HeroScript
|
||||
|
||||
## Overview
|
||||
|
||||
HeroScript is a simple, declarative scripting language designed to define workflows and execute commands in a structured manner. It follows a straightforward syntax where each action is prefixed with `!!`, indicating the actor and action name.
|
||||
|
||||
## Example
|
||||
|
||||
A basic HeroScript script for virtual machine management looks like this:
|
||||
|
||||
```heroscript
|
||||
!!vm.define name:'test_vm' cpu:4
|
||||
memory: '8GB'
|
||||
storage: '100GB'
|
||||
description: '
|
||||
A virtual machine configuration
|
||||
with specific resources.
|
||||
'
|
||||
|
||||
!!vm.start name:'test_vm'
|
||||
|
||||
!!vm.disk_add
|
||||
name: 'test_vm'
|
||||
size: '50GB'
|
||||
type: 'SSD'
|
||||
|
||||
!!vm.delete
|
||||
name: 'test_vm'
|
||||
force: true
|
||||
```
|
||||
|
||||
### Key Features
|
||||
|
||||
- Every action starts with `!!`.
|
||||
- The first part after `!!` is the actor (e.g., `vm`).
|
||||
- The second part is the action name (e.g., `define`, `start`, `delete`).
|
||||
- Multi-line values are supported (e.g., the `description` field).
|
||||
- Lists are comma-separated where applicable and inside ''.
|
||||
- If items one 1 line, then no space between name & argument e.g. name:'test_vm'
|
||||
|
||||
## Parsing HeroScript
|
||||
|
||||
Internally, HeroScript gets parsed into an action object with parameters. Each parameter follows a `key: value` format.
|
||||
|
||||
### Parsing Example
|
||||
|
||||
```heroscript
|
||||
!!actor.action
|
||||
id:a1 name6:aaaaa
|
||||
name:'need to do something 1'
|
||||
description:
|
||||
'
|
||||
## markdown works in it
|
||||
description can be multiline
|
||||
lets see what happens
|
||||
|
||||
- a
|
||||
- something else
|
||||
|
||||
### subtitle
|
||||
'
|
||||
|
||||
name2: test
|
||||
name3: hi
|
||||
name10:'this is with space' name11:aaa11
|
||||
|
||||
name4: 'aaa'
|
||||
|
||||
//somecomment
|
||||
name5: 'aab'
|
||||
```
|
||||
|
||||
### Parsing Details
|
||||
- Each parameter follows a `key: value` format.
|
||||
- Multi-line values (such as descriptions) support Markdown formatting.
|
||||
- Comments can be added using `//`.
|
||||
- Keys and values can have spaces, and values can be enclosed in single quotes.
|
||||
|
267
aiprompts/instructions/knowledge/3_heroscript_vlang.md
Normal file
267
aiprompts/instructions/knowledge/3_heroscript_vlang.md
Normal file
@@ -0,0 +1,267 @@
|
||||
|
||||
## how to process heroscript in Vlang
|
||||
|
||||
- heroscript can be converted to a struct,
|
||||
- the methods available to get the params are in 'params' section further in this doc
|
||||
|
||||
|
||||
```vlang
|
||||
|
||||
fn test_play_dagu() ! {
|
||||
mut plbook := playbook.new(text: thetext_from_above)!
|
||||
play_dagu(mut plbook)! //see below in vlang block there it all happens
|
||||
}
|
||||
|
||||
|
||||
pub fn play_dagu(mut plbook playbook.PlayBook) ! {
|
||||
|
||||
//find all actions are !!$actor.$actionname. in this case above the actor is !!dagu, we check with the fitler if it exists, if not we return
|
||||
dagu_actions := plbook.find(filter: 'dagu.')!
|
||||
if dagu_actions.len == 0 {
|
||||
return
|
||||
}
|
||||
play_dagu_basic(mut plbook)!
|
||||
}
|
||||
|
||||
pub struct DaguScript {
|
||||
pub mut:
|
||||
name string
|
||||
homedir string
|
||||
title string
|
||||
reset bool
|
||||
start bool
|
||||
colors []string
|
||||
}
|
||||
|
||||
// play_dagu plays the dagu play commands
|
||||
pub fn play_dagu_basic(mut plbook playbook.PlayBook) ! {
|
||||
|
||||
//now find the specific ones for dagu.script_define
|
||||
mut actions := plbook.find(filter: 'dagu.script_define')!
|
||||
|
||||
if actions.len > 0 {
|
||||
for myaction in actions {
|
||||
mut p := myaction.params //get the params object from the action object, this can then be processed using the param getters
|
||||
mut obj := DaguScript{
|
||||
//INFO: all details about the get methods can be found in 'params get methods' section
|
||||
name : p.get('name')! //will give error if not exist
|
||||
homedir : p.get('homedir')!
|
||||
title : p.get_default('title', 'My Hero DAG')! //uses a default if not set
|
||||
reset : p.get_default_false('reset')
|
||||
start : p.get_default_true('start')
|
||||
colors : p.get_list('colors')
|
||||
description : p.get_default('description','')!
|
||||
}
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
//there can be more actions which will have other filter
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## params get methods (param getters)
|
||||
|
||||
```vlang
|
||||
|
||||
fn (params &Params) exists(key_ string) bool
|
||||
|
||||
//check if arg exist (arg is just a value in the string e.g. red, not value:something)
|
||||
fn (params &Params) exists_arg(key_ string) bool
|
||||
|
||||
//see if the kwarg with the key exists if yes return as string trimmed
|
||||
fn (params &Params) get(key_ string) !string
|
||||
|
||||
//return the arg with nr, 0 is the first
|
||||
fn (params &Params) get_arg(nr int) !string
|
||||
|
||||
//return arg, if the nr is larger than amount of args, will return the defval
|
||||
fn (params &Params) get_arg_default(nr int, defval string) !string
|
||||
|
||||
fn (params &Params) get_default(key string, defval string) !string
|
||||
|
||||
fn (params &Params) get_default_false(key string) bool
|
||||
|
||||
fn (params &Params) get_default_true(key string) bool
|
||||
|
||||
fn (params &Params) get_float(key string) !f64
|
||||
|
||||
fn (params &Params) get_float_default(key string, defval f64) !f64
|
||||
|
||||
fn (params &Params) get_from_hashmap(key_ string, defval string, hashmap map[string]string) !string
|
||||
|
||||
fn (params &Params) get_int(key string) !int
|
||||
|
||||
fn (params &Params) get_int_default(key string, defval int) !int
|
||||
|
||||
//Looks for a list of strings in the parameters. ',' are used as deliminator to list
|
||||
fn (params &Params) get_list(key string) ![]string
|
||||
|
||||
fn (params &Params) get_list_default(key string, def []string) ![]string
|
||||
|
||||
fn (params &Params) get_list_f32(key string) ![]f32
|
||||
|
||||
fn (params &Params) get_list_f32_default(key string, def []f32) []f32
|
||||
|
||||
fn (params &Params) get_list_f64(key string) ![]f64
|
||||
|
||||
fn (params &Params) get_list_f64_default(key string, def []f64) []f64
|
||||
|
||||
fn (params &Params) get_list_i16(key string) ![]i16
|
||||
|
||||
fn (params &Params) get_list_i16_default(key string, def []i16) []i16
|
||||
|
||||
fn (params &Params) get_list_i64(key string) ![]i64
|
||||
|
||||
fn (params &Params) get_list_i64_default(key string, def []i64) []i64
|
||||
|
||||
fn (params &Params) get_list_i8(key string) ![]i8
|
||||
|
||||
fn (params &Params) get_list_i8_default(key string, def []i8) []i8
|
||||
|
||||
fn (params &Params) get_list_int(key string) ![]int
|
||||
|
||||
fn (params &Params) get_list_int_default(key string, def []int) []int
|
||||
|
||||
fn (params &Params) get_list_namefix(key string) ![]string
|
||||
|
||||
fn (params &Params) get_list_namefix_default(key string, def []string) ![]string
|
||||
|
||||
fn (params &Params) get_list_u16(key string) ![]u16
|
||||
|
||||
fn (params &Params) get_list_u16_default(key string, def []u16) []u16
|
||||
|
||||
fn (params &Params) get_list_u32(key string) ![]u32
|
||||
|
||||
fn (params &Params) get_list_u32_default(key string, def []u32) []u32
|
||||
|
||||
fn (params &Params) get_list_u64(key string) ![]u64
|
||||
|
||||
fn (params &Params) get_list_u64_default(key string, def []u64) []u64
|
||||
|
||||
fn (params &Params) get_list_u8(key string) ![]u8
|
||||
|
||||
fn (params &Params) get_list_u8_default(key string, def []u8) []u8
|
||||
|
||||
fn (params &Params) get_map() map[string]string
|
||||
|
||||
fn (params &Params) get_path(key string) !string
|
||||
|
||||
fn (params &Params) get_path_create(key string) !string
|
||||
|
||||
fn (params &Params) get_percentage(key string) !f64
|
||||
|
||||
fn (params &Params) get_percentage_default(key string, defval string) !f64
|
||||
|
||||
//convert GB, MB, KB to bytes e.g. 10 GB becomes bytes in u64
|
||||
fn (params &Params) get_storagecapacity_in_bytes(key string) !u64
|
||||
|
||||
fn (params &Params) get_storagecapacity_in_bytes_default(key string, defval u64) !u64
|
||||
|
||||
fn (params &Params) get_storagecapacity_in_gigabytes(key string) !u64
|
||||
|
||||
//Get Expiration object from time string input input can be either relative or absolute## Relative time
|
||||
fn (params &Params) get_time(key string) !ourtime.OurTime
|
||||
|
||||
fn (params &Params) get_time_default(key string, defval ourtime.OurTime) !ourtime.OurTime
|
||||
|
||||
fn (params &Params) get_time_interval(key string) !Duration
|
||||
|
||||
fn (params &Params) get_timestamp(key string) !Duration
|
||||
|
||||
fn (params &Params) get_timestamp_default(key string, defval Duration) !Duration
|
||||
|
||||
fn (params &Params) get_u32(key string) !u32
|
||||
|
||||
fn (params &Params) get_u32_default(key string, defval u32) !u32
|
||||
|
||||
fn (params &Params) get_u64(key string) !u64
|
||||
|
||||
fn (params &Params) get_u64_default(key string, defval u64) !u64
|
||||
|
||||
fn (params &Params) get_u8(key string) !u8
|
||||
|
||||
fn (params &Params) get_u8_default(key string, defval u8) !u8
|
||||
|
||||
```
|
||||
|
||||
## how internally a heroscript gets parsed for params
|
||||
|
||||
- example to show how a heroscript gets parsed in action with params
|
||||
- params are part of action object
|
||||
|
||||
```heroscript
|
||||
example text to parse (heroscript)
|
||||
|
||||
id:a1 name6:aaaaa
|
||||
name:'need to do something 1'
|
||||
description:
|
||||
'
|
||||
## markdown works in it
|
||||
description can be multiline
|
||||
lets see what happens
|
||||
|
||||
- a
|
||||
- something else
|
||||
|
||||
### subtitle
|
||||
'
|
||||
|
||||
name2: test
|
||||
name3: hi
|
||||
name10:'this is with space' name11:aaa11
|
||||
|
||||
name4: 'aaa'
|
||||
|
||||
//somecomment
|
||||
name5: 'aab'
|
||||
```
|
||||
|
||||
the params are part of the action and are represented as follow for the above:
|
||||
|
||||
```vlang
|
||||
Params{
|
||||
params: [Param{
|
||||
key: 'id'
|
||||
value: 'a1'
|
||||
}, Param{
|
||||
key: 'name6'
|
||||
value: 'aaaaa'
|
||||
}, Param{
|
||||
key: 'name'
|
||||
value: 'need to do something 1'
|
||||
}, Param{
|
||||
key: 'description'
|
||||
value: '## markdown works in it
|
||||
|
||||
description can be multiline
|
||||
lets see what happens
|
||||
|
||||
- a
|
||||
- something else
|
||||
|
||||
### subtitle
|
||||
'
|
||||
}, Param{
|
||||
key: 'name2'
|
||||
value: 'test'
|
||||
}, Param{
|
||||
key: 'name3'
|
||||
value: 'hi'
|
||||
}, Param{
|
||||
key: 'name10'
|
||||
value: 'this is with space'
|
||||
}, Param{
|
||||
key: 'name11'
|
||||
value: 'aaa11'
|
||||
}, Param{
|
||||
key: 'name4'
|
||||
value: 'aaa'
|
||||
}, Param{
|
||||
key: 'name5'
|
||||
value: 'aab'
|
||||
}]
|
||||
}
|
||||
```
|
446
aiprompts/jet_instructions.md
Normal file
446
aiprompts/jet_instructions.md
Normal file
@@ -0,0 +1,446 @@
|
||||
# Jet Template Engine Syntax Reference
|
||||
|
||||
## Delimiters
|
||||
|
||||
Template delimiters are `{{` and `}}`.
|
||||
Delimiters can use `.` to output the execution context:
|
||||
|
||||
```jet
|
||||
hello {{ . }} <!-- context = "world" => "hello world" -->
|
||||
```
|
||||
|
||||
### Whitespace Trimming
|
||||
|
||||
Whitespace around delimiters can be trimmed using `{{-` and `-}}`:
|
||||
|
||||
```jet
|
||||
foo {{- "bar" -}} baz <!-- outputs "foobarbaz" -->
|
||||
```
|
||||
|
||||
Whitespace includes spaces, tabs, carriage returns, and newlines.
|
||||
|
||||
### Comments
|
||||
|
||||
Comments use `{* ... *}`:
|
||||
|
||||
```jet
|
||||
{* this is a comment *}
|
||||
|
||||
{*
|
||||
Multiline
|
||||
{{ expressions }} are ignored
|
||||
*}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Variables
|
||||
|
||||
### Initialization
|
||||
|
||||
```jet
|
||||
{{ foo := "bar" }}
|
||||
```
|
||||
|
||||
### Assignment
|
||||
|
||||
```jet
|
||||
{{ foo = "asd" }}
|
||||
{{ foo = 4711 }}
|
||||
```
|
||||
|
||||
Skip assignment but still evaluate:
|
||||
|
||||
```jet
|
||||
{{ _ := stillRuns() }}
|
||||
{{ _ = stillRuns() }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Expressions
|
||||
|
||||
### Identifiers
|
||||
|
||||
Identifiers resolve to values:
|
||||
|
||||
```jet
|
||||
{{ len("hello") }}
|
||||
{{ isset(foo, bar) }}
|
||||
```
|
||||
|
||||
### Indexing
|
||||
|
||||
#### String
|
||||
|
||||
```jet
|
||||
{{ s := "helloworld" }}
|
||||
{{ s[1] }} <!-- 101 (ASCII of 'e') -->
|
||||
```
|
||||
|
||||
#### Slice / Array
|
||||
|
||||
```jet
|
||||
{{ s := slice("foo", "bar", "asd") }}
|
||||
{{ s[0] }}
|
||||
{{ s[2] }}
|
||||
```
|
||||
|
||||
#### Map
|
||||
|
||||
```jet
|
||||
{{ m := map("foo", 123, "bar", 456) }}
|
||||
{{ m["foo"] }}
|
||||
```
|
||||
|
||||
#### Struct
|
||||
|
||||
```jet
|
||||
{{ user["Name"] }}
|
||||
```
|
||||
|
||||
### Field Access
|
||||
|
||||
#### Map
|
||||
|
||||
```jet
|
||||
{{ m.foo }}
|
||||
{{ range s }}
|
||||
{{ .foo }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### Struct
|
||||
|
||||
```jet
|
||||
{{ user.Name }}
|
||||
{{ range users }}
|
||||
{{ .Name }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### Slicing
|
||||
|
||||
```jet
|
||||
{{ s := slice(6, 7, 8, 9, 10, 11) }}
|
||||
{{ sevenEightNine := s[1:4] }}
|
||||
```
|
||||
|
||||
### Arithmetic
|
||||
|
||||
```jet
|
||||
{{ 1 + 2 * 3 - 4 }}
|
||||
{{ (1 + 2) * 3 - 4.1 }}
|
||||
```
|
||||
|
||||
### String Concatenation
|
||||
|
||||
```jet
|
||||
{{ "HELLO" + " " + "WORLD!" }}
|
||||
```
|
||||
|
||||
#### Logical Operators
|
||||
|
||||
- `&&`
|
||||
- `||`
|
||||
- `!`
|
||||
- `==`, `!=`
|
||||
- `<`, `>`, `<=`, `>=`
|
||||
|
||||
```jet
|
||||
{{ item == true || !item2 && item3 != "test" }}
|
||||
{{ item >= 12.5 || item < 6 }}
|
||||
```
|
||||
|
||||
### Ternary Operator
|
||||
|
||||
```jet
|
||||
<title>{{ .HasTitle ? .Title : "Title not set" }}</title>
|
||||
```
|
||||
|
||||
### Method Calls
|
||||
|
||||
```jet
|
||||
{{ user.Rename("Peter") }}
|
||||
{{ range users }}
|
||||
{{ .FullName() }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### Function Calls
|
||||
|
||||
```jet
|
||||
{{ len(s) }}
|
||||
{{ isset(foo, bar) }}
|
||||
```
|
||||
|
||||
#### Prefix Syntax
|
||||
|
||||
```jet
|
||||
{{ len: s }}
|
||||
{{ isset: foo, bar }}
|
||||
```
|
||||
|
||||
#### Pipelining
|
||||
|
||||
```jet
|
||||
{{ "123" | len }}
|
||||
{{ "FOO" | lower | len }}
|
||||
{{ "hello" | repeat: 2 | len }}
|
||||
```
|
||||
|
||||
**Escapers must be last in a pipeline:**
|
||||
|
||||
```jet
|
||||
{{ "hello" | upper | raw }} <!-- valid -->
|
||||
{{ raw: "hello" }} <!-- valid -->
|
||||
{{ raw: "hello" | upper }} <!-- invalid -->
|
||||
```
|
||||
|
||||
#### Piped Argument Slot
|
||||
|
||||
```jet
|
||||
{{ 2 | repeat("foo", _) }}
|
||||
{{ 2 | repeat("foo", _) | repeat(_, 3) }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Control Structures
|
||||
|
||||
### if
|
||||
|
||||
```jet
|
||||
{{ if foo == "asd" }}
|
||||
foo is 'asd'!
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### if / else
|
||||
|
||||
```jet
|
||||
{{ if foo == "asd" }}
|
||||
...
|
||||
{{ else }}
|
||||
...
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### if / else if
|
||||
|
||||
```jet
|
||||
{{ if foo == "asd" }}
|
||||
{{ else if foo == 4711 }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### if / else if / else
|
||||
|
||||
```jet
|
||||
{{ if foo == "asd" }}
|
||||
{{ else if foo == 4711 }}
|
||||
{{ else }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### range
|
||||
|
||||
#### Slices / Arrays
|
||||
|
||||
```jet
|
||||
{{ range s }}
|
||||
{{ . }}
|
||||
{{ end }}
|
||||
|
||||
{{ range i := s }}
|
||||
{{ i }}: {{ . }}
|
||||
{{ end }}
|
||||
|
||||
{{ range i, v := s }}
|
||||
{{ i }}: {{ v }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### Maps
|
||||
|
||||
```jet
|
||||
{{ range k := m }}
|
||||
{{ k }}: {{ . }}
|
||||
{{ end }}
|
||||
|
||||
{{ range k, v := m }}
|
||||
{{ k }}: {{ v }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### Channels
|
||||
|
||||
```jet
|
||||
{{ range v := c }}
|
||||
{{ v }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
#### Custom Ranger
|
||||
|
||||
Any Go type implementing `Ranger` can be ranged over.
|
||||
|
||||
#### else
|
||||
|
||||
```jet
|
||||
{{ range searchResults }}
|
||||
{{ . }}
|
||||
{{ else }}
|
||||
No results found :(
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### try
|
||||
|
||||
```jet
|
||||
{{ try }}
|
||||
{{ foo }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### try / catch
|
||||
|
||||
```jet
|
||||
{{ try }}
|
||||
{{ foo }}
|
||||
{{ catch }}
|
||||
Fallback content
|
||||
{{ end }}
|
||||
|
||||
{{ try }}
|
||||
{{ foo }}
|
||||
{{ catch err }}
|
||||
{{ log(err.Error()) }}
|
||||
Error: {{ err.Error() }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Templates
|
||||
|
||||
### include
|
||||
|
||||
```jet
|
||||
{{ include "./user.jet" }}
|
||||
|
||||
<!-- user.jet -->
|
||||
<div class="user">
|
||||
{{ .["name"] }}: {{ .["email"] }}
|
||||
</div>
|
||||
```
|
||||
|
||||
### return
|
||||
|
||||
```jet
|
||||
<!-- foo.jet -->
|
||||
{{ return "foo" }}
|
||||
|
||||
<!-- bar.jet -->
|
||||
{{ foo := exec("./foo.jet") }}
|
||||
Hello, {{ foo }}!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Blocks
|
||||
|
||||
### block
|
||||
|
||||
```jet
|
||||
{{ block copyright() }}
|
||||
<div>© ACME, Inc. 2020</div>
|
||||
{{ end }}
|
||||
|
||||
{{ block inputField(type="text", label, id, value="", required=false) }}
|
||||
<label for="{{ id }}">{{ label }}</label>
|
||||
<input type="{{ type }}" value="{{ value }}" id="{{ id }}" {{ required ? "required" : "" }} />
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### yield
|
||||
|
||||
```jet
|
||||
{{ yield copyright() }}
|
||||
|
||||
{{ yield inputField(id="firstname", label="First name", required=true) }}
|
||||
|
||||
{{ block buff() }}
|
||||
<strong>{{ . }}</strong>
|
||||
{{ end }}
|
||||
|
||||
{{ yield buff() "Batman" }}
|
||||
```
|
||||
|
||||
### content
|
||||
|
||||
```jet
|
||||
{{ block link(target) }}
|
||||
<a href="{{ target }}">{{ yield content }}</a>
|
||||
{{ end }}
|
||||
|
||||
{{ yield link(target="https://example.com") content }}
|
||||
Example Inc.
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
```jet
|
||||
{{ block header() }}
|
||||
<div class="header">
|
||||
{{ yield content }}
|
||||
</div>
|
||||
{{ content }}
|
||||
<h1>Hey {{ name }}!</h1>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### Recursion
|
||||
|
||||
```jet
|
||||
{{ block menu() }}
|
||||
<ul>
|
||||
{{ range . }}
|
||||
<li>{{ .Text }}{{ if len(.Children) }}{{ yield menu() .Children }}{{ end }}</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### extends
|
||||
|
||||
```jet
|
||||
<!-- content.jet -->
|
||||
{{ extends "./layout.jet" }}
|
||||
{{ block body() }}
|
||||
<main>This content can be yielded anywhere.</main>
|
||||
{{ end }}
|
||||
|
||||
<!-- layout.jet -->
|
||||
<html>
|
||||
<body>
|
||||
{{ yield body() }}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### import
|
||||
|
||||
```jet
|
||||
<!-- my_blocks.jet -->
|
||||
{{ block body() }}
|
||||
<main>This content can be yielded anywhere.</main>
|
||||
{{ end }}
|
||||
|
||||
<!-- index.jet -->
|
||||
{{ import "./my_blocks.jet" }}
|
||||
<html>
|
||||
<body>
|
||||
{{ yield body() }}
|
||||
</body>
|
||||
</html>
|
||||
```
|
154
aiprompts/jet_usage.md
Normal file
154
aiprompts/jet_usage.md
Normal file
@@ -0,0 +1,154 @@
|
||||
|
||||
# Rendering Templates
|
||||
|
||||
> This section covers the Go side of things: preparing and executing your templates. See [Jet template syntax](https://github.com/CloudyKit/jet/wiki/3.-Jet-template-syntax) for help on writing your template files.
|
||||
|
||||
In the [Getting Started](https://github.com/CloudyKit/jet/wiki/1.-Getting-Started) section, we had this piece of code as the last step to execute a template:
|
||||
|
||||
```go
|
||||
templateName := "home.jet"
|
||||
t, err := set.GetTemplate(templateName)
|
||||
if err != nil {
|
||||
// template could not be loaded
|
||||
}
|
||||
var w bytes.Buffer // needs to conform to io.Writer interface (like gin's context.Writer for example)
|
||||
vars := make(jet.VarMap)
|
||||
if err = t.Execute(&w, vars, nil); err != nil {
|
||||
// error when executing template
|
||||
}
|
||||
```
|
||||
|
||||
What's the `vars` map there as the second parameter? And why did we pass `nil` as the third parameter? How are templates located and loaded? Let's start there.
|
||||
|
||||
## Loading a Template
|
||||
|
||||
When you instantiate a `Set` and give it the directories for template lookup, it will not search them right away. Templates are located and loaded on-demand.
|
||||
|
||||
Imagine this tree of templates in your project folder:
|
||||
|
||||
```
|
||||
├── main.go
|
||||
├── README.md
|
||||
└── views
|
||||
├── common
|
||||
│ ├── _footer.jet
|
||||
│ └── _menu.jet
|
||||
├── auth
|
||||
│ ├── _logo.jet
|
||||
│ └── login.jet
|
||||
├── home.jet
|
||||
└── layouts
|
||||
└── application.jet
|
||||
```
|
||||
|
||||
The `Set` might have been initialized in the `main.go` like this:
|
||||
|
||||
```go
|
||||
var viewSet = jet.NewHTMLSet("./views")
|
||||
```
|
||||
|
||||
Jet loads templates relative to the lookup directory; to load the `login.jet` template, you'd do:
|
||||
|
||||
```go
|
||||
t, err := viewSet.GetTemplate("auth/login.jet")
|
||||
```
|
||||
|
||||
Loading a template parses it and all included, imported, or extended templates – and caches the result so parsing only happens once.
|
||||
|
||||
## Reloading a Template in Development
|
||||
|
||||
While developing a website or web app in Go, it'd be nice to not cache the result after loading a template so you can leave your Go app running and still make incremental changes to the template(s). For this, Jet includes a development mode which disables caching the templates:
|
||||
|
||||
```go
|
||||
viewSet.SetDevelopmentMode(true)
|
||||
```
|
||||
|
||||
Be advised to disable the development mode on staging and in production to achieve maximum performance.
|
||||
|
||||
## Passing Variables When Executing a Template
|
||||
|
||||
When executing a template, you are passing the `io.Writer` object as well as the variable map and a context. Both of these will be explained next.
|
||||
|
||||
The variable map is a `jet.VarMap` for variables you want to access by name in your templates. Use the convenience method `Set(key, value)` to add variables:
|
||||
|
||||
```go
|
||||
vars := make(jet.VarMap)
|
||||
vars.Set("user", &User{})
|
||||
```
|
||||
|
||||
You usually build up the variable map in one of your controller functions in response to an HTTP request by the user. One thing to be aware of is that the `jet.VarMap` is not safe to use across multiple goroutines concurrently because the backing type is a regular `map[string]reflect.Value`. If you're using wait groups to coordinate multiple concurrent fetches of data in your controllers or a similar construct, you may need to use a mutex to guard against data races. The decision was made to not do this in the core `jet.VarMap` implementation for ease of use and also because it's not a common usage scenario.
|
||||
|
||||
> The Appendix has a basic implementation of a mutex-protected variable map that you can use if the need arises.
|
||||
|
||||
Lastly, the context: the context is passed as the third parameter to the `t.Execute` template execution function and is accessed in the template using the dot. Anything can be used as a context, but if you are rendering a user edit form, it'd be best to pass the user as the context.
|
||||
|
||||
```html
|
||||
<form action="/user" method="post">
|
||||
<input name="firstname" value="{{ .Firstname }}" />
|
||||
</form>
|
||||
```
|
||||
|
||||
Using a context can also be helpful when making blocks more reusable because the context can change while the template stays the same: `{{ .Text }}`.
|
||||
|
||||
|
||||
# Built-in Functions
|
||||
|
||||
Some functions are available to you in every template. They may be invoked as regular functions:
|
||||
|
||||
```jet
|
||||
{{ lower("TEST") }} <!-- outputs "test" -->
|
||||
```
|
||||
|
||||
Or, which may be preferred, they can be invoked as pipelines, which also allows chaining:
|
||||
|
||||
```jet
|
||||
{{ "test"|upper|trimSpace }} <!-- outputs "TEST" -->
|
||||
```
|
||||
|
||||
For documentation on how to add your own (global) functions, see [Jet Template Syntax](https://github.com/CloudyKit/jet/wiki/3.-Jet-template-syntax).
|
||||
|
||||
## `isset()`
|
||||
|
||||
Can be used to check against truthy or falsy expressions:
|
||||
|
||||
```jet
|
||||
{{ isset(.Title) ? .Title : "Title not set" }}
|
||||
```
|
||||
|
||||
It can also be used to check for map key existence:
|
||||
|
||||
```jet
|
||||
{{ isset(.["non_existent_key"]) ? "key does exist" : "key does not exist" }}
|
||||
<!-- will print "key does not exist" -->
|
||||
```
|
||||
|
||||
The example above uses the context, but of course, this also works with maps registered on the variable map.
|
||||
|
||||
## `len()`
|
||||
|
||||
Counts the elements in arrays, channels, slices, maps, and strings. When used on a struct argument, it returns the number of fields.
|
||||
|
||||
## From Go's `strings` package
|
||||
|
||||
- `lower` (`strings.ToLower`)
|
||||
- `upper` (`strings.ToUpper`)
|
||||
- `hasPrefix` (`strings.HasPrefix`)
|
||||
- `hasSuffix` (`strings.HasSuffix`)
|
||||
- `repeat` (`strings.Repeat`)
|
||||
- `replace` (`strings.Replace`)
|
||||
- `split` (`strings.Split`)
|
||||
- `trimSpace` (`strings.TrimSpace`)
|
||||
|
||||
## Escape Helpers
|
||||
|
||||
- `html` (`html.EscapeString`)
|
||||
- `url` (`url.QueryEscape`)
|
||||
- `safeHtml` (escape HTML)
|
||||
- `safeJs` (escape JavaScript)
|
||||
- `raw`, `unsafe` (no escaping)
|
||||
- `writeJson`, `json` to dump variables as JSON strings
|
||||
|
||||
## On-the-fly Map Creation
|
||||
|
||||
- `map`: `map(key1, value1, key2, value2)`
|
||||
– use with caution because accessing these is slow when used with lots of elements and checked/read in loops.
|
Reference in New Issue
Block a user