diff --git a/cmd/prefixes.go b/cmd/prefixes.go
new file mode 100644
index 0000000..31b1472
--- /dev/null
+++ b/cmd/prefixes.go
@@ -0,0 +1,49 @@
+package cmd
+
+import (
+ wisski_distillery "github.com/FAU-CDI/wisski-distillery"
+ "github.com/FAU-CDI/wisski-distillery/internal/core"
+ "github.com/tkw1536/goprogram/exit"
+)
+
+// Prefixes is then 'prefixes' command
+var Prefixes wisski_distillery.Command = prefixes{}
+
+type prefixes struct {
+ Positionals struct {
+ Slug string `positional-arg-name:"SLUG" required:"1-1" description:"slug of instance to show prefixes for"`
+ } `positional-args:"true"`
+}
+
+func (prefixes) Description() wisski_distillery.Description {
+ return wisski_distillery.Description{
+ Requirements: core.Requirements{
+ NeedsDistillery: true,
+ },
+ Command: "prefixes",
+ Description: "List all Prefixes for a specific WissKI",
+ }
+}
+
+var errPrefixesGeneric = exit.Error{
+ ExitCode: exit.ExitGeneric,
+ Message: "Unable to load prefixes",
+}
+
+func (p prefixes) Run(context wisski_distillery.Context) error {
+ instance, err := context.Environment.Instances().WissKI(p.Positionals.Slug)
+ if err != nil {
+ return err
+ }
+
+ prefixes, err := instance.Prefixes()
+ if err != nil {
+ return errPrefixesGeneric.Wrap(err)
+ }
+
+ for _, p := range prefixes {
+ context.Println(p)
+ }
+
+ return nil
+}
diff --git a/cmd/wdcli/main.go b/cmd/wdcli/main.go
index 3033788..a612f3d 100644
--- a/cmd/wdcli/main.go
+++ b/cmd/wdcli/main.go
@@ -43,6 +43,7 @@ func init() {
wdcli.Register(cmd.BlindUpdate)
wdcli.Register(cmd.UpdatePrefixConfig) // TODO: Move into post-instance configuration
wdcli.Register(cmd.Pathbuilders)
+ wdcli.Register(cmd.Prefixes)
// backup & cron
wdcli.Register(cmd.Snapshot)
diff --git a/internal/component/control/html/instance.html b/internal/component/control/html/instance.html
index 2c2017f..00ff40d 100644
--- a/internal/component/control/html/instance.html
+++ b/internal/component/control/html/instance.html
@@ -12,6 +12,13 @@
Slug: {{ .Info.Slug }}
URL: {{ .Info.URL }}
+ Resolver Prefixes:
+
+ {{ range .Info.Prefixes }}
+ {{ . }}
+ {{ end}}
+
+
Running: {{ .Info.Running }}
diff --git a/internal/component/instances/instances/barrel/wisskiutils/list_uri_prefixes.php b/internal/component/instances/instances/barrel/wisskiutils/list_uri_prefixes.php
deleted file mode 100644
index f9a52ba..0000000
--- a/internal/component/instances/instances/barrel/wisskiutils/list_uri_prefixes.php
+++ /dev/null
@@ -1,19 +0,0 @@
-getStorage('wisski_salz_adapter');
-foreach ($storage->loadMultiple() as $adapter) {
- // read the configuration, and check if we have a default graph
- $conf = $adapter->getEngine()->getConfiguration();
- if(!array_key_exists('default_graph', $conf)) {
- continue;
- }
-
- // and echo it out
- echo $conf['default_graph'] . "\n";
-}
\ No newline at end of file
diff --git a/internal/component/instances/php/list_uri_prefixes.php b/internal/component/instances/php/list_uri_prefixes.php
new file mode 100644
index 0000000..6df7248
--- /dev/null
+++ b/internal/component/instances/php/list_uri_prefixes.php
@@ -0,0 +1,48 @@
+getStorage('wisski_salz_adapter');
+ foreach ($storage->loadMultiple() as $adapter) {
+ // load all the prefixes from the triplestore
+ $engine = $adapter->getEngine();
+ getTriplestorePrefixes($adapter->getEngine(), $prefixes);
+
+ // read the configuration to check if we have a default graph
+ $conf = $engine->getConfiguration();
+ if(!array_key_exists('default_graph', $conf)) {
+ continue;
+ }
+ $prefixes[] = $conf['default_graph'];
+ }
+ return $prefixes;
+}
+
+function getTriplestorePrefixes($engine, &$prefixes) {
+ $results = $engine->directQuery('
+ select distinct ?base where {
+ {
+ select distinct ?iri where {
+ {
+ select distinct (?s as ?iri) { ?s ?p ?o }
+ } union {
+ select distinct (?o as ?iri) { ?s ?p ?o FILTER(isiri(?o)) }
+ }
+ }
+ }
+ BIND(replace(str(?iri), "/[^/]*/?$", "/") as ?base)
+ FILTER(!REGEX(?base, "/wisski/navigate/[\\\\d]+/$"))
+ } ORDER BY ?base');
+ if (!$results) return FALSE;
+
+ foreach($results as $result) {
+ $prefixes[] = $result->base->getValue();
+ }
+
+ return TRUE;
+}
+
diff --git a/internal/component/instances/wisski_prefix.go b/internal/component/instances/wisski_prefix.go
index df255a9..8ff7b6d 100644
--- a/internal/component/instances/wisski_prefix.go
+++ b/internal/component/instances/wisski_prefix.go
@@ -7,7 +7,10 @@ import (
"strings"
"github.com/FAU-CDI/wisski-distillery/pkg/fsx"
+ "github.com/FAU-CDI/wisski-distillery/pkg/slicesx"
"github.com/tkw1536/goprogram/stream"
+
+ _ "embed"
)
// NoPrefix checks if this WissKI instance is excluded from generating prefixes.
@@ -16,6 +19,43 @@ func (wisski *WissKI) NoPrefix() bool {
return fsx.IsFile(wisski.instances.Environment, filepath.Join(wisski.FilesystemBase, "prefixes.skip"))
}
+//go:embed php/list_uri_prefixes.php
+var listURIPrefixesPHP string
+
+// Prefixes returns the prefixes
+func (wisski *WissKI) Prefixes() (prefixes []string, err error) {
+ // get all the ugly prefixes
+ err = wisski.ExecPHPScript(stream.FromEnv(), &prefixes, listURIPrefixesPHP, "list_prefixes")
+ if err != nil {
+ return nil, err
+ }
+
+ // filter out sequential prefixes
+ prefixes = slicesx.NonSequential(prefixes, func(prev, now string) bool {
+ return strings.HasPrefix(now, prev)
+ })
+
+ // filter out blocked prefixes
+ return slicesx.Filter(prefixes, func(uri string) bool { return !IsNonServedURI(uri) }), nil
+}
+
+// TODO: Eventually move this into a configuration file.
+// But for now this is fine
+var blockedURIs = []string{
+ "http://erlangen-crm.org/",
+ "http://www.w3.org/",
+ "xsd:",
+}
+
+func IsNonServedURI(candidate string) bool {
+ return slicesx.Any(
+ blockedURIs,
+ func(prefix string) bool {
+ return strings.HasPrefix(candidate, prefix)
+ },
+ )
+}
+
var errPrefixExecFailed = errors.New("PrefixConfig: Failed to call list_uri_prefixes")
// PrefixConfig returns the prefix config belonging to this instance.
@@ -32,10 +72,15 @@ func (wisski *WissKI) PrefixConfig() (config string, err error) {
builder.WriteString("\n")
// default prefixes
- wu := stream.NewIOStream(&builder, nil, nil, 0)
- code, err := wisski.Barrel().Exec(wu, "barrel", "/bin/bash", "/user_shell.sh", "-c", "drush php:script /wisskiutils/list_uri_prefixes.php")
- if err != nil || code != 0 {
- return "", errPrefixExecFailed
+ prefixes, err := wisski.Prefixes()
+ if err != nil {
+ return "", err
+ }
+
+ // predefined prefixes
+ for _, prefix := range prefixes {
+ builder.WriteString(prefix)
+ builder.WriteRune('\n')
}
// custom prefixes
diff --git a/internal/component/instances/wisski_status.go b/internal/component/instances/wisski_status.go
index b8a83b3..d8c8953 100644
--- a/internal/component/instances/wisski_status.go
+++ b/internal/component/instances/wisski_status.go
@@ -17,6 +17,7 @@ type Info struct {
Running bool // is the instance running?
Pathbuilders map[string]string // list of pathbuilders
+ Prefixes []string // list of uri prefixes
}
// Info returns information about this WissKI instance.
@@ -46,6 +47,10 @@ func (wisski *WissKI) Info(quick bool) (info Info, err error) {
info.LastRebuild, _ = wisski.LastRebuild()
return nil
})
+ group.Go(func() (err error) {
+ info.Prefixes, _ = wisski.Prefixes()
+ return nil
+ })
}
err = group.Wait()
diff --git a/pkg/slicesx/slicesx.go b/pkg/slicesx/slicesx.go
new file mode 100644
index 0000000..37d0ef6
--- /dev/null
+++ b/pkg/slicesx/slicesx.go
@@ -0,0 +1,50 @@
+package slicesx
+
+import (
+ "golang.org/x/exp/constraints"
+ "golang.org/x/exp/slices"
+)
+
+// Any returns true if test returns true for any of values.
+func Any[T any](values []T, test func(T) bool) bool {
+ for _, v := range values {
+ if test(v) {
+ return true
+ }
+ }
+ return false
+}
+
+// Filter filters values in place
+func Filter[T any](values []T, filter func(T) bool) []T {
+ results := values[:0]
+ for _, value := range values {
+ if filter(value) {
+ results = append(results, value)
+ }
+ }
+ return results
+}
+
+// NonSequential sorts values, and then removes elements for which test() returns true.
+// NonSequential does not re-allocate, but uses the existing slice.
+func NonSequential[T constraints.Ordered](values []T, test func(prev, current T) bool) []T {
+ if len(values) < 2 {
+ return values
+ }
+
+ // sort the values and make a results array
+ slices.Sort(values)
+ results := values[:1]
+
+ // do the filter loop
+ prev := results[0]
+ for _, current := range values[1:] {
+ if !test(prev, current) {
+ results = append(results, current)
+ }
+ prev = current
+ }
+
+ return results
+}