From bd3bc6769547e89dc5d3e255aa4babc2cc5ffe48 Mon Sep 17 00:00:00 2001 From: Patrick Spek Date: Sat, 4 Jul 2020 13:46:25 +0200 Subject: Overhaul Config to 3.0.0 --- lib/Config.pm6 | 654 +++++++++++++++++++++++------------ lib/Config/Parser.pm6 | 29 +- lib/Config/Parser/NULL.pm6 | 45 +-- lib/X/Config/AbstractParser.rakumod | 36 ++ lib/X/Config/FileNoExtension.rakumod | 38 ++ lib/X/Config/FileNotFound.rakumod | 38 ++ lib/X/Config/MissingParser.rakumod | 38 ++ lib/X/Config/NotSupported.rakumod | 39 +++ 8 files changed, 653 insertions(+), 264 deletions(-) create mode 100644 lib/X/Config/AbstractParser.rakumod create mode 100644 lib/X/Config/FileNoExtension.rakumod create mode 100644 lib/X/Config/FileNotFound.rakumod create mode 100644 lib/X/Config/MissingParser.rakumod create mode 100644 lib/X/Config/NotSupported.rakumod (limited to 'lib') diff --git a/lib/Config.pm6 b/lib/Config.pm6 index 3db882e..65ba7b6 100644 --- a/lib/Config.pm6 +++ b/lib/Config.pm6 @@ -1,315 +1,539 @@ #! /usr/bin/env false -use v6.c; +use v6.d; -use Config::Exception::FileNotFoundException; -use Config::Exception::MissingParserException; -use Config::Parser; use Hash::Merge; +use IO::Path::XDG; +use Log; -unit class Config is Associative; +use Config::Parser; +use X::Config::AbstractParser; +use X::Config::FileNoExtension; +use X::Config::FileNotFound; +use X::Config::MissingParser; +use X::Config::NotSupported; -has Hash $!content = {}; -has Str $.path = ""; -has Str $.parser = ""; +#| A simple, flexible Config class. +unit class Config does Associative; -#| Clear the config. -method clear() -{ - $!content = {}; - $!path = ""; - $!parser = ""; -} +#| The template for the configuration data structure. +has %.template; -method clone ( - --> Config -) { - Config.new(:$!path, :$!parser).read: $!content; -} +#| The actual values to represent the configuration items. +has %.data; -#| Return the entire config hash. -multi method get() -{ - return $!content; -} +#| The name for this Config object. The name is used for autodiscovery of +#| configuration values from the shell environment and standard configuration +#| file locations. +has Str $.name; -#| Fallback method in case the key is Nil. Will always return the default -#| value. -multi method get(Nil $key, Any $default = Nil) -{ - $default; -} +#| Create a new Config object. +method new ( + #| The template for the Config object. In its simplest form, this is + #| just a Hash with all keys you want, including default values. + %template = {}, -#| Get a value from the config object. To get a nested -#| key, use a . to descent a level. -multi method get(Str $key, Any $default = Nil) -{ - self.get($key.split(".").list, $default); -} + #| The name of the name. + Str :$name, -#| Get a value from the config object using a list -#| to indicate the nested key to get. -multi method get(List $keyparts, Any $default = Nil) -{ - my $index = $!content; + #| Immediately set some Config values. This is rarely desired for most + #| use-cases. + :%data? is copy, - for $keyparts.list -> $part { - return $default unless defined($index{$part}); + #| Try to read configuration data from the shell's environment. + Bool:D :$from-env = True, - $index = $index{$part}; - } + #| Try to read configuration files from XDG_CONFIG_HOME and + #| XDG_CONFIG_DIRS. + Bool:D :$from-xdg = True, - $index; -} + --> Config:D +) { + %data ||= %template; -#| Get the name of the parser module to use for the -#| given path. -multi method get-parser(Str $path, Str $parser = "" --> Str) -{ - return $parser if $parser ne ""; - return $!parser if $!parser ne ""; + %data = merge-hash(%data, self!read-from-env(%template, :$name)) if $from-env && $name; + %data = merge-hash(%data, self!read-from-xdg-files(:$name)) if $from-xdg && $name; - my $type = self.get-parser-type($path); + self.bless( + :%template, + :%data, + ) +} - "Config::Parser::" ~ $type; +#| Retrieve the entire Config object as a Hash. +multi method get ( + --> Hash:D +) { + %!data } -multi method get-parser(IO::Path $path, Str $parser = "" --> Str) -{ - samewith($path.absolute, $parser) +#| Retrieve a specific Config item at $key. +multi method get ( + #| The key to check at. + Str:D $key, + + #| A default value, in case the key does not exist. + Any $default = Nil, + + --> Any +) { + self.get($key.split('.'), $default) } -#| Get the type of parser required for the given path. -multi method get-parser-type(Str $path --> Str) -{ - given ($path) { - when .ends-with(".yml") { return "yaml"; }; - } +#| Retrieve a specific Config item specified by a list of nested keys. +multi method get ( + #| The list of nested keys. + @parts, - my $file = $path; + #| A default value, in case the key does not exist. + Any $default = Nil, - if (defined($path.index("/"))) { - $file = $path.split("/")[*-1]; - } + --> Any +) { + my $index = %!data; - if (defined($file.index("."))) { - return $file.split(".")[*-1].lc; - } + for @parts { + return $default unless $index{$_}:exists; - return ""; -} + $index = $index{$_}; + } -multi method get-parser-type(IO::Path $path --> Str) -{ - samewith($path.absolute) + $index; } -#| Check wether a given key exists. -multi method has(Str $key) { - self.has($key.split(".").list); +#| Check whether the Config object has a value at $key. +multi method has ( + #| The key to check the existence of. + Str:D $key, + + --> Bool:D +) { + self.has($key.split('.')); } -#| Check wether a given key exists using a list to supply -#| the nested key to check. -multi method has(List $keyparts) -{ - my $index = $!content; +#| Check whether the Config object has a value at the location specified in +#| @parts. +multi method has ( + #| A list of the parts of the key to check the existence of. + @parts, + + --> Bool:D +) { + my $index = %!data; - for $keyparts.list -> $part { - return False unless defined($index{$part}); + for @parts { + return False unless $index{$_}:exists; + last if $index ~~ Scalar; - $index = $index{$part}; - } + $index = $index{$_}; + } - defined($index); + True; } -#| Return a sorted list of all available keys in the current Config. -method keys() -{ - my @keys; +#| Get a flat list of all keys in the Config object. +method keys ( + --> Iterable:D +) { + self!recurse-keys(%!data) +} - for $!content.keys -> $key { - @keys.append: self.extract-keys($key); - } +#| Create a new Config object with %data merged in. +multi method read ( + #| A Hash of configuration data to merge in. + %data, - @keys.sort; + --> Config:D +) { + Config.new( + %!template, + :$!name, + data => merge-hash(%!data, %data), + :!read-from-env, + :!read-from-xdg, + ) } -#| Reload the configuration. Requires the configuration to -#| have been loaded from a file. +#| Update the Config values from a given file. multi method read ( - --> Config + #| The path to the configuration file. + IO() $path, + + #| An explicit Config::Parser class to use. If left empty, it will be + #| guessed based on the file's extension. + Config::Parser:U $parser? is copy, + + --> Config:D ) { - die "Configuration was not loaded from a file, cannot reload" if $!path eq ""; + X::Config::FileNotFound.new(:$path).throw if !$path.f; + + # We need an implementation of Config::Parser, not the base + # Config::Parser role. + if ($parser.^name eq 'Config::Parser') { + $parser = self!parser-for-file($path); + } - self.read($!path); + self.read(self!read-from-file($path, $parser)); } -#| Load a configuration file from the given path. Optionally -#| set a parser module name to use. If not set, Config will -#| attempt to deduce the parser to use. +#| Update the Config values from a list of files. multi method read ( - Str $path, - Str $parser = "", - Bool :$skip-not-found = False, - --> Config + #| A list of paths to configuration files to load. + @paths, + + #| An explicit Config::Parser class to use. If left empty, it will be + #| guessed based on each file's extension. + Config::Parser:U $parser? is copy, + + #| Silently skip over files which don't exist. + Bool:D :$skip-not-found = False, + + --> Config:D ) { - samewith($path.IO, $parser, :$skip-not-found) + my %data = %!data; + + for @paths -> $path { + next if !$path.f && $skip-not-found; + X::Config::FileNotFound.new(:$path).throw if !$path.f; + + # We need an implementation of Config::Parser, not the base + # Config::Parser role. + if ($parser.^name eq 'Config::Parser') { + $parser = self!parser-for-file($path); + } + + %data = merge-hash(%data, self!read-from-file($path, $parser)); + } + + self.read(%data); } -#| Load a configuration file from the given path. Optionally -#| set a parser module name to use. If not set, Config will -#| attempt to deduce the parser to use. -multi method read ( - IO::Path $path, - Str $parser = "", - Bool :$skip-not-found = False, - --> Config -) { - if (!$path.f && !$skip-not-found) { - Config::Exception::FileNotFoundException.new(path => $path.absolute).throw(); - } +#| Set a $key to $value. +multi method set ( + #| The key to change the value of. + Str:D $key, - $!parser = self.get-parser($path, $parser); + #| The new value of the key. + $value, - try { - CATCH { - when X::CompUnit::UnsatisfiedDependency { - Config::Exception::MissingParserException.new( - parser => $!parser - ).throw(); - } - } + --> Config:D +) { + self.set($key.split('.'), $value); +} - require ::($!parser); +#| Set a specific Config item specified by a list of nested keys to $value. +multi method set ( + #| A list of parts of the key to change the value of. + @parts, - self.read(::($!parser).read($path)); - } + #| THe new value of the key. + $value, - self; + --> Config:D +) { + my %data = %!data; + my $index := %data; + + for @parts { + $index := $index{$_}; + } + + $index = $value; + + Config.new( + %!template, + :$!name, + :%data, + :!read-from-env, + :!read-from-xdg, + ) } -#| Read a list of paths. Will fail on the first file that fails to load for -#| whatever reason. -multi method read ( - List $paths, - Str $parser = "", - Bool :$skip-not-found = False, - --> Config +#| Remove a key from the Config object. +multi method unset ( + #| The key to remove. + Str:D $key, + + --> Config:D ) { - for $paths.list -> $path { - samewith($path, $parser, :$skip-not-found); - } + self.unset($key.split('.')); +} + +#| Remove a key from the Config object. +multi method unset ( + #| A list of parts of the key to remove. + @parts, - self; + --> Config:D +) { + my %data = %!data; + my $index := %data; + + for 0..(@parts.elems - 2) { + $index := $index{@parts[$_]}; + } + + $index{@parts[*-1]}:delete; + + Config.new( + %!template, + :$!name, + :%data, + :!read-from-env, + :!read-from-xdg, + ) } -#| Read a plain Hash into the configuration. -multi method read ( - Hash $hash, - --> Config +#| Write the Config object to a file. +method write ( + #| The path to write the configuration values to. + IO::Path:D $path, + + #| The Config::Parser object to use. + Config::Parser:U $parser, + + --> Bool:D ) { - $!content = merge-hash($!content, $hash); + if ($parser.^name eq 'Config::Parser') { + X::Config::AbstractParser.new.throw; + } - return self; + $parser.write($path, %!data); } -#| Set a single key to a given value; -multi method set(Str $key, Any $value) -{ - self.set($key.split(".").list, $value); -} +#| Return the default Config::Parser implementation for the given file. This is +#| based solely on the file's extension. +method !parser-for-file ( + #| The path to the file. + IO::Path:D $path, -multi method set(List $keyparts, Any $value) -{ - my $index := $!content; + --> Config::Parser:U +) { + my $extension = $path.extension; - for $keyparts.list -> $part { - $index{$part} = {} unless defined($index{$part}); + X::Config::FileNoExtension.new(:$path).throw unless $extension; - $index := $index{$part}; - } + my $parser = "Config::Parser::$extension"; - $index = $value; + .info("Loading $parser") with $Log::instance; - self; -} + try require ::($parser); -multi method unset(Str $key) -{ - self.unset($key.split(".").Array); -} + return ::($parser) unless ::($parser) ~~ Failure; -multi method unset(@parts) -{ - my %index := $!content; - my $target = @parts.pop; + if (::($parser) ~~ Failure) { + given (::($parser).exception) { + when X::NoSuchSymbol { + X::Config::MissingParser.new(:$parser).throw; + } + default { + .alert("Failed to load $parser!") with $Log::instance; + } + } + } - for @parts.list -> $part { - %index{$part} = {} unless defined(%index{$part}); + Config::Parser +} - %index := %index{$part}; - } +#| Read configuration data from environment variables. +method !read-from-env ( + #| The template in use by the Config object. This is needed to generate + #| a list of the keys to check for. + %template, - %index{$target}:delete if %index{$target}:exists; + #| The name of the application. This will be used to prefix the + #| environment variables used. + Str:D :$name is copy, - self; + --> Hash:D +) { + my %data; + + $name ~= '.'; + + self!recurse-keys(%template) + .sort + .map(sub ($key) { + # Convert $key to something more reminiscient of + # Shell-style variable names. + my $var = "$name$key" + .subst('.', '_', :g) + .uc + ; + + # Check if the env var exists. + .debug("Checking \$$var") with $Log::instance; + + return unless %*ENV{$var}:exists; + + # Insert the value from the env var into the data + # structure. + .info("Using \$$var for $key") with $Log::instance; + + my @key-parts = $key.split('.'); + my $index := %data; + my $cast := %template; + + for @key-parts { + last if $index ~~ Scalar; + + $index := $index{$_}; + $cast := $cast{$_}; + } + + # Cast the value appropriately + given ($cast.WHAT) { + when Numeric { $index = +%*ENV{$var} } + when Str { $index = ~%*ENV{$var} } + when Bool { $index = ?%*ENV{$var} } + default { $index = %*ENV{$var} } + } + }) + ; + + %data; } -#| Write the current configuration to the given path. If -#| no parser is given, it tries to use the parser that -#| was used when loading the configuration. -multi method write(IO::Path $path, Str $parser = "") -{ - samewith($path.absolute, $parser) +#| Read configuration data from a file. +method !read-from-file ( + #| The path to the file to read. + IO::Path $file, + + #| An explicit Config::Parser to parse the file with. If left empty, + #| the Config::Parser implementation to use will be deduced from the + #| file's extension. + Config::Parser:U $parser, + + --> Hash:D +) { + if ($parser.^name eq 'Config::Parser') { + X::Config::AbstractParser.new.throw; + } + + # Use the Parser to read the file contents. + $parser.read($file.absolute) } -multi method write(Str $path, Str $parser = "") -{ - my $chosen-parser = self.get-parser($path, $parser); +#| Check the XDG_CONFIG_DIRS and XDG_CONFIG_HOME locations for configuration +#| files, and load any that are found. +method !read-from-xdg-files ( + #| The name of the application. This will be used for the filename to + #| look for. + Str:D :$name, + + --> Hash:D +) { + my %data; - require ::($chosen-parser); - return ::($chosen-parser).write($path, $!content); + # Generate a list of all potential config file locations, based on the + # XDG base directory spec. + my @files = xdg-config-dirs() + .reverse + .map(sub ($dir) { + (« $name "$name/config" » X~ < .json .toml .yaml >).map({ + $dir.add($_) + }).Slip + }) + ; + + # Check each file. + for @files -> $file { + .debug("Checking $file") with $Log::instance; + + # Nothing to do if the file doesn't exist. + next unless $file.f; + + .info("Reading config from $file") with $Log::instance; + + # Delegate to the file reader method. + %data = merge-hash(%data, self!read-from-file($file, self!parser-for-file($file))); + } + + %data; } -multi method AT-KEY(::?CLASS:D: $key) -{ - self.get($key); +#| Get a flat list of all available keys. +method !recurse-keys ( + #| The internal data Hash to generate a list of keys from. + %data, + + #| The prefix to use. Only relevant for the recursive nature of this + #| method. + $prefix = '', + + --> Iterable:D +) { + my @keys; + + for %data.keys -> $key { + if (%data{$key} ~~ Hash) { + @keys.append(self!recurse-keys(%data{$key}, "$key.")); + next; + } + + @keys.append("$prefix$key") + } + + @keys; } -multi method EXISTS-KEY(::?CLASS:D: $key) +#| Implementation for Associative role. This is set here for the friendly error +#| message that can be generated with it. +method ASSIGN-KEY (::?CLASS:D: $key, $new) { - self.has($key); + X::Config::NotSupported( + call => 'ASSIGN-KEY', + help => 'To set a key, use Config.set($key, $value)', + ).new.throw } -multi method DELETE-KEY(::?CLASS:D: $key) +#| Implementation for Associative role. This allows the user to retrieve a +#| Configuration key in the same way they'd retrieve an element from a Hash. +method AT-KEY (::?CLASS:D: $key) { - self.unset($key); + self.get($key) } -multi method ASSIGN-KEY(::?CLASS:D: $key, $new) +#| Implementation for Associative role. This is set here for the friendly error +#| message that can be generated with it. +method DELETE-KEY (::?CLASS:D: $key) { - self.set($key, $new); + X::Config::NotSupported( + call => 'DELETE-KEY', + help => 'To remove a key, use Config.unset($key)', + ).new.throw } -multi method BIND-KEY(::?CLASS:D: $key, \new) +#| Implementation for Associative role. This allows the user to check for the +#| existence of a Configuration key in the same way they'd check existence of a +#| Hash key. +method EXISTS-KEY (::?CLASS:D: $key) { - self.set($key, new); + self.has($key) } -submethod extract-keys($key) -{ - my $value = self.get($key); - return $key if $value !~~ Iterable; +=begin pod - my @keys; +=NAME Config +=VERSION 3.0.0 +=AUTHOR Patrick Spek - for $value.keys -> $nested-key { - @keys.append: self.extract-keys("{$key}.{$nested-key}"); - } +=begin LICENSE +Copyright © 2020 - return @keys; -} +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation, version 3. + +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 Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this program. If not, see http://www.gnu.org/licenses/. +=end LICENSE + +=end pod -# vim: ft=perl6 sw=4 ts=4 et +# vim: ft=perl6 sw=8 ts=8 noet diff --git a/lib/Config/Parser.pm6 b/lib/Config/Parser.pm6 index d067513..5d0343b 100644 --- a/lib/Config/Parser.pm6 +++ b/lib/Config/Parser.pm6 @@ -1,26 +1,13 @@ #! /usr/bin/env false -use v6.c; +use v6.d; -use Config::Exception::UnimplementedMethodException; +unit class Config::Parser; -class Config::Parser -{ - #| Attempt to read the file at a given $path, and returns its - #| parsed contents as a Hash. - method read(Str $path --> Hash) - { - Config::Exception::UnimplementedMethodException.new( - method => "read" - ).throw(); - } +#| Attempt to read the file at a given $path, and returns its +#| parsed contents as a Hash. +method read(IO() $path --> Hash) { … } - #| Attempt to write the $config Hash at a given $path. Returns - #| True on success, False on failure. - method write(Str $path, Hash $config --> Bool) - { - Config::Exception::UnimplementedMethodException.new( - method => "write" - ).throw(); - } -} +#| Attempt to write the $config Hash at a given $path. Returns +#| True on success, False on failure. +method write(IO() $path, Hash $config --> Bool) { … } diff --git a/lib/Config/Parser/NULL.pm6 b/lib/Config/Parser/NULL.pm6 index 2352cfc..2845e3c 100644 --- a/lib/Config/Parser/NULL.pm6 +++ b/lib/Config/Parser/NULL.pm6 @@ -1,41 +1,30 @@ #! /usr/bin/env false -use v6.c; +use v6.d; use Config::Parser; #| The Config::Parser::NULL is a parser to mock with for testing purposes. #| It exposes an additional method, set-config, so you can set a config #| Hash to return when calling `read`. -class Config::Parser::NULL is Config::Parser -{ - my %mock-config = (); - - #| Return the mock config, skipping the file entirely. - multi method read(Str $path --> Hash) - { - %mock-config; - } +unit class Config::Parser::NULL is Config::Parser; - multi method read(IO::Path $path --> Hash) - { - %mock-config; - } +my %mock-config; - #| Set the mock config to return on read. - method set-config(Hash $config) - { - %mock-config = $config; - } +#| Return the mock config, skipping the file entirely. +multi method read(IO() $path --> Hash) +{ + %mock-config; +} - #| Return True, as if writing succeeded. - multi method write(Str $path, Hash $config --> Bool) - { - True; - } +#| Set the mock config to return on read. +method set-config(Hash $config) +{ + %mock-config = $config; +} - multi method write(IO::Path $path, Hash $config --> Bool) - { - True; - } +#| Return True, as if writing succeeded. +multi method write(IO() $path, Hash $config --> Bool) +{ + True; } diff --git a/lib/X/Config/AbstractParser.rakumod b/lib/X/Config/AbstractParser.rakumod new file mode 100644 index 0000000..712f5af --- /dev/null +++ b/lib/X/Config/AbstractParser.rakumod @@ -0,0 +1,36 @@ +#! /usr/bin/env false + +use v6.d; + +unit class X::Config::AbstractParser is Exception; + +method message +{ + 'You must use an implementation class of Config::Parser, not the base role' +} + +=begin pod + +=NAME X::Config::AbstractParser +=VERSION 3.0.0 +=AUTHOR Patrick Spek + +=begin LICENSE +Copyright © 2020 + +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation, version 3. + +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 Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this program. If not, see http://www.gnu.org/licenses/. +=end LICENSE + +=end pod + +# vim: ft=raku noet sw=8 ts=8 diff --git a/lib/X/Config/FileNoExtension.rakumod b/lib/X/Config/FileNoExtension.rakumod new file mode 100644 index 0000000..7bf30f4 --- /dev/null +++ b/lib/X/Config/FileNoExtension.rakumod @@ -0,0 +1,38 @@ +#! /usr/bin/env false + +use v6.d; + +unit class X::Config::FileNoExtension is Exception; + +has IO::Path $.path; + +method message +{ + "The file at $!path does not have an extension, so no Config::Parser implementation can be deduced for it. Try setting an explicit parser." +} + +=begin pod + +=NAME X::Config::FileNoExtension +=VERSION 3.0.0 +=AUTHOR Patrick Spek + +=begin LICENSE +Copyright © 2020 + +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation, version 3. + +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 Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this program. If not, see http://www.gnu.org/licenses/. +=end LICENSE + +=end pod + +# vim: ft=raku noet sw=8 ts=8 diff --git a/lib/X/Config/FileNotFound.rakumod b/lib/X/Config/FileNotFound.rakumod new file mode 100644 index 0000000..68ae347 --- /dev/null +++ b/lib/X/Config/FileNotFound.rakumod @@ -0,0 +1,38 @@ +#! /usr/bin/env false + +use v6.d; + +unit class X::Config::FileNotFound is Exception; + +has IO::Path $.path; + +method message +{ + "Could not find file at $!path.absolute()" +} + +=begin pod + +=NAME X::Config::FileNotFound +=VERSION 3.0.0 +=AUTHOR Patrick Spek + +=begin LICENSE +Copyright © 2020 + +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation, version 3. + +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 Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this program. If not, see http://www.gnu.org/licenses/. +=end LICENSE + +=end pod + +# vim: ft=raku noet sw=8 ts=8 diff --git a/lib/X/Config/MissingParser.rakumod b/lib/X/Config/MissingParser.rakumod new file mode 100644 index 0000000..9d11cd5 --- /dev/null +++ b/lib/X/Config/MissingParser.rakumod @@ -0,0 +1,38 @@ +#! /usr/bin/env false + +use v6.d; + +unit class X::Config::MissingParser is Exception; + +has Str $.parser; + +method message +{ + "$!parser is not a valid parser. Are you sure its installed?" +} + +=begin pod + +=NAME X::Config::MissingParser +=VERSION 3.0.0 +=AUTHOR Patrick Spek + +=begin LICENSE +Copyright © 2020 + +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation, version 3. + +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 Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this program. If not, see http://www.gnu.org/licenses/. +=end LICENSE + +=end pod + +# vim: ft=raku noet sw=8 ts=8 diff --git a/lib/X/Config/NotSupported.rakumod b/lib/X/Config/NotSupported.rakumod new file mode 100644 index 0000000..f5c2d84 --- /dev/null +++ b/lib/X/Config/NotSupported.rakumod @@ -0,0 +1,39 @@ +#! /usr/bin/env false + +use v6.d; + +unit class X::Config::NotSupported is Exception; + +has Str $.call; +has Str $.help; + +method message +{ + $!help +} + +=begin pod + +=NAME X::Config::NotSupported +=VERSION 3.0.0 +=AUTHOR Patrick Spek + +=begin LICENSE +Copyright © 2020 + +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation, version 3. + +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 Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this program. If not, see http://www.gnu.org/licenses/. +=end LICENSE + +=end pod + +# vim: ft=raku noet sw=8 ts=8 -- cgit v1.1