Skip to content

Commit

Permalink
Merge pull request #49 from argonui/xml
Browse files Browse the repository at this point in the history
Enable bundle & unbundling of XML
  • Loading branch information
argonui authored Nov 22, 2022
2 parents 60feee4 + 2dd822e commit b753425
Show file tree
Hide file tree
Showing 17 changed files with 1,887 additions and 184 deletions.
2 changes: 1 addition & 1 deletion bundler/bundler.go → bundler/luabundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func Unbundle(rawlua string) (string, error) {
}

// Bundle grabs all dependencies and creates a single luascript
func Bundle(rawlua string, l file.LuaReader) (string, error) {
func Bundle(rawlua string, l file.TextReader) (string, error) {
if IsBundled(rawlua) {
return rawlua, nil
}
Expand Down
File renamed without changes.
110 changes: 110 additions & 0 deletions bundler/xmlbundler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package bundler

import (
"ModCreator/file"
"fmt"
"regexp"
"strings"
)

// BundleXML converts <Include ... >'s into full xml
func BundleXML(rawxml string, xr file.TextReader) (string, error) {
lines := strings.Split(rawxml, "\n")
final := []string{}
inctag := regexp.MustCompile(`(?m)^(.*)<Include src="(.*)"/>`)

for _, v := range lines {
if !inctag.Match([]byte(v)) {
final = append(final, v)
continue
}
subs := inctag.FindSubmatch([]byte(v))
indent := string(subs[1])
name := string(subs[2])

replacement := fmt.Sprintf("%s<!-- include %s -->", indent, name)
final = append(final, replacement)

fname := name
if !strings.HasSuffix(name, ".xml") {
fname = name + ".xml"
}
incXMLRaw, err := xr.EncodeFromFile(fname)
if err != nil {
return "", fmt.Errorf("EncodeFromFile(%s): %v", fname, err)
}
incXMLBundled, err := BundleXML(incXMLRaw, xr)
if err != nil {
return "", fmt.Errorf("BundleXML(<%s>): %v", fname, err)
}
final = append(final, indentString(incXMLBundled, indent))

final = append(final, replacement)

}
return strings.Join(final, "\n"), nil
}

func indentString(s string, indent string) string {
lines := strings.Split(s, "\n")
final := []string{}
for _, v := range lines {
if v == "" {
final = append(final, "")
continue
}
final = append(final, fmt.Sprintf("%s%s", indent, v))
}

return strings.Join(final, "\n")
}

// UnbundleAllXML converts a bundled xml file to mapping of filenames to
// contents
func UnbundleAllXML(rawxml string) (map[string]string, error) {
type inc struct {
name string
start int
}
store := map[string]string{}
inctag := regexp.MustCompile(`(?m)^(.*?)<!-- include (.*) -->`)
stack := []inc{}
xmlarray := strings.Split(rawxml, "\n")

for ln := 0; ln < len(xmlarray); ln++ {
val := xmlarray[ln]
if !inctag.Match([]byte(val)) {
continue
}
submatches := inctag.FindSubmatch([]byte(val))
key := string(submatches[2])
if len(stack) > 0 && stack[0].name == key {
// found end of include, process it
indent := string(submatches[1])
indentedvals := xmlarray[stack[0].start+1 : ln]
store[stack[0].name] = unindentAndJoin(indentedvals, indent)

insertLine := fmt.Sprintf("%s<Include src=\"%s\"/>", indent, stack[0].name)
// remove the include from xmlarray
tmp := append(xmlarray[:stack[0].start], insertLine)
xmlarray = append(tmp, xmlarray[ln+1:]...)
ln = stack[0].start
stack = stack[1:]
} else {
stack = append([]inc{inc{name: key, start: ln}}, stack...)
}
}
if len(stack) != 0 {
return nil, fmt.Errorf("Bundled xml left after finished reading file: %v", stack)
}
store[Rootname] = unindentAndJoin(xmlarray, "")
return store, nil
}

func unindentAndJoin(raw []string, indent string) string {
ret := []string{}
for _, v := range raw {
ret = append(ret, strings.Replace(v, indent, "", 1))
}
return strings.Join(ret, "\n")
}
140 changes: 140 additions & 0 deletions bundler/xmlbundler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package bundler

import (
"ModCreator/tests"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestUnbundleXML(t *testing.T) {
input := `<!-- include Main -->
<Canvas raycastTarget="false">
<Defaults>
<Button color="a"/>
</Defaults>
<!-- include ui/CameraControl -->
<Panel id="CameraControl"
visibility="false"
</Panel>
<!-- include ui/CameraControl -->
<!-- include ui/Shop -->
<Panel id="shop.window" class="drag"
offsetXY="480 -70"
width="260" height="500">
<!-- include deep -->
<Button id="deepinclude">
</Button>
<!-- include deep -->
</Panel>
<!-- include ui/Shop -->
</Canvas>
<!-- include Main -->
`

want := map[string]string{
"__root": `<Include src="Main"/>
`,
"Main": `<Canvas raycastTarget="false">
<Defaults>
<Button color="a"/>
</Defaults>
<Include src="ui/CameraControl"/>
<Include src="ui/Shop"/>
</Canvas>
`,
"deep": `<Button id="deepinclude">
</Button>`,
"ui/Shop": `<Panel id="shop.window" class="drag"
offsetXY="480 -70"
width="260" height="500">
<Include src="deep"/>
</Panel>`,
"ui/CameraControl": `<Panel id="CameraControl"
visibility="false"
</Panel>
`,
}

got, err := UnbundleAllXML(input)
if err != nil {
t.Fatalf("UnbundleAllXML(): %v", err)
}

if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("want != got:\n%v\n", diff)
}
}

func TestBundleXML(t *testing.T) {
want := `<!-- include Main -->
<Canvas raycastTarget="false">
<Defaults>
<Button color="a"/>
</Defaults>
<!-- include ui/CameraControl -->
<Panel id="CameraControl"
visibility="false"
</Panel>
<!-- include ui/CameraControl -->
<!-- include ui/Shop -->
<Panel id="shop.window" class="drag"
offsetXY="480 -70"
width="260" height="500">
<!-- include deep -->
<Button id="deepinclude">
</Button>
<!-- include deep -->
</Panel>
<!-- include ui/Shop -->
</Canvas>
<!-- include Main -->
`

input := map[string]string{
"__root": `<Include src="Main"/>
`,
"Main.xml": `<Canvas raycastTarget="false">
<Defaults>
<Button color="a"/>
</Defaults>
<Include src="ui/CameraControl"/>
<Include src="ui/Shop"/>
</Canvas>
`,
"deep.xml": `<Button id="deepinclude">
</Button>`,
"ui/Shop.xml": `<Panel id="shop.window" class="drag"
offsetXY="480 -70"
width="260" height="500">
<Include src="deep"/>
</Panel>`,
"ui/CameraControl.xml": `<Panel id="CameraControl"
visibility="false"
</Panel>
`,
}
ff := tests.NewFF()
ff.Fs = input

got, err := BundleXML(input[Rootname], ff)
if err != nil {
t.Fatalf("UnbundleAllXML(): %v", err)
}

if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("want != got:\n%v\n", diff)
}
}
32 changes: 14 additions & 18 deletions file/luaops.go → file/textops.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,32 @@ import (
"path"
)

const (
expectedSuffix = ".ttslua"
)

// LuaOps allows for arbitrary reads and writes of luascript
type LuaOps struct {
// TextOps allows for arbitrary reads and writes of text files
type TextOps struct {
basepaths []string
writeBasepath string
readFileToBytes func(string) ([]byte, error)
writeBytesToFile func(string, []byte) error
}

// LuaReader serves to describe all ways to read luascripts
type LuaReader interface {
// TextReader serves to describe all ways to read luascripts
type TextReader interface {
EncodeFromFile(string) (string, error)
}

// LuaWriter serves to describe all ways to write luascripts
type LuaWriter interface {
// TextWriter serves to describe all ways to write luascripts
type TextWriter interface {
EncodeToFile(script, file string) error
}

// NewLuaOps initializes our object on a directory
func NewLuaOps(base string) *LuaOps {
return NewLuaOpsMulti([]string{base}, base)
// NewTextOps initializes our object on a directory
func NewTextOps(base string) *TextOps {
return NewTextOpsMulti([]string{base}, base)
}

// NewLuaOpsMulti allows for luascript to be read from multiple directories
func NewLuaOpsMulti(readDirs []string, writeDir string) *LuaOps {
return &LuaOps{
// NewTextOpsMulti allows for luascript to be read from multiple directories
func NewTextOpsMulti(readDirs []string, writeDir string) *TextOps {
return &TextOps{
basepaths: readDirs,
writeBasepath: writeDir,
readFileToBytes: func(s string) ([]byte, error) {
Expand Down Expand Up @@ -69,7 +65,7 @@ func NewLuaOpsMulti(readDirs []string, writeDir string) *LuaOps {
}

// EncodeFromFile pulls a file from configs and encodes it as a string.
func (l *LuaOps) EncodeFromFile(filename string) (string, error) {
func (l *TextOps) EncodeFromFile(filename string) (string, error) {
for _, base := range l.basepaths {
p := path.Join(base, filename)
b, err := l.readFileToBytes(p)
Expand All @@ -83,7 +79,7 @@ func (l *LuaOps) EncodeFromFile(filename string) (string, error) {
}

// EncodeToFile takes a single string and decodes escape characters; writes it.
func (l *LuaOps) EncodeToFile(script, file string) error {
func (l *TextOps) EncodeToFile(script, file string) error {
p := path.Join(l.writeBasepath, file)
return l.writeBytesToFile(p, []byte(script))
}
8 changes: 4 additions & 4 deletions file/luaops_test.go → file/textops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func TestRead(t *testing.T) {
ff := &fakeFiles{
fs: map[string][]byte{"foo/bar": []byte("bar = 2")},
}
l := &LuaOps{
l := &TextOps{
basepaths: []string{"foo"},
readFileToBytes: ff.read,
}
Expand All @@ -52,7 +52,7 @@ func TestReadMulti(t *testing.T) {
"foo2/bar": []byte("bar = 2"),
},
}
l := &LuaOps{
l := &TextOps{
basepaths: []string{"foo", "foo2"},
readFileToBytes: ff.read,
}
Expand All @@ -75,7 +75,7 @@ func TestReadErr(t *testing.T) {
"foo2/bar": []byte("bar = 2"),
},
}
l := &LuaOps{
l := &TextOps{
basepaths: []string{"foo", "foo2"},
readFileToBytes: ff.read,
}
Expand All @@ -90,7 +90,7 @@ func TestWrite(t *testing.T) {
ff := &fakeFiles{
fs: map[string][]byte{},
}
l := LuaOps{
l := TextOps{
writeBasepath: "foo",
writeBytesToFile: ff.write,
}
Expand Down
Loading

0 comments on commit b753425

Please sign in to comment.