diff --git a/plab/flatten/flatten.go b/plab/flatten/flatten.go
index 7f4e6168f96407fd3cb09a30cdaeaf6ae7081389..af95aa8ac5f0baedc44cf22382f448f990aab093 100644
--- a/plab/flatten/flatten.go
+++ b/plab/flatten/flatten.go
@@ -3,6 +3,8 @@ package flatten
 import (
 	"fmt"
 	"git.sch.bme.hu/insert-epic-projlab-team-name-here/tooling/plab/helpers"
+	"io/ioutil"
+	"os"
 )
 
 var Flatten = helpers.Subcommand{
@@ -26,7 +28,18 @@ var Flatten = helpers.Subcommand{
 				panic(e)
 			}
 		}
-		fmt.Println("flattened")
+		b, e := ioutil.ReadFile(entry)
+		if e != nil {
+			panic(e)
+		}
+		o, e := InlineAllFuncs(string(b))
+		if e != nil {
+			panic(e)
+		}
+		e = ioutil.WriteFile(out, []byte(o), os.ModePerm)
+		if e != nil {
+			panic(e)
+		}
 	},
 	Help: "flaten a .tex file",
 }
diff --git a/plab/flatten/flattenable.go b/plab/flatten/flattenable.go
index b84178b213a47061711f0127b2035bf91d53fdb5..090c552daf4234a7f1a346c2cccdbaefd4a83f9b 100644
--- a/plab/flatten/flattenable.go
+++ b/plab/flatten/flattenable.go
@@ -1,5 +1,7 @@
 package flatten
 
 type flattenable interface {
-	Flatten() string
+	Name() string
+	Inline(args ...string) string
+	ArgNum() int
 }
diff --git a/plab/flatten/function.go b/plab/flatten/function.go
index 2bc80526d9fe9155ae107812052e7247190efbb5..e04e12010a84c92ac29a62988894d6995ee96602 100644
--- a/plab/flatten/function.go
+++ b/plab/flatten/function.go
@@ -2,8 +2,8 @@ package flatten
 
 import (
 	"bytes"
-	"errors"
 	"fmt"
+	"git.sch.bme.hu/insert-epic-projlab-team-name-here/tooling/plab/helpers"
 	"io/ioutil"
 	"regexp"
 	"strconv"
@@ -11,15 +11,25 @@ import (
 )
 
 type FunctionImplementation struct {
-	Name string
-	Body string
-	ArgNum int
+	flattenable
+	name   string
+	body   string
+	argNum int
+}
+
+func (f *FunctionImplementation) Name() string {
+	return f.name
+}
+
+func (f *FunctionImplementation) ArgNum() int {
+	return f.argNum
 }
 
 func InlineAllFuncs(str string) (string, error) {
 	for _, f := range funcs {
 		var soFar bytes.Buffer
-		tmpRegexp, e := regexp.Compile(fmt.Sprintf(`\\%s`, f.Name))
+		rx := helpers.Escape(f.Name()) + "{"
+		tmpRegexp, e := regexp.Compile(rx)
 		if e != nil {
 			return "", e
 		}
@@ -29,44 +39,65 @@ func InlineAllFuncs(str string) (string, error) {
 			nameStart := i[0]
 			argsStart := i[1]
 
-			name := str[nameStart:argsStart]
-			var fi *FunctionImplementation = nil
-			for fidx, f := range funcs {
-				if f.Name == name {
-					fi = &funcs[fidx]
-				}
-			}
-			if fi == nil {
-				continue
-			}
-
 			soFar.WriteString(str[prevStop:nameStart])
+			prevStop = nameStart
 
 			args := make([]string, 0)
-			for i := 0; i < fi.ArgNum; i++ {
+			argStop := argsStart
+			for i := 0; i < f.ArgNum(); i++ {
 				depth := 0
 				var arg string
-				for
+				for i, c := range str[argStop:] {
+					switch c {
+					case '{':
+						depth++
+					case '}':
+						depth--
+					default:
+						arg += string(c)
+					}
+					if depth < 0 {
+						argStop += i
+						break
+					}
+				}
+				args = append(args, arg)
+				argStop++
 			}
+
+			inlined := f.Inline(args...)
+			soFar.WriteString("\n")
+			soFar.WriteString(inlined)
+			soFar.WriteString("\n")
+			prevStop = argStop
 		}
+		soFar.WriteString(str[prevStop:])
+		str = soFar.String()
 	}
-	return str
+	return str, nil
 }
 
 func (f *FunctionImplementation) Inline(args ...string) string {
 	const canary = "🙂🙃🙂"
 	str := "{"
-	str = f.Body
+	str += f.body
 	str = strings.ReplaceAll(str, `\#`, canary)
 	for i, a := range args {
-		str = strings.ReplaceAll(str, fmt.Sprintf("#%d", i), a)
+		str = strings.ReplaceAll(str, fmt.Sprintf("#%d", i+1), a)
 	}
 	str = strings.ReplaceAll(str, canary, `\#`)
 	str += "}"
+	var e error
+	str, e = InlineAllFuncs(str)
+	if e != nil {
+		panic(e)
+	}
 	return str
 }
 
-var funcs = make([]FunctionImplementation, 0)
+var funcs = []flattenable{
+	&Input,
+}
 
 var cmdRegex = regexp.MustCompile(`\\newcommand{`)
 
@@ -82,10 +113,10 @@ func ParseFile(filename string) error {
 
 	for _, i := range idx {
 		nameStart := i[1]
-		nameStop := strings.Index(str[nameStart:], "}")+nameStart
+		nameStop := strings.Index(str[nameStart:], "}") + nameStart
 		name := str[nameStart:nameStop]
 
-		bodystart := strings.Index(str[nameStop:], "{")+nameStop+1
+		bodystart := strings.Index(str[nameStop:], "{") + nameStop + 1
 		var depth = 0
 		var bodyend = 0
 		for i, r := range str[bodystart:] {
@@ -121,14 +152,13 @@ func ParseFile(filename string) error {
 		}
 
 		fi := FunctionImplementation{
-			Name: name,
-			Body: body,
-			ArgNum: argNum,
+			name:   name,
+			body:   body,
+			argNum: argNum,
 		}
 
-		funcs = append(funcs, fi)
+		funcs = append(funcs, &fi)
 	}
 
 	return nil
 }
-
diff --git a/plab/flatten/input.go b/plab/flatten/input.go
new file mode 100644
index 0000000000000000000000000000000000000000..e9032221cc3f773d1adc3e34c5ff502b840c5223
--- /dev/null
+++ b/plab/flatten/input.go
@@ -0,0 +1,55 @@
+package flatten
+
+import (
+	"bytes"
+	"fmt"
+	"git.sch.bme.hu/insert-epic-projlab-team-name-here/tooling/plab/helpers"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+)
+
+type input struct {
+	flattenable
+}
+
+func (i *input) Inline(args ...string) string {
+	arg := args[0]
+
+	if arg[0] == '|' {
+		cmd := exec.Command("sh", "-c", helpers.Unescape(arg[1:]))
+		out, e := cmd.Output()
+		if e != nil {
+			_, _ = fmt.Fprint(os.Stderr, "error running command:", helpers.Unescape(arg[1:]), "\n")
+			var stde bytes.Buffer
+			rd, er := cmd.StderrPipe()
+			if er == nil {
+				_, _ = io.Copy(&stde, rd)
+				_, _ = fmt.Fprint(os.Stderr, stde.String(), "\n")
+			}
+			panic(e)
+		}
+		return string(out)
+	} else {
+		by, e := ioutil.ReadFile(arg)
+		if e != nil {
+			panic(e)
+		}
+		in, e := InlineAllFuncs(string(by))
+		if e != nil {
+			panic(e)
+		}
+		return in
+	}
+}
+
+func (i *input) Name() string {
+	return "\\input"
+}
+
+func (i *input) ArgNum() int {
+	return 1
+}
+
+var Input = input{}
diff --git a/plab/helpers/unescape.go b/plab/helpers/unescape.go
index 0e8fc623780464c54fcd1596f322515d240c0e8c..b4f3ff753fcd51495e68c85d7b510d6a791614f4 100644
--- a/plab/helpers/unescape.go
+++ b/plab/helpers/unescape.go
@@ -7,3 +7,9 @@ func Unescape(s string) string {
 	_ = json.Unmarshal([]byte(s), &ret)
 	return ret
 }
+
+func Escape(s string) string {
+	ret, _ := json.Marshal(s)
+	ret = ret[1 : len(ret)-1]
+	return string(ret)
+}