├── go.sum ├── go.mod ├── Makefile ├── README.md ├── gsc_test.go ├── gsc.go ├── LICENSE ├── templating.go ├── extract.go ├── attributes.go └── components.go /go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Phillip-England/gsc 2 | 3 | go 1.23.0 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Define your TailwindCSS input and output files 2 | TAILWIND_INPUT = ./static/input.css 3 | TAILWIND_OUTPUT = ./static/output.css 4 | 5 | # Default target that runs when you just run 'make' 6 | all: build 7 | 8 | # The build target runs the TailwindCSS command 9 | tw: 10 | tailwindcss -i $(TAILWIND_INPUT) -o $(TAILWIND_OUTPUT) --watch 11 | 12 | test: 13 | tailwindcss -i $(TAILWIND_INPUT) -o $(TAILWIND_OUTPUT); go test 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gsc 2 | 3 | go simple component, keeping things.. simple ⚡ 4 | 5 | ## Philosophy 6 | 7 | ### Components are Functions 8 | gsc encourages the use of functions to compose and output HTML. Functions are simple. Simple good. 9 | 10 | echo, echo 👂 11 | ```go 12 | package main 13 | 14 | import "fmt" 15 | 16 | func Echo(phrase string) string { 17 | return fmt.Sprintf( `
%s
`, phrase) 18 | } 19 | 20 | func main() { 21 | fmt.Println(Echo("moms spaghetti 🍝")) 22 | } 23 | ``` -------------------------------------------------------------------------------- /gsc_test.go: -------------------------------------------------------------------------------- 1 | package gsc 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func Layout() Component { 9 | return HTMLDoc().In( 10 | Head().In( 11 | Meta().Name("viewport").Content("width=device-width, initial-scale=1.0"), 12 | Link().Rel("stylesheet").Href("/static/css/output.css"), 13 | Title().Text("Receipt Tracker"), 14 | ), 15 | Body().In( 16 | H1().ID("header").Text("yuooooooo"), 17 | ), 18 | ) 19 | } 20 | 21 | func Test_G(t *testing.T) { 22 | 23 | c := Layout().Query("#header", func(child Component) Component { 24 | return child.Class("text-xl font-bold") 25 | }) 26 | fmt.Println(c) 27 | 28 | } 29 | -------------------------------------------------------------------------------- /gsc.go: -------------------------------------------------------------------------------- 1 | package gsc 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Component string 9 | 10 | func NewComponent(s string, args ...string) Component { 11 | if len(args) != 0 { 12 | return Component(fmt.Sprintf(strings.TrimSpace(s), args)) 13 | } else { 14 | return Component(strings.TrimSpace(s)) 15 | } 16 | } 17 | 18 | func (c Component) ToString() string { 19 | return string(c) 20 | } 21 | 22 | func (c Component) Text(text string) Component { 23 | s := string(c) 24 | rightAngleIndex := strings.Index(s, ">") 25 | if rightAngleIndex == -1 { 26 | return c 27 | } 28 | if s[rightAngleIndex-1] == '/' { 29 | return c 30 | } 31 | s = s[:rightAngleIndex+1] + text + s[rightAngleIndex+1:] 32 | return NewComponent(s) 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Phillip England 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /templating.go: -------------------------------------------------------------------------------- 1 | package gsc 2 | 3 | import "strings" 4 | 5 | func (c Component) In(components ...Component) Component { 6 | componentStr := "" 7 | for _, component := range components { 8 | componentStr = componentStr + string(component) 9 | } 10 | s := string(c) 11 | rightAngleIndex := strings.Index(s, ">") 12 | if rightAngleIndex == -1 { 13 | return c 14 | } 15 | if s[rightAngleIndex-1] == '/' { 16 | return c 17 | } 18 | s = s[:rightAngleIndex+1] + componentStr + s[rightAngleIndex+1:] 19 | return NewComponent(s) 20 | } 21 | 22 | func Map(f func(item string) Component, items ...string) Component { 23 | output := "" 24 | for _, item := range items { 25 | output = output + string(f(item)) 26 | } 27 | return NewComponent(output) 28 | } 29 | 30 | func If(condition bool, trueComponent Component) Component { 31 | if condition { 32 | return trueComponent 33 | } 34 | return NewComponent("") 35 | } 36 | 37 | func IfElse(condition bool, trueComponent, falseComponent Component) Component { 38 | if condition { 39 | return trueComponent 40 | } 41 | return falseComponent 42 | } 43 | 44 | func (c Component) Of(funcs ...func(component Component) Component) Component { 45 | for _, fn := range funcs { 46 | c = fn(c) 47 | } 48 | return c 49 | } 50 | -------------------------------------------------------------------------------- /extract.go: -------------------------------------------------------------------------------- 1 | package gsc 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func (c Component) TagName() string { 8 | s := c.ToString() 9 | name := "" 10 | for i := 1; i < len(s); i++ { 11 | char := string(s[i]) 12 | if char == " " || char == ">" || char == "/" { 13 | if i > 1 { 14 | break 15 | } 16 | continue 17 | } 18 | name = name + char 19 | } 20 | return name 21 | } 22 | 23 | func (c Component) GetAttr(name string) string { 24 | s := c.ToString() 25 | if !strings.Contains(s, name+"=") { 26 | return "" 27 | } 28 | qTypeIndex := strings.Index(s, name+"=") + len(name) + 1 29 | qType := string(s[qTypeIndex]) 30 | attr := "" 31 | for i := qTypeIndex + 1; i < len(s); i++ { 32 | char := string(s[i]) 33 | if char == qType && string(s[i-1]) != `\` { 34 | break 35 | } 36 | attr = attr + char 37 | } 38 | return attr 39 | } 40 | 41 | func (c Component) Query(selector string, fn func(child Component) Component) Component { 42 | str := c.ToString() 43 | child := c.ExtractChild(selector) 44 | newChild := fn(child) 45 | str = strings.Replace(str, child.ToString(), newChild.ToString(), 1) 46 | return NewComponent(str) 47 | } 48 | 49 | func (c Component) ExtractChild(selector string) Component { 50 | if selector == "" { 51 | return c 52 | } 53 | str := c.ToString() 54 | selectorPrefix := string(selector[0]) 55 | selectorAttribute := "" 56 | if selectorPrefix == "#" || selectorPrefix == "." { 57 | selector = selector[1:] 58 | if selectorPrefix == "#" { 59 | selectorAttribute = "id=\"" 60 | } 61 | if selectorPrefix == "." { 62 | selectorAttribute = "class=\"" 63 | } 64 | } 65 | selector = selectorAttribute + selector 66 | componentStart := 0 67 | selectorIndex := strings.Index(str, selector) 68 | for i := selectorIndex; i > 0; i-- { 69 | char := string(str[i]) 70 | if char == "<" { 71 | componentStart = i 72 | break 73 | } 74 | } 75 | foundFirstClose := false 76 | foundStartOfClosingTag := false 77 | for i := 0; i < len(str); i++ { 78 | if i < componentStart { 79 | continue 80 | } 81 | char := string(str[i]) 82 | if char == ">" { 83 | foundFirstClose = true 84 | // is this a self closing tag? 85 | if string(str[i-1]) == "/" { 86 | // return the self closing tag here! 87 | return NewComponent(str[componentStart : i+1]) 88 | } 89 | } 90 | if foundFirstClose && char == "<" && string(str[i+1]) == "/" { 91 | foundStartOfClosingTag = true 92 | } 93 | if foundFirstClose && foundStartOfClosingTag && char == ">" { 94 | return NewComponent(str[componentStart : i+1]) 95 | } 96 | } 97 | if componentStart == 0 { 98 | return c 99 | } 100 | return c 101 | } 102 | -------------------------------------------------------------------------------- /attributes.go: -------------------------------------------------------------------------------- 1 | package gsc 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func (c Component) Attr(attrName string, value string) Component { 9 | strComponent := string(c) 10 | rightBracketIndex := strings.Index(strComponent, ">") 11 | if rightBracketIndex == -1 { 12 | return "" 13 | } 14 | if strings.Contains(strComponent, attrName+"=\"") { 15 | return NewComponent(strings.Replace(strComponent, ``+attrName+`="`, ``+attrName+`="`+value+` `, 1)) 16 | } 17 | if string(strComponent[rightBracketIndex-1]) == "/" { 18 | return NewComponent(strings.Replace(strComponent, "/>", ` `+attrName+`="`+value+`"/>`, 1)) 19 | } 20 | return NewComponent(strings.Replace(strComponent, ">", ` `+attrName+`="`+value+`">`, 1)) 21 | } 22 | 23 | func (c Component) Class(className string) Component { 24 | return c.Attr("class", className) 25 | } 26 | 27 | func (c Component) Src(s string) Component { 28 | return c.Attr("src", s) 29 | } 30 | 31 | func (c Component) Href(s string) Component { 32 | return c.Attr("href", s) 33 | } 34 | 35 | func (c Component) Rel(s string) Component { 36 | return c.Attr("rel", s) 37 | } 38 | 39 | func (c Component) Name(s string) Component { 40 | return c.Attr("name", s) 41 | } 42 | 43 | func (c Component) ID(s string) Component { 44 | return c.Attr("id", s) 45 | } 46 | 47 | func (c Component) Type(s string) Component { 48 | return c.Attr("type", s) 49 | } 50 | 51 | func (c Component) Value(s string) Component { 52 | return c.Attr("value", s) 53 | } 54 | 55 | func (c Component) Placeholder(s string) Component { 56 | return c.Attr("placeholder", s) 57 | } 58 | 59 | func (c Component) Content(s string) Component { 60 | return c.Attr("content", s) 61 | } 62 | 63 | func (c Component) Alt(s string) Component { 64 | return c.Attr("alt", s) 65 | } 66 | 67 | func (c Component) Title(s string) Component { 68 | return c.Attr("title", s) 69 | } 70 | 71 | func (c Component) Fill(s string) Component { 72 | return c.Attr("fill", s) 73 | } 74 | 75 | func (c Component) ViewBox(s string) Component { 76 | return c.Attr("viewBox", s) 77 | } 78 | 79 | func (c Component) D(s string) Component { 80 | return c.Attr("d", s) 81 | } 82 | 83 | func (c Component) StrokeLineCap(s string) Component { 84 | return c.Attr("stroke-linecap", s) 85 | } 86 | 87 | func (c Component) StrokeWidth(s string) Component { 88 | return c.Attr("stroke-width", s) 89 | } 90 | 91 | func (c Component) StrokeLineJoin(s string) Component { 92 | return c.Attr("stroke-linejoin", s) 93 | } 94 | 95 | func (c Component) Stroke(s string) Component { 96 | return c.Attr("stroke", s) 97 | } 98 | 99 | func (c Component) Disabled() Component { 100 | return c.Attr("disabled", "disabled") 101 | } 102 | 103 | func (c Component) ReadOnly() Component { 104 | return c.Attr("readonly", "readonly") 105 | } 106 | 107 | func (c Component) Required() Component { 108 | return c.Attr("required", "required") 109 | } 110 | 111 | func (c Component) Checked() Component { 112 | return c.Attr("checked", "checked") 113 | } 114 | 115 | func (c Component) MaxLength(n int) Component { 116 | return c.Attr("maxlength", fmt.Sprintf("%d", n)) 117 | } 118 | 119 | func (c Component) MinLength(n int) Component { 120 | return c.Attr("minlength", fmt.Sprintf("%d", n)) 121 | } 122 | 123 | func (c Component) Pattern(s string) Component { 124 | return c.Attr("pattern", s) 125 | } 126 | 127 | func (c Component) Step(s string) Component { 128 | return c.Attr("step", s) 129 | } 130 | 131 | func (c Component) Width(s string) Component { 132 | return c.Attr("width", s) 133 | } 134 | 135 | func (c Component) Height(s string) Component { 136 | return c.Attr("height", s) 137 | } 138 | 139 | func (c Component) Data(key, value string) Component { 140 | return c.Attr("data-"+key, value) 141 | } 142 | 143 | func (c Component) Aria(key, value string) Component { 144 | return c.Attr("aria-"+key, value) 145 | } 146 | 147 | func (c Component) Xmlns(s string) Component { 148 | return c.Attr("pattern", s) 149 | } 150 | 151 | func (c Component) Rows(s string) Component { 152 | return c.Attr("rows", s) 153 | } 154 | -------------------------------------------------------------------------------- /components.go: -------------------------------------------------------------------------------- 1 | package gsc 2 | 3 | func Link() Component { 4 | return NewComponent(``) 5 | } 6 | 7 | func Meta() Component { 8 | return NewComponent(``) 9 | } 10 | 11 | func P() Component { 12 | return NewComponent(``) 13 | } 14 | 15 | func H1() Component { 16 | return NewComponent(``) 17 | } 18 | 19 | func H2() Component { 20 | return NewComponent(``) 21 | } 22 | 23 | func H3() Component { 24 | return NewComponent(``) 25 | } 26 | 27 | func A() Component { 28 | return NewComponent(``) 29 | } 30 | 31 | func Head() Component { 32 | return NewComponent(``) 33 | } 34 | 35 | func HTMLDoc() Component { 36 | return NewComponent(``) 37 | } 38 | 39 | func Div() Component { 40 | return NewComponent(``) 41 | } 42 | 43 | func Header() Component { 44 | return NewComponent(``)
188 | }
189 |
190 | func Pre() Component {
191 | return NewComponent(``)
192 | }
193 |
194 | func Figure() Component {
195 | return NewComponent(`