From 5e48d38c6d7aa5c4950a3d09612ad6c3a188f2e6 Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Fri, 19 Apr 2024 23:11:54 +0530 Subject: [PATCH 01/22] refactor: initial restructure --- commands/root.go | 28 + go.mod | 15 +- go.sum | 142 +--- main.go | 1088 ++--------------------------- pkg/alvu/alvu.go | 94 +++ transformers/markdown/markdown.go | 86 +++ transformers/transformers.go | 11 + 7 files changed, 297 insertions(+), 1167 deletions(-) create mode 100644 commands/root.go create mode 100644 pkg/alvu/alvu.go create mode 100644 transformers/markdown/markdown.go create mode 100644 transformers/transformers.go diff --git a/commands/root.go b/commands/root.go new file mode 100644 index 0000000..40ae0ff --- /dev/null +++ b/commands/root.go @@ -0,0 +1,28 @@ +package commands + +import ( + "github.com/barelyhuman/alvu/pkg/alvu" + "github.com/urfave/cli/v2" +) + +func Alvu(c *cli.Context) (err error) { + baseConfig := alvu.AlvuConfig{} + + // Basics + baseConfig.HookDir = c.String("hooks") + baseConfig.OutDir = c.String("out") + baseConfig.RootPath = c.String("path") + + // Transformation Config + baseConfig.BaseURL = c.String("baseurl") + baseConfig.EnableHardWrap = c.Bool("hard-wrap") + baseConfig.EnableHighlighting = c.Bool("highlight") + baseConfig.HighlightingTheme = c.String("highlight-theme") + + // Serve config + baseConfig.Serve = c.Bool("serve") + baseConfig.PollDuration = c.Int("poll") + baseConfig.PortNumber = c.String("port") + + return baseConfig.Run() +} diff --git a/go.mod b/go.mod index 648c2cd..20d9b96 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,17 @@ module github.com/barelyhuman/alvu go 1.18 require ( - github.com/barelyhuman/go v0.2.2-0.20230713173609-2ee88bb52634 - github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9 github.com/joho/godotenv v1.5.1 - github.com/otiai10/copy v1.9.0 - github.com/vadv/gopher-lua-libs v0.4.1 - github.com/yuin/goldmark v1.5.4 + github.com/urfave/cli/v2 v2.27.1 + github.com/yuin/goldmark v1.7.1 github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 github.com/yuin/gopher-lua v1.1.0 - golang.org/x/net v0.0.0-20200202094626-16171245cfb2 - gopkg.in/yaml.v3 v3.0.1 - layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf ) require ( github.com/alecthomas/chroma v0.10.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect - golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect ) diff --git a/go.sum b/go.sum index b123d5a..e1acb86 100644 --- a/go.sum +++ b/go.sum @@ -1,154 +1,32 @@ -github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= -github.com/aws/aws-sdk-go v1.33.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/barelyhuman/go v0.2.2-0.20230713173609-2ee88bb52634 h1:a53Bc1LuSAB9rGbQkBopsYFJNVTgeoUSgnd0do7PDxw= -github.com/barelyhuman/go v0.2.2-0.20230713173609-2ee88bb52634/go.mod h1:hox2iDYZAarjpS7jKQeYIi2F+qMA8KLMtCws++L2sSY= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cbroglie/mustache v1.0.1/go.mod h1:R/RUa+SobQ14qkP4jtx5Vke5sDytONDQXNLPY/PO69g= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheggaaa/pb/v3 v3.0.5/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9 h1:rdWOzitWlNYeUsXmz+IQfa9NkGEq3gA/qQ3mOEqBU6o= -github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9/go.mod h1:X97UjDTXp+7bayQSFZk2hPvCTmTZIicUjZQRtkwgAKY= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4= -github.com/otiai10/copy v1.9.0/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.4.0 h1:umwcf7gbpEwf7WFzqmWwSv0CzbeMsae2u9ZvpP8j2q4= -github.com/otiai10/mint v1.4.0/go.mod h1:gifjb2MYOoULtKLqUAEILUG/9KONW6f7YsJ6vQLTlFI= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= -github.com/vadv/gopher-lua-libs v0.4.1 h1:NgxYEQ0C027X1U348GnFBxf6S8nqYtgHUEuZnA6w2bU= -github.com/vadv/gopher-lua-libs v0.4.1/go.mod h1:j16bcBLqJUwpQT75QztdmfOa8J7CXMmf8BLbtvAR9NY= -github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7/go.mod h1:bbMEM6aU1WDF1ErA5YJ0p91652pGv140gGw4Ww3RGp8= +github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= +github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.4.5/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= -github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= -github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= +github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZyN55+5dp1wG7wDKv8HQ044moxkyGq12KFFMFDxg= github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594/go.mod h1:U9ihbh+1ZN7fR5Se3daSPoz1CGF9IYtSvWwVQtnzGHU= -github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc/go.mod h1:N8UOSI6/c2yOpa/XDz3KVUiegocTziPiqNkeNTMiG1k= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf h1:rRz0YsF7VXj9fXRF6yQgFI7DzST+hsI3TeFSGupntu0= -layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf/go.mod h1:ivKkcY8Zxw5ba0jldhZCYYQfGdb2K6u9tbYK1AwMIBc= diff --git a/main.go b/main.go index 660a0a9..6df31e0 100644 --- a/main.go +++ b/main.go @@ -1,1043 +1,81 @@ package main import ( - "bytes" - "encoding/json" - "errors" - "flag" + _ "embed" "fmt" - "html/template" - textTmpl "text/template" - - "io" - "io/fs" - "log" - "net/http" "os" - "path" - "path/filepath" - "regexp" - "runtime" - "strings" - "sync" - - _ "embed" - - "github.com/barelyhuman/go/env" - "github.com/barelyhuman/go/poller" - ghttp "github.com/cjoudrey/gluahttp" - - "github.com/barelyhuman/go/color" - cp "github.com/otiai10/copy" - - stringsLib "github.com/vadv/gopher-lua-libs/strings" - - yamlLib "github.com/vadv/gopher-lua-libs/yaml" - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/extension" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/renderer" - "github.com/yuin/goldmark/renderer/html" - - highlighting "github.com/yuin/goldmark-highlighting" - - lua "github.com/yuin/gopher-lua" - "gopkg.in/yaml.v3" + "time" - luaAlvu "github.com/barelyhuman/alvu/lua/alvu" - "golang.org/x/net/websocket" - luajson "layeh.com/gopher-json" + "github.com/barelyhuman/alvu/commands" + "github.com/urfave/cli/v2" ) -const logPrefix = "[alvu] " - -var mdProcessor goldmark.Markdown -var baseurl string -var basePath string -var outPath string -var hardWraps bool -var hookCollection HookCollection -var reloadCh = []chan bool{} -var serveFlag *bool -var notFoundPageExists bool - //go:embed .commitlog.release -var release string +var version string -var layoutFiles []string = []string{"_head.html", "_tail.html", "_layout.html"} - -type SiteMeta struct { - BaseURL string -} - -type PageRenderData struct { - Meta SiteMeta - Data map[string]interface{} - Extras map[string]interface{} -} - -type LayoutRenderData struct { - PageRenderData - Content template.HTML -} - -// TODO: move stuff into the alvu struct type -// on each newly added feature or during improving -// older features. -type Alvu struct { - publicPath string - files []*AlvuFile - filesIndex []string -} - -func (al *Alvu) AddFile(file *AlvuFile) { - al.files = append(al.files, file) - al.filesIndex = append(al.filesIndex, file.sourcePath) -} - -func (al *Alvu) IsAlvuFile(filePath string) bool { - for _, af := range al.filesIndex { - if af == filePath { - return true - } - } - return false -} - -func (al *Alvu) Build() { - for ind := range al.files { - alvuFile := al.files[ind] - alvuFile.Build() - } - - onDebug(func() { - debugInfo("Run all OnFinish Hooks") - memuse() - }) - - // right before completion run all hooks again but for the onFinish - hookCollection.RunAll("OnFinish") -} - -func (al *Alvu) CopyPublic() { - onDebug(func() { - debugInfo("Before copying files") - memuse() - }) - // copy public to out - _, err := os.Stat(al.publicPath) - if err == nil { - err = cp.Copy(al.publicPath, outPath) - if err != nil { - bail(err) - } - } - onDebug(func() { - debugInfo("After copying files") - memuse() - }) -} +const logPrefix string = "[alvu] %v" func main() { - onDebug(func() { - debugInfo("Before Exec") - memuse() - }) - - var versionFlag bool - - flag.BoolVar(&versionFlag, "version", false, "version info") - flag.BoolVar(&versionFlag, "v", false, "version info") - basePathFlag := flag.String("path", ".", "`DIR` to search for the needed folders in") - outPathFlag := flag.String("out", "./dist", "`DIR` to output the compiled files to") - baseurlFlag := flag.String("baseurl", "/", "`URL` to be used as the root of the project") - hooksPathFlag := flag.String("hooks", "./hooks", "`DIR` that contains hooks for the content") - enableHighlightingFlag := flag.Bool("highlight", false, "enable highlighting for markdown files") - highlightThemeFlag := flag.String("highlight-theme", "bw", "`THEME` to use for highlighting (supports most themes from pygments)") - serveFlag = flag.Bool("serve", false, "start a local server") - hardWrapsFlag := flag.Bool("hard-wrap", true, "enable hard wrapping of elements with `
`") - portFlag := flag.String("port", "3000", "`PORT` to start the server on") - pollDurationFlag := flag.Int("poll", 350, "Polling duration for file changes in milliseconds") - - flag.Parse() - - // Show version and exit - if versionFlag { - println(release) - os.Exit(0) - } - - baseurl = *baseurlFlag - basePath = path.Join(*basePathFlag) - pagesPath := path.Join(*basePathFlag, "pages") - publicPath := path.Join(*basePathFlag, "public") - headFilePath := path.Join(pagesPath, "_head.html") - baseFilePath := path.Join(pagesPath, "_layout.html") - tailFilePath := path.Join(pagesPath, "_tail.html") - notFoundFilePath := path.Join(pagesPath, "404.html") - outPath = path.Join(*outPathFlag) - hooksPath := path.Join(*basePathFlag, *hooksPathFlag) - hardWraps = *hardWrapsFlag - - headTailDeprecationWarning := color.ColorString{} - headTailDeprecationWarning.Yellow(logPrefix).Yellow("[WARN] use of _tail.html and _head.html is deprecated, please use _layout.html instead") - - os.MkdirAll(publicPath, os.ModePerm) - - alvuApp := &Alvu{ - publicPath: publicPath, - } - - watcher := NewWatcher(alvuApp, *pollDurationFlag) - - if *serveFlag { - watcher.AddDir(pagesPath) - watcher.AddDir(publicPath) - } - - onDebug(func() { - debugInfo("Opening _head") - memuse() - }) - headFileFd, err := os.Open(headFilePath) - if err != nil { - if err == fs.ErrNotExist { - log.Println("no _head.html found,skipping") - } - } else { - fmt.Println(headTailDeprecationWarning.String()) - } - - onDebug(func() { - debugInfo("Opening _layout") - memuse() - }) - baseFileFd, err := os.Open(baseFilePath) - if err != nil { - if err == fs.ErrNotExist { - log.Println("no _layout.html found,skipping") - } - } - - onDebug(func() { - debugInfo("Opening _tail") - memuse() - }) - tailFileFd, err := os.Open(tailFilePath) - if err != nil { - if err == fs.ErrNotExist { - log.Println("no _tail.html found, skipping") - } - } else { - fmt.Println(headTailDeprecationWarning.String()) - } - - onDebug(func() { - debugInfo("Checking if 404.html exists") - memuse() - }) - if _, err := os.Stat(notFoundFilePath); errors.Is(err, os.ErrNotExist) { - notFoundPageExists = false - log.Println("no 404.html found, skipping") - } else { - notFoundPageExists = true - } - - alvuApp.CopyPublic() - - onDebug(func() { - debugInfo("Reading hook and to process files") - memuse() - }) - CollectHooks(basePath, hooksPath) - toProcess := CollectFilesToProcess(pagesPath) - onDebug(func() { - log.Println("printing files to process") - log.Println(toProcess) - }) - - initMDProcessor(*enableHighlightingFlag, *highlightThemeFlag) - - onDebug(func() { - debugInfo("Running all OnStart hooks") - memuse() - }) - - hookCollection.RunAll("OnStart") - - prefixSlashPath := regexp.MustCompile(`^\/`) - - onDebug(func() { - debugInfo("Creating Alvu Files") - memuse() - }) - for _, toProcessItem := range toProcess { - fileName := strings.Replace(toProcessItem, pagesPath, "", 1) - fileName = prefixSlashPath.ReplaceAllString(fileName, "") - destFilePath := strings.Replace(toProcessItem, pagesPath, outPath, 1) - isHTML := strings.HasSuffix(fileName, ".html") - - alvuFile := &AlvuFile{ - lock: &sync.Mutex{}, - sourcePath: toProcessItem, - hooks: hookCollection, - destPath: destFilePath, - name: fileName, - isHTML: isHTML, - headFile: headFileFd, - tailFile: tailFileFd, - baseTemplate: baseFileFd, - data: map[string]interface{}{}, - extras: map[string]interface{}{}, - } - - alvuApp.AddFile(alvuFile) - - // If serving, also add the nested path into it - if *serveFlag { - watcher.AddDir(path.Dir(alvuFile.sourcePath)) - } - } - - alvuApp.Build() - - onDebug(func() { - runtime.GC() - debugInfo("On Completions") - memuse() - }) - - cs := &color.ColorString{} - fmt.Println(cs.Blue(logPrefix).Green("Compiled ").Cyan("\"" + basePath + "\"").Green(" to ").Cyan("\"" + outPath + "\"").String()) - - if *serveFlag { - watcher.StartWatching() - runServer(*portFlag) - } - - hookCollection.Shutdown() -} - -func runServer(port string) { - normalizedPort := port - - if !strings.HasPrefix(normalizedPort, ":") { - normalizedPort = ":" + normalizedPort - } - - cs := &color.ColorString{} - cs.Blue(logPrefix).Green("Serving on").Reset(" ").Cyan(normalizedPort) - fmt.Println(cs.String()) - - http.Handle("/", http.HandlerFunc(ServeHandler)) - AddWebsocketHandler() - - err := http.ListenAndServe(normalizedPort, nil) - - if strings.Contains(err.Error(), "address already in use") { - bail(errors.New("port already in use, use another port with the `-port` flag instead")) - } -} - -func CollectFilesToProcess(basepath string) []string { - files := []string{} - - pathstoprocess, err := os.ReadDir(basepath) - if err != nil { - panic(err) - } - - for _, pathInfo := range pathstoprocess { - _path := path.Join(basepath, pathInfo.Name()) - - if Contains(layoutFiles, pathInfo.Name()) { - continue - } - - if pathInfo.IsDir() { - files = append(files, CollectFilesToProcess(_path)...) - } else { - files = append(files, _path) - } - - } - - return files -} - -func CollectHooks(basePath, hooksBasePath string) { - if _, err := os.Stat(hooksBasePath); err != nil { - return - } - pathsToProcess, err := os.ReadDir(hooksBasePath) - if err != nil { - panic(err) - } - - for _, pathInfo := range pathsToProcess { - if !strings.HasSuffix(pathInfo.Name(), ".lua") { - continue - } - hook := NewHook() - hookPath := path.Join(hooksBasePath, pathInfo.Name()) - if err := hook.DoFile(hookPath); err != nil { - panic(err) - } - hookCollection = append(hookCollection, &Hook{ - path: hookPath, - state: hook, - }) - } - -} - -func initMDProcessor(highlight bool, theme string) { - - rendererOptions := []renderer.Option{ - html.WithXHTML(), - html.WithUnsafe(), - } - - if hardWraps { - rendererOptions = append(rendererOptions, html.WithHardWraps()) - } - gmPlugins := []goldmark.Option{ - goldmark.WithExtensions(extension.GFM, extension.Footnote), - goldmark.WithParserOptions( - parser.WithAutoHeadingID(), - ), - goldmark.WithRendererOptions( - rendererOptions..., - ), - } - - if highlight { - gmPlugins = append(gmPlugins, goldmark.WithExtensions( - highlighting.NewHighlighting( - highlighting.WithStyle(theme), - ), - )) - } - - mdProcessor = goldmark.New(gmPlugins...) -} - -type Hook struct { - path string - state *lua.LState -} - -type HookCollection []*Hook - -func (hc HookCollection) Shutdown() { - for _, hook := range hc { - hook.state.Close() - } -} - -func (hc HookCollection) RunAll(funcName string) { - for _, hook := range hc { - hookFunc := hook.state.GetGlobal(funcName) - - if hookFunc == lua.LNil { - continue - } - - if err := hook.state.CallByParam(lua.P{ - Fn: hookFunc, - NRet: 0, - Protect: true, - }); err != nil { - bail(err) - } - } -} - -type AlvuFile struct { - lock *sync.Mutex - hooks HookCollection - name string - sourcePath string - isHTML bool - destPath string - meta map[string]interface{} - content []byte - writeableContent []byte - headFile *os.File - tailFile *os.File - baseTemplate *os.File - targetName []byte - data map[string]interface{} - extras map[string]interface{} -} - -func (alvuFile *AlvuFile) Build() { - bail(alvuFile.ReadFile()) - bail(alvuFile.ParseMeta()) - - if len(alvuFile.hooks) == 0 { - alvuFile.ProcessFile(nil) - } - - for _, hook := range hookCollection { - - isForSpecificFile := hook.state.GetGlobal("ForFile") - - if isForSpecificFile != lua.LNil { - if alvuFile.name == isForSpecificFile.String() { - alvuFile.ProcessFile(hook.state) - } else { - bail(alvuFile.ProcessFile(nil)) - } - } else { - bail(alvuFile.ProcessFile(hook.state)) - } - } - - alvuFile.FlushFile() -} - -func (af *AlvuFile) ReadFile() error { - filecontent, err := os.ReadFile(af.sourcePath) - if err != nil { - return fmt.Errorf("error reading file, error: %v", err) - } - af.content = filecontent - return nil -} - -func (af *AlvuFile) ParseMeta() error { - sep := []byte("---") - if !bytes.HasPrefix(af.content, sep) { - af.writeableContent = af.content - return nil - } - - metaParts := bytes.SplitN(af.content, sep, 3) - - var meta map[string]interface{} - err := yaml.Unmarshal([]byte(metaParts[1]), &meta) - if err != nil { - return err - } - - af.meta = meta - af.writeableContent = []byte(metaParts[2]) - - return nil -} - -func (af *AlvuFile) ProcessFile(hook *lua.LState) error { - // pre process hook => should return back json with `content` and `data` - af.lock.Lock() - defer af.lock.Unlock() - - af.targetName = regexp.MustCompile(`\.md$`).ReplaceAll([]byte(af.name), []byte(".html")) - onDebug(func() { - debugInfo(af.name + " will be changed to " + string(af.targetName)) - }) - - buf := bytes.NewBuffer([]byte("")) - mdToHTML := "" - - if filepath.Ext(af.name) == ".md" { - newName := strings.Replace(af.name, filepath.Ext(af.name), ".html", 1) - af.targetName = []byte(newName) - mdProcessor.Convert(af.writeableContent, buf) - mdToHTML = buf.String() - } - - if hook == nil { - return nil - } - - hookInput := struct { - Name string `json:"name"` - SourcePath string `json:"source_path"` - DestPath string `json:"dest_path"` - Meta map[string]interface{} `json:"meta"` - WriteableContent string `json:"content"` - HTMLContent string `json:"html"` - }{ - Name: string(af.targetName), - SourcePath: af.sourcePath, - DestPath: af.destPath, - Meta: af.meta, - WriteableContent: string(af.writeableContent), - HTMLContent: mdToHTML, - } - - hookJsonInput, err := json.Marshal(hookInput) - bail(err) - - if err := hook.CallByParam(lua.P{ - Fn: hook.GetGlobal("Writer"), - NRet: 1, - Protect: true, - }, lua.LString(hookJsonInput)); err != nil { - panic(err) - } - - ret := hook.Get(-1) - - var fromPlug map[string]interface{} - - err = json.Unmarshal([]byte(ret.String()), &fromPlug) - bail(err) - - if fromPlug["content"] != nil { - stringVal := fmt.Sprintf("%s", fromPlug["content"]) - af.writeableContent = []byte(stringVal) - } - - if fromPlug["name"] != nil { - af.targetName = []byte(fmt.Sprintf("%v", fromPlug["name"])) - } - - if fromPlug["data"] != nil { - af.data = mergeMapWithCheck(af.data, fromPlug["data"]) - } - - if fromPlug["extras"] != nil { - af.extras = mergeMapWithCheck(af.extras, fromPlug["extras"]) - } - - hook.Pop(1) - return nil -} - -func (af *AlvuFile) FlushFile() { - destFolder := filepath.Dir(af.destPath) - os.MkdirAll(destFolder, os.ModePerm) - - targetFile := strings.Replace(path.Join(af.destPath), af.name, string(af.targetName), 1) - onDebug(func() { - debugInfo("flushing for file: " + af.name + string(af.targetName)) - debugInfo("flusing file: " + targetFile) - }) - - f, err := os.Create(targetFile) - bail(err) - defer f.Sync() - - writeHeadTail := false - - if af.baseTemplate == nil && (filepath.Ext(af.sourcePath) == ".md" || filepath.Ext(af.sourcePath) == "html") { - writeHeadTail = true - } - - if writeHeadTail && af.headFile != nil { - shouldCopyContentsWithReset(af.headFile, f) - } - - renderData := PageRenderData{ - Meta: SiteMeta{ - BaseURL: baseurl, + app := &cli.App{ + Name: "alvu", + Usage: "A scriptable static site generator", + CommandNotFound: cli.ShowCommandCompletions, + Action: func(c *cli.Context) error { + return commands.Alvu(c) }, - Data: af.data, - Extras: af.extras, - } - - // Run the Markdown file through the conversion - // process to be able to use template variables in - // the markdown instead of writing them in - // raw HTML - var preConvertHTML bytes.Buffer - preConvertTmpl := textTmpl.New("temporary_pre_template") - preConvertTmpl.Parse(string(af.writeableContent)) - err = preConvertTmpl.Execute(&preConvertHTML, renderData) - bail(err) - - var toHtml bytes.Buffer - if !af.isHTML { - err = mdProcessor.Convert(preConvertHTML.Bytes(), &toHtml) - bail(err) - } else { - toHtml = preConvertHTML - } - - layoutData := LayoutRenderData{ - PageRenderData: renderData, - Content: template.HTML(toHtml.Bytes()), - } - - // If a layout file was found - // write the converted html content into the - // layout template file - - layout := template.New("layout") - var layoutTemplateData string - if af.baseTemplate != nil { - layoutTemplateData = string(readFileToBytes(af.baseTemplate)) - } else { - layoutTemplateData = `{{.Content}}` - } - - layoutTemplateData = _injectLiveReload(&layoutTemplateData) - toHtml.Reset() - layout.Parse(layoutTemplateData) - layout.Execute(&toHtml, layoutData) - - io.Copy( - f, &toHtml, - ) - - if writeHeadTail && af.tailFile != nil && af.baseTemplate == nil { - shouldCopyContentsWithReset(af.tailFile, f) - } - - data, err := os.ReadFile(targetFile) - bail(err) - - onDebug(func() { - debugInfo("template path: %v", af.sourcePath) - }) - - t := template.New(path.Join(af.sourcePath)) - t.Parse(string(data)) - - f.Seek(0, 0) - - err = t.Execute(f, renderData) - bail(err) -} - -func NewHook() *lua.LState { - lState := lua.NewState() - luaAlvu.Preload(lState) - luajson.Preload(lState) - yamlLib.Preload(lState) - stringsLib.Preload(lState) - lState.PreloadModule("http", ghttp.NewHttpModule(&http.Client{}).Loader) - if basePath == "." { - lState.SetGlobal("workingdir", lua.LString("")) - } else { - lState.SetGlobal("workingdir", lua.LString(basePath)) - } - return lState -} - -// UTILS -func memuse() { - var m runtime.MemStats - runtime.ReadMemStats(&m) - fmt.Printf("heap: %v MiB\n", bytesToMB(m.HeapAlloc)) -} - -func bytesToMB(inBytes uint64) uint64 { - return inBytes / 1024 / 1024 -} - -func bail(err error) { - if err == nil { - return - } - cs := &color.ColorString{} - fmt.Fprintln(os.Stderr, cs.Red(logPrefix).Red(": "+err.Error()).String()) - panic("") -} - -func debugInfo(msg string, a ...any) { - cs := &color.ColorString{} - prefix := logPrefix - baseMessage := cs.Reset("").Yellow(prefix).Reset(" ").Gray(msg).String() - fmt.Fprintf(os.Stdout, baseMessage+" \n", a...) -} - -func showDebug() bool { - showInfo := env.Get("DEBUG_ALVU", "") - return len(showInfo) != 0 -} - -func onDebug(fn func()) { - if !showDebug() { - return - } - - fn() -} - -func mergeMapWithCheck(maps ...any) (source map[string]interface{}) { - source = map[string]interface{}{} - for _, toCheck := range maps { - if pairs, ok := toCheck.(map[string]interface{}); ok { - for k, v := range pairs { - source[k] = v - } - } - } - return source -} - -func readFileToBytes(fd *os.File) []byte { - buf := &bytes.Buffer{} - fd.Seek(0, 0) - _, err := io.Copy(buf, fd) - bail(err) - return buf.Bytes() -} - -func shouldCopyContentsWithReset(src *os.File, target *os.File) { - src.Seek(0, 0) - _, err := io.Copy(target, src) - bail(err) -} - -func ServeHandler(rw http.ResponseWriter, req *http.Request) { - path := req.URL.Path - - if path == "/" { - path = filepath.Join(outPath, "index.html") - http.ServeFile(rw, req, path) - return - } - - // check if the requested file already exists - file := filepath.Join(outPath, path) - info, err := os.Stat(file) - - // if not, check if it's a directory - // and if it's a directory, we look for - // a index.html inside the directory to return instead - if err == nil { - if info.Mode().IsDir() { - file = filepath.Join(outPath, path, "index.html") - _, err := os.Stat(file) - if err != nil { - notFoundHandler(rw, req) - return - } - } - - http.ServeFile(rw, req, file) - return + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "hooks", + Value: "./hooks", + }, + &cli.StringFlag{ + Name: "out", + Value: "./dist", + }, + &cli.StringFlag{ + Name: "path", + Value: ".", + }, + &cli.StringFlag{ + Name: "baseurl", + Value: "/", + }, + &cli.BoolFlag{ + Name: "hard-wrap", + Value: false, + }, + &cli.BoolFlag{ + Name: "highlight", + Value: false, + }, + &cli.StringFlag{ + Name: "highlight-theme", + Value: "bw", + }, + &cli.BoolFlag{ + Name: "serve", + Value: false, + Aliases: []string{"s"}, + }, + &cli.IntFlag{ + Name: "poll", + Usage: "Define the poll duration in seconds", + Value: 1000, + }, + &cli.StringFlag{ + Name: "port", + Usage: "port to use for serving the application", + Value: ":3000", + Aliases: []string{"p"}, + }, + }, + Version: version, + Compiled: time.Now(), + HideVersion: false, } - // if neither a directory or file was found - // try a secondary case where the file might be missing - // a `.html` extension for cleaner url so append a .html - // to look for the file. + err := app.Run(os.Args) if err != nil { - file := filepath.Join(outPath, normalizeFilePath(path)) - _, err := os.Stat(file) - - if err != nil { - notFoundHandler(rw, req) - return - } - - http.ServeFile(rw, req, file) - return - } - - notFoundHandler(rw, req) -} - -// _webSocketHandler Internal function to setup a listener loop -// for the live reload setup -func _webSocketHandler(ws *websocket.Conn) { - reloadCh = append(reloadCh, make(chan bool, 1)) - currIndex := len(reloadCh) - 1 - - defer ws.Close() - - for range reloadCh[currIndex] { - err := websocket.Message.Send(ws, "reload") - if err != nil { - // For debug only - // log.Printf("Error sending message: %s", err.Error()) - break - } - onDebug(func() { - debugInfo("Reload message sent") - }) - } - -} - -func AddWebsocketHandler() { - wsHandler := websocket.Handler(_webSocketHandler) - - // Use a custom HTTP handler function to upgrade the HTTP request to WebSocket - http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { - // Check the request's 'Upgrade' header to see if it's a WebSocket request - if r.Header.Get("Upgrade") != "websocket" { - http.Error(w, "Not a WebSocket handshake request", http.StatusBadRequest) - return - } - - // Upgrade the HTTP connection to a WebSocket connection - wsHandler.ServeHTTP(w, r) - }) - -} - -// _clientNotifyReload Internal function to -// report changes to all possible reload channels -func _clientNotifyReload() { - for ind := range reloadCh { - reloadCh[ind] <- true - } - reloadCh = []chan bool{} -} - -func normalizeFilePath(path string) string { - if strings.HasSuffix(path, ".html") { - return path + fmt.Fprintf(os.Stderr, logPrefix, err) } - return path + ".html" -} - -func notFoundHandler(w http.ResponseWriter, r *http.Request) { - if notFoundPageExists { - compiledNotFoundFile := filepath.Join(outPath, "404.html") - notFoundFile, err := os.ReadFile(compiledNotFoundFile) - if err != nil { - http.Error(w, "404, Page not found....", http.StatusNotFound) - return - } - w.WriteHeader(http.StatusNotFound) - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.Write(notFoundFile) - return - } - http.Error(w, "404, Page not found....", http.StatusNotFound) -} - -func Contains(collection []string, item string) bool { - for _, x := range collection { - if item == x { - return true - } - } - return false -} - -// Watcher , create an interface over the fsnotify watcher -// to be able to run alvu compile processes again -// FIXME: redundant compile process for the files -type Watcher struct { - alvu *Alvu - poller *poller.Poller - dirs []string -} - -func NewWatcher(alvu *Alvu, interval int) *Watcher { - watcher := &Watcher{ - alvu: alvu, - poller: poller.NewPollWatcher(interval), - } - - return watcher -} - -func (w *Watcher) AddDir(dirPath string) { - - for _, pth := range w.dirs { - if pth == dirPath { - return - } - } - - w.dirs = append(w.dirs, dirPath) - w.poller.Add(dirPath) -} - -func (w *Watcher) RebuildAlvu() { - onDebug(func() { - debugInfo("Rebuild Started") - }) - w.alvu.CopyPublic() - w.alvu.Build() - onDebug(func() { - debugInfo("Build Completed") - }) -} - -func (w *Watcher) RebuildFile(filePath string) { - onDebug(func() { - debugInfo("RebuildFile Started") - }) - for i, af := range w.alvu.files { - if af.sourcePath != filePath { - continue - } - - w.alvu.files[i].Build() - break - } - onDebug(func() { - debugInfo("RebuildFile Completed") - }) -} - -func (w *Watcher) StartWatching() { - go w.poller.StartPoller() - go func() { - for { - select { - case evt := <-w.poller.Events: - onDebug(func() { - debugInfo("Events registered") - }) - - recompiledText := &color.ColorString{} - recompiledText.Blue(logPrefix).Green("Recompiled!").Reset(" ") - - _, err := os.Stat(evt.Path) - - // Do nothing if the file doesn't exit, just continue - if err != nil { - continue - } - - // If alvu file then just build the file, else - // just rebuilt the whole folder since it could - // be a file from the public folder or the _layout file - if w.alvu.IsAlvuFile(evt.Path) { - recompilingText := &color.ColorString{} - recompilingText.Blue(logPrefix).Cyan("Recompiling: ").Gray(evt.Path).Reset(" ") - fmt.Println(recompilingText.String()) - w.RebuildFile(evt.Path) - } else { - recompilingText := &color.ColorString{} - recompilingText.Blue(logPrefix).Cyan("Recompiling: ").Gray("All").Reset(" ") - fmt.Println(recompilingText.String()) - w.RebuildAlvu() - } - - _clientNotifyReload() - fmt.Println(recompiledText.String()) - continue - - case err := <-w.poller.Errors: - // If the poller has an error, just crash, - // digesting polling issues without killing the program would make it complicated - // to handle cleanup of all the kind of files that are being maintained by alvu - bail(err) - } - } - }() -} - -func _injectLiveReload(layoutHTML *string) string { - if !*serveFlag { - return *layoutHTML - } - return *layoutHTML + `` } diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go new file mode 100644 index 0000000..01de021 --- /dev/null +++ b/pkg/alvu/alvu.go @@ -0,0 +1,94 @@ +package alvu + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/barelyhuman/alvu/transformers" + "github.com/barelyhuman/alvu/transformers/markdown" +) + +type AlvuConfig struct { + HookDir string + OutDir string + RootPath string + + BaseURL string + EnableHardWrap bool + EnableHighlighting bool + HighlightingTheme string + + Serve bool + PollDuration int + PortNumber string + + Transformers map[string][]transformers.Transfomer +} + +func (ac *AlvuConfig) Run() error { + ac.Transformers = map[string][]transformers.Transfomer{ + ".md": { + &markdown.MarkdownTransformer{ + EnableHardWrap: ac.EnableHardWrap, + EnableHighlighting: ac.EnableHighlighting, + HighlightingTheme: ac.HighlightingTheme, + }, + }, + } + + filesToProcess, err := ac.ReadDir(filepath.Join(ac.RootPath, "pages")) + if err != nil { + return err + } + + normalizedFiles := []transformers.TransformedFile{} + + // transform phase + for _, fileToNormalize := range filesToProcess { + extension := filepath.Ext(fileToNormalize) + + if len(ac.Transformers[extension]) < 1 { + continue + } + for _, transformer := range ac.Transformers[extension] { + transformedFile, err := transformer.Transform(fileToNormalize) + if err != nil { + return fmt.Errorf("failed to transform file: %v, with error: %v", fileToNormalize, err) + } + normalizedFiles = append(normalizedFiles, transformedFile) + } + } + + fmt.Printf("normalizedFiles: %v\n", normalizedFiles) + + return nil +} + +func (ac *AlvuConfig) ReadDir(dir string) (dirs []string, err error) { + return recursiveRead(dir) +} + +func recursiveRead(dir string) (dirs []string, err error) { + dirEntry, err := os.ReadDir( + dir, + ) + + if err != nil { + return + } + + for _, de := range dirEntry { + if de.IsDir() { + subDirs, err := recursiveRead(filepath.Join(dir, de.Name())) + if err != nil { + return dirs, err + } + dirs = append(dirs, subDirs...) + } else { + dirs = append(dirs, filepath.Join(dir, de.Name())) + } + } + + return +} diff --git a/transformers/markdown/markdown.go b/transformers/markdown/markdown.go new file mode 100644 index 0000000..4143dbc --- /dev/null +++ b/transformers/markdown/markdown.go @@ -0,0 +1,86 @@ +package markdown + +import ( + "os" + "path/filepath" + "strings" + + "github.com/barelyhuman/alvu/transformers" + "github.com/yuin/goldmark" + highlighting "github.com/yuin/goldmark-highlighting" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/renderer" + "github.com/yuin/goldmark/renderer/html" +) + +type MarkdownTransformer struct { + processor goldmark.Markdown + EnableHardWrap bool + EnableHighlighting bool + HighlightingTheme string +} + +func (mt *MarkdownTransformer) Transform(filePath string) (transformedFile transformers.TransformedFile, err error) { + if mt.processor == nil { + mt.Init() + } + + fileBytes, err := os.ReadFile(filePath) + if err != nil { + return + } + + tmpDir := os.TempDir() + filename := filepath.Base(filePath) + fileExt := filepath.Ext(filename) + filename = strings.TrimSuffix(filename, fileExt) + filename += ".html" + tmpFile := filepath.Join(tmpDir, filename) + + fileWriter, err := os.Create(tmpFile) + if err != nil { + return + } + + err = mt.processor.Convert(fileBytes, fileWriter) + if err != nil { + return + } + + return transformers.TransformedFile{ + SourcePath: filePath, + TransformedFile: tmpFile, + Extension: fileExt, + }, nil +} + +func (mt *MarkdownTransformer) Init() { + rendererOptions := []renderer.Option{ + html.WithXHTML(), + html.WithUnsafe(), + } + + if mt.EnableHardWrap { + rendererOptions = append(rendererOptions, html.WithHardWraps()) + } + gmPlugins := []goldmark.Option{ + goldmark.WithExtensions(extension.GFM, extension.Footnote), + goldmark.WithParserOptions( + parser.WithAutoHeadingID(), + ), + goldmark.WithRendererOptions( + rendererOptions..., + ), + } + + if mt.EnableHighlighting { + gmPlugins = append(gmPlugins, goldmark.WithExtensions( + highlighting.NewHighlighting( + highlighting.WithStyle(mt.HighlightingTheme), + ), + )) + } + + mt.processor = goldmark.New(gmPlugins...) +} diff --git a/transformers/transformers.go b/transformers/transformers.go new file mode 100644 index 0000000..f0b386e --- /dev/null +++ b/transformers/transformers.go @@ -0,0 +1,11 @@ +package transformers + +type TransformedFile struct { + SourcePath string + TransformedFile string + Extension string +} + +type Transfomer interface { + Transform(filePath string) (TransformedFile, error) +} From c6e994f23b53ecc28679f8c2c4815cffec30b7eb Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Sat, 20 Apr 2024 01:16:15 +0530 Subject: [PATCH 02/22] feat(core): basic asset creation layout wrapping and handle flushing --- go.mod | 1 + go.sum | 2 + pkg/alvu/alvu.go | 152 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 149 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 20d9b96..a6eaf9d 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/yuin/goldmark v1.7.1 github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 github.com/yuin/gopher-lua v1.1.0 + golang.org/x/net v0.24.0 ) require ( diff --git a/go.sum b/go.sum index e1acb86..7d37a59 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZ github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594/go.mod h1:U9ihbh+1ZN7fR5Se3daSPoz1CGF9IYtSvWwVQtnzGHU= github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index 01de021..e37523b 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -1,12 +1,17 @@ package alvu import ( + "bytes" "fmt" + "io" "os" "path/filepath" + "strings" + "sync" "github.com/barelyhuman/alvu/transformers" "github.com/barelyhuman/alvu/transformers/markdown" + "golang.org/x/net/html" ) type AlvuConfig struct { @@ -24,6 +29,9 @@ type AlvuConfig struct { PortNumber string Transformers map[string][]transformers.Transfomer + + // Internals + _layoutBuffer bytes.Buffer } func (ac *AlvuConfig) Run() error { @@ -42,27 +50,128 @@ func (ac *AlvuConfig) Run() error { return err } + publicFiles, err := ac.ReadDir(filepath.Join(ac.RootPath, "public")) + if err != nil { + return err + } + + normalizedFiles, err := runTransfomers(filesToProcess, ac) + if err != nil { + return err + } + + processedFiles := normalizedFiles + + ac.HandlePublicFiles(publicFiles) + return ac.FlushFiles(processedFiles) + +} + +func (ac *AlvuConfig) ReadLayout() string { + layoutFilePath := filepath.Join(ac.RootPath, "pages", "_layout.html") + fileInfo, err := os.Stat(layoutFilePath) + if os.IsNotExist(err) { + return "" + } + if fileInfo.IsDir() { + return "" + } + data, _ := os.ReadFile( + layoutFilePath, + ) + return string(data) +} + +func (ac *AlvuConfig) HandlePublicFiles(files []string) (err error) { + var wg sync.WaitGroup + for _, v := range files { + wg.Add(1) + file := v + go func() { + destFile := filepath.Clean(file) + destFile = strings.TrimPrefix(destFile, "public") + destFile = filepath.Join(ac.OutDir, destFile) + os.MkdirAll(filepath.Dir(destFile), os.ModePerm) + + fileToCreate, _ := os.Create(destFile) + reader, _ := os.OpenFile(file, os.O_RDONLY, os.ModePerm) + io.Copy(fileToCreate, reader) + wg.Done() + }() + } + wg.Wait() + return +} + +func (ac *AlvuConfig) FlushFiles(files []transformers.TransformedFile) error { + if err := os.MkdirAll(ac.OutDir, os.ModePerm); err != nil { + return err + } + + for _, tf := range files { + originalDir, baseFile := filepath.Split(tf.SourcePath) + newDir := strings.TrimPrefix(originalDir, "pages") + fileWithNewExtension := strings.TrimSuffix(baseFile, tf.Extension) + ".html" + destFile := filepath.Join( + ac.OutDir, + newDir, + fileWithNewExtension, + ) + + err := os.MkdirAll(filepath.Dir(destFile), os.ModePerm) + if err != nil { + return err + } + + destWriter, err := os.Create(destFile) + if err != nil { + return err + } + defer destWriter.Close() + + sourceFileData, err := os.ReadFile(tf.TransformedFile) + if err != nil { + return err + } + + replaced, _ := replaceBodyTag( + ac.ReadLayout(), + string(sourceFileData), + ) + + _, err = io.Copy(destWriter, bytes.NewBuffer([]byte(replaced))) + + if err != nil { + return err + } + } + + return nil +} + +func runTransfomers(filesToProcess []string, ac *AlvuConfig) ([]transformers.TransformedFile, error) { normalizedFiles := []transformers.TransformedFile{} - // transform phase for _, fileToNormalize := range filesToProcess { extension := filepath.Ext(fileToNormalize) if len(ac.Transformers[extension]) < 1 { + normalizedFiles = append(normalizedFiles, transformers.TransformedFile{ + SourcePath: fileToNormalize, + Extension: filepath.Ext(fileToNormalize), + TransformedFile: fileToNormalize, + }) continue } for _, transformer := range ac.Transformers[extension] { transformedFile, err := transformer.Transform(fileToNormalize) if err != nil { - return fmt.Errorf("failed to transform file: %v, with error: %v", fileToNormalize, err) + return nil, fmt.Errorf("failed to transform file: %v, with error: %v", fileToNormalize, err) } normalizedFiles = append(normalizedFiles, transformedFile) } } - - fmt.Printf("normalizedFiles: %v\n", normalizedFiles) - - return nil + return normalizedFiles, nil } func (ac *AlvuConfig) ReadDir(dir string) (dirs []string, err error) { @@ -92,3 +201,34 @@ func recursiveRead(dir string) (dirs []string, err error) { return } + +func replaceBodyTag(htmlString string, replacement string) (string, error) { + doc, err := html.Parse(strings.NewReader(htmlString)) + if err != nil { + return "", err + } + + var traverse func(*html.Node) + traverse = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "body" { + replaceNodes, _ := html.ParseFragment(strings.NewReader(replacement), n) + for _, childNodes := range replaceNodes { + n.AppendChild(childNodes) + } + return + } + + for c := n.FirstChild; c != nil; c = c.NextSibling { + traverse(c) + } + } + + traverse(doc) + + var buf strings.Builder + if err := html.Render(&buf, doc); err != nil { + return "", err + } + + return buf.String(), nil +} From 9dedfbb1f21dc007f2cad33fb7ecd9f95738cd21 Mon Sep 17 00:00:00 2001 From: reaper Date: Sun, 21 Apr 2024 10:36:42 +0530 Subject: [PATCH 03/22] refactor(transformer): move html out into it's own transformer --- pkg/alvu/alvu.go | 11 ++++++----- transformers/html/html.go | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 transformers/html/html.go diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index e37523b..5783c93 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -12,6 +12,8 @@ import ( "github.com/barelyhuman/alvu/transformers" "github.com/barelyhuman/alvu/transformers/markdown" "golang.org/x/net/html" + + htmlT "github.com/barelyhuman/alvu/transformers/html" ) type AlvuConfig struct { @@ -36,6 +38,9 @@ type AlvuConfig struct { func (ac *AlvuConfig) Run() error { ac.Transformers = map[string][]transformers.Transfomer{ + ".html": { + &htmlT.HTMLTransformer{}, + }, ".md": { &markdown.MarkdownTransformer{ EnableHardWrap: ac.EnableHardWrap, @@ -156,13 +161,9 @@ func runTransfomers(filesToProcess []string, ac *AlvuConfig) ([]transformers.Tra extension := filepath.Ext(fileToNormalize) if len(ac.Transformers[extension]) < 1 { - normalizedFiles = append(normalizedFiles, transformers.TransformedFile{ - SourcePath: fileToNormalize, - Extension: filepath.Ext(fileToNormalize), - TransformedFile: fileToNormalize, - }) continue } + for _, transformer := range ac.Transformers[extension] { transformedFile, err := transformer.Transform(fileToNormalize) if err != nil { diff --git a/transformers/html/html.go b/transformers/html/html.go new file mode 100644 index 0000000..635b250 --- /dev/null +++ b/transformers/html/html.go @@ -0,0 +1,22 @@ +package html + +import ( + "path/filepath" + + "github.com/barelyhuman/alvu/transformers" +) + +type HTMLTransformer struct { +} + +func (mt *HTMLTransformer) Transform(filePath string) (transformedFile transformers.TransformedFile, err error) { + return transformers.TransformedFile{ + SourcePath: filePath, + TransformedFile: filePath, + Extension: filepath.Ext(filePath), + }, nil +} + +func (mt *HTMLTransformer) Init() { + +} From 9aef3ea368d5ba9572c96f6379a7de74f9486576 Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Sun, 21 Apr 2024 13:39:01 +0530 Subject: [PATCH 04/22] refactor(html): string manipulation instead of parsing --- go.mod | 2 +- go.sum | 4 +-- pkg/alvu/alvu.go | 63 ++++++++++++++++++++++++++-------------------- pkg/alvu/logger.go | 29 +++++++++++++++++++++ 4 files changed, 68 insertions(+), 30 deletions(-) create mode 100644 pkg/alvu/logger.go diff --git a/go.mod b/go.mod index a6eaf9d..b5cd491 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/barelyhuman/alvu go 1.18 require ( + github.com/barelyhuman/go v0.2.2 github.com/joho/godotenv v1.5.1 github.com/urfave/cli/v2 v2.27.1 github.com/yuin/goldmark v1.7.1 github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 github.com/yuin/gopher-lua v1.1.0 - golang.org/x/net v0.24.0 ) require ( diff --git a/go.sum b/go.sum index 7d37a59..655b5ff 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/barelyhuman/go v0.2.2 h1:Lpk1XrlP40F3II8BibVzViZUOJ1GgDdzXUBb8ENwb0U= +github.com/barelyhuman/go v0.2.2/go.mod h1:hox2iDYZAarjpS7jKQeYIi2F+qMA8KLMtCws++L2sSY= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -27,8 +29,6 @@ github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZ github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594/go.mod h1:U9ihbh+1ZN7fR5Se3daSPoz1CGF9IYtSvWwVQtnzGHU= github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index 5783c93..2943bc0 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -11,11 +11,16 @@ import ( "github.com/barelyhuman/alvu/transformers" "github.com/barelyhuman/alvu/transformers/markdown" - "golang.org/x/net/html" htmlT "github.com/barelyhuman/alvu/transformers/html" ) +// Constants +const slotStartTag = "" +const slotEndTag = "" +const slotSelfEndingTag = "" +const contentTag = "{{.Content}}" + type AlvuConfig struct { HookDir string OutDir string @@ -33,10 +38,13 @@ type AlvuConfig struct { Transformers map[string][]transformers.Transfomer // Internals - _layoutBuffer bytes.Buffer + logger Logger } func (ac *AlvuConfig) Run() error { + ac.logger = Logger{ + logPrefix: "[alvu]", + } ac.Transformers = map[string][]transformers.Transfomer{ ".html": { &htmlT.HTMLTransformer{}, @@ -69,7 +77,6 @@ func (ac *AlvuConfig) Run() error { ac.HandlePublicFiles(publicFiles) return ac.FlushFiles(processedFiles) - } func (ac *AlvuConfig) ReadLayout() string { @@ -113,6 +120,10 @@ func (ac *AlvuConfig) FlushFiles(files []transformers.TransformedFile) error { return err } + if hasLegacySlot(ac.ReadLayout()) { + ac.logger.Warning("Please use `` or `` instead of `{{.Content}}` in _layout.html") + } + for _, tf := range files { originalDir, baseFile := filepath.Split(tf.SourcePath) newDir := strings.TrimPrefix(originalDir, "pages") @@ -139,7 +150,7 @@ func (ac *AlvuConfig) FlushFiles(files []transformers.TransformedFile) error { return err } - replaced, _ := replaceBodyTag( + replaced, _ := ac.injectInSlot( ac.ReadLayout(), string(sourceFileData), ) @@ -151,6 +162,8 @@ func (ac *AlvuConfig) FlushFiles(files []transformers.TransformedFile) error { } } + ac.logger.Info("Output in: " + ac.OutDir) + ac.logger.Success("Done") return nil } @@ -203,33 +216,29 @@ func recursiveRead(dir string) (dirs []string, err error) { return } -func replaceBodyTag(htmlString string, replacement string) (string, error) { - doc, err := html.Parse(strings.NewReader(htmlString)) - if err != nil { - return "", err +func (ac *AlvuConfig) injectInSlot(htmlString string, replacement string) (string, error) { + if hasLegacySlot(htmlString) { + return injectInLegacySlot(htmlString, replacement), nil } - var traverse func(*html.Node) - traverse = func(n *html.Node) { - if n.Type == html.ElementNode && n.Data == "body" { - replaceNodes, _ := html.ParseFragment(strings.NewReader(replacement), n) - for _, childNodes := range replaceNodes { - n.AppendChild(childNodes) - } - return - } - - for c := n.FirstChild; c != nil; c = c.NextSibling { - traverse(c) - } + slotStartPos := strings.Index(htmlString, slotStartTag) + slotJSXTagPos := strings.Index(htmlString, slotSelfEndingTag) + slotEndPos := strings.Index(htmlString, slotEndTag) + if slotStartPos == -1 && slotEndPos == -1 && slotJSXTagPos == -1 { + return htmlString, nil } + baseString := strings.Replace(htmlString, slotEndTag, "", slotEndPos) + return strings.Replace(baseString, slotStartTag, replacement, slotStartPos), nil +} - traverse(doc) +func hasLegacySlot(htmlString string) bool { + return strings.Contains(htmlString, contentTag) +} - var buf strings.Builder - if err := html.Render(&buf, doc); err != nil { - return "", err +func injectInLegacySlot(htmlString string, replacement string) string { + contentTagPos := strings.Index(htmlString, contentTag) + if contentTagPos == -1 { + return htmlString } - - return buf.String(), nil + return strings.Replace(htmlString, contentTag, replacement, contentTagPos) } diff --git a/pkg/alvu/logger.go b/pkg/alvu/logger.go new file mode 100644 index 0000000..9da890e --- /dev/null +++ b/pkg/alvu/logger.go @@ -0,0 +1,29 @@ +package alvu + +import ( + "fmt" + + "github.com/barelyhuman/go/color" +) + +type Logger struct { + logPrefix string +} + +func (l *Logger) Success(msg string) { + cs := color.ColorString{} + cs.Gray(l.logPrefix).Reset(" ").Green("✔").Reset(" ").Green(msg) + fmt.Println(cs.String()) +} + +func (l *Logger) Info(msg string) { + cs := color.ColorString{} + cs.Gray(l.logPrefix).Reset(" ").Cyan("ℹ").Reset(" ").Cyan(msg) + fmt.Println(cs.String()) +} + +func (l *Logger) Warning(msg string) { + cs := color.ColorString{} + cs.Gray(l.logPrefix).Reset(" ").Yellow(msg) + fmt.Println(cs.String()) +} From 5f1a85c2b7e56ea23f627370abcc19d926c4d7d6 Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Sun, 21 Apr 2024 18:22:32 +0530 Subject: [PATCH 05/22] refactor(hooks): rewrite hook execution and selective --- go.mod | 4 + go.sum | 161 +++++++++++++++++++++++++++++++++ pkg/alvu/alvu.go | 87 +++++++++++++----- pkg/alvu/hooks.go | 217 +++++++++++++++++++++++++++++++++++++++++++++ pkg/alvu/logger.go | 6 ++ 5 files changed, 451 insertions(+), 24 deletions(-) create mode 100644 pkg/alvu/hooks.go diff --git a/go.mod b/go.mod index b5cd491..4b22b75 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,14 @@ go 1.18 require ( github.com/barelyhuman/go v0.2.2 + github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9 github.com/joho/godotenv v1.5.1 github.com/urfave/cli/v2 v2.27.1 + github.com/vadv/gopher-lua-libs v0.5.0 github.com/yuin/goldmark v1.7.1 github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 github.com/yuin/gopher-lua v1.1.0 + layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf ) require ( @@ -17,4 +20,5 @@ require ( github.com/dlclark/regexp2 v1.4.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect ) diff --git a/go.sum b/go.sum index 655b5ff..bd4ff58 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,27 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/barelyhuman/go v0.2.2 h1:Lpk1XrlP40F3II8BibVzViZUOJ1GgDdzXUBb8ENwb0U= github.com/barelyhuman/go v0.2.2/go.mod h1:hox2iDYZAarjpS7jKQeYIi2F+qMA8KLMtCws++L2sSY= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cbroglie/mustache v1.0.1/go.mod h1:R/RUa+SobQ14qkP4jtx5Vke5sDytONDQXNLPY/PO69g= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheggaaa/pb/v3 v3.0.5/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9 h1:rdWOzitWlNYeUsXmz+IQfa9NkGEq3gA/qQ3mOEqBU6o= +github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9/go.mod h1:X97UjDTXp+7bayQSFZk2hPvCTmTZIicUjZQRtkwgAKY= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -9,26 +29,167 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/vadv/gopher-lua-libs v0.5.0 h1:m0hhWia1A1U3PIRmtdHWBj88ogzuIjm6HUBmtUa0Tz4= +github.com/vadv/gopher-lua-libs v0.5.0/go.mod h1:mlSOxmrjug7DwisiH7xBFnBellHobPbvAIhVeI/4SYY= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7/go.mod h1:bbMEM6aU1WDF1ErA5YJ0p91652pGv140gGw4Ww3RGp8= github.com/yuin/goldmark v1.4.5/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZyN55+5dp1wG7wDKv8HQ044moxkyGq12KFFMFDxg= github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594/go.mod h1:U9ihbh+1ZN7fR5Se3daSPoz1CGF9IYtSvWwVQtnzGHU= +github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc/go.mod h1:N8UOSI6/c2yOpa/XDz3KVUiegocTziPiqNkeNTMiG1k= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf h1:rRz0YsF7VXj9fXRF6yQgFI7DzST+hsI3TeFSGupntu0= +layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf/go.mod h1:ivKkcY8Zxw5ba0jldhZCYYQfGdb2K6u9tbYK1AwMIBc= diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index 2943bc0..211ca2e 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -1,7 +1,6 @@ package alvu import ( - "bytes" "fmt" "io" "os" @@ -9,6 +8,8 @@ import ( "strings" "sync" + templateHTML "html/template" + "github.com/barelyhuman/alvu/transformers" "github.com/barelyhuman/alvu/transformers/markdown" @@ -18,9 +19,18 @@ import ( // Constants const slotStartTag = "" const slotEndTag = "" -const slotSelfEndingTag = "" const contentTag = "{{.Content}}" +type SiteMeta struct { + BaseURL string +} + +type PageRenderData struct { + Meta SiteMeta + Data map[string]interface{} + Extras map[string]interface{} +} + type AlvuConfig struct { HookDir string OutDir string @@ -45,6 +55,11 @@ func (ac *AlvuConfig) Run() error { ac.logger = Logger{ logPrefix: "[alvu]", } + + hooksHandler := Hooks{ + ac: *ac, + } + ac.Transformers = map[string][]transformers.Transfomer{ ".html": { &htmlT.HTMLTransformer{}, @@ -73,7 +88,12 @@ func (ac *AlvuConfig) Run() error { return err } - processedFiles := normalizedFiles + var processedFiles []HookedFile + + hooksHandler.Load() + for _, tf := range normalizedFiles { + processedFiles = append(processedFiles, hooksHandler.ProcessFile(tf)) + } ac.HandlePublicFiles(publicFiles) return ac.FlushFiles(processedFiles) @@ -115,19 +135,20 @@ func (ac *AlvuConfig) HandlePublicFiles(files []string) (err error) { return } -func (ac *AlvuConfig) FlushFiles(files []transformers.TransformedFile) error { +func (ac *AlvuConfig) FlushFiles(files []HookedFile) error { if err := os.MkdirAll(ac.OutDir, os.ModePerm); err != nil { return err } if hasLegacySlot(ac.ReadLayout()) { - ac.logger.Warning("Please use `` or `` instead of `{{.Content}}` in _layout.html") + ac.logger.Warning("Please use `` instead of `{{.Content}}` in _layout.html") } - for _, tf := range files { - originalDir, baseFile := filepath.Split(tf.SourcePath) + for i := range files { + hookedFile := files[i] + originalDir, baseFile := filepath.Split(hookedFile.SourcePath) newDir := strings.TrimPrefix(originalDir, "pages") - fileWithNewExtension := strings.TrimSuffix(baseFile, tf.Extension) + ".html" + fileWithNewExtension := strings.TrimSuffix(baseFile, hookedFile.Extension) + ".html" destFile := filepath.Join( ac.OutDir, newDir, @@ -145,18 +166,27 @@ func (ac *AlvuConfig) FlushFiles(files []transformers.TransformedFile) error { } defer destWriter.Close() - sourceFileData, err := os.ReadFile(tf.TransformedFile) - if err != nil { - return err - } - replaced, _ := ac.injectInSlot( ac.ReadLayout(), - string(sourceFileData), + string(hookedFile.content), ) - _, err = io.Copy(destWriter, bytes.NewBuffer([]byte(replaced))) + template := templateHTML.New("temporaryTemplate") + template, err = template.Parse(replaced) + if err != nil { + ac.logger.Error(fmt.Sprintf("Failed to write to dist file with error: %v", err)) + panic("") + } + renderData := PageRenderData{ + Meta: SiteMeta{ + BaseURL: ac.BaseURL, + }, + Data: hookedFile.data, + Extras: hookedFile.extras, + } + + err = template.Execute(destWriter, renderData) if err != nil { return err } @@ -188,11 +218,22 @@ func runTransfomers(filesToProcess []string, ac *AlvuConfig) ([]transformers.Tra return normalizedFiles, nil } -func (ac *AlvuConfig) ReadDir(dir string) (dirs []string, err error) { - return recursiveRead(dir) +func (ac *AlvuConfig) ReadDir(dir string) (filepaths []string, err error) { + readFilepaths, err := recursiveRead(dir) + if err != nil { + return + } + sanitizedCollection := []string{} + for _, v := range readFilepaths { + if filepath.Base(v) == "_layout.html" { + continue + } + sanitizedCollection = append(sanitizedCollection, v) + } + return sanitizedCollection, nil } -func recursiveRead(dir string) (dirs []string, err error) { +func recursiveRead(dir string) (filepaths []string, err error) { dirEntry, err := os.ReadDir( dir, ) @@ -205,11 +246,11 @@ func recursiveRead(dir string) (dirs []string, err error) { if de.IsDir() { subDirs, err := recursiveRead(filepath.Join(dir, de.Name())) if err != nil { - return dirs, err + return filepaths, err } - dirs = append(dirs, subDirs...) + filepaths = append(filepaths, subDirs...) } else { - dirs = append(dirs, filepath.Join(dir, de.Name())) + filepaths = append(filepaths, filepath.Join(dir, de.Name())) } } @@ -220,11 +261,9 @@ func (ac *AlvuConfig) injectInSlot(htmlString string, replacement string) (strin if hasLegacySlot(htmlString) { return injectInLegacySlot(htmlString, replacement), nil } - slotStartPos := strings.Index(htmlString, slotStartTag) - slotJSXTagPos := strings.Index(htmlString, slotSelfEndingTag) slotEndPos := strings.Index(htmlString, slotEndTag) - if slotStartPos == -1 && slotEndPos == -1 && slotJSXTagPos == -1 { + if slotStartPos == -1 && slotEndPos == -1 { return htmlString, nil } baseString := strings.Replace(htmlString, slotEndTag, "", slotEndPos) diff --git a/pkg/alvu/hooks.go b/pkg/alvu/hooks.go new file mode 100644 index 0000000..4bbb30b --- /dev/null +++ b/pkg/alvu/hooks.go @@ -0,0 +1,217 @@ +package alvu + +import ( + "encoding/json" + "fmt" + "io/fs" + "net/http" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/barelyhuman/alvu/transformers" + lua "github.com/yuin/gopher-lua" + + luaAlvu "github.com/barelyhuman/alvu/lua/alvu" + ghttp "github.com/cjoudrey/gluahttp" + stringsLib "github.com/vadv/gopher-lua-libs/strings" + yamlLib "github.com/vadv/gopher-lua-libs/yaml" + luajson "layeh.com/gopher-json" +) + +type HookSource struct { + luaState *lua.LState + filename string + ForSingleFile bool + ForFile string +} + +type Hooks struct { + ac AlvuConfig + collection []*HookSource + forSpecificFiles map[string][]*HookSource +} + +type HookedFile struct { + transformers.TransformedFile + content []byte + data map[string]interface{} + extras map[string]interface{} +} + +func (h *Hooks) Load() { + hookFiles := []string{} + folderInfo, err := os.Stat(h.ac.HookDir) + if err != nil { + if os.IsNotExist(err) { + return + } + readHookDirError(err, h.ac.HookDir, h.ac.logger) + } + + file, err := os.Open(folderInfo.Name()) + readHookDirError(err, h.ac.HookDir, h.ac.logger) + childs, err := file.Readdirnames(1) + readHookDirError(err, h.ac.HookDir, h.ac.logger) + if len(childs) == 0 { + return + } + + filepath.WalkDir(h.ac.HookDir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + h.ac.logger.Error(fmt.Sprintf("Issue reading %v, with error: %v", path, err)) + return nil + } + if d.IsDir() { + return nil + } + if filepath.Ext(path) != ".lua" { + return nil + } + hookFiles = append(hookFiles, filepath.Join(h.ac.RootPath, path)) + return nil + }) + + h.forSpecificFiles = map[string][]*HookSource{} + for _, filename := range hookFiles { + hookSource := h.readHookFile(filename, h.ac.RootPath, h.ac.logger) + if hookSource.ForSingleFile { + if h.forSpecificFiles[hookSource.ForFile] == nil { + h.forSpecificFiles[hookSource.ForFile] = []*HookSource{} + } + h.forSpecificFiles[hookSource.ForFile] = append(h.forSpecificFiles[hookSource.ForFile], hookSource) + } else { + h.collection = append(h.collection, hookSource) + } + } +} + +func (h *Hooks) readHookFile(filename string, basepath string, logger Logger) *HookSource { + lState := lua.NewState() + luaAlvu.Preload(lState) + luajson.Preload(lState) + yamlLib.Preload(lState) + stringsLib.Preload(lState) + lState.PreloadModule("http", ghttp.NewHttpModule(&http.Client{}).Loader) + if err := lState.DoFile(filename); err != nil { + logger.Error(fmt.Sprintf("Failed to execute hook: %v, with error: %v\n", filename, err)) + panic("") + } + if basepath == "." { + lState.SetGlobal("workingdir", lua.LString("")) + } else { + lState.SetGlobal("workingdir", lua.LString(basepath)) + } + forFile := lState.GetGlobal("ForFile") + forFileValue := forFile.String() + return &HookSource{ + filename: filename, + luaState: lState, + ForSingleFile: forFileValue != "nil", + ForFile: forFileValue, + } +} + +func (h *Hooks) ProcessFile(file transformers.TransformedFile) (hookedFile HookedFile) { + hookedFile.TransformedFile = file + fileData, _ := os.ReadFile(file.TransformedFile) + + hookInput := struct { + Name string `json:"name"` + SourcePath string `json:"source_path"` + // DestPath string `json:"dest_path"` + // Meta map[string]interface{} `json:"meta"` + WriteableContent string `json:"content"` + // HTMLContent string `json:"html"` + }{ + Name: strings.TrimPrefix(file.SourcePath, filepath.Join(h.ac.RootPath, "pages")), + SourcePath: file.SourcePath, + WriteableContent: string(fileData), + } + + hookJsonInput, _ := json.Marshal(hookInput) + + localCollection := []*HookSource{} + + filePathSplits := strings.Split(file.SourcePath, string(filepath.Separator)) + nonRootPath := filepath.Join(filePathSplits[1:]...) + + if len(h.forSpecificFiles[nonRootPath]) > 0 { + localCollection = append(localCollection, h.forSpecificFiles[nonRootPath]...) + } + localCollection = append(localCollection, h.collection...) + + sort.Slice(localCollection, func(i, j int) bool { + return strings.Compare(localCollection[i].filename, localCollection[j].filename) == -1 + }) + + for i := range localCollection { + hook := localCollection[i] + hookFunc := hook.luaState.GetGlobal("Writer") + + if hookFunc == lua.LNil { + continue + } + + if err := hook.luaState.CallByParam(lua.P{ + Fn: hookFunc, + NRet: 1, + Protect: true, + }, lua.LString(hookJsonInput)); err != nil { + h.ac.logger.Error(fmt.Sprintf("Failed to execute %v's Writer on %v, with err: %v", hook.filename, file.SourcePath, err)) + panic("") + } + + ret := hook.luaState.Get(-1) + + var fromPlug map[string]interface{} + + err := json.Unmarshal([]byte(ret.String()), &fromPlug) + if err != nil { + h.ac.logger.Error(fmt.Sprintf("Invalid return value in hook %v", hook.filename)) + return + } + + if fromPlug["content"] != nil { + stringVal := fmt.Sprintf("%s", fromPlug["content"]) + hookedFile.content = []byte(stringVal) + } + + // if fromPlug["name"] != nil { + // hookedFile.content = []byte(fmt.Sprintf("%v", fromPlug["name"])) + // } + + if fromPlug["data"] != nil { + hookedFile.data = mergeMapWithCheck(hookedFile.data, fromPlug["data"]) + } + + if fromPlug["extras"] != nil { + hookedFile.extras = mergeMapWithCheck(hookedFile.extras, fromPlug["data"]) + } + + hook.luaState.Pop(1) + } + return +} + +func readHookDirError(err error, directory string, logger Logger) { + if err == nil { + return + } + logger.Error( + fmt.Sprintf("Failed to read the hooks dir: %v, with error: %v\n", directory, err), + ) +} + +func mergeMapWithCheck(maps ...any) (source map[string]interface{}) { + source = map[string]interface{}{} + for _, toCheck := range maps { + if pairs, ok := toCheck.(map[string]interface{}); ok { + for k, v := range pairs { + source[k] = v + } + } + } + return source +} diff --git a/pkg/alvu/logger.go b/pkg/alvu/logger.go index 9da890e..082bed4 100644 --- a/pkg/alvu/logger.go +++ b/pkg/alvu/logger.go @@ -27,3 +27,9 @@ func (l *Logger) Warning(msg string) { cs.Gray(l.logPrefix).Reset(" ").Yellow(msg) fmt.Println(cs.String()) } + +func (l *Logger) Error(msg string) { + cs := color.ColorString{} + cs.Gray(l.logPrefix).Reset(" ").Red(msg) + fmt.Println(cs.String()) +} From d34c01220e9806280467c8ec53bce0aecf82ddcc Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Mon, 22 Apr 2024 22:46:12 +0530 Subject: [PATCH 06/22] refactor: add the final hooks --- pkg/alvu/alvu.go | 11 +++++++++-- pkg/alvu/hooks.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index 211ca2e..ef6774e 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -48,7 +48,8 @@ type AlvuConfig struct { Transformers map[string][]transformers.Transfomer // Internals - logger Logger + logger Logger + hookHandler *Hooks } func (ac *AlvuConfig) Run() error { @@ -59,6 +60,7 @@ func (ac *AlvuConfig) Run() error { hooksHandler := Hooks{ ac: *ac, } + ac.hookHandler = &hooksHandler ac.Transformers = map[string][]transformers.Transfomer{ ".html": { @@ -90,7 +92,10 @@ func (ac *AlvuConfig) Run() error { var processedFiles []HookedFile - hooksHandler.Load() + ac.hookHandler.Load() + + ac.hookHandler.runLifeCycleHooks("OnStart") + for _, tf := range normalizedFiles { processedFiles = append(processedFiles, hooksHandler.ProcessFile(tf)) } @@ -194,6 +199,8 @@ func (ac *AlvuConfig) FlushFiles(files []HookedFile) error { ac.logger.Info("Output in: " + ac.OutDir) ac.logger.Success("Done") + ac.hookHandler.runLifeCycleHooks("OnFinish") + return nil } diff --git a/pkg/alvu/hooks.go b/pkg/alvu/hooks.go index 4bbb30b..8a0bedd 100644 --- a/pkg/alvu/hooks.go +++ b/pkg/alvu/hooks.go @@ -87,6 +87,43 @@ func (h *Hooks) Load() { } } +func (h *Hooks) runLifeCycleHooks(hookType string) error { + keys := make([]string, 0, len(h.forSpecificFiles)) + for k := range h.forSpecificFiles { + keys = append(keys, k) + } + + localCollection := []*HookSource{} + localCollection = append(localCollection, h.collection...) + + for _, v := range keys { + localCollection = append(localCollection, h.forSpecificFiles[v]...) + } + + sort.Slice(localCollection, func(i, j int) bool { + return strings.Compare(localCollection[i].filename, localCollection[j].filename) == -1 + }) + + for i := range localCollection { + s := localCollection[i] + hookFunc := s.luaState.GetGlobal(hookType) + + if hookFunc == lua.LNil { + continue + } + + if err := s.luaState.CallByParam(lua.P{ + Fn: hookFunc, + NRet: 0, + Protect: true, + }); err != nil { + return err + } + } + + return nil +} + func (h *Hooks) readHookFile(filename string, basepath string, logger Logger) *HookSource { lState := lua.NewState() luaAlvu.Preload(lState) From 37cc23ab81b0dd46df8a853dfffb07ea62f89342 Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Fri, 26 Apr 2024 00:41:14 +0530 Subject: [PATCH 07/22] chore: add debug logger --- pkg/alvu/logger.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/alvu/logger.go b/pkg/alvu/logger.go index 082bed4..c075f23 100644 --- a/pkg/alvu/logger.go +++ b/pkg/alvu/logger.go @@ -4,12 +4,23 @@ import ( "fmt" "github.com/barelyhuman/go/color" + "github.com/barelyhuman/go/env" ) type Logger struct { logPrefix string } +func (l *Logger) Debug(msg string) { + env := env.Get("DEBUG", "false") + if env == "false" { + return + } + cs := color.ColorString{} + cs.Gray(l.logPrefix).Reset(" ").Green("✔").Reset(" ").Green(msg) + fmt.Println(cs.String()) +} + func (l *Logger) Success(msg string) { cs := color.ColorString{} cs.Gray(l.logPrefix).Reset(" ").Green("✔").Reset(" ").Green(msg) From 8a9b9292224bf036db103a4d28cf9f9eb5223612 Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Fri, 26 Apr 2024 00:42:01 +0530 Subject: [PATCH 08/22] feat: simplify transformations --- pkg/alvu/alvu.go | 53 +++++++++++++++++++++++++++++-- pkg/alvu/hooks.go | 15 +++++---- transformers/html/html.go | 22 +++---------- transformers/markdown/markdown.go | 40 ++++++----------------- transformers/transformers.go | 2 +- 5 files changed, 75 insertions(+), 57 deletions(-) diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index ef6774e..bea49b9 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -80,6 +80,8 @@ func (ac *AlvuConfig) Run() error { return err } + ac.logger.Debug(fmt.Sprintf("filesToProcess: %v", filesToProcess)) + publicFiles, err := ac.ReadDir(filepath.Join(ac.RootPath, "public")) if err != nil { return err @@ -140,6 +142,22 @@ func (ac *AlvuConfig) HandlePublicFiles(files []string) (err error) { return } +func (ac *AlvuConfig) createTransformedFile(filePath string, content string) (tranformedFile transformers.TransformedFile, err error) { + fileExt := filepath.Ext(filePath) + fileWriter, err := os.CreateTemp("", "alvu-") + if err != nil { + return + } + _, err = fileWriter.WriteString(content) + if err != nil { + return + } + tranformedFile.TransformedFile = fileWriter.Name() + tranformedFile.SourcePath = filePath + tranformedFile.Extension = fileExt + return +} + func (ac *AlvuConfig) FlushFiles(files []HookedFile) error { if err := os.MkdirAll(ac.OutDir, os.ModePerm); err != nil { return err @@ -171,12 +189,31 @@ func (ac *AlvuConfig) FlushFiles(files []HookedFile) error { } defer destWriter.Close() + if len(hookedFile.transform) > 1 { + for _, t := range ac.Transformers[hookedFile.transform] { + afterTransform, err := t.TransformContent(hookedFile.content) + if err != nil { + return err + } + hookedFile.content = afterTransform + } + } + replaced, _ := ac.injectInSlot( ac.ReadLayout(), string(hookedFile.content), ) template := templateHTML.New("temporaryTemplate") + template = template.Funcs(templateHTML.FuncMap{ + "transform": func(extension string, content string) templateHTML.HTML { + var transformed []byte = []byte(content) + for _, t := range ac.Transformers[extension] { + transformed, _ = t.TransformContent(transformed) + } + return templateHTML.HTML(transformed) + }, + }) template, err = template.Parse(replaced) if err != nil { ac.logger.Error(fmt.Sprintf("Failed to write to dist file with error: %v", err)) @@ -214,13 +251,25 @@ func runTransfomers(filesToProcess []string, ac *AlvuConfig) ([]transformers.Tra continue } + originalContent, err := os.ReadFile(fileToNormalize) + if err != nil { + return nil, fmt.Errorf("failed to read file %v with error %v", fileToNormalize, err) + } + for _, transformer := range ac.Transformers[extension] { - transformedFile, err := transformer.Transform(fileToNormalize) + nextContent, err := transformer.TransformContent(originalContent) if err != nil { return nil, fmt.Errorf("failed to transform file: %v, with error: %v", fileToNormalize, err) } - normalizedFiles = append(normalizedFiles, transformedFile) + originalContent = nextContent } + + transformedFile, err := ac.createTransformedFile(fileToNormalize, string(originalContent)) + if err != nil { + return nil, fmt.Errorf("failed to transform file: %v, with error: %v", fileToNormalize, err) + } + + normalizedFiles = append(normalizedFiles, transformedFile) } return normalizedFiles, nil } diff --git a/pkg/alvu/hooks.go b/pkg/alvu/hooks.go index 8a0bedd..641696e 100644 --- a/pkg/alvu/hooks.go +++ b/pkg/alvu/hooks.go @@ -35,9 +35,10 @@ type Hooks struct { type HookedFile struct { transformers.TransformedFile - content []byte - data map[string]interface{} - extras map[string]interface{} + content []byte + transform string + data map[string]interface{} + extras map[string]interface{} } func (h *Hooks) Load() { @@ -162,7 +163,7 @@ func (h *Hooks) ProcessFile(file transformers.TransformedFile) (hookedFile Hooke WriteableContent string `json:"content"` // HTMLContent string `json:"html"` }{ - Name: strings.TrimPrefix(file.SourcePath, filepath.Join(h.ac.RootPath, "pages")), + Name: filepath.Clean(strings.TrimPrefix(strings.TrimPrefix(file.SourcePath, filepath.Join(h.ac.RootPath, "pages")), "/")), SourcePath: file.SourcePath, WriteableContent: string(fileData), } @@ -215,9 +216,9 @@ func (h *Hooks) ProcessFile(file transformers.TransformedFile) (hookedFile Hooke hookedFile.content = []byte(stringVal) } - // if fromPlug["name"] != nil { - // hookedFile.content = []byte(fmt.Sprintf("%v", fromPlug["name"])) - // } + if fromPlug["transform"] != nil { + hookedFile.transform = fmt.Sprintf("%v", fromPlug["transform"]) + } if fromPlug["data"] != nil { hookedFile.data = mergeMapWithCheck(hookedFile.data, fromPlug["data"]) diff --git a/transformers/html/html.go b/transformers/html/html.go index 635b250..1465774 100644 --- a/transformers/html/html.go +++ b/transformers/html/html.go @@ -1,22 +1,10 @@ package html -import ( - "path/filepath" +type HTMLTransformer struct{} - "github.com/barelyhuman/alvu/transformers" -) - -type HTMLTransformer struct { -} - -func (mt *HTMLTransformer) Transform(filePath string) (transformedFile transformers.TransformedFile, err error) { - return transformers.TransformedFile{ - SourcePath: filePath, - TransformedFile: filePath, - Extension: filepath.Ext(filePath), - }, nil +func (mt *HTMLTransformer) TransformContent(input []byte) (result []byte, err error) { + result = input + return } -func (mt *HTMLTransformer) Init() { - -} +func (mt *HTMLTransformer) Init() {} diff --git a/transformers/markdown/markdown.go b/transformers/markdown/markdown.go index 4143dbc..f6b73b2 100644 --- a/transformers/markdown/markdown.go +++ b/transformers/markdown/markdown.go @@ -1,11 +1,8 @@ package markdown import ( - "os" - "path/filepath" - "strings" + "bytes" - "github.com/barelyhuman/alvu/transformers" "github.com/yuin/goldmark" highlighting "github.com/yuin/goldmark-highlighting" "github.com/yuin/goldmark/extension" @@ -21,41 +18,24 @@ type MarkdownTransformer struct { HighlightingTheme string } -func (mt *MarkdownTransformer) Transform(filePath string) (transformedFile transformers.TransformedFile, err error) { - if mt.processor == nil { - mt.Init() - } +func (mt *MarkdownTransformer) TransformContent(input []byte) (result []byte, err error) { + mt.EnsureProcessor() - fileBytes, err := os.ReadFile(filePath) + var buffer bytes.Buffer + err = mt.processor.Convert(input, &buffer) if err != nil { return } - tmpDir := os.TempDir() - filename := filepath.Base(filePath) - fileExt := filepath.Ext(filename) - filename = strings.TrimSuffix(filename, fileExt) - filename += ".html" - tmpFile := filepath.Join(tmpDir, filename) - - fileWriter, err := os.Create(tmpFile) - if err != nil { - return - } + result = buffer.Bytes() + return +} - err = mt.processor.Convert(fileBytes, fileWriter) - if err != nil { +func (mt *MarkdownTransformer) EnsureProcessor() { + if mt.processor != nil { return } - return transformers.TransformedFile{ - SourcePath: filePath, - TransformedFile: tmpFile, - Extension: fileExt, - }, nil -} - -func (mt *MarkdownTransformer) Init() { rendererOptions := []renderer.Option{ html.WithXHTML(), html.WithUnsafe(), diff --git a/transformers/transformers.go b/transformers/transformers.go index f0b386e..4119553 100644 --- a/transformers/transformers.go +++ b/transformers/transformers.go @@ -7,5 +7,5 @@ type TransformedFile struct { } type Transfomer interface { - Transform(filePath string) (TransformedFile, error) + TransformContent(data []byte) ([]byte, error) } From ce1457eb951f63963791f9b197d7b47e2984160e Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Fri, 26 Apr 2024 02:23:55 +0530 Subject: [PATCH 09/22] fix: dynamic paths and other nit-picks --- Makefile | 3 +-- main.go | 2 +- pkg/alvu/alvu.go | 58 ++++++++++++++++++++++++++++++++-------------- pkg/alvu/hooks.go | 29 ++++++++++++++--------- pkg/alvu/logger.go | 2 +- 5 files changed, 62 insertions(+), 32 deletions(-) diff --git a/Makefile b/Makefile index dbf92fa..3112c61 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,3 @@ - .PHONY.: all all: clean build @@ -16,7 +15,7 @@ docs: build ./alvu --path="docs" --baseurl="/alvu/" --highlight --hard-wrap=false docs_dev: build - ./alvu --highlight --hard-wrap=false --serve --path='./docs' + DEBUG=true ./alvu --highlight --hard-wrap=false --serve --path='./docs' pages: docs rm -rf alvu diff --git a/main.go b/main.go index 6df31e0..de842a7 100644 --- a/main.go +++ b/main.go @@ -65,7 +65,7 @@ func main() { &cli.StringFlag{ Name: "port", Usage: "port to use for serving the application", - Value: ":3000", + Value: "3000", Aliases: []string{"p"}, }, }, diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index bea49b9..cadeb61 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -3,6 +3,7 @@ package alvu import ( "fmt" "io" + "net/http" "os" "path/filepath" "strings" @@ -103,7 +104,12 @@ func (ac *AlvuConfig) Run() error { } ac.HandlePublicFiles(publicFiles) - return ac.FlushFiles(processedFiles) + err = ac.FlushFiles(processedFiles) + if err != nil { + return err + } + + return ac.StartServer() } func (ac *AlvuConfig) ReadLayout() string { @@ -128,7 +134,7 @@ func (ac *AlvuConfig) HandlePublicFiles(files []string) (err error) { file := v go func() { destFile := filepath.Clean(file) - destFile = strings.TrimPrefix(destFile, "public") + destFile = strings.TrimPrefix(destFile, filepath.Join(ac.RootPath, "public")) destFile = filepath.Join(ac.OutDir, destFile) os.MkdirAll(filepath.Dir(destFile), os.ModePerm) @@ -148,6 +154,8 @@ func (ac *AlvuConfig) createTransformedFile(filePath string, content string) (tr if err != nil { return } + defer fileWriter.Close() + _, err = fileWriter.WriteString(content) if err != nil { return @@ -170,7 +178,7 @@ func (ac *AlvuConfig) FlushFiles(files []HookedFile) error { for i := range files { hookedFile := files[i] originalDir, baseFile := filepath.Split(hookedFile.SourcePath) - newDir := strings.TrimPrefix(originalDir, "pages") + newDir := strings.TrimPrefix(originalDir, filepath.Join(ac.RootPath, "pages")) fileWithNewExtension := strings.TrimSuffix(baseFile, hookedFile.Extension) + ".html" destFile := filepath.Join( ac.OutDir, @@ -178,6 +186,8 @@ func (ac *AlvuConfig) FlushFiles(files []HookedFile) error { fileWithNewExtension, ) + ac.logger.Debug(fmt.Sprintf("originalFile:%v, desFile: %v", hookedFile.SourcePath, destFile)) + err := os.MkdirAll(filepath.Dir(destFile), os.ModePerm) if err != nil { return err @@ -199,11 +209,15 @@ func (ac *AlvuConfig) FlushFiles(files []HookedFile) error { } } - replaced, _ := ac.injectInSlot( + replaced, err := ac.injectInSlot( ac.ReadLayout(), string(hookedFile.content), ) + if err != nil { + return err + } + template := templateHTML.New("temporaryTemplate") template = template.Funcs(templateHTML.FuncMap{ "transform": func(extension string, content string) templateHTML.HTML { @@ -289,6 +303,29 @@ func (ac *AlvuConfig) ReadDir(dir string) (filepaths []string, err error) { return sanitizedCollection, nil } +func (ac *AlvuConfig) injectInSlot(htmlString string, replacement string) (string, error) { + if hasLegacySlot(htmlString) { + return injectInLegacySlot(htmlString, replacement), nil + } + slotStartPos := strings.Index(htmlString, slotStartTag) + slotEndPos := strings.Index(htmlString, slotEndTag) + if slotStartPos == -1 && slotEndPos == -1 { + return htmlString, nil + } + baseString := strings.Replace(htmlString, slotEndTag, "", slotEndPos) + return strings.Replace(baseString, slotStartTag, replacement, slotStartPos), nil +} + +func (ac *AlvuConfig) StartServer() error { + if !ac.Serve { + return nil + } + + http.Handle("/", http.FileServer(http.Dir(ac.OutDir))) + ac.logger.Info(fmt.Sprintf("Starting Server - %v:%v", "http://localhost", ac.PortNumber)) + return http.ListenAndServe(fmt.Sprintf(":%v", ac.PortNumber), nil) +} + func recursiveRead(dir string) (filepaths []string, err error) { dirEntry, err := os.ReadDir( dir, @@ -313,19 +350,6 @@ func recursiveRead(dir string) (filepaths []string, err error) { return } -func (ac *AlvuConfig) injectInSlot(htmlString string, replacement string) (string, error) { - if hasLegacySlot(htmlString) { - return injectInLegacySlot(htmlString, replacement), nil - } - slotStartPos := strings.Index(htmlString, slotStartTag) - slotEndPos := strings.Index(htmlString, slotEndTag) - if slotStartPos == -1 && slotEndPos == -1 { - return htmlString, nil - } - baseString := strings.Replace(htmlString, slotEndTag, "", slotEndPos) - return strings.Replace(baseString, slotStartTag, replacement, slotStartPos), nil -} - func hasLegacySlot(htmlString string) bool { return strings.Contains(htmlString, contentTag) } diff --git a/pkg/alvu/hooks.go b/pkg/alvu/hooks.go index 641696e..09a9db8 100644 --- a/pkg/alvu/hooks.go +++ b/pkg/alvu/hooks.go @@ -42,24 +42,26 @@ type HookedFile struct { } func (h *Hooks) Load() { + hookDir := filepath.Clean(filepath.Join(h.ac.RootPath, h.ac.HookDir)) + h.ac.logger.Debug(fmt.Sprintf("hookDir: %v\n", hookDir)) hookFiles := []string{} - folderInfo, err := os.Stat(h.ac.HookDir) + _, err := os.Stat(hookDir) if err != nil { if os.IsNotExist(err) { return } - readHookDirError(err, h.ac.HookDir, h.ac.logger) + readHookDirError(err, hookDir, h.ac.logger) } - file, err := os.Open(folderInfo.Name()) - readHookDirError(err, h.ac.HookDir, h.ac.logger) + file, err := os.Open(hookDir) + readHookDirError(err, hookDir, h.ac.logger) childs, err := file.Readdirnames(1) - readHookDirError(err, h.ac.HookDir, h.ac.logger) + readHookDirError(err, hookDir, h.ac.logger) if len(childs) == 0 { return } - filepath.WalkDir(h.ac.HookDir, func(path string, d fs.DirEntry, err error) error { + filepath.WalkDir(hookDir, func(path string, d fs.DirEntry, err error) error { if err != nil { h.ac.logger.Error(fmt.Sprintf("Issue reading %v, with error: %v", path, err)) return nil @@ -70,7 +72,7 @@ func (h *Hooks) Load() { if filepath.Ext(path) != ".lua" { return nil } - hookFiles = append(hookFiles, filepath.Join(h.ac.RootPath, path)) + hookFiles = append(hookFiles, filepath.Join(path)) return nil }) @@ -132,15 +134,17 @@ func (h *Hooks) readHookFile(filename string, basepath string, logger Logger) *H yamlLib.Preload(lState) stringsLib.Preload(lState) lState.PreloadModule("http", ghttp.NewHttpModule(&http.Client{}).Loader) - if err := lState.DoFile(filename); err != nil { - logger.Error(fmt.Sprintf("Failed to execute hook: %v, with error: %v\n", filename, err)) - panic("") - } + if basepath == "." { lState.SetGlobal("workingdir", lua.LString("")) } else { lState.SetGlobal("workingdir", lua.LString(basepath)) } + + if err := lState.DoFile(filename); err != nil { + logger.Error(fmt.Sprintf("Failed to execute hook: %v, with error: %v\n", filename, err)) + panic("") + } forFile := lState.GetGlobal("ForFile") forFileValue := forFile.String() return &HookSource{ @@ -153,7 +157,9 @@ func (h *Hooks) readHookFile(filename string, basepath string, logger Logger) *H func (h *Hooks) ProcessFile(file transformers.TransformedFile) (hookedFile HookedFile) { hookedFile.TransformedFile = file + fileData, _ := os.ReadFile(file.TransformedFile) + hookedFile.content = fileData hookInput := struct { Name string `json:"name"` @@ -240,6 +246,7 @@ func readHookDirError(err error, directory string, logger Logger) { logger.Error( fmt.Sprintf("Failed to read the hooks dir: %v, with error: %v\n", directory, err), ) + panic("") } func mergeMapWithCheck(maps ...any) (source map[string]interface{}) { diff --git a/pkg/alvu/logger.go b/pkg/alvu/logger.go index c075f23..6f35d6b 100644 --- a/pkg/alvu/logger.go +++ b/pkg/alvu/logger.go @@ -17,7 +17,7 @@ func (l *Logger) Debug(msg string) { return } cs := color.ColorString{} - cs.Gray(l.logPrefix).Reset(" ").Green("✔").Reset(" ").Green(msg) + cs.Gray(l.logPrefix).Reset(" ").Gray("-").Reset(" ").Gray(msg) fmt.Println(cs.String()) } From 159fe0d12ed52a7e91277348b174a00c239d4abb Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Fri, 26 Apr 2024 02:24:21 +0530 Subject: [PATCH 10/22] fix: formatting --- docs/hooks/00-copy-readme.lua | 19 +++++---- docs/hooks/01-add-navigation.lua | 67 ++++++++++++++++---------------- docs/pages/01-basics.md | 2 +- docs/pages/_layout.html | 4 +- 4 files changed, 47 insertions(+), 45 deletions(-) diff --git a/docs/hooks/00-copy-readme.lua b/docs/hooks/00-copy-readme.lua index dbc53c5..4596386 100644 --- a/docs/hooks/00-copy-readme.lua +++ b/docs/hooks/00-copy-readme.lua @@ -6,13 +6,12 @@ local json = require("json") ForFile = "00-readme.md" function Writer(filedata) - local sourcedata = json.decode(filedata) - if sourcedata.name == "00-readme.html" - then - local f = assert(io.open(wdir.."/../readme.md", "rb")) - local content = f:read("*all") - f:close() - sourcedata.content = content - end - return json.encode(sourcedata) -end \ No newline at end of file + local sourcedata = json.decode(filedata) + if sourcedata.name == "00-readme.html" then + local f = assert(io.open(wdir .. "/../readme.md", "rb")) + local content = f:read("*all") + f:close() + sourcedata.content = content + end + return json.encode(sourcedata) +end diff --git a/docs/hooks/01-add-navigation.lua b/docs/hooks/01-add-navigation.lua index 1c1bfc0..5be4761 100644 --- a/docs/hooks/01-add-navigation.lua +++ b/docs/hooks/01-add-navigation.lua @@ -1,5 +1,6 @@ ---@diagnostic disable-next-line: undefined-global local wdir = workingdir + package.path = package.path .. ";" .. wdir .. "/lib/?.lua" local json = require("json") @@ -7,33 +8,34 @@ local alvu = require("alvu") local utils = require(wdir .. ".lib.utils") function Writer(filedata) - local pagesPath = wdir .. "/pages" - local index = {} - local files = alvu.files(pagesPath) - - for fileIndex = 1, #files do - local file_name = files[fileIndex] - if not (file_name == "_layout.html" or file_name == "index.md" or utils.starts_with(file_name,"concepts/")) - then - local name = string.gsub(file_name, ".md", "") - name = string.gsub(name, ".html", "") - local title, _ = utils.normalize(name):lower() - - table.insert(index, { - name = title, - slug = name - }) - end - end - - table.insert(index, 1, { - name = "..", - slug = "index" - }) - - local source_data = json.decode(filedata) - - local template = [[ + local pagesPath = wdir .. "/pages" + local index = {} + local files = alvu.files(pagesPath) + + for fileIndex = 1, #files do + local file_name = files[fileIndex] + if + not (file_name == "_layout.html" or file_name == "index.md" or utils.starts_with(file_name, "concepts/")) + then + local name = string.gsub(file_name, ".md", "") + name = string.gsub(name, ".html", "") + local title, _ = utils.normalize(name):lower() + + table.insert(index, { + name = title, + slug = name, + }) + end + end + + table.insert(index, 1, { + name = "..", + slug = "index", + }) + + local source_data = json.decode(filedata) + + local template = [[
-
]] - source_data.content = template .. "\n" .. source_data.content .. "
" - source_data.data = { - index = index - } + source_data.content = template .. "\n" .. source_data.content + source_data.data = { + index = index, + } - return json.encode(source_data) + return json.encode(source_data) end diff --git a/docs/pages/01-basics.md b/docs/pages/01-basics.md index 53c5f8c..19cae64 100644 --- a/docs/pages/01-basics.md +++ b/docs/pages/01-basics.md @@ -47,7 +47,7 @@ can be defined as shown below - { { .Content } } + ``` diff --git a/docs/pages/_layout.html b/docs/pages/_layout.html index 7545bfd..3460ec8 100644 --- a/docs/pages/_layout.html +++ b/docs/pages/_layout.html @@ -15,7 +15,9 @@ - {{.Content}} +
+ +
From 9771756441b7fa03b614ac42e61ec91de3da80fd Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Fri, 26 Apr 2024 02:38:33 +0530 Subject: [PATCH 11/22] feat: rudrimentary baseurl replacement --- docs/pages/index.md | 2 +- pkg/alvu/alvu.go | 1 + transformers/markdown/markdown.go | 53 +++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/docs/pages/index.md b/docs/pages/index.md index ff7ca63..ec4c5a0 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -16,4 +16,4 @@ rather tight flow made me think this'd work well. As always, a tiny little tool built for me and hopefully someday someone else might like it. -Well, let's head to [the basics →]({{.Meta.BaseURL}}01-basics) +Well, let's head to [the basics →](/01-basics) diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index cadeb61..df5e4e6 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -72,6 +72,7 @@ func (ac *AlvuConfig) Run() error { EnableHardWrap: ac.EnableHardWrap, EnableHighlighting: ac.EnableHighlighting, HighlightingTheme: ac.HighlightingTheme, + BaseURL: ac.BaseURL, }, }, } diff --git a/transformers/markdown/markdown.go b/transformers/markdown/markdown.go index f6b73b2..75f8730 100644 --- a/transformers/markdown/markdown.go +++ b/transformers/markdown/markdown.go @@ -2,13 +2,18 @@ package markdown import ( "bytes" + "net/url" + "strings" "github.com/yuin/goldmark" highlighting "github.com/yuin/goldmark-highlighting" + "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/renderer" "github.com/yuin/goldmark/renderer/html" + "github.com/yuin/goldmark/text" + "github.com/yuin/goldmark/util" ) type MarkdownTransformer struct { @@ -16,6 +21,7 @@ type MarkdownTransformer struct { EnableHardWrap bool EnableHighlighting bool HighlightingTheme string + BaseURL string } func (mt *MarkdownTransformer) TransformContent(input []byte) (result []byte, err error) { @@ -44,9 +50,15 @@ func (mt *MarkdownTransformer) EnsureProcessor() { if mt.EnableHardWrap { rendererOptions = append(rendererOptions, html.WithHardWraps()) } + + linkRewriter := &relativeLinkRewriter{ + baseURL: mt.BaseURL, + } + gmPlugins := []goldmark.Option{ goldmark.WithExtensions(extension.GFM, extension.Footnote), goldmark.WithParserOptions( + parser.WithASTTransformers(util.Prioritized(linkRewriter, 100)), parser.WithAutoHeadingID(), ), goldmark.WithRendererOptions( @@ -64,3 +76,44 @@ func (mt *MarkdownTransformer) EnsureProcessor() { mt.processor = goldmark.New(gmPlugins...) } + +type relativeLinkRewriter struct { + baseURL string +} + +func (rlr *relativeLinkRewriter) Transform(doc *ast.Document, reader text.Reader, pctx parser.Context) { + ast.Walk(doc, func(node ast.Node, enter bool) (ast.WalkStatus, error) { + if !enter { + return ast.WalkContinue, nil + } + + link, ok := node.(*ast.Link) + if !ok { + return ast.WalkContinue, nil + } + + validURL, _ := url.Parse(string(link.Destination)) + + if validURL.Scheme == "http" || validURL.Scheme == "https" || validURL.Scheme == "mailto" { + return ast.WalkContinue, nil + } + + // from root + if strings.HasPrefix(validURL.Path, "/") { + newDestination, _ := url.JoinPath( + rlr.baseURL, + validURL.Path, + ) + link.Destination = []byte(newDestination) + } + + // from current file + // TODO: add handling for relative + // path and then prepend the baseURL + if strings.HasPrefix(validURL.Path, "./") { + + } + + return ast.WalkSkipChildren, nil + }) +} From a81f68a2164a7a13334d3c7e83a2cfbdcc7b6cad Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Fri, 26 Apr 2024 10:18:36 +0530 Subject: [PATCH 12/22] fix: add legacy compat for links --- transformers/markdown/markdown.go | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/transformers/markdown/markdown.go b/transformers/markdown/markdown.go index 75f8730..01c0446 100644 --- a/transformers/markdown/markdown.go +++ b/transformers/markdown/markdown.go @@ -2,9 +2,11 @@ package markdown import ( "bytes" + "fmt" "net/url" "strings" + "github.com/barelyhuman/go/color" "github.com/yuin/goldmark" highlighting "github.com/yuin/goldmark-highlighting" "github.com/yuin/goldmark/ast" @@ -98,8 +100,15 @@ func (rlr *relativeLinkRewriter) Transform(doc *ast.Document, reader text.Reader return ast.WalkContinue, nil } - // from root - if strings.HasPrefix(validURL.Path, "/") { + if strings.HasPrefix(validURL.Path, "{{.Meta.BaseURL}}") { + newDestination, _ := url.JoinPath( + rlr.baseURL, + strings.TrimPrefix(validURL.Path, "{{.Meta.BaseURL}}"), + ) + link.Destination = []byte(newDestination) + printMetaLinkWarning() + } else if strings.HasPrefix(validURL.Path, "/") { + // from root newDestination, _ := url.JoinPath( rlr.baseURL, validURL.Path, @@ -107,13 +116,14 @@ func (rlr *relativeLinkRewriter) Transform(doc *ast.Document, reader text.Reader link.Destination = []byte(newDestination) } - // from current file - // TODO: add handling for relative - // path and then prepend the baseURL - if strings.HasPrefix(validURL.Path, "./") { - - } - return ast.WalkSkipChildren, nil }) } + +// TODO: remove in v0.3 +func printMetaLinkWarning() { + warning := "{{.Meta.BaseURL}} is no more needed in markdown files, links will be rewritten automatically.\n Use root first links, eg: pages/docs/some-topic.md would be linked as /docs/some-topic" + cs := color.ColorString{} + cs.Reset(" ").Yellow(warning) + fmt.Println(cs.String()) +} From de61584bacc30c9cb055249620e6b0e0006660e0 Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Fri, 26 Apr 2024 10:21:02 +0530 Subject: [PATCH 13/22] feat(server): add a basic server Pending addition of websocket for live reload --- pkg/alvu/alvu.go | 103 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index df5e4e6..b62d470 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -322,9 +322,108 @@ func (ac *AlvuConfig) StartServer() error { return nil } - http.Handle("/", http.FileServer(http.Dir(ac.OutDir))) + normalizedPort := string(ac.PortNumber) + + if !strings.HasPrefix(normalizedPort, ":") { + normalizedPort = ":" + normalizedPort + } + + http.Handle("/", http.HandlerFunc(ac.ServeHandler)) ac.logger.Info(fmt.Sprintf("Starting Server - %v:%v", "http://localhost", ac.PortNumber)) - return http.ListenAndServe(fmt.Sprintf(":%v", ac.PortNumber), nil) + + err := http.ListenAndServe(normalizedPort, nil) + if strings.Contains(err.Error(), "address already in use") { + ac.logger.Error("port already in use, use another port with the `-port` flag instead") + return err + } + + return nil +} + +func (ac *AlvuConfig) ServeHandler(rw http.ResponseWriter, req *http.Request) { + path := req.URL.Path + + if path == "/" { + path = filepath.Join(ac.OutDir, "index.html") + http.ServeFile(rw, req, path) + return + } + + // check if the requested file already exists + file := filepath.Join(ac.OutDir, path) + info, err := os.Stat(file) + + // if not, check if it's a directory + // and if it's a directory, we look for + // a index.html inside the directory to return instead + if err == nil { + if info.Mode().IsDir() { + file = filepath.Join(ac.OutDir, path, "index.html") + _, err := os.Stat(file) + if err != nil { + ac.notFoundHandler(rw, req) + return + } + } + + http.ServeFile(rw, req, file) + return + } + + // if neither a directory or file was found + // try a secondary case where the file might be missing + // a `.html` extension for cleaner url so append a .html + // to look for the file. + if err != nil { + file := filepath.Join(ac.OutDir, normalizeStaticLookupPath(path)) + _, err := os.Stat(file) + + if err != nil { + ac.notFoundHandler(rw, req) + return + } + + http.ServeFile(rw, req, file) + return + } + + ac.notFoundHandler(rw, req) +} + +func (ac *AlvuConfig) notFoundHandler(w http.ResponseWriter, r *http.Request) { + var notFoundPageExists bool + filePointer, err := os.Stat(filepath.Join(ac.OutDir, "404.html")) + if err != nil { + if os.IsNotExist(err) { + notFoundPageExists = false + } + } + + if filePointer.Size() > 0 { + notFoundPageExists = true + } + + if notFoundPageExists { + compiledNotFoundFile := filepath.Join(ac.OutDir, "404.html") + notFoundFile, err := os.ReadFile(compiledNotFoundFile) + if err != nil { + http.Error(w, "404, Page not found....", http.StatusNotFound) + return + } + w.WriteHeader(http.StatusNotFound) + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Write(notFoundFile) + return + } + + http.Error(w, "404, Page not found....", http.StatusNotFound) +} + +func normalizeStaticLookupPath(path string) string { + if strings.HasSuffix(path, ".html") { + return path + } + return path + ".html" } func recursiveRead(dir string) (filepaths []string, err error) { From e19586f4cbd5f393a73ead9f0689734ad948536b Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Fri, 26 Apr 2024 10:21:36 +0530 Subject: [PATCH 14/22] fix(hooks): overlapping of content fixes a bug where if 2 hooks replaced the content of the same file it'd fail --- pkg/alvu/hooks.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/pkg/alvu/hooks.go b/pkg/alvu/hooks.go index 09a9db8..c300f48 100644 --- a/pkg/alvu/hooks.go +++ b/pkg/alvu/hooks.go @@ -161,6 +161,13 @@ func (h *Hooks) ProcessFile(file transformers.TransformedFile) (hookedFile Hooke fileData, _ := os.ReadFile(file.TransformedFile) hookedFile.content = fileData + fileTargetName := strings.TrimPrefix( + file.SourcePath, + filepath.Join(h.ac.RootPath, "pages"), + ) + fileTargetName = filepath.Clean(strings.TrimPrefix(fileTargetName, "/")) + fileTargetName = strings.Replace(fileTargetName, filepath.Ext(fileTargetName), ".html", 1) + hookInput := struct { Name string `json:"name"` SourcePath string `json:"source_path"` @@ -169,17 +176,14 @@ func (h *Hooks) ProcessFile(file transformers.TransformedFile) (hookedFile Hooke WriteableContent string `json:"content"` // HTMLContent string `json:"html"` }{ - Name: filepath.Clean(strings.TrimPrefix(strings.TrimPrefix(file.SourcePath, filepath.Join(h.ac.RootPath, "pages")), "/")), - SourcePath: file.SourcePath, - WriteableContent: string(fileData), + Name: fileTargetName, + SourcePath: file.SourcePath, } - hookJsonInput, _ := json.Marshal(hookInput) - localCollection := []*HookSource{} - filePathSplits := strings.Split(file.SourcePath, string(filepath.Separator)) - nonRootPath := filepath.Join(filePathSplits[1:]...) + nonRootPath := strings.TrimPrefix(file.SourcePath, filepath.Join(h.ac.RootPath, "pages")) + nonRootPath = strings.TrimPrefix(nonRootPath, "/") if len(h.forSpecificFiles[nonRootPath]) > 0 { localCollection = append(localCollection, h.forSpecificFiles[nonRootPath]...) @@ -194,6 +198,9 @@ func (h *Hooks) ProcessFile(file transformers.TransformedFile) (hookedFile Hooke hook := localCollection[i] hookFunc := hook.luaState.GetGlobal("Writer") + hookInput.WriteableContent = string(hookedFile.content) + hookJsonInput, _ := json.Marshal(hookInput) + if hookFunc == lua.LNil { continue } @@ -224,6 +231,9 @@ func (h *Hooks) ProcessFile(file transformers.TransformedFile) (hookedFile Hooke if fromPlug["transform"] != nil { hookedFile.transform = fmt.Sprintf("%v", fromPlug["transform"]) + } else { + h.ac.logger.Warning("Auto transformation of content returned from the hooks will be removed in v0.3,\n please return a `transform` property from the hooks instead.") + hookedFile.transform = ".md" } if fromPlug["data"] != nil { From 4075a861152600bd89d956ca13e3d6f9ef899a19 Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Tue, 30 Apr 2024 22:14:36 +0530 Subject: [PATCH 15/22] feat: add watcher --- pkg/alvu/alvu.go | 48 ++++++++++++++++++++++----- pkg/alvu/hooks.go | 7 +++- pkg/alvu/watcher.go | 54 +++++++++++++++++++++++++++++++ transformers/markdown/markdown.go | 7 ++++ 4 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 pkg/alvu/watcher.go diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index b62d470..b7ebd7b 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -51,6 +51,13 @@ type AlvuConfig struct { // Internals logger Logger hookHandler *Hooks + watcher *Watcher +} + +func (ac *AlvuConfig) Rebuild(path string) { + ac.logger.Info(fmt.Sprintf("Changed: %v, Recompiling.", path)) + err := ac.Build() + ac.logger.Error(err.Error()) } func (ac *AlvuConfig) Run() error { @@ -58,6 +65,30 @@ func (ac *AlvuConfig) Run() error { logPrefix: "[alvu]", } + if ac.Serve { + ac.watcher = NewWatcher() + ac.watcher.logger = ac.logger + go func(ac *AlvuConfig) { + for path := range ac.watcher.recompile { + ac.logger.Info(fmt.Sprintf("Changed: %v, recompiling...", path)) + ac.Build() + } + }(ac) + } + + err := ac.Build() + if err != nil { + return err + } + + if ac.Serve { + ac.watcher.Start() + } + + return ac.StartServer() +} + +func (ac *AlvuConfig) Build() error { hooksHandler := Hooks{ ac: *ac, } @@ -77,18 +108,24 @@ func (ac *AlvuConfig) Run() error { }, } - filesToProcess, err := ac.ReadDir(filepath.Join(ac.RootPath, "pages")) + pageDir := filepath.Join(ac.RootPath, "pages") + publicDir := filepath.Join(ac.RootPath, "public") + + filesToProcess, err := ac.ReadDir(pageDir) if err != nil { return err } ac.logger.Debug(fmt.Sprintf("filesToProcess: %v", filesToProcess)) - publicFiles, err := ac.ReadDir(filepath.Join(ac.RootPath, "public")) + publicFiles, err := ac.ReadDir(publicDir) if err != nil { return err } + ac.watcher.AddDir(pageDir) + ac.watcher.AddDir(publicDir) + normalizedFiles, err := runTransfomers(filesToProcess, ac) if err != nil { return err @@ -105,12 +142,7 @@ func (ac *AlvuConfig) Run() error { } ac.HandlePublicFiles(publicFiles) - err = ac.FlushFiles(processedFiles) - if err != nil { - return err - } - - return ac.StartServer() + return ac.FlushFiles(processedFiles) } func (ac *AlvuConfig) ReadLayout() string { diff --git a/pkg/alvu/hooks.go b/pkg/alvu/hooks.go index c300f48..ea1f1c3 100644 --- a/pkg/alvu/hooks.go +++ b/pkg/alvu/hooks.go @@ -31,6 +31,8 @@ type Hooks struct { ac AlvuConfig collection []*HookSource forSpecificFiles map[string][]*HookSource + + _legacyTransformLogSent bool } type HookedFile struct { @@ -232,7 +234,10 @@ func (h *Hooks) ProcessFile(file transformers.TransformedFile) (hookedFile Hooke if fromPlug["transform"] != nil { hookedFile.transform = fmt.Sprintf("%v", fromPlug["transform"]) } else { - h.ac.logger.Warning("Auto transformation of content returned from the hooks will be removed in v0.3,\n please return a `transform` property from the hooks instead.") + if !h._legacyTransformLogSent { + h.ac.logger.Warning("Auto transformation of content returned from the hooks will be removed in v0.3,\n please return a `transform` property from the hooks instead.") + h._legacyTransformLogSent = true + } hookedFile.transform = ".md" } diff --git a/pkg/alvu/watcher.go b/pkg/alvu/watcher.go new file mode 100644 index 0000000..fe5cb0e --- /dev/null +++ b/pkg/alvu/watcher.go @@ -0,0 +1,54 @@ +package alvu + +import ( + "fmt" + "os" + + "github.com/barelyhuman/go/poller" +) + +type Watcher struct { + poller *poller.Poller + logger Logger + recompile chan string +} + +type HookFn func(path string) + +func NewWatcher() *Watcher { + return &Watcher{ + poller: poller.NewPollWatcher(2000), + recompile: make(chan string, 1), + } +} + +func (p *Watcher) AddDir(path string) { + p.poller.Add(path) +} + +func (p *Watcher) Start() { + go p.poller.Start() + go func() { + for { + select { + case evt := <-p.poller.Events: + _, err := os.Stat(evt.Path) + + p.logger.Debug(fmt.Sprintf("Change Event: %v", evt)) + + // Do nothing if the file doesn't exit, just continue + if err != nil { + if os.IsNotExist(err) { + continue + } + p.logger.Error(err.Error()) + } + + p.recompile <- evt.Path + continue + case err := <-p.poller.Errors: + p.logger.Error(err.Error()) + } + } + }() +} diff --git a/transformers/markdown/markdown.go b/transformers/markdown/markdown.go index 01c0446..3ddd4ad 100644 --- a/transformers/markdown/markdown.go +++ b/transformers/markdown/markdown.go @@ -120,8 +120,15 @@ func (rlr *relativeLinkRewriter) Transform(doc *ast.Document, reader text.Reader }) } +// TODO: remove in v0.3 +var _warningPrinted bool = false + // TODO: remove in v0.3 func printMetaLinkWarning() { + if _warningPrinted { + return + } + _warningPrinted = true warning := "{{.Meta.BaseURL}} is no more needed in markdown files, links will be rewritten automatically.\n Use root first links, eg: pages/docs/some-topic.md would be linked as /docs/some-topic" cs := color.ColorString{} cs.Reset(" ").Yellow(warning) From 2963074fce740000ea4220ab1bea1c31488eb720 Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Tue, 30 Apr 2024 22:53:34 +0530 Subject: [PATCH 16/22] fix: meta extraction at transformer --- go.mod | 1 + go.sum | 1 + pkg/alvu/alvu.go | 24 +++++++++++++++++++++--- pkg/alvu/hooks.go | 6 ++++-- transformers/html/html.go | 5 +++++ transformers/markdown/markdown.go | 31 ++++++++++++++++++++++++++++++- transformers/transformers.go | 2 ++ 7 files changed, 64 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 4b22b75..7242a7a 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/yuin/goldmark v1.7.1 github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 github.com/yuin/gopher-lua v1.1.0 + gopkg.in/yaml.v3 v3.0.1 layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf ) diff --git a/go.sum b/go.sum index bd4ff58..7f62078 100644 --- a/go.sum +++ b/go.sum @@ -191,5 +191,6 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf h1:rRz0YsF7VXj9fXRF6yQgFI7DzST+hsI3TeFSGupntu0= layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf/go.mod h1:ivKkcY8Zxw5ba0jldhZCYYQfGdb2K6u9tbYK1AwMIBc= diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index b7ebd7b..6d20ad5 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -299,23 +299,33 @@ func runTransfomers(filesToProcess []string, ac *AlvuConfig) ([]transformers.Tra } originalContent, err := os.ReadFile(fileToNormalize) + mutableContent := originalContent if err != nil { return nil, fmt.Errorf("failed to read file %v with error %v", fileToNormalize, err) } + var meta map[string]interface{} for _, transformer := range ac.Transformers[extension] { - nextContent, err := transformer.TransformContent(originalContent) + nextContent, err := transformer.TransformContent(mutableContent) if err != nil { return nil, fmt.Errorf("failed to transform file: %v, with error: %v", fileToNormalize, err) } - originalContent = nextContent + newMeta, _, _ := transformer.ExtractMeta(originalContent) + if err != nil { + return nil, fmt.Errorf("failed to extract meta from file: %v, with error: %v", fileToNormalize, err) + } + if hasKeys(newMeta) { + meta = newMeta + } + mutableContent = nextContent } - transformedFile, err := ac.createTransformedFile(fileToNormalize, string(originalContent)) + transformedFile, err := ac.createTransformedFile(fileToNormalize, string(mutableContent)) if err != nil { return nil, fmt.Errorf("failed to transform file: %v, with error: %v", fileToNormalize, err) } + transformedFile.Meta = meta normalizedFiles = append(normalizedFiles, transformedFile) } return normalizedFiles, nil @@ -493,3 +503,11 @@ func injectInLegacySlot(htmlString string, replacement string) string { } return strings.Replace(htmlString, contentTag, replacement, contentTagPos) } + +func hasKeys(i map[string]interface{}) bool { + keys := make([]string, 0, len(i)) + for k := range i { + keys = append(keys, k) + } + return len(keys) > 0 +} diff --git a/pkg/alvu/hooks.go b/pkg/alvu/hooks.go index ea1f1c3..7874cde 100644 --- a/pkg/alvu/hooks.go +++ b/pkg/alvu/hooks.go @@ -169,17 +169,19 @@ func (h *Hooks) ProcessFile(file transformers.TransformedFile) (hookedFile Hooke ) fileTargetName = filepath.Clean(strings.TrimPrefix(fileTargetName, "/")) fileTargetName = strings.Replace(fileTargetName, filepath.Ext(fileTargetName), ".html", 1) + fileTargetName = strings.TrimSpace(fileTargetName) hookInput := struct { Name string `json:"name"` SourcePath string `json:"source_path"` // DestPath string `json:"dest_path"` - // Meta map[string]interface{} `json:"meta"` - WriteableContent string `json:"content"` + Meta map[string]interface{} `json:"meta"` + WriteableContent string `json:"content"` // HTMLContent string `json:"html"` }{ Name: fileTargetName, SourcePath: file.SourcePath, + Meta: file.Meta, } localCollection := []*HookSource{} diff --git a/transformers/html/html.go b/transformers/html/html.go index 1465774..64d5391 100644 --- a/transformers/html/html.go +++ b/transformers/html/html.go @@ -7,4 +7,9 @@ func (mt *HTMLTransformer) TransformContent(input []byte) (result []byte, err er return } +func (mt *HTMLTransformer) ExtractMeta(input []byte) (result map[string]interface{}, content []byte, err error) { + result = map[string]interface{}{} + return +} + func (mt *HTMLTransformer) Init() {} diff --git a/transformers/markdown/markdown.go b/transformers/markdown/markdown.go index 3ddd4ad..7abf164 100644 --- a/transformers/markdown/markdown.go +++ b/transformers/markdown/markdown.go @@ -16,6 +16,7 @@ import ( "github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" + "gopkg.in/yaml.v3" ) type MarkdownTransformer struct { @@ -30,7 +31,13 @@ func (mt *MarkdownTransformer) TransformContent(input []byte) (result []byte, er mt.EnsureProcessor() var buffer bytes.Buffer - err = mt.processor.Convert(input, &buffer) + _, content, err := mt.ExtractMeta(input) + + if err != nil { + return + } + + err = mt.processor.Convert(content, &buffer) if err != nil { return } @@ -39,6 +46,28 @@ func (mt *MarkdownTransformer) TransformContent(input []byte) (result []byte, er return } +func (mt *MarkdownTransformer) ExtractMeta(input []byte) (result map[string]interface{}, content []byte, err error) { + result = map[string]interface{}{} + sep := []byte("---") + + content = input + + if !bytes.HasPrefix(input, sep) { + return + } + + metaParts := bytes.SplitN(content, sep, 3) + if len(metaParts) > 2 { + fmt.Printf("metaParts: %v\n", metaParts) + err = yaml.Unmarshal([]byte(metaParts[1]), &result) + if err != nil { + return + } + content = metaParts[2] + } + return +} + func (mt *MarkdownTransformer) EnsureProcessor() { if mt.processor != nil { return diff --git a/transformers/transformers.go b/transformers/transformers.go index 4119553..bbd8638 100644 --- a/transformers/transformers.go +++ b/transformers/transformers.go @@ -4,8 +4,10 @@ type TransformedFile struct { SourcePath string TransformedFile string Extension string + Meta map[string]interface{} } type Transfomer interface { TransformContent(data []byte) ([]byte, error) + ExtractMeta(data []byte) (map[string]interface{}, []byte, error) } From 66deb1d98be3e086e4c41a70e5409f71d6205a1b Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Tue, 30 Apr 2024 23:40:12 +0530 Subject: [PATCH 17/22] feat: basic live reload not stable since the web socket is lost --- go.mod | 1 + go.sum | 1 + pkg/alvu/alvu.go | 85 +++++++++++++++++++++++++++---- pkg/alvu/watcher.go | 2 +- transformers/markdown/markdown.go | 1 - 5 files changed, 78 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 7242a7a..5ff9a83 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/yuin/goldmark v1.7.1 github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 github.com/yuin/gopher-lua v1.1.0 + golang.org/x/net v0.0.0-20200625001655-4c5254603344 gopkg.in/yaml.v3 v3.0.1 layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf ) diff --git a/go.sum b/go.sum index 7f62078..59ced9f 100644 --- a/go.sum +++ b/go.sum @@ -145,6 +145,7 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index 6d20ad5..266a109 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -13,6 +13,7 @@ import ( "github.com/barelyhuman/alvu/transformers" "github.com/barelyhuman/alvu/transformers/markdown" + "golang.org/x/net/websocket" htmlT "github.com/barelyhuman/alvu/transformers/html" ) @@ -52,15 +53,20 @@ type AlvuConfig struct { logger Logger hookHandler *Hooks watcher *Watcher + rebuildChan chan bool } func (ac *AlvuConfig) Rebuild(path string) { ac.logger.Info(fmt.Sprintf("Changed: %v, Recompiling.", path)) err := ac.Build() - ac.logger.Error(err.Error()) + if err != nil { + ac.logger.Error(err.Error()) + } + ac.rebuildChan <- true } func (ac *AlvuConfig) Run() error { + ac.rebuildChan = make(chan bool, 1) ac.logger = Logger{ logPrefix: "[alvu]", } @@ -70,8 +76,7 @@ func (ac *AlvuConfig) Run() error { ac.watcher.logger = ac.logger go func(ac *AlvuConfig) { for path := range ac.watcher.recompile { - ac.logger.Info(fmt.Sprintf("Changed: %v, recompiling...", path)) - ac.Build() + ac.Rebuild(path) } }(ac) } @@ -148,15 +153,26 @@ func (ac *AlvuConfig) Build() error { func (ac *AlvuConfig) ReadLayout() string { layoutFilePath := filepath.Join(ac.RootPath, "pages", "_layout.html") fileInfo, err := os.Stat(layoutFilePath) + defaultLayout := "" + + if ac.Serve { + defaultLayout = injectWebsocketConnection(defaultLayout, ac.PortNumber) + } + if os.IsNotExist(err) { - return "" + return defaultLayout } if fileInfo.IsDir() { - return "" + return defaultLayout } data, _ := os.ReadFile( layoutFilePath, ) + + if ac.Serve { + return injectWebsocketConnection(string(data), ac.PortNumber) + } + return string(data) } @@ -359,21 +375,39 @@ func (ac *AlvuConfig) injectInSlot(htmlString string, replacement string) (strin return strings.Replace(baseString, slotStartTag, replacement, slotStartPos), nil } +func (ac *AlvuConfig) NormalisePort(port string) string { + normalisedPort := port + + if !strings.HasPrefix(normalisedPort, ":") { + normalisedPort = ":" + normalisedPort + } + + return normalisedPort +} + func (ac *AlvuConfig) StartServer() error { if !ac.Serve { return nil } - normalizedPort := string(ac.PortNumber) + wsHandler := websocket.Handler(ac._webSocketHandler) + http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + // Check the request's 'Upgrade' header to see if it's a WebSocket request + if r.Header.Get("Upgrade") != "websocket" { + http.Error(w, "Not a WebSocket handshake request", http.StatusBadRequest) + return + } - if !strings.HasPrefix(normalizedPort, ":") { - normalizedPort = ":" + normalizedPort - } + // Upgrade the HTTP connection to a WebSocket connection + wsHandler.ServeHTTP(w, r) + }) + + normalisedPort := ac.NormalisePort(ac.PortNumber) http.Handle("/", http.HandlerFunc(ac.ServeHandler)) ac.logger.Info(fmt.Sprintf("Starting Server - %v:%v", "http://localhost", ac.PortNumber)) - err := http.ListenAndServe(normalizedPort, nil) + err := http.ListenAndServe(normalisedPort, nil) if strings.Contains(err.Error(), "address already in use") { ac.logger.Error("port already in use, use another port with the `-port` flag instead") return err @@ -382,6 +416,18 @@ func (ac *AlvuConfig) StartServer() error { return nil } +// _webSocketHandler Internal function to setup a listener loop +// for the live reload setup +func (ac *AlvuConfig) _webSocketHandler(ws *websocket.Conn) { + defer ws.Close() + for range ac.rebuildChan { + err := websocket.Message.Send(ws, "reload") + if err != nil { + break + } + } +} + func (ac *AlvuConfig) ServeHandler(rw http.ResponseWriter, req *http.Request) { path := req.URL.Path @@ -504,6 +550,25 @@ func injectInLegacySlot(htmlString string, replacement string) string { return strings.Replace(htmlString, contentTag, replacement, contentTagPos) } +func injectWebsocketConnection(htmlString string, port string) string { + return htmlString + fmt.Sprintf(``, port) +} + func hasKeys(i map[string]interface{}) bool { keys := make([]string, 0, len(i)) for k := range i { diff --git a/pkg/alvu/watcher.go b/pkg/alvu/watcher.go index fe5cb0e..0ad0f89 100644 --- a/pkg/alvu/watcher.go +++ b/pkg/alvu/watcher.go @@ -34,7 +34,7 @@ func (p *Watcher) Start() { case evt := <-p.poller.Events: _, err := os.Stat(evt.Path) - p.logger.Debug(fmt.Sprintf("Change Event: %v", evt)) + p.logger.Debug(fmt.Sprintf("Change Event: %v", evt.Path)) // Do nothing if the file doesn't exit, just continue if err != nil { diff --git a/transformers/markdown/markdown.go b/transformers/markdown/markdown.go index 7abf164..da10257 100644 --- a/transformers/markdown/markdown.go +++ b/transformers/markdown/markdown.go @@ -58,7 +58,6 @@ func (mt *MarkdownTransformer) ExtractMeta(input []byte) (result map[string]inte metaParts := bytes.SplitN(content, sep, 3) if len(metaParts) > 2 { - fmt.Printf("metaParts: %v\n", metaParts) err = yaml.Unmarshal([]byte(metaParts[1]), &result) if err != nil { return From a15cc211ea9020b6a8a36f98e9b1ca1576a4725b Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Tue, 30 Apr 2024 23:47:49 +0530 Subject: [PATCH 18/22] fix: add live-reload for hooks --- pkg/alvu/alvu.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index 266a109..c8771e8 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -115,6 +115,7 @@ func (ac *AlvuConfig) Build() error { pageDir := filepath.Join(ac.RootPath, "pages") publicDir := filepath.Join(ac.RootPath, "public") + hooksDir := filepath.Join(ac.RootPath, ac.HookDir) filesToProcess, err := ac.ReadDir(pageDir) if err != nil { @@ -130,6 +131,7 @@ func (ac *AlvuConfig) Build() error { ac.watcher.AddDir(pageDir) ac.watcher.AddDir(publicDir) + ac.watcher.AddDir(hooksDir) normalizedFiles, err := runTransfomers(filesToProcess, ac) if err != nil { From 3ffb933f9b83b147ed323b8147f67d43c2e949b1 Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Wed, 1 May 2024 03:13:35 +0530 Subject: [PATCH 19/22] fix: stable live reload --- go.mod | 2 +- go.sum | 3 +- pkg/alvu/alvu.go | 96 +++++++++++++++++++++++++++++++++++------------ pkg/alvu/hooks.go | 2 +- 4 files changed, 75 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index 5ff9a83..f6b5011 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/yuin/goldmark v1.7.1 github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 github.com/yuin/gopher-lua v1.1.0 - golang.org/x/net v0.0.0-20200625001655-4c5254603344 + golang.org/x/net v0.17.0 gopkg.in/yaml.v3 v3.0.1 layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf ) diff --git a/go.sum b/go.sum index 59ced9f..6b28e4c 100644 --- a/go.sum +++ b/go.sum @@ -145,8 +145,9 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index c8771e8..6d15e23 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -53,32 +53,46 @@ type AlvuConfig struct { logger Logger hookHandler *Hooks watcher *Watcher - rebuildChan chan bool + + // Internals - Websockets // can be separated + shouldRebuild bool + rebuildLock sync.Mutex + rebuildCond *sync.Cond + connections map[*websocket.Conn]struct{} } func (ac *AlvuConfig) Rebuild(path string) { + ac.rebuildLock.Lock() ac.logger.Info(fmt.Sprintf("Changed: %v, Recompiling.", path)) err := ac.Build() if err != nil { ac.logger.Error(err.Error()) } - ac.rebuildChan <- true + ac.shouldRebuild = true + ac.rebuildLock.Unlock() + ac.rebuildCond.Broadcast() } func (ac *AlvuConfig) Run() error { - ac.rebuildChan = make(chan bool, 1) ac.logger = Logger{ logPrefix: "[alvu]", } + ac.rebuildCond = sync.NewCond(&ac.rebuildLock) + ac.connections = make(map[*websocket.Conn]struct{}) + if ac.Serve { ac.watcher = NewWatcher() ac.watcher.logger = ac.logger + go func(ac *AlvuConfig) { for path := range ac.watcher.recompile { ac.Rebuild(path) } }(ac) + + ac.watcher.Start() + ac.monitorRebuilds() } err := ac.Build() @@ -86,16 +100,31 @@ func (ac *AlvuConfig) Run() error { return err } - if ac.Serve { - ac.watcher.Start() - } - return ac.StartServer() } +func (ac *AlvuConfig) monitorRebuilds() { + go func() { + for { + ac.rebuildCond.L.Lock() + for !ac.shouldRebuild { + ac.rebuildCond.Wait() + } + for conn := range ac.connections { + err := websocket.Message.Send(conn, "reload") + if err != nil { + delete(ac.connections, conn) + } + } + ac.shouldRebuild = false + ac.rebuildCond.L.Unlock() + } + }() +} + func (ac *AlvuConfig) Build() error { hooksHandler := Hooks{ - ac: *ac, + ac: ac, } ac.hookHandler = &hooksHandler @@ -421,10 +450,16 @@ func (ac *AlvuConfig) StartServer() error { // _webSocketHandler Internal function to setup a listener loop // for the live reload setup func (ac *AlvuConfig) _webSocketHandler(ws *websocket.Conn) { + // collect connections + ac.connections[ws] = struct{}{} + defer ws.Close() - for range ac.rebuildChan { - err := websocket.Message.Send(ws, "reload") + // message loop, till connection breaks + for { + var msg string + err := websocket.Message.Receive(ws, &msg) if err != nil { + delete(ac.connections, ws) break } } @@ -554,21 +589,32 @@ func injectInLegacySlot(htmlString string, replacement string) string { func injectWebsocketConnection(htmlString string, port string) string { return htmlString + fmt.Sprintf(``, port) + connect(); + + function connect() { + let socket = new WebSocket("ws://localhost:%v/ws"); + + // Connection opened + socket.addEventListener("open", (event) => { + socket.send("Init"); + }); + + socket.addEventListener("close", (event) => { + socket = null; + setTimeout(() => { + connect(); + }, 5000); + }); + + // Listen for messages + socket.addEventListener("message", (event) => { + if (event.data == "reload") { + socket.close(); + window.location.reload(); + } + }); + } + `, port) } func hasKeys(i map[string]interface{}) bool { diff --git a/pkg/alvu/hooks.go b/pkg/alvu/hooks.go index 7874cde..9b46d7c 100644 --- a/pkg/alvu/hooks.go +++ b/pkg/alvu/hooks.go @@ -28,7 +28,7 @@ type HookSource struct { } type Hooks struct { - ac AlvuConfig + ac *AlvuConfig collection []*HookSource forSpecificFiles map[string][]*HookSource From dbd599c2916a404cb62f29510e70fcdc136e1144 Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Fri, 17 May 2024 01:14:37 +0530 Subject: [PATCH 20/22] feat: add init command and it's actions --- commands/init.go | 159 +++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- main.go | 18 +++++ pkg/alvu/alvu.go | 13 ++-- pkg/alvu/hooks.go | 10 ++- pkg/alvu/logger.go | 16 +++-- 7 files changed, 206 insertions(+), 16 deletions(-) create mode 100644 commands/init.go diff --git a/commands/init.go b/commands/init.go new file mode 100644 index 0000000..a60a1ea --- /dev/null +++ b/commands/init.go @@ -0,0 +1,159 @@ +package commands + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/barelyhuman/alvu/pkg/alvu" + "github.com/barelyhuman/go/color" + "github.com/urfave/cli/v2" +) + +func AlvuInit(c *cli.Context) (err error) { + basePath := c.Args().First() + forceFlag := c.Bool("force") + logger := alvu.NewLogger() + logger.LogPrefix = "[alvu]" + + fileInfo, err := os.Stat(basePath) + + if err == nil { + if fileInfo.IsDir() && !forceFlag { + logger.Error(fmt.Sprintf("Directory: %v , already exists, cannot overwrite, if you wish to force overwrite use the -f flag with the `init` command", basePath)) + os.Exit(1) + } + } + + mustCreateDir(basePath, "public") + mustCreateDir(basePath, "hooks") + mustCreateDir(basePath, "pages") + prepareBaseStyles(basePath) + preparePages(basePath) + + logger.Success( + fmt.Sprintf("Alvu initialized in: %v", basePath), + ) + + runStr := color.ColorString{} + + fmt.Println(runStr.Dim("\n> Run the following to get started").String()) + + commandStr := color.ColorString{} + commandStr.Cyan( + fmt.Sprintf("\n alvu -s -path %v\n", basePath), + ) + fmt.Println(commandStr.String()) + return +} + +func mustCreateDir(root, dir string) { + pathToCreate := filepath.Join(root, dir) + err := os.MkdirAll(pathToCreate, os.ModePerm) + if err != nil { + panic(fmt.Sprintf("Failed to create %v due to error: %v\n", pathToCreate, err)) + } +} + +func prepareBaseStyles(root string) { + fileHandle, err := os.OpenFile(filepath.Join(root, "public", "styles.css"), os.O_CREATE|os.O_RDWR, os.ModePerm) + if err != nil { + fmt.Printf("Failed to open file public/styles.css with error: %v", err) + } + defer fileHandle.Sync() + defer fileHandle.Close() + + fileHandle.WriteString(` +/* Resets */ +html { + box-sizing: border-box; + font-size: 16px; + font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif; +} + +*, *:before, *:after { + box-sizing: inherit; +} + +body, h1, h2, h3, h4, h5, h6, p { + margin: 0; + padding: 0; + font-weight: normal; +} + +img { + max-width: 100%; + height: auto; +} + +/* Styles */ + +:root { + --base: #efefef; + --text: #181819; +} + +@media (prefers-color-scheme: dark) { + :root { + --base: #181819; + --text: #efefef; + } +} + +body { + + background: var(--base); + color: var(--text); + + max-width: 900px; + margin: 0 auto; + padding: 4px; + display: flex; + flex-direction: column; + justify-content: center; + min-height: 100vh; + } + + + ol,ul,p{ + line-height: 1.7; + } + + `) + +} + +func preparePages(root string) { + layoutHandle, _ := os.OpenFile(filepath.Join(root, "pages", "_layout.html"), os.O_CREATE|os.O_RDWR, os.ModePerm) + defer layoutHandle.Sync() + defer layoutHandle.Close() + + rootPageHandle, _ := os.OpenFile(filepath.Join(root, "pages", "index.md"), os.O_CREATE|os.O_RDWR, os.ModePerm) + + defer rootPageHandle.Sync() + defer rootPageHandle.Close() + + layoutHandle.WriteString(` + + + + + Alvu | Minimal Starter + + + + + +`) + + rootPageHandle.WriteString(`# Alvu + +- Scriptable +- Fast +- Tiny + +In whatever order you'd like... + +`) + +} diff --git a/go.mod b/go.mod index f6b5011..fa135a7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/barelyhuman/alvu go 1.18 require ( - github.com/barelyhuman/go v0.2.2 + github.com/barelyhuman/go v0.2.3-0.20240516192751-30a6c804e4e5 github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9 github.com/joho/godotenv v1.5.1 github.com/urfave/cli/v2 v2.27.1 diff --git a/go.sum b/go.sum index 6b28e4c..c7ecdcf 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/barelyhuman/go v0.2.2 h1:Lpk1XrlP40F3II8BibVzViZUOJ1GgDdzXUBb8ENwb0U= -github.com/barelyhuman/go v0.2.2/go.mod h1:hox2iDYZAarjpS7jKQeYIi2F+qMA8KLMtCws++L2sSY= +github.com/barelyhuman/go v0.2.3-0.20240516192751-30a6c804e4e5 h1:AbJ6ZaRkEc6CguQ6rXe0epKmFe/TmwSSFX5N6hsNO+A= +github.com/barelyhuman/go v0.2.3-0.20240516192751-30a6c804e4e5/go.mod h1:hox2iDYZAarjpS7jKQeYIi2F+qMA8KLMtCws++L2sSY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= diff --git a/main.go b/main.go index de842a7..f477a70 100644 --- a/main.go +++ b/main.go @@ -72,6 +72,24 @@ func main() { Version: version, Compiled: time.Now(), HideVersion: false, + Commands: []*cli.Command{ + { + Name: "init", + Description: "Initialise a new alvu Project", + Args: true, + ArgsUsage: "", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "force", + Aliases: []string{"f"}, + Usage: "Force create in the directory even overwriting any files that exist", + }, + }, + Action: func(ctx *cli.Context) error { + return commands.AlvuInit(ctx) + }, + }, + }, } err := app.Run(os.Args) diff --git a/pkg/alvu/alvu.go b/pkg/alvu/alvu.go index 6d15e23..6926661 100644 --- a/pkg/alvu/alvu.go +++ b/pkg/alvu/alvu.go @@ -74,9 +74,8 @@ func (ac *AlvuConfig) Rebuild(path string) { } func (ac *AlvuConfig) Run() error { - ac.logger = Logger{ - logPrefix: "[alvu]", - } + ac.logger = NewLogger() + ac.logger.LogPrefix = "[alvu]" ac.rebuildCond = sync.NewCond(&ac.rebuildLock) ac.connections = make(map[*websocket.Conn]struct{}) @@ -158,9 +157,11 @@ func (ac *AlvuConfig) Build() error { return err } - ac.watcher.AddDir(pageDir) - ac.watcher.AddDir(publicDir) - ac.watcher.AddDir(hooksDir) + if ac.Serve { + ac.watcher.AddDir(pageDir) + ac.watcher.AddDir(publicDir) + ac.watcher.AddDir(hooksDir) + } normalizedFiles, err := runTransfomers(filesToProcess, ac) if err != nil { diff --git a/pkg/alvu/hooks.go b/pkg/alvu/hooks.go index 9b46d7c..60df1f4 100644 --- a/pkg/alvu/hooks.go +++ b/pkg/alvu/hooks.go @@ -3,6 +3,7 @@ package alvu import ( "encoding/json" "fmt" + "io" "io/fs" "net/http" "os" @@ -52,13 +53,20 @@ func (h *Hooks) Load() { if os.IsNotExist(err) { return } + readHookDirError(err, hookDir, h.ac.logger) } file, err := os.Open(hookDir) readHookDirError(err, hookDir, h.ac.logger) childs, err := file.Readdirnames(1) - readHookDirError(err, hookDir, h.ac.logger) + if err != nil { + if err == io.EOF { + return + } + readHookDirError(err, hookDir, h.ac.logger) + } + if len(childs) == 0 { return } diff --git a/pkg/alvu/logger.go b/pkg/alvu/logger.go index 6f35d6b..67511bd 100644 --- a/pkg/alvu/logger.go +++ b/pkg/alvu/logger.go @@ -8,7 +8,11 @@ import ( ) type Logger struct { - logPrefix string + LogPrefix string +} + +func NewLogger() Logger { + return Logger{} } func (l *Logger) Debug(msg string) { @@ -17,30 +21,30 @@ func (l *Logger) Debug(msg string) { return } cs := color.ColorString{} - cs.Gray(l.logPrefix).Reset(" ").Gray("-").Reset(" ").Gray(msg) + cs.Gray(l.LogPrefix).Reset(" ").Gray("-").Reset(" ").Gray(msg) fmt.Println(cs.String()) } func (l *Logger) Success(msg string) { cs := color.ColorString{} - cs.Gray(l.logPrefix).Reset(" ").Green("✔").Reset(" ").Green(msg) + cs.Gray(l.LogPrefix).Reset(" ").Green("✔").Reset(" ").Green(msg) fmt.Println(cs.String()) } func (l *Logger) Info(msg string) { cs := color.ColorString{} - cs.Gray(l.logPrefix).Reset(" ").Cyan("ℹ").Reset(" ").Cyan(msg) + cs.Gray(l.LogPrefix).Reset(" ").Cyan("ℹ").Reset(" ").Cyan(msg) fmt.Println(cs.String()) } func (l *Logger) Warning(msg string) { cs := color.ColorString{} - cs.Gray(l.logPrefix).Reset(" ").Yellow(msg) + cs.Gray(l.LogPrefix).Reset(" ").Yellow(msg) fmt.Println(cs.String()) } func (l *Logger) Error(msg string) { cs := color.ColorString{} - cs.Gray(l.logPrefix).Reset(" ").Red(msg) + cs.Gray(l.LogPrefix).Reset(" ").Red(msg) fmt.Println(cs.String()) } From 0816fd8a930be8d6b01706d07fc6650d382cc7d7 Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Fri, 17 May 2024 01:21:30 +0530 Subject: [PATCH 21/22] feat: basic env impl --- commands/root.go | 9 +++++++++ main.go | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/commands/root.go b/commands/root.go index 40ae0ff..ece0e6a 100644 --- a/commands/root.go +++ b/commands/root.go @@ -1,11 +1,20 @@ package commands import ( + "os" + "github.com/barelyhuman/alvu/pkg/alvu" + "github.com/joho/godotenv" "github.com/urfave/cli/v2" ) func Alvu(c *cli.Context) (err error) { + // Prepare Environment + envFilePath := c.String("env") + if _, err := os.Stat(envFilePath); err == nil { + godotenv.Load(envFilePath) + } + baseConfig := alvu.AlvuConfig{} // Basics diff --git a/main.go b/main.go index f477a70..bea4a29 100644 --- a/main.go +++ b/main.go @@ -62,6 +62,11 @@ func main() { Usage: "Define the poll duration in seconds", Value: 1000, }, + &cli.StringFlag{ + Name: "env", + Usage: "Environment File to consider", + Value: ".env", + }, &cli.StringFlag{ Name: "port", Usage: "port to use for serving the application", From 52dfcc39a4f2f6a32f9648facacb46fc0a4ac249 Mon Sep 17 00:00:00 2001 From: Reaper Gelera Date: Fri, 17 May 2024 22:57:56 +0530 Subject: [PATCH 22/22] chore: v0.2.16-beta.0 --- .commitlog.release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.commitlog.release b/.commitlog.release index c714c07..a898134 100755 --- a/.commitlog.release +++ b/.commitlog.release @@ -1 +1 @@ -v0.2.15 \ No newline at end of file +v0.2.16-beta.0 \ No newline at end of file