├── 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(`
`) 45 | } 46 | 47 | func Li() Component { 48 | return NewComponent(`
  • `) 49 | } 50 | 51 | func Main() Component { 52 | return NewComponent(`
    `) 53 | } 54 | 55 | func Nav() Component { 56 | return NewComponent(``) 57 | } 58 | 59 | func Ol() Component { 60 | return NewComponent(`
      `) 61 | } 62 | 63 | func Script() Component { 64 | return NewComponent(``) 65 | } 66 | 67 | func Ul() Component { 68 | return NewComponent(``) 69 | } 70 | 71 | func Article() Component { 72 | return NewComponent(`
      `) 73 | } 74 | 75 | func Body() Component { 76 | return NewComponent(``) 77 | } 78 | 79 | func Title() Component { 80 | return NewComponent(``) 81 | } 82 | func Span() Component { 83 | return NewComponent(``) 84 | } 85 | 86 | func Img() Component { 87 | return NewComponent(``) 88 | } 89 | 90 | func Button() Component { 91 | return NewComponent(``) 92 | } 93 | 94 | func Input() Component { 95 | return NewComponent(``) 96 | } 97 | 98 | func Form() Component { 99 | return NewComponent(`
      `) 100 | } 101 | 102 | func Label() Component { 103 | return NewComponent(``) 104 | } 105 | 106 | func Footer() Component { 107 | return NewComponent(``) 108 | } 109 | 110 | func Section() Component { 111 | return NewComponent(`
      `) 112 | } 113 | 114 | func Aside() Component { 115 | return NewComponent(``) 116 | } 117 | 118 | func Table() Component { 119 | return NewComponent(`
      `) 120 | } 121 | 122 | func Tr() Component { 123 | return NewComponent(``) 124 | } 125 | 126 | func Td() Component { 127 | return NewComponent(``) 128 | } 129 | 130 | func Th() Component { 131 | return NewComponent(``) 132 | } 133 | 134 | func Thead() Component { 135 | return NewComponent(``) 136 | } 137 | 138 | func Tbody() Component { 139 | return NewComponent(``) 140 | } 141 | 142 | func Tfoot() Component { 143 | return NewComponent(``) 144 | } 145 | 146 | func Select() Component { 147 | return NewComponent(``) 148 | } 149 | 150 | func Option() Component { 151 | return NewComponent(``) 152 | } 153 | 154 | func Textarea() Component { 155 | return NewComponent(``) 156 | } 157 | 158 | func Strong() Component { 159 | return NewComponent(``) 160 | } 161 | 162 | func Em() Component { 163 | return NewComponent(``) 164 | } 165 | 166 | func B() Component { 167 | return NewComponent(``) 168 | } 169 | 170 | func I() Component { 171 | return NewComponent(``) 172 | } 173 | 174 | func Hr() Component { 175 | return NewComponent(`
      `) 176 | } 177 | 178 | func Br() Component { 179 | return NewComponent(`
      `) 180 | } 181 | 182 | func Blockquote() Component { 183 | return NewComponent(`
      `) 184 | } 185 | 186 | func Code() Component { 187 | return NewComponent(``) 188 | } 189 | 190 | func Pre() Component { 191 | return NewComponent(`
      `)
      192 | }
      193 | 
      194 | func Figure() Component {
      195 | 	return NewComponent(`
      `) 196 | } 197 | 198 | func Figcaption() Component { 199 | return NewComponent(`
      `) 200 | } 201 | 202 | func Audio() Component { 203 | return NewComponent(``) 204 | } 205 | 206 | func Video() Component { 207 | return NewComponent(``) 208 | } 209 | 210 | func Source() Component { 211 | return NewComponent(``) 212 | } 213 | 214 | func Canvas() Component { 215 | return NewComponent(``) 216 | } 217 | 218 | func Embed() Component { 219 | return NewComponent(``) 220 | } 221 | 222 | func Object() Component { 223 | return NewComponent(``) 224 | } 225 | 226 | func Param() Component { 227 | return NewComponent(``) 228 | } 229 | 230 | func Svg() Component { 231 | return NewComponent(``) 232 | } 233 | 234 | func Path() Component { 235 | return NewComponent(``) 236 | } 237 | 238 | func Polygon() Component { 239 | return NewComponent(``) 240 | } 241 | 242 | func Circle() Component { 243 | return NewComponent(``) 244 | } 245 | 246 | func Rect() Component { 247 | return NewComponent(``) 248 | } 249 | 250 | func Line() Component { 251 | return NewComponent(``) 252 | } 253 | --------------------------------------------------------------------------------