diff --git a/docs/function-guide.md b/docs/function-guide.md index 8461814c..65184840 100644 --- a/docs/function-guide.md +++ b/docs/function-guide.md @@ -124,16 +124,15 @@ An example explains it best: ### Example ```golang -package simplepoly - import ( "fmt" "github.com/purpleidea/mgmt/lang/types" + "github.com/purpleidea/mgmt/lang/funcs/simplepoly" ) func init() { - Register("len", []*types.FuncValue{ + simplepoly.Register("len", []*types.FuncValue{ { T: types.NewType("func([]variant) int"), V: Len, diff --git a/docs/language-guide.md b/docs/language-guide.md index dbb75546..51946347 100644 --- a/docs/language-guide.md +++ b/docs/language-guide.md @@ -285,11 +285,12 @@ class baz($a str, $b) { Classes can also be nested within other classes. Here's a contrived example: ```mcl +import "fmt" class c1($a, $b) { # nested class definition class c2($c) { test $a { - stringptr => printf("%s is %d", $b, $c), + stringptr => fmt.printf("%s is %d", $b, $c), } } diff --git a/examples/lang/answer.mcl b/examples/lang/answer.mcl index 2096f728..0de5f3bf 100644 --- a/examples/lang/answer.mcl +++ b/examples/lang/answer.mcl @@ -1,4 +1,6 @@ # it was a lovely surprise to me, when i realized that mgmt had the answer! +import "fmt" +import "example" print "answer" { - msg => printf("the answer to life, the universe, and everything is: %d", answer()), + msg => fmt.printf("the answer to life, the universe, and everything is: %d", example.answer()), } diff --git a/examples/lang/contains0.mcl b/examples/lang/contains0.mcl index e434fc6a..374f8014 100644 --- a/examples/lang/contains0.mcl +++ b/examples/lang/contains0.mcl @@ -1,3 +1,6 @@ +import "fmt" +import "sys" + $set = ["a", "b", "c", "d",] $c1 = "x1" in ["x1", "x2", "x3",] @@ -5,18 +8,18 @@ $c2 = 42 in [4, 13, 42,] $c3 = "x" in $set $c4 = "b" in $set -$s = printf("1: %t, 2: %t, 3: %t, 4: %t\n", $c1, $c2, $c3, $c4) +$s = fmt.printf("1: %t, 2: %t, 3: %t, 4: %t\n", $c1, $c2, $c3, $c4) file "/tmp/mgmt/contains" { content => $s, } -$x = if hostname() in ["h1", "h3",] { - printf("i (%s) am one of the chosen few!\n", hostname()) +$x = if sys.hostname() in ["h1", "h3",] { + fmt.printf("i (%s) am one of the chosen few!\n", sys.hostname()) } else { - printf("i (%s) was not chosen :(\n", hostname()) + fmt.printf("i (%s) was not chosen :(\n", sys.hostname()) } -file "/tmp/mgmt/hello-${hostname()}" { +file "/tmp/mgmt/hello-${sys.hostname()}" { content => $x, } diff --git a/examples/lang/datetime1.mcl b/examples/lang/datetime1.mcl index e7654684..2d7f737b 100644 --- a/examples/lang/datetime1.mcl +++ b/examples/lang/datetime1.mcl @@ -1,4 +1,6 @@ -$d = datetime() +import "datetime" + +$d = datetime.now() file "/tmp/mgmt/datetime" { content => template("Hello! It is now: {{ datetime_print . }}\n", $d), } diff --git a/examples/lang/datetime2.mcl b/examples/lang/datetime2.mcl index 16a7e2d1..9ca66dbe 100644 --- a/examples/lang/datetime2.mcl +++ b/examples/lang/datetime2.mcl @@ -1,11 +1,14 @@ -$secplusone = datetime() + $ayear +import "datetime" +import "sys" + +$secplusone = datetime.now() + $ayear # note the order of the assignment (year can come later in the code) $ayear = 60 * 60 * 24 * 365 # is a year in seconds (31536000) $tmplvalues = struct{year => $secplusone, load => $theload,} -$theload = structlookup(load(), "x1") +$theload = structlookup(sys.load(), "x1") if 5 > 3 { file "/tmp/mgmt/datetime" { diff --git a/examples/lang/datetime3.mcl b/examples/lang/datetime3.mcl index a5591bdc..5667ad52 100644 --- a/examples/lang/datetime3.mcl +++ b/examples/lang/datetime3.mcl @@ -1,11 +1,14 @@ -$secplusone = datetime() + $ayear +import "datetime" +import "sys" + +$secplusone = datetime.now() + $ayear # note the order of the assignment (year can come later in the code) $ayear = 60 * 60 * 24 * 365 # is a year in seconds (31536000) $tmplvalues = struct{year => $secplusone, load => $theload, vumeter => $vumeter,} -$theload = structlookup(load(), "x1") +$theload = structlookup(sys.load(), "x1") $vumeter = vumeter("====", 10, 0.9) diff --git a/examples/lang/env0.mcl b/examples/lang/env0.mcl index 4e7c7ea6..18d9c276 100644 --- a/examples/lang/env0.mcl +++ b/examples/lang/env0.mcl @@ -1,20 +1,23 @@ # read and print environment variable # env TEST=123 EMPTY= ./mgmt run --tmp-prefix --lang=examples/lang/env0.mcl --converged-timeout=5 -$x = getenv("TEST", "321") +import "fmt" +import "sys" + +$x = sys.getenv("TEST", "321") print "print1" { - msg => printf("the value of the environment variable TEST is: %s", $x), + msg => fmt.printf("the value of the environment variable TEST is: %s", $x), } -$y = getenv("DOESNOTEXIT", "321") +$y = sys.getenv("DOESNOTEXIT", "321") print "print2" { - msg => printf("environment variable DOESNOTEXIT does not exist, defaulting to: %s", $y), + msg => fmt.printf("environment variable DOESNOTEXIT does not exist, defaulting to: %s", $y), } -$z = getenv("EMPTY", "456") +$z = sys.getenv("EMPTY", "456") print "print3" { - msg => printf("same goes for epmty variables like EMPTY: %s", $z), + msg => fmt.printf("same goes for epmty variables like EMPTY: %s", $z), } diff --git a/examples/lang/env1.mcl b/examples/lang/env1.mcl index 001223d0..aa57c2cb 100644 --- a/examples/lang/env1.mcl +++ b/examples/lang/env1.mcl @@ -1,9 +1,12 @@ -$env = env() +import "fmt" +import "sys" + +$env = sys.env() $m = maplookup($env, "GOPATH", "") print "print0" { - msg => if hasenv("GOPATH") { - printf("GOPATH is: %s", $m) + msg => if sys.hasenv("GOPATH") { + fmt.printf("GOPATH is: %s", $m) } else { "GOPATH is missing!" }, diff --git a/examples/lang/exchange0.mcl b/examples/lang/exchange0.mcl index 97af41e9..731a273c 100644 --- a/examples/lang/exchange0.mcl +++ b/examples/lang/exchange0.mcl @@ -5,9 +5,11 @@ # time ./mgmt run --lang examples/lang/exchange0.mcl --hostname h3 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2383 --server-urls http://127.0.0.1:2384 --tmp-prefix --no-pgp # time ./mgmt run --lang examples/lang/exchange0.mcl --hostname h4 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2385 --server-urls http://127.0.0.1:2386 --tmp-prefix --no-pgp +import "sys" + $rand = random1(8) $exchanged = exchange("keyns", $rand) -file "/tmp/mgmt/exchange-${hostname()}" { +file "/tmp/mgmt/exchange-${sys.hostname()}" { content => template("Found: {{ . }}\n", $exchanged), } diff --git a/examples/lang/history1.mcl b/examples/lang/history1.mcl index 541f328b..66c77fd9 100644 --- a/examples/lang/history1.mcl +++ b/examples/lang/history1.mcl @@ -1,4 +1,6 @@ -$dt = datetime() +import "datetime" + +$dt = datetime.now() $hystvalues = {"ix0" => $dt, "ix1" => $dt{1}, "ix2" => $dt{2}, "ix3" => $dt{3},} diff --git a/examples/lang/hostname0.mcl b/examples/lang/hostname0.mcl index 4538a350..264e410a 100644 --- a/examples/lang/hostname0.mcl +++ b/examples/lang/hostname0.mcl @@ -1,4 +1,6 @@ -file "/tmp/mgmt/${hostname()}" { - content => "hello from ${hostname()}!\n", +import "sys" + +file "/tmp/mgmt/${sys.hostname()}" { + content => "hello from ${sys.hostname()}!\n", state => "exists", } diff --git a/examples/lang/len0.mcl b/examples/lang/len0.mcl index 251ec3b1..72f320f5 100644 --- a/examples/lang/len0.mcl +++ b/examples/lang/len0.mcl @@ -1,9 +1,11 @@ +import "fmt" + $x1 = ["a", "b", "c", "d",] print "print4" { - msg => printf("length is: %d", len($x1)), + msg => fmt.printf("length is: %d", len($x1)), } $x2 = {"a" => 1, "b" => 2, "c" => 3,} print "print3" { - msg => printf("length is: %d", len($x2)), + msg => fmt.printf("length is: %d", len($x2)), } diff --git a/examples/lang/load0.mcl b/examples/lang/load0.mcl index 960fe4e5..8333b57e 100644 --- a/examples/lang/load0.mcl +++ b/examples/lang/load0.mcl @@ -1,9 +1,12 @@ -$theload = load() +import "fmt" +import "sys" + +$theload = sys.load() $x1 = structlookup($theload, "x1") $x5 = structlookup($theload, "x5") $x15 = structlookup($theload, "x15") print "print1" { - msg => printf("load average: %f, %f, %f", $x1, $x5, $x15), + msg => fmt.printf("load average: %f, %f, %f", $x1, $x5, $x15), } diff --git a/examples/lang/maplookup1.mcl b/examples/lang/maplookup1.mcl index 2a7aca28..12f1a366 100644 --- a/examples/lang/maplookup1.mcl +++ b/examples/lang/maplookup1.mcl @@ -1,13 +1,15 @@ +import "fmt" + $m = {"k1" => 42, "k2" => 13,} $found = maplookup($m, "k1", 99) print "print1" { - msg => printf("found value of: %d", $found), + msg => fmt.printf("found value of: %d", $found), } $notfound = maplookup($m, "k3", 99) print "print2" { - msg => printf("notfound value of: %d", $notfound), + msg => fmt.printf("notfound value of: %d", $notfound), } diff --git a/examples/lang/math1.mcl b/examples/lang/math1.mcl index 98a46331..518b9914 100644 --- a/examples/lang/math1.mcl +++ b/examples/lang/math1.mcl @@ -1,4 +1,6 @@ +import "fmt" + test "t1" { int64 => (4 + 32) * 15 - 8, - anotherstr => printf("the answer is: %d", 42), + anotherstr => fmt.printf("the answer is: %d", 42), } diff --git a/examples/lang/math2.mcl b/examples/lang/math2.mcl index 65bf9dfe..664d8582 100644 --- a/examples/lang/math2.mcl +++ b/examples/lang/math2.mcl @@ -1,3 +1,6 @@ +import "fmt" +import "math" + print "print0" { - msg => printf("13.0 ^ 4.2 is: %f", pow(13.0, 4.2)), + msg => fmt.printf("13.0 ^ 4.2 is: %f", math.pow(13.0, 4.2)), } diff --git a/examples/lang/printf1.mcl b/examples/lang/printf1.mcl index 9a3b9abf..ab8a97a9 100644 --- a/examples/lang/printf1.mcl +++ b/examples/lang/printf1.mcl @@ -1,8 +1,10 @@ +import "fmt" + test "printf-a" { - anotherstr => printf("the %s is: %d", "answer", 42), + anotherstr => fmt.printf("the %s is: %d", "answer", 42), } $format = "a %s is: %f" test "printf-b" { - anotherstr => printf($format, "cool number", 3.14159), + anotherstr => fmt.printf($format, "cool number", 3.14159), } diff --git a/examples/lang/schedule0.mcl b/examples/lang/schedule0.mcl index cbbc05f9..2b4979b9 100644 --- a/examples/lang/schedule0.mcl +++ b/examples/lang/schedule0.mcl @@ -1,3 +1,5 @@ +import "sys" + # here are all the possible options: #$opts = struct{strategy => "rr", max => 3, reuse => false, ttl => 10,} @@ -13,6 +15,6 @@ $set = schedule("xsched", $opts) # and if you want, you can omit the options entirely: #$set = schedule("xsched") -file "/tmp/mgmt/scheduled-${hostname()}" { +file "/tmp/mgmt/scheduled-${sys.hostname()}" { content => template("set: {{ . }}\n", $set), } diff --git a/examples/lang/sendrecv3.mcl b/examples/lang/sendrecv3.mcl index 898c1e98..17c998bf 100644 --- a/examples/lang/sendrecv3.mcl +++ b/examples/lang/sendrecv3.mcl @@ -1,3 +1,5 @@ +import "fmt" + $ns = "estate" $exchanged = kvlookup($ns) $state = maplookup($exchanged, $hostname, "default") @@ -16,6 +18,6 @@ Exec["exec0"].output -> Kv["kv0"].value if $state != "default" { file "/tmp/mgmt/state" { - content => printf("state: %s\n", $state), + content => fmt.printf("state: %s\n", $state), } } diff --git a/examples/lang/structlookup1.mcl b/examples/lang/structlookup1.mcl index c3812246..45690364 100644 --- a/examples/lang/structlookup1.mcl +++ b/examples/lang/structlookup1.mcl @@ -1,13 +1,15 @@ +import "fmt" + $st = struct{f1 => 42, f2 => true, f3 => 3.14,} $f1 = structlookup($st, "f1") print "print1" { - msg => printf("f1 field is: %d", $f1), + msg => fmt.printf("f1 field is: %d", $f1), } $f2 = structlookup($st, "f2") print "print2" { - msg => printf("f2 field is: %t", $f2), + msg => fmt.printf("f2 field is: %t", $f2), } diff --git a/examples/lang/template0.mcl b/examples/lang/template0.mcl index d4a1b09f..5106faa8 100644 --- a/examples/lang/template0.mcl +++ b/examples/lang/template0.mcl @@ -1,8 +1,11 @@ +import "fmt" +import "example" + $answer = 42 -$s = int2str($answer) +$s = example.int2str($answer) print "print1" { - msg => printf("an str is: %s", $s), + msg => fmt.printf("an str is: %s", $s), } print "print2" { diff --git a/lang/funcs/core/core.go b/lang/funcs/core/core.go new file mode 100644 index 00000000..68d8a48f --- /dev/null +++ b/lang/funcs/core/core.go @@ -0,0 +1,27 @@ +// Mgmt +// Copyright (C) 2013-2018+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package core + +import ( + // import so the funcs register + _ "github.com/purpleidea/mgmt/lang/funcs/core/coredatetime" + _ "github.com/purpleidea/mgmt/lang/funcs/core/coreexample" + _ "github.com/purpleidea/mgmt/lang/funcs/core/corefmt" + _ "github.com/purpleidea/mgmt/lang/funcs/core/coremath" + _ "github.com/purpleidea/mgmt/lang/funcs/core/coresys" +) diff --git a/lang/funcs/core/coredatetime/coredatetime.go b/lang/funcs/core/coredatetime/coredatetime.go new file mode 100644 index 00000000..a6f2065a --- /dev/null +++ b/lang/funcs/core/coredatetime/coredatetime.go @@ -0,0 +1,23 @@ +// Mgmt +// Copyright (C) 2013-2018+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package coredatetime + +const ( + // moduleName is the prefix given to all the functions in this module. + moduleName = "datetime" +) diff --git a/lang/funcs/facts/core/datetime_fact.go b/lang/funcs/core/coredatetime/now_fact.go similarity index 94% rename from lang/funcs/facts/core/datetime_fact.go rename to lang/funcs/core/coredatetime/now_fact.go index 2b60bccf..22e8229a 100644 --- a/lang/funcs/facts/core/datetime_fact.go +++ b/lang/funcs/core/coredatetime/now_fact.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package core // TODO: should this be in its own individual package? +package coredatetime import ( "time" @@ -25,7 +25,7 @@ import ( ) func init() { - facts.Register("datetime", func() facts.Fact { return &DateTimeFact{} }) // must register the fact and name + facts.ModuleRegister(moduleName, "now", func() facts.Fact { return &DateTimeFact{} }) // must register the fact and name } // DateTimeFact is a fact which returns the current date and time. diff --git a/lang/funcs/simple/datetime_print_func.go b/lang/funcs/core/coredatetime/print_func.go similarity index 87% rename from lang/funcs/simple/datetime_print_func.go rename to lang/funcs/core/coredatetime/print_func.go index 9baf0ab3..3340afef 100644 --- a/lang/funcs/simple/datetime_print_func.go +++ b/lang/funcs/core/coredatetime/print_func.go @@ -15,19 +15,19 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package simple // TODO: should this be in its own individual package? +package coredatetime import ( "fmt" "time" + "github.com/purpleidea/mgmt/lang/funcs/simple" "github.com/purpleidea/mgmt/lang/types" ) func init() { - // TODO: should we support namespacing these, eg: datetime.print ? // FIXME: consider renaming this to printf, and add in a format string? - Register("datetime_print", &types.FuncValue{ + simple.ModuleRegister(moduleName, "print", &types.FuncValue{ T: types.NewType("func(a int) str"), V: func(input []types.Value) (types.Value, error) { epochDelta := input[0].Int() diff --git a/lang/funcs/simple/answer_func.go b/lang/funcs/core/coreexample/answer_func.go similarity index 89% rename from lang/funcs/simple/answer_func.go rename to lang/funcs/core/coreexample/answer_func.go index 081daaeb..4841c500 100644 --- a/lang/funcs/simple/answer_func.go +++ b/lang/funcs/core/coreexample/answer_func.go @@ -15,9 +15,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package simple // TODO: should this be in its own individual package? +package coreexample import ( + "github.com/purpleidea/mgmt/lang/funcs/simple" "github.com/purpleidea/mgmt/lang/types" ) @@ -25,7 +26,7 @@ import ( const Answer = 42 func init() { - Register("answer", &types.FuncValue{ + simple.ModuleRegister(moduleName, "answer", &types.FuncValue{ T: types.NewType("func() int"), V: func([]types.Value) (types.Value, error) { return &types.IntValue{V: Answer}, nil diff --git a/lang/funcs/core/coreexample/coreexample.go b/lang/funcs/core/coreexample/coreexample.go new file mode 100644 index 00000000..e90c5db0 --- /dev/null +++ b/lang/funcs/core/coreexample/coreexample.go @@ -0,0 +1,23 @@ +// Mgmt +// Copyright (C) 2013-2018+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package coreexample + +const ( + // moduleName is the prefix given to all the functions in this module. + moduleName = "example" +) diff --git a/lang/funcs/simple/example_errorbool_func.go b/lang/funcs/core/coreexample/errorbool_func.go similarity index 85% rename from lang/funcs/simple/example_errorbool_func.go rename to lang/funcs/core/coreexample/errorbool_func.go index b3e4905e..4fe01ab6 100644 --- a/lang/funcs/simple/example_errorbool_func.go +++ b/lang/funcs/core/coreexample/errorbool_func.go @@ -15,17 +15,17 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package simple // TODO: should this be in its own individual package? +package coreexample import ( "fmt" + "github.com/purpleidea/mgmt/lang/funcs/simple" "github.com/purpleidea/mgmt/lang/types" ) func init() { - // TODO: should we support namespacing these, eg: example.errorbool ? - Register("example_errorbool", &types.FuncValue{ + simple.ModuleRegister(moduleName, "errorbool", &types.FuncValue{ T: types.NewType("func(a bool) str"), V: func(input []types.Value) (types.Value, error) { if input[0].Bool() { diff --git a/lang/funcs/facts/core/flipflop_fact.go b/lang/funcs/core/coreexample/flipflop_fact.go similarity index 91% rename from lang/funcs/facts/core/flipflop_fact.go rename to lang/funcs/core/coreexample/flipflop_fact.go index e2c7a70b..cace4d2f 100644 --- a/lang/funcs/facts/core/flipflop_fact.go +++ b/lang/funcs/core/coreexample/flipflop_fact.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package core // TODO: should this be in its own individual package? +package coreexample import ( "time" @@ -25,8 +25,7 @@ import ( ) func init() { - // TODO: rename these `play` facts to start with a test_ prefix or similar - facts.Register("flipflop", func() facts.Fact { return &FlipFlopFact{} }) // must register the fact and name + facts.ModuleRegister(moduleName, "flipflop", func() facts.Fact { return &FlipFlopFact{} }) // must register the fact and name } // FlipFlopFact is a fact which flips a bool repeatedly. This is an example fact diff --git a/lang/funcs/simple/int2str_func.go b/lang/funcs/core/coreexample/int2str_func.go similarity index 88% rename from lang/funcs/simple/int2str_func.go rename to lang/funcs/core/coreexample/int2str_func.go index c7550f01..f3e007a4 100644 --- a/lang/funcs/simple/int2str_func.go +++ b/lang/funcs/core/coreexample/int2str_func.go @@ -15,16 +15,17 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package simple // TODO: should this be in its own individual package? +package coreexample import ( "fmt" + "github.com/purpleidea/mgmt/lang/funcs/simple" "github.com/purpleidea/mgmt/lang/types" ) func init() { - Register("int2str", &types.FuncValue{ + simple.ModuleRegister(moduleName, "int2str", &types.FuncValue{ T: types.NewType("func(a int) str"), V: func(input []types.Value) (types.Value, error) { return &types.StrValue{ diff --git a/lang/funcs/core/vumeter_func.go b/lang/funcs/core/coreexample/vumeter_func.go similarity index 96% rename from lang/funcs/core/vumeter_func.go rename to lang/funcs/core/coreexample/vumeter_func.go index f7b7209e..7032b166 100644 --- a/lang/funcs/core/vumeter_func.go +++ b/lang/funcs/core/coreexample/vumeter_func.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package core // TODO: should this be in its own individual package? +package coreexample import ( "fmt" @@ -34,7 +34,7 @@ import ( ) func init() { - funcs.Register("vumeter", func() interfaces.Func { return &VUMeterFunc{} }) // must register the func and name + funcs.ModuleRegister(moduleName, "vumeter", func() interfaces.Func { return &VUMeterFunc{} }) // must register the func and name } // VUMeterFunc is a gimmic function to display a vu meter from the microphone. diff --git a/lang/funcs/core/corefmt/corefmt.go b/lang/funcs/core/corefmt/corefmt.go new file mode 100644 index 00000000..84ece4c7 --- /dev/null +++ b/lang/funcs/core/corefmt/corefmt.go @@ -0,0 +1,23 @@ +// Mgmt +// Copyright (C) 2013-2018+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package corefmt + +const ( + // moduleName is the prefix given to all the functions in this module. + moduleName = "fmt" +) diff --git a/lang/funcs/core/printf_polyfunc.go b/lang/funcs/core/corefmt/printf_func.go similarity index 98% rename from lang/funcs/core/printf_polyfunc.go rename to lang/funcs/core/corefmt/printf_func.go index 2b4b9ddb..f6c796ea 100644 --- a/lang/funcs/core/printf_polyfunc.go +++ b/lang/funcs/core/corefmt/printf_func.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package core // TODO: should this be in its own individual package? +package corefmt import ( "fmt" @@ -29,7 +29,7 @@ import ( ) func init() { - funcs.Register("printf", func() interfaces.Func { return &PrintfFunc{} }) + funcs.ModuleRegister(moduleName, "printf", func() interfaces.Func { return &PrintfFunc{} }) } const ( diff --git a/lang/funcs/core/coremath/coremath.go b/lang/funcs/core/coremath/coremath.go new file mode 100644 index 00000000..89721ead --- /dev/null +++ b/lang/funcs/core/coremath/coremath.go @@ -0,0 +1,23 @@ +// Mgmt +// Copyright (C) 2013-2018+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package coremath + +const ( + // moduleName is the prefix given to all the functions in this module. + moduleName = "math" +) diff --git a/lang/funcs/simple/math_func.go b/lang/funcs/core/coremath/pow_func.go similarity index 92% rename from lang/funcs/simple/math_func.go rename to lang/funcs/core/coremath/pow_func.go index e29bef0a..6629d7d9 100644 --- a/lang/funcs/simple/math_func.go +++ b/lang/funcs/core/coremath/pow_func.go @@ -15,17 +15,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package simple // TODO: should this be in its own individual package? +package coremath import ( "fmt" "math" + "github.com/purpleidea/mgmt/lang/funcs/simple" "github.com/purpleidea/mgmt/lang/types" ) func init() { - Register("pow", &types.FuncValue{ + simple.ModuleRegister(moduleName, "pow", &types.FuncValue{ T: types.NewType("func(x float, y float) float"), V: Pow, }) diff --git a/lang/funcs/core/coresys/coresys.go b/lang/funcs/core/coresys/coresys.go new file mode 100644 index 00000000..d202bf0f --- /dev/null +++ b/lang/funcs/core/coresys/coresys.go @@ -0,0 +1,23 @@ +// Mgmt +// Copyright (C) 2013-2018+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package coresys + +const ( + // moduleName is the prefix given to all the functions in this module. + moduleName = "sys" +) diff --git a/lang/funcs/simple/env_func.go b/lang/funcs/core/coresys/env_func.go similarity index 87% rename from lang/funcs/simple/env_func.go rename to lang/funcs/core/coresys/env_func.go index 34d6a117..8e24dd3f 100644 --- a/lang/funcs/simple/env_func.go +++ b/lang/funcs/core/coresys/env_func.go @@ -15,29 +15,30 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package simple // TODO: should this be in its own individual package? +package coresys import ( "os" "strings" + "github.com/purpleidea/mgmt/lang/funcs/simple" "github.com/purpleidea/mgmt/lang/types" ) func init() { - Register("getenv", &types.FuncValue{ + simple.ModuleRegister(moduleName, "getenv", &types.FuncValue{ T: types.NewType("func(str) str"), V: GetEnv, }) - Register("defaultenv", &types.FuncValue{ + simple.ModuleRegister(moduleName, "defaultenv", &types.FuncValue{ T: types.NewType("func(str, str) str"), V: DefaultEnv, }) - Register("hasenv", &types.FuncValue{ + simple.ModuleRegister(moduleName, "hasenv", &types.FuncValue{ T: types.NewType("func(str) bool"), V: HasEnv, }) - Register("env", &types.FuncValue{ + simple.ModuleRegister(moduleName, "env", &types.FuncValue{ T: types.NewType("func() map{str: str}"), V: Env, }) diff --git a/lang/funcs/facts/core/hostname_fact.go b/lang/funcs/core/coresys/hostname_fact.go similarity index 92% rename from lang/funcs/facts/core/hostname_fact.go rename to lang/funcs/core/coresys/hostname_fact.go index 88926700..5ea555c7 100644 --- a/lang/funcs/facts/core/hostname_fact.go +++ b/lang/funcs/core/coresys/hostname_fact.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package core // TODO: should this be in its own individual package? +package coresys import ( "github.com/purpleidea/mgmt/lang/funcs/facts" @@ -23,7 +23,7 @@ import ( ) func init() { - facts.Register("hostname", func() facts.Fact { return &HostnameFact{} }) // must register the fact and name + facts.ModuleRegister(moduleName, "hostname", func() facts.Fact { return &HostnameFact{} }) // must register the fact and name } // HostnameFact is a function that returns the hostname. diff --git a/lang/funcs/facts/core/load_fact.go b/lang/funcs/core/coresys/load_fact.go similarity index 94% rename from lang/funcs/facts/core/load_fact.go rename to lang/funcs/core/coresys/load_fact.go index 2402bcee..1a687df3 100644 --- a/lang/funcs/facts/core/load_fact.go +++ b/lang/funcs/core/coresys/load_fact.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package core // TODO: should this be in its own individual package? +package coresys import ( "time" @@ -31,7 +31,7 @@ const ( ) func init() { - facts.Register("load", func() facts.Fact { return &LoadFact{} }) // must register the fact and name + facts.ModuleRegister(moduleName, "load", func() facts.Fact { return &LoadFact{} }) // must register the fact and name } // LoadFact is a fact which returns the current system load. diff --git a/lang/funcs/facts/core/load_fact_darwin.go b/lang/funcs/core/coresys/load_fact_darwin.go similarity index 94% rename from lang/funcs/facts/core/load_fact_darwin.go rename to lang/funcs/core/coresys/load_fact_darwin.go index f546c2e5..6d27b484 100644 --- a/lang/funcs/facts/core/load_fact_darwin.go +++ b/lang/funcs/core/coresys/load_fact_darwin.go @@ -17,7 +17,7 @@ // +build darwin -package core // TODO: should this be in its own individual package? +package coresys /* #include diff --git a/lang/funcs/facts/core/load_fact_posix.go b/lang/funcs/core/coresys/load_fact_posix.go similarity index 95% rename from lang/funcs/facts/core/load_fact_posix.go rename to lang/funcs/core/coresys/load_fact_posix.go index f80c2d99..d9e80720 100644 --- a/lang/funcs/facts/core/load_fact_posix.go +++ b/lang/funcs/core/coresys/load_fact_posix.go @@ -17,7 +17,7 @@ // +build !darwin -package core // TODO: should this be in its own individual package? +package coresys import ( "syscall" diff --git a/lang/funcs/core/exchange_polyfunc.go b/lang/funcs/core/exchange_func.go similarity index 100% rename from lang/funcs/core/exchange_polyfunc.go rename to lang/funcs/core/exchange_func.go diff --git a/lang/funcs/simplepoly/len_polyfunc.go b/lang/funcs/core/len_func.go similarity index 93% rename from lang/funcs/simplepoly/len_polyfunc.go rename to lang/funcs/core/len_func.go index 59632a07..16f43be7 100644 --- a/lang/funcs/simplepoly/len_polyfunc.go +++ b/lang/funcs/core/len_func.go @@ -15,16 +15,17 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package simplepoly // TODO: should this be in its own individual package? +package core import ( "fmt" + "github.com/purpleidea/mgmt/lang/funcs/simplepoly" "github.com/purpleidea/mgmt/lang/types" ) func init() { - Register("len", []*types.FuncValue{ + simplepoly.Register("len", []*types.FuncValue{ { T: types.NewType("func([]variant) int"), V: Len, diff --git a/lang/funcs/core/schedule_polyfunc.go b/lang/funcs/core/schedule_func.go similarity index 100% rename from lang/funcs/core/schedule_polyfunc.go rename to lang/funcs/core/schedule_func.go diff --git a/lang/funcs/core/template_polyfunc.go b/lang/funcs/core/template_func.go similarity index 98% rename from lang/funcs/core/template_polyfunc.go rename to lang/funcs/core/template_func.go index 22f88c4d..3a9a3ee9 100644 --- a/lang/funcs/core/template_polyfunc.go +++ b/lang/funcs/core/template_func.go @@ -188,6 +188,8 @@ func (obj *TemplateFunc) run(templateText string, vars types.Value) (string, err // FIXME: should we do this once in init() instead, or in the Register // function in the simple package? // TODO: loop through this map in a sorted, deterministic order + // XXX: should this use the scope instead (so imports are used properly) ? + // XXX: dots are not valid here, so maybe replace dots with underscores so we can do fmt_print??? for name, fn := range simple.RegisteredFuncs { if _, exists := funcMap[name]; exists { obj.init.Logf("warning, existing function named: `%s` exists", name) diff --git a/lang/funcs/facts/facts.go b/lang/funcs/facts/facts.go index fb01a61d..7a94bce6 100644 --- a/lang/funcs/facts/facts.go +++ b/lang/funcs/facts/facts.go @@ -47,6 +47,12 @@ func Register(name string, fn func() Fact) { RegisteredFacts[name] = fn } +// ModuleRegister is exactly like Register, except that it registers within a +// named module. This is a helper function. +func ModuleRegister(module, name string, fn func() Fact) { + Register(module+funcs.ModuleSep+name, fn) +} + // Info is a static representation of some information about the fact. It is // used for static analysis and type checking. If you break this contract, you // might cause a panic. diff --git a/lang/funcs/funcs.go b/lang/funcs/funcs.go index ea79509d..21518f90 100644 --- a/lang/funcs/funcs.go +++ b/lang/funcs/funcs.go @@ -20,10 +20,18 @@ package funcs import ( "fmt" + "strings" "github.com/purpleidea/mgmt/lang/interfaces" ) +const ( + // ModuleSep is the character used for the module scope separation. For + // example when using `fmt.printf` or `math.sin` this is the char used. + // It is included here for convenience when importing this package. + ModuleSep = interfaces.ModuleSep +) + // registeredFuncs is a global map of all possible funcs which can be used. You // should never touch this map directly. Use methods like Register instead. It // includes implementations which also satisfy PolyFunc as well. @@ -32,15 +40,33 @@ var registeredFuncs = make(map[string]func() interfaces.Func) // must initialize // Register takes a func and its name and makes it available for use. It is // commonly called in the init() method of the func at program startup. There is // no matching Unregister function. You may also register functions which -// satisfy the PolyFunc interface. +// satisfy the PolyFunc interface. To register a function which lives in a +// module, you must join the module name to the function name with the ModuleSep +// character. It is defined as a const and is probably the period character. func Register(name string, fn func() interfaces.Func) { if _, exists := registeredFuncs[name]; exists { panic(fmt.Sprintf("a func named %s is already registered", name)) } + + // can't contain more than one period in a row + if strings.Index(name, ModuleSep+ModuleSep) >= 0 { + panic(fmt.Sprintf("a func named %s is invalid", name)) + } + // can't start or end with a period + if strings.HasPrefix(name, ModuleSep) || strings.HasSuffix(name, ModuleSep) { + panic(fmt.Sprintf("a func named %s is invalid", name)) + } + //gob.Register(fn()) registeredFuncs[name] = fn } +// ModuleRegister is exactly like Register, except that it registers within a +// named module. +func ModuleRegister(module, name string, fn func() interfaces.Func) { + Register(module+ModuleSep+name, fn) +} + // Lookup returns a pointer to the function's struct. It may be convertible to a // PolyFunc if the particular function implements those additional methods. func Lookup(name string) (interfaces.Func, error) { @@ -50,3 +76,21 @@ func Lookup(name string) (interfaces.Func, error) { } return f(), nil } + +// LookupPrefix returns a map of names to functions that start with a module +// prefix. This search automatically adds the period separator. So if you want +// functions in the `fmt` package, search for `fmt`, not `fmt.` and it will find +// all the correctly registered functions. This removes that prefix from the +// result in the map keys that it returns. +func LookupPrefix(prefix string) (map[string]interfaces.Func, error) { + result := make(map[string]interfaces.Func) + for name, f := range registeredFuncs { + sep := prefix + ModuleSep + if !strings.HasPrefix(name, sep) { + continue + } + s := strings.TrimPrefix(name, sep) // TODO: is it okay to remove the prefix? + result[s] = f() // build + } + return result, nil +} diff --git a/lang/funcs/simple/simple.go b/lang/funcs/simple/simple.go index 37fba1e2..079e9f96 100644 --- a/lang/funcs/simple/simple.go +++ b/lang/funcs/simple/simple.go @@ -42,6 +42,12 @@ func Register(name string, fn *types.FuncValue) { funcs.Register(name, func() interfaces.Func { return &simpleFunc{Fn: fn} }) } +// ModuleRegister is exactly like Register, except that it registers within a +// named module. This is a helper function. +func ModuleRegister(module, name string, fn *types.FuncValue) { + Register(module+funcs.ModuleSep+name, fn) +} + // simpleFunc is a scaffolding function struct which fulfills the boiler-plate // for the function API, but that can run a very simple, static, pure function. type simpleFunc struct { diff --git a/lang/funcs/simplepoly/simplepoly.go b/lang/funcs/simplepoly/simplepoly.go index 87211403..cce234ed 100644 --- a/lang/funcs/simplepoly/simplepoly.go +++ b/lang/funcs/simplepoly/simplepoly.go @@ -62,6 +62,12 @@ func Register(name string, fns []*types.FuncValue) { funcs.Register(name, func() interfaces.Func { return &simplePolyFunc{Fns: fns} }) } +// ModuleRegister is exactly like Register, except that it registers within a +// named module. This is a helper function. +func ModuleRegister(module, name string, fns []*types.FuncValue) { + Register(module+funcs.ModuleSep+name, fns) +} + // simplePolyFunc is a scaffolding function struct which fulfills the // boiler-plate for the function API, but that can run a very simple, static, // pure, polymorphic function. diff --git a/lang/interfaces/const.go b/lang/interfaces/const.go new file mode 100644 index 00000000..38d2687d --- /dev/null +++ b/lang/interfaces/const.go @@ -0,0 +1,24 @@ +// Mgmt +// Copyright (C) 2013-2018+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package interfaces + +const ( + // ModuleSep is the character used for the module scope separation. For + // example when using `fmt.printf` or `math.sin` this is the char used. + ModuleSep = "." +) diff --git a/lang/lang.go b/lang/lang.go index 500ff5c9..83cccc49 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -24,10 +24,7 @@ import ( "github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/lang/funcs" - _ "github.com/purpleidea/mgmt/lang/funcs/core" // import so the funcs register - _ "github.com/purpleidea/mgmt/lang/funcs/facts/core" // import so the facts register - _ "github.com/purpleidea/mgmt/lang/funcs/simple" // import so the funcs register - _ "github.com/purpleidea/mgmt/lang/funcs/simplepoly" // import so the funcs register + _ "github.com/purpleidea/mgmt/lang/funcs/core" // import so the funcs register "github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/unification" "github.com/purpleidea/mgmt/pgraph" diff --git a/lang/lang_test.go b/lang/lang_test.go index 49ddf6f0..c729b4f4 100644 --- a/lang/lang_test.go +++ b/lang/lang_test.go @@ -26,7 +26,7 @@ import ( "github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine/resources" - _ "github.com/purpleidea/mgmt/lang/funcs/facts/core" // load facts + _ "github.com/purpleidea/mgmt/lang/funcs/core" // import so the funcs register "github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/util" @@ -497,9 +497,10 @@ func TestInterpretMany(t *testing.T) { // values = append(values, test{ // name: "double include different printf types allowed", // code: ` + // import "fmt" // class c1($a, $b) { // test $a { - // stringptr => printf("value is: %v", $b), + // stringptr => fmt.printf("value is: %v", $b), // } // } // include c1("t1", "hello") @@ -667,6 +668,8 @@ func TestInterpretMany(t *testing.T) { values = append(values, test{ name: "nested classes 1", code: ` + import "fmt" + include c1("t1", "hello") # test["t1"] -> hello is 42 include c1("t2", "world") # test["t2"] -> world is 13 @@ -674,7 +677,7 @@ func TestInterpretMany(t *testing.T) { # nested class definition class c2($c) { test $a { - stringptr => printf("%s is %d", $b, $c), + stringptr => fmt.printf("%s is %d", $b, $c), } } @@ -693,6 +696,8 @@ func TestInterpretMany(t *testing.T) { values = append(values, test{ name: "nested classes out of scope 1", code: ` + import "fmt" + include c1("t1", "hello") # test["t1"] -> hello is 42 include c2(99) # out of scope @@ -700,7 +705,7 @@ func TestInterpretMany(t *testing.T) { # nested class definition class c2($c) { test $a { - stringptr => printf("%s is %d", $b, $c), + stringptr => fmt.printf("%s is %d", $b, $c), } } @@ -742,13 +747,14 @@ func TestInterpretMany(t *testing.T) { // values = append(values, test{ // name: "recursive classes 1", // code: ` + // import "fmt" // $max = 3 // include c1(0) # start at zero // # test["done"] -> count is 3 // class c1($count) { // if $count == $max { // test "done" { - // stringptr => printf("count is %d", $count), + // stringptr => fmt.printf("count is %d", $count), // } // } else { // include c1($count + 1) @@ -779,6 +785,7 @@ func TestInterpretMany(t *testing.T) { // values = append(values, test{ // name: "recursive classes 2", // code: ` + // import "fmt" // include c1("ix", 3) // # test["ix:3"] -> count is 3 // # test["ix:2"] -> count is 2 @@ -787,12 +794,12 @@ func TestInterpretMany(t *testing.T) { // class c1($name, $count) { // if $count == 0 { // test "zero" { - // stringptr => printf("count is %d", $count), + // stringptr => fmt.printf("count is %d", $count), // } // } else { // include c1($name, $count - 1) // test "${name}:${count}" { - // stringptr => printf("count is %d", $count), + // stringptr => fmt.printf("count is %d", $count), // } // } // } @@ -806,12 +813,13 @@ func TestInterpretMany(t *testing.T) { values = append(values, test{ name: "recursive classes fail 1", code: ` + import "fmt" $max = 3 include c1(0) # start at zero class c1($count) { if $count == $max { test "done" { - stringptr => printf("count is %d", $count), + stringptr => fmt.printf("count is %d", $count), } } else { include c1($count + 1) # recursion not supported atm @@ -826,12 +834,13 @@ func TestInterpretMany(t *testing.T) { values = append(values, test{ name: "recursive classes fail 2", code: ` + import "fmt" $max = 5 include c1(0) # start at zero class c1($count) { if $count == $max { test "done" { - stringptr => printf("count is %d", $count), + stringptr => fmt.printf("count is %d", $count), } } else { include c2($count + 1) # recursion not supported atm @@ -840,7 +849,7 @@ func TestInterpretMany(t *testing.T) { class c2($count) { if $count == $max { test "done" { - stringptr => printf("count is %d", $count), + stringptr => fmt.printf("count is %d", $count), } } else { include c1($count + 1) # recursion not supported atm diff --git a/test/shell/env0.mcl b/test/shell/env0.mcl index 530d16e4..baa22dfc 100644 --- a/test/shell/env0.mcl +++ b/test/shell/env0.mcl @@ -1,19 +1,22 @@ -$tmpdir = defaultenv("TMPDIR", "/tmp") +import "fmt" +import "sys" -$x = getenv("TEST") -$y = getenv("DOESNOTEXIST") -$z = getenv("EMPTY") +$tmpdir = sys.defaultenv("TMPDIR", "/tmp") -$a = defaultenv("TEST", "321") -$b = defaultenv("DOESNOTEXIST", "321") -$c = defaultenv("EMPTY", "456") +$x = sys.getenv("TEST") +$y = sys.getenv("DOESNOTEXIST") +$z = sys.getenv("EMPTY") -$t = hasenv("TEST") -$f = hasenv("DOESNOTEXIST") +$a = sys.defaultenv("TEST", "321") +$b = sys.defaultenv("DOESNOTEXIST", "321") +$c = sys.defaultenv("EMPTY", "456") -$env = env() +$t = sys.hasenv("TEST") +$f = sys.hasenv("DOESNOTEXIST") + +$env = sys.env() $m = maplookup($env, "TEST", "321") file "${tmpdir}/environ" { - content => printf("%s,%s,%s:%s,%s,%s:%t,%t:%s", $x, $y, $z, $a, $b, $c, $t, $f, $m), + content => fmt.printf("%s,%s,%s:%s,%s,%s:%t,%t:%s", $x, $y, $z, $a, $b, $c, $t, $f, $m), } diff --git a/test/shell/load0.sh b/test/shell/load0.sh index 169306d4..94b0eadd 100755 --- a/test/shell/load0.sh +++ b/test/shell/load0.sh @@ -25,14 +25,17 @@ if [[ ! "$tmpdir" =~ "/tmp" ]]; then fi cat > "$tmpdir/load0.mcl" < printf("load average: %f, %f, %f", \$x1, \$x5, \$x15), + content => fmt.printf("load average: %f, %f, %f", \$x1, \$x5, \$x15), state => "exists", } EOF