A Raku module for managing the users GUI Editor preferences in a variety of programs.

# GUI::Editors

## Table of Contents

- NAME

- AUTHOR

- VERSION

- TITLE

- SUBTITLE

- COPYRIGHT

- Introduction

- $editor-config

- @config-files

- grammar Editors & action class EditorsActions

- grammar EditorLine & class EditorLineActions

- grammar OverrideGUIEditor & actions class OverrideGUIEditorActions

- Some useful variables

- $GUI_EDITOR

- $GUI_EDITOR

- $VISUAL

- $EDITOR

- @GUIEDITORS

- @gui-editors

- @default-editors

- @override-gui_editor or on raku.land @override-gui_editor

- $override-GUI_EDITOR or on raku.land $override-GUI_EDITOR

- In `init-gui-editors`

- $editor

- edit-configs()

- Editor functions

- list-editors(…)

- editors-stats(…)

- BadEditor

- set-editor

- add-gui-editor(…)

- set-override-GUI_EDITOR(…) or on raku.land set-override-GUI_EDITOR(…)

- backup-editors(…)

- restore-editors(…)

- list-editors-backups(…)

- backups-menu-restore-editors(…)

- edit-files(…)

# NAME

GUI::Editors

# COPYRIGHT

LGPL V3.0+ LICENSE

Top of Document

# Introduction

A Raku module for managing the users GUI Editor preferences in a variety of programs.

## $editor-config

A constant which contains the location of the users editors file

    # the home dir #
    constant $home = %*ENV<HOME>.Str();

    # config files
    constant $editor-config is export = "$home/.local/share/gui-editors";

NB: the `$home` is the value of the users HOME environment variable.

Top of Document

## @config-files

An array containing the configuration files of the program, by default it is set to contain editors the editors configuration file the remainder should be added by `init-gui-editors(...)` the initialization procedure for the module.

    # The config files to test for #
    my Str:D @config-files = qw{editors};

    sub config-files( --> Array[Str:D]) is export {
        return @config-files;
    }

## @guieditors

An array of known GUI editors.

    my Str:D @guieditors;

    sub guieditors( --> Array[Str:D]) is export {
        return @guieditors;
    }

Top of Document

## grammar Editors & action class EditorsActions

    grammar Editors is BasePaths is export {
        regex TOP                 { [ <line> [ \v+ <line> ]* \v* ]? }
        regex line                { [ <white-space-line> || <override-gui_editor> || <config-line> || <editor-to-use> || <comment-line> ] }
        regex white-space-line    { ^^ \h* $$ }
        regex override-gui_editor { ^^ \h* 'override' \h+ 'GUI_EDITOR' [ \h+ '#' <comment> ]? \h* $$ }
        regex comment-line        { ^^ \h* '#' <-[\v]>* $$ }
        regex config-line         { ^^ \h* 'guieditors' \h* '+'? '=' \h* <editor> \h* [ '#' <comment> \h* ]? $$ }
        regex editor-to-use       { ^^ \h* 'editor' \h* ':'? '=' \h* <editor> \h* [ '#' <comment> \h* ]? $$ }
        regex editor              { <editor-name> || <base-path> <editor-name> }
        regex comment             { <-[\n]>* }
        token editor-name         { <with-other-stuff> }
    }

    class EditorsActions does BasePathsActions is export {
        method white-space-line($/) {
            my %wspln = type => 'white-space-line', value => ~$/;
            make %wspln;
        }
        method comment-line($/) {
            my %comln = type => 'comment-line', value => ~$/;
            make %comln;
        }
        #token editor-name         { <with-other-stuff> }
        method editor-name($/) {
            my $edname = $/<with-other-stuff>.made;
            make $edname;
        }
        method editor($/) {
            my $ed-name;
            if $/<base-path> {
                $ed-name = $/<base-path>.made ~ $/<editor-name>.made;
            } else {
                $ed-name = $/<editor-name>.made;
            }
            make $ed-name;
        }
        method comment($/) {
            my $comm = (~$/).trim;
            make $comm;
        }
        method config-line($/) {
            my %cfg-line = type => 'config-line', value => $/<editor>.made;
            if $/<comment> {
                my $com = $/<comment>.made;
                %cfg-line«comment» = $com;
            }
            make %cfg-line;
        }
        method editor-to-use($/) {
            my %editor-to-use = type => 'editor-to-use', value => $/<editor>.made;
            if $/<comment> {
                my $com = $/<comment>.made;
                %editor-to-use«comment» = $com;
            }
            make %editor-to-use;
        }
        method override-gui_editor($/) {
            my %override-gui_editor = type => 'override-gui_editor', :value;
            if $/<comment> {
                my $com = $/<comment>.made;
                %override-gui_editor«comment» = $com;
            }
            make %override-gui_editor;
        }
        method line($/) {
            my %ln;
            if $/<white-space-line> {
                %ln = $/<white-space-line>.made;
            } elsif $/<comment-line> {
                %ln = $/<comment-line>.made;
            } elsif $/<config-line> {
                %ln = $/<config-line>.made;
            } elsif $/<editor-to-use> {
                %ln = $/<editor-to-use>.made;
            } elsif $/<override-gui_editor> {
                %ln = $/<override-gui_editor>.made;
            }
            make %ln;
        }
        method TOP($made) {
            my @top = $made<line>».made;
            $made.make: @top;
        }
    } # class EditorsActions does BasePathsActions is export #

Top of Document

### grammar EditorLine & class EditorLineActions

A grammar and associated action class to parse and recognise the `editor := value # comment` lines in the editors file.

    grammar EditorLine is BasePaths is export {
        regex TOP                 { ^ \h* 'editor' \h* ':'? '=' \h* <editor> \h* [ '#' <comment> \h* ]? $ }
        regex editor              { <editor-name> || <base-path> <editor-name> }
        regex comment             { <-[\n]>* }
        token editor-name         { <with-other-stuff> }
    }

    class EditorLineActions does BasePathsActions is export {
        #token editor-name         { <with-other-stuff> }
        method editor-name($/) {
            my $edname = $/<with-other-stuff>.made;
            make $edname;
        }
        method editor($/) {
            my $ed-name;
            if $/<base-path> {
                $ed-name = $/<base-path>.made ~ '/' ~ $/<editor-name>.made;
            } else {
                $ed-name = $/<editor-name>.made;
            }
            make $ed-name;
        }
        method comment($/) {
            my $comm = (~$/).trim;
            make $comm;
        }
        method config-line($/) {
            my %cfg-line = type => 'config-line', value => $/<editor>.made;
            if $/<comment> {
                my $com = $/<comment>.made;
                %cfg-line«comment» = $com;
            }
            make %cfg-line;
        }
        method TOP($made) {
            my %top = type => 'editor-to-use', value => $made<editor>.made;
            if $made<comment> {
                my $com = $made<comment>.made;
                %top«comment» = $com;
            }
            $made.make: %top;
        }
    } # class EditorLineActions does BasePathsActions is export #

Top of Document

### grammar OverrideGUIEditor & actions class OverrideGUIEditorActions

A grammar to parse/recognise the `override GUI_EDITOR # comment` line.

    grammar OverrideGUIEditor is export {
        regex TOP     { ^ \h* [ <commented> \h* ]? 'override' \h+ 'GUI_EDITOR' [ \h+ '#' <comment> ]? \h* $ }
        regex comment { <-[\n]>* }
        token commented { '#' }
    }

    class OverrideGUIEditorActions is export {
        method comment($/) {
            my $comment = (~$/).trim;
            make $comment;
        }
        method commented($/) {
            my $commented = (~$/).trim;
            make $commented;
        }
        method TOP($made) {
            my %top = type => 'override-gui_editor', :value;
            if $made<commented> {
                %top«value» = False;
            }
            if $made<comment> {
                my $com = $made<comment>.made;
                %top«comment» = $com;
            }
            $made.make: %top;
        }
    } # class OverrideGUIEditorActions #

Top of Document

## Some useful variables

NB: All these variables are available outside of the module as a sub of the same name. That way I can give read only access to them.

### $GUI_EDITOR

The value of the `%*ENV«GUI_EDITOR»` environment variable or `''` if not set.

### $VISUAL

The value of the `%*ENV«VISUAL»` environment variable or `''` if not set.

### $EDITOR

The value of the `%*ENV«EDITOR»` environment variable or `''` if not set.

    my Str:D $GUI_EDITOR = ((%*ENV<GUI_EDITOR>:exists) ?? ~%*ENV<GUI_EDITOR> !! '');
    my Str:D $VISUAL     = ((%*ENV<VISUAL>:exists) ?? ~%*ENV<VISUAL> !! '');
    my Str:D $EDITOR     = ((%*ENV<EDITOR>:exists) ?? ~%*ENV<EDITOR> !! '');

    sub GUI_EDITOR( --> Str:D) is export {
        return $GUI_EDITOR;
    }

    sub VISUAL( --> Str:D) is export {
        return $VISUAL;
    }

    sub EDITOR( --> Str:D) is export {
        return $EDITOR;
    }

Top of Document

### @GUIEDITORS

The Array of Hashes that the `Editors` grammar and `EditorsActions` generate from parsing the editors file.

### @gui-editors

The Array of GUI Editors defined in the editors file.

### @default-editors

The array of editors selected in the file should have only one element otherwise the file is miss configured.

    my Hash @GUIEDITORS;
    my Str:D @gui-editors;
    my Str:D @default-editors;

    sub GUIEDITORS( --> Array[Hash]) is export {
        return @GUIEDITORS;
    }

    sub gui-editors( --> Array[Str:D]) is export {
        return @gui-editors;
    }

    sub default-editors( --> Array[Str:D]) is export {
        return @default-editors;
    }

Top of Document

### @override-gui_editor

An array of True values one for each of the times the `override GUI_EDITOR` directive appears in the editors file, it is an error for it to appear more than once, (it's a zero or one rule).

### $override-GUI_EDITOR

True if the `override GUI_EDITOR` directive is present in the editors file. If True then the setting in the file overrides the `%*ENV«GUI_EDITOR»` variable, otherwise `%*ENV«GUI_EDITOR»` wins.

    my Bool:D @override-gui_editor;

    sub override-gui_editor( --> Array[Bool:D]) is export {
        return @override-gui_editor;
    }

    my Bool:D $override-GUI_EDITOR = False;

    sub override-GUI_EDITOR( --> Bool:D) is export {
        return $override-GUI_EDITOR;
    }

Top of Document

#### In `init-gui-editors`

    sub init-gui-editors(Str:D @client-config-files, Str:D $client-config-path,
                                  &gen-configs:(Str:D, Str:D --> Bool:D),
                                      &check:(Str:D @cfg-files, Str:D $config --> Bool:D)
                                                                        --> Bool:D) is  export

...

...

...

    @GUIEDITORS = Editors.parse(@editors-file.join("\x0A"), :enc('UTF-8'), :$actions).made;
    @gui-editors = @GUIEDITORS.grep( -> %l { %l«type» eq 'config-line' } ).map: -> %ln { %ln«value»; };
    @default-editors = @GUIEDITORS.grep( -> %l { %l«type» eq 'editor-to-use' } ).map: -> %ln { %ln«value»; };
    if @default-editors > 1 {
        $*ERR.say: "Error: file $editor-config/editors is miss configured  more than one editor defined should be 0 or 1";
    }
    @override-gui_editor = @GUIEDITORS.grep( -> %l { %l«type» eq 'override-gui_editor' } ).map: -> %ln { %ln«value»; };
    if @override-gui_editor > 1 {
        my Int:D $elems = @override-gui_editor.elems;
        $*ERR.say: qq[Make up your mind only one "override GUI_EDITOR" is required, you supplied $elems are you insane???];
        $override-GUI_EDITOR = True;
    } elsif @override-gui_editor == 1 {
        $override-GUI_EDITOR = True;
    }
    if @gui-editors {
        #@gui-editors.raku.say;
        for @gui-editors -> $geditor {
            if !@guieditors.grep: { $geditor } {
                my Str $guieditor = $geditor;
                $guieditor .=trim;
                @guieditors.append($guieditor);
            }
        }
    }

    if $override-GUI_EDITOR && @default-editors {
        $editor = @default-editors[@default-editors - 1];
    }elsif %*ENV<GUI_EDITOR>:exists {
        my Str $guieditor = ~%*ENV<GUI_EDITOR>;
        if ! @guieditors.grep( { $_ eq $guieditor.IO.basename } ) {
            @guieditors.prepend($guieditor.IO.basename);
        }
    } elsif $editor-guessed && @default-editors {
        $editor = @default-editors[@default-editors - 1];
    }

Top of Document

### $editor

The editor the user has chosen.

    # the editor to use #
    my Str:D $editor = '';

    sub editor( --> Str:D) is export {
        return $editor;
    }

## edit-configs()

A function to open the users configuration files in their chosen editor.

    sub edit-configs() returns Bool:D is export {
        if $editor {
            my $option = '';
            my @args;
            my $edbase = $editor.IO.basename;
            if $edbase eq 'gvim' {
                $option = '-p';
                @args.append('-p');
            }
            for @config-files -> $file {
                if $file eq 'editors' {
                    @args.append("$editor-config/$file");
                } else {
                    @args.append("$client-config/$file");
                }
            }
            my $proc = run($editor, |@args);
            return $proc.exitcode == 0 || $proc.exitcode == -1;
        } else {
            $*ERR.say: "no editor found please set GUI_EDITOR, VISUAL or EDITOR to your preferred editor.";
            $*ERR.say: "e.g. export GUI_EDITOR=/usr/bin/gvim";
            $*ERR.say: "or set editor in the $editor-config/editors file this can be done with the set editor command.";
            $*ERR.say: qq[NB: the editor will be set by first checking GUI_EDITOR then VISUAL then EDITOR and
                        finally editor in the config file so GUI_EDITOR will win over all.
                        Unless you supply the "override GUI_EDITOR" directive in the $editor-config/editors file
                        and also supplied the "editor := <editor>" directive];
            return False;
        }
    }

Top of Document

## Editor functions

### list-editors(…)

List all known GUI Editors, flagging the selected editor with '*' note if none is flagged either `$editor` is set to a non GUI Editor or `$editor` is set to the empty string.

    sub list-editors(Str:D $prefix,
                     Bool:D $colour,
                     Bool:D $syntax,
                     Int:D $page-length,
                     Regex:D $pattern --> Bool) is export

### editors-stats(…)

Show the values of some editors parameters.

    sub editors-stats(Str:D $prefix,
                      Bool:D $colour,
                      Bool:D $syntax,
                      Int:D $page-length,
                      Regex:D $pattern --> Bool) is export

Top of Document

### BadEditor

`BadEditor` is an Exception class for the `GUI::Editors` module.

    class BadEditor is Exception is export {
        has Str:D $.msg = 'Error: bad editor specified';
        method message( --> Str:D) {
            $!msg;
        }
    }

### set-editor(…)

A function to set the editor of choice.

    sub set-editor(Str:D $editor, Str $comment = Str --> Bool:D) is export

NB: this will still be overridden by `%*ENV«GUI_EDITOR»` unless you set override GUI_EDITOR.

### add-gui-editor(…)

Add an editor to the list of known GUI Editors.

    sub add-gui-editor(Str:D $editor, Str $comment = Str --> Bool:D) is export

NB: please make sure it really is a GUI Editor otherwise this module will not work correctly. You are completely free to set the chosen editor to what ever you like.

### set-override-GUI_EDITOR(…)

Set or unset the override GUI_EDITOR flag.

    sub set-override-GUI_EDITOR(Bool:D $value, Str $comment = Str --> Bool:D) is export

If set then the file always wins else `%*ENV«GUI_EDITOR»` always wins if set.

Top of Document

### backup-editors(…)

Backup the editors file.

    sub backup-editors(Bool:D $use-windows-formatting --> Bool) is export

NB: if $use-windows-formatting is true or the program is running on windows then `.` will become `·` and `:` will become `.`, this is to avoid problems with the special meaning of `:` on windows.

Top of Document

### restore-editors(…)

Restore the editors file from a backup.

    sub restore-editors(IO::Path $restore-from --> Bool) is export

If `$restore-from` is relative and not found from the current directory `$editor-config/$restore-from` will be tried.

### list-editors-backups(…)

List all the available backups in the `$editor-config`.

    sub list-editors-backups(Str:D $prefix,
                             Bool:D $colour is copy,
                             Bool:D $syntax,
                             Regex:D $pattern,
                             Int:D $page-length --> Bool:D) is export

- Where

- `$prefix` a filter only files starting with this are included in the result.

- `$colour` colour the output.

- `$syntax` syntax highlight the results colour on steroids.

- `$pattern` a regex to filter the results by only files matching this will be included in the results.

- `$page-length` set the length of the pages before it repeats the header.

Top of Document

### backups-menu-restore-editors(…)

Presents a menu so you can choose which backup to restore from.

    sub backups-menu-restore-editors(Bool:D $colour,
                                     Bool:D $syntax,
                                     Str:D $message = "" --> Bool:D) is export

- Where:

- `$colour` if `True` represents the menu in colours.

- `$syntax` if `True` represents the menu syntax highlighted.

- basically colour on steroids.

- uses the `Gzz::Text::Utils::menu(…)`, which uses the `Gzz::Text::Utils::dropdown(…)` function for colour and syntax.

Top of Document

### edit-files(…)

Edit arbitrary files using chosen editor.

    sub edit-files(Str:D @files --> Bool:D) is export

- Where

- `@files` a list of files to open in the chosen GUI editor.

Top of Document