Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

beebox / crossover   deb

Repository URL to install this package:

Version: 18.5.0-1 

/ opt / cxoffice / lib / perl / CXRWConfig.pm

# (c) Copyright 2003-2010. CodeWeavers, Inc.
use warnings;
use strict;

{
    #
    # Manipulating the fields in a section
    #

    package CXRWSection;
    use CXUtils;
    use CXLog;

    sub new($$$)
    {
        my ($class, $name, $line)=@_;
        my $self={
            name => $name, # Section name
            lines => [],   # List of all lines
            fields => {}   # For quick access to Field and CommentField lines
        };
        bless $self, $class;
        return $self;
    }

    # For CXRWConfig's internal  use only
    sub _add_line($$)
    {
        my ($self, $field)=@_;
        push @{$self->{lines}}, $field;
        if (@$field[0] eq "Field")
        {
            my $key=@$field[1];
            $key =~ tr/A-Z/a-z/;
            $self->{fields}->{$key}=$field;
        }
        elsif (@$field[0] eq "CommentField")
        {
            my $key=@$field[1];
            $key =~ tr/A-Z/a-z/;
            my $previous=$self->{fields}->{$key};
            $self->{fields}->{$key}=$field if (!defined $previous);
        }
    }

    # For CXRWConfig's internal  use only
    sub _escape_string($$)
    {
        my ($self, $str)=@_;
        my $escaping=($self->{file} ? $self->{file}->{escaping} : "");
        if ($escaping)
        {
            if ($escaping eq "shell")
            {
                $str =~ s/\$/\\\$/g;
            }
            elsif ($escaping eq "xdg")
            {
                # Here we assume that the field names won't trigger escaping.
                # We further assume that string lists won't contain escaped
                # semi-colons.
                $str =~ s/\n/\\n/g;
                $str =~ s/\t/\\t/g;
                $str =~ s/\r/\\r/g;
                $str =~ s/\\/\\\\/g;
            }
            else
            {
                cxlog("unknown escaping scheme '$escaping'\n");
            }
        }
        return $str;
    }

    sub _unescape_char($)
    {
        my ($c)=@_;
        return "\\" if ($c eq "\\");
        return "\n" if ($c eq "n");
        return "\r" if ($c eq "r");
        return "\t" if ($c eq "t");
        return " "  if ($c eq "s");
        return $c;
    }

    # For CXRWConfig's internal  use only
    sub _unescape_string($$)
    {
        my ($self, $str)=@_;
        return undef if (!defined $str);
        my $escaping=($self->{file} ? $self->{file}->{escaping} : "");
        if ($escaping)
        {
            if ($escaping eq "shell")
            {
                $str =~ s/\\\$/\$/g;
            }
            elsif ($escaping eq "xdg")
            {
                $str =~ s!\\(.)!_unescape_char($1)!eg;
            }
            else
            {
                cxlog("unknown escaping scheme '$escaping'\n");
            }
        }
        return $str;
    }

    sub get_name($)
    {
        my ($self)=@_;
        return $self->{name};
    }

    sub get_field_list($)
    {
        my ($self)=@_;
        my @fields=map { @{$_}[1] } grep { @{$_}[0] eq "Field" } @{$self->{lines}};
        return \@fields;
    }

    sub is_commented_out($$)
    {
        my ($self, $key)=@_;
        $key =~ tr/A-Z/a-z/;
        my $field=$self->{fields}->{$key};
        return 1 if (defined $field and @$field[0] eq "CommentField");
        return 0;
    }

    sub get($$;$)
    {
        my ($self, $key, $default)=@_;
        $key =~ tr/A-Z/a-z/;
        my $field=$self->{fields}->{$key};
        my $value;
        $value=@$field[2] if (defined $field and @$field[0] eq "Field");
        $value=$default if (!defined $value);
        return $value;
    }

    # For CXRWConfig's internal  use only
    sub _rebuild_line($$)
    {
        my ($self, $field)=@_;
        my $name=@$field[1];
        my $value=@$field[2];
        if (@$field[3] eq "\"")
        {
            $name="\"" . escape_string($name) . "\"";
            $value="\"" . escape_string($value) . "\"";
        }
        my $comment=(@$field[0] eq "Field" ? "" : ";");
        my $equal=@$field[4];
        @$field[-1]=join("", $comment, $self->_escape_string($name),
                         $equal, $self->_escape_string($value));
    }

    sub comment_out($$)
    {
        my ($self, $name)=@_;
        my $key=$name;
        $key =~ tr/A-Z/a-z/;
        my $field=$self->{fields}->{$key};
        if (defined $field and @$field[0] eq "Field")
        {
            @$field[0]="CommentField";
            $self->_rebuild_line($field);
            $self->{file}->{modified}=1 if (defined $self->{file});
        }
    }

    sub set($$$)
    {
        my ($self, $name, $value)=@_;
        my $key=$name;
        $key =~ tr/A-Z/a-z/;
        my $field=$self->{fields}->{$key};
        if (!defined $field)
        {
            my $quote=$self->{file}->{quote} || "";
            my $equal=$quote ? " = " : "=";
            my $field=["Field", $name, $value, $quote, $equal, undef];
            $self->_rebuild_line($field);
            push @{$self->{lines}}, $field;
            $self->{fields}->{$key}=$field;
            $self->{file}->{modified}=1 if (defined $self->{file});
        }
        elsif ($value ne @$field[2] or $name ne @$field[1] or
               @$field[0] ne "Field")
        {
            @$field[0]="Field";
            @$field[1]=$name;
            @$field[2]=$value;
            $self->_rebuild_line($field);
            $self->{file}->{modified}=1 if (defined $self->{file});
        }
    }

    sub remove($$)
    {
        my ($self, $key)=@_;
        $key =~ tr/A-Z/a-z/;
        my $field=$self->{fields}->{$key};
        if (defined $field)
        {
            my $lines=$self->{lines};
            my $i=@$lines;
            while ($i > 0)
            {
                $i--;
                my $lfield=@$lines[$i];
                splice @$lines, $i, 1 if (defined $lfield->[1] and $lfield->[1] =~ /^\Q$key\E$/i);
            }
            delete $self->{fields}->{$key};
            $self->{file}->{modified}=1 if (defined $self->{file});
        }
    }

    sub remove_all($)
    {
        my ($self)=@_;
        $self->{lines}=[["Empty",undef,undef,undef,undef,""],
                        ["Section",undef,undef,undef,undef,"[$self->{name}]"]
                       ];
        $self->{fields}={};
        $self->{file}->{modified}=1 if (defined $self->{file});
    }
}

{
    package CXRWConfig;
    use CXLog;
    use CXUtils;


    #
    # Debug functions
    #

    sub dump_fields($)
    {
        my ($self)=@_;
        foreach my $name (@{$self->{section_list}})
        {
            cxlog("[$name]\n");
            my $section=$self->get_section($name);
            foreach my $key (sort { $a cmp $b } keys %{$section->{fields}})
            {
                my $comment=($section->is_commented_out($key) ? ";" : "");
                cxlog("  $comment","[$key] = [", $section->{fields}->{$key}->[2], "]\n");
            }
        }
    }

    sub dump_lines($)
    {
        my ($self)=@_;
        foreach my $name ("[begin]", @{$self->{section_list}})
        {
            my $section=$self->get_section($name);
            next if (!$section);
            foreach my $line (@{$section->{lines}})
            {
                cxlog("@$line[-1]\n");
            }
        }
    }


    #
    # Add / remove sections
    #

    sub _add_section($$)
    {
        my ($self, $name)=@_;
        my $key=$name;
        $key =~ tr/A-Z/a-z/;
        my $section=$self->{sections}->{$key};
        if (!defined $section)
        {
            $section=CXRWSection->new($name);
            $section->{file}=$self;
            $self->{sections}->{$key}=$section;
            push @{$self->{section_list}}, $name if ($name ne "[begin]");
            $self->{modified}=1;
        }
        return $section;
    }

    sub append_section($$)
    {
        my ($self, $name)=@_;
        my $section=$self->get_section($name);
        return $section if ($section);

        # Add an empty line before the new section so the file is readable.
        # Only add it if needed to prevent an accumulation of empty lines
        # after multiple section additions and removals.
        my $last=$self->{section_list}->[-1] || "[begin]";
        $section=$self->get_section($last);
        if ($section)
        {
            my $lines=$section->{lines};
            if (@$lines and $lines->[-1]->[0] ne "Empty")
            {
                push @$lines, ["Empty",undef,undef,undef,undef,""];
            }
        }
        $section=$self->_add_section($name);
        push @{$section->{lines}}, ["Section",undef,undef,undef,undef,"[$name]"];
        return $section;
    }

    sub get_section_keys($)
    {
        my ($self)=@_;
        return map { tr/A-Z/a-z/; } @{$self->{section_list}};
    }

    sub get_section_names($)
    {
        my ($self)=@_;
        return @{$self->{section_list}};
    }

    sub get_section($$)
    {
        my ($self, $key)=@_;
        $key =~ tr/A-Z/a-z/;
        return $self->{sections}->{$key};
    }

    sub rename_section($$$)
    {
        my ($self, $old, $new)=@_;
        my $key=$old;
        $key =~ tr/A-Z/a-z/;
        my $section=$self->{sections}->{$key};
        return undef if (!defined $section);

        my $section_list=$self->{section_list};
        for (my $i=0; $i <@$section_list; $i++)
        {
            if (@$section_list[$i] =~ /^\Q$old\E$/i)
            {
                splice @$section_list, $i, 1, $new;
                last;
            }
        }
        delete $self->{sections}->{$key};

        $section->{name}=$new;
        my $lines=$section->{lines};
        foreach my $field (@$lines)
        {
            if (@$field[0] eq "Section")
            {
                @$field[-1]="[$new]";
                last;
            }
        }
        $key=$new;
        $key =~ tr/A-Z/a-z/;
        $self->{sections}->{$key}=$section;

        $self->{modified}=1;
        return $section;
    }

    sub remove_section($$)
    {
        my ($self, $name)=@_;
        my $key=$name;
        $key =~ tr/A-Z/a-z/;
        my $section=$self->{sections}->{$key};
        return undef if (!defined $section);

        delete $self->{sections}->{$key};
        my $section_list=$self->{section_list};
        for (my $i=0; $i<@$section_list; $i++)
        {
            if (@$section_list[$i] =~ /^\Q$name\E$/i)
            {
                splice @$section_list, $i, 1;
                last;
            }
        }

        $self->{modified}=1;
        return $section;
    }

    sub remove_all($)
    {
        my ($self)=@_;
        $self->{sections}={};
        $self->{section_list}=[];
        $self->{modified}=1;
    }


    #
    # Shebang manipulation
    #

    sub set_shebang($$)
    {
        my ($self, $shebang)=@_;
        my $section=$self->_add_section("[begin]");
        $self->dump_lines();
        my $field=["raw", "", undef, undef, undef, "#!$shebang"];
        if (!@{$section->{lines}})
        {
            $section->{lines}=[$field];
        }
        elsif ($section->{lines}->[0]->[-1] =~ /^#!/)
        {
            $section->{lines}->[0]=$field;
        }
        else
        {
            unshift @{$section->{lines}}, $field;
        }
    }


    #
    # Field manipulation shortcuts
    #

    sub get($$$;$)
    {
        my ($self, $section, $field, $default)=@_;
        my $s=$self->get_section($section);
        my $value;
        $value=$s->get($field) if (defined $s);
        $value=$default if (!defined $value);
        return $value;
    }

    sub set($$$$)
    {
        my ($self, $section, $field, $value)=@_;
        my $s=$self->append_section($section);
        $s->set($field, $value);
    }


    #
    # Load / Save the configuration file
    #

    my %cxrwcache;

    sub new($$;$$)
    {
        my ($class, $filename, $escaping, $quote)=@_;
        $quote="\"" if (!defined $quote);

        # Try to get the file from the cache first
        my $self;
        if (defined $filename)
        {
            # Canonize the filename a bit for the cache
            $filename =~ s!/+!/!g;
            $self=$cxrwcache{$filename};
            return $self if ($self);
        }

        $self={
            filename => $filename,
            quote => $quote,       # The quoting style to use for new fields
            escaping => $escaping || "", # How to escape special characters
            section_list => [],    # Ordered list of the sections
            sections => {}         # For quick access to each section
        };
        bless $self, $class;
        $cxrwcache{$filename}=$self if (defined $filename);

        if (defined $filename and -e $filename)
        {
            my $fh;
            return undef if (!open($fh, "<", $filename));
            cxlog("CXRWConfig->new($filename)\n");

            my $section=$self->_add_section("[begin]");
            foreach my $line (<$fh>)
            {
                chomp $line;
                $self->{crlf}=1 if ($line =~ s/\r$//);

                my ($type, $name, $value, $quote, $equal);
                if ($line =~ /^\s*$/)
                {
                    $type="Empty";
                }
                elsif ($line =~ /^\[(.*)\]\s*(?:;[^\]]*)?$/)
                {
                    # New section
                    $section=$self->get_section($1);
                    next if (defined $section);
                    $section=$self->_add_section($1);
                    $type="Section";
                }
                elsif ($line =~ /^\s*(;+\s*)*\"((?:[^\\\"]|\\.)*)\"(\s*=\s*)\"((?:[^\\\"]|\\.)*)\"\s*(?:;.*)?$/)
                {
                    # This is a field in the following format:
                    #    "Name"="Value"
                    # or "Name" = "Value" ; comment
                    # or ;"Name" = "Value"
                    # where Name and Value are escaped strings which can
                    # contain backslashes and quotes.
                    $type=(defined $1 ? "CommentField" : "Field");
                    $name=unescape_string($2);
                    $quote="\"";
                    $equal=$3;
                    $value=unescape_string($4);
                }
                elsif ($line =~ /^\s*(;+\s*)*([^=;][^=]*?)(\s*=\s*)(.*?)\s*$/)
                {
                    # This is a field in the following format:
                    #    Name=Value
                    # or Name = Value ; also part of the value
                    # or ;Name = Value
                    # Note that this intentionally also matches
                    #    Name="Value"
                    # where the quotes are part of the value.
                    $type=(defined $1 ? "CommentField" : "Field");
                    $name=$2;
                    $quote="";
                    $equal=$3;
                    $value=$4;
                }
                else
                {
                    $type="Raw";
                }
                $name="" if (!defined $name);

                $section->_add_line([$type, $section->_unescape_string($name),
                                     $section->_unescape_string($value),
                                     $quote, $equal, $line]);
            }
            close($fh);
        }
        $self->{modified}=0;

        return $self;
    }

    sub uncache_file($)
    {
        my ($filename)=@_;
        delete $cxrwcache{$filename};
    }

    sub get_filename($)
    {
        my ($self)=@_;
        return $self->{filename};
    }

    sub get_filenames($)
    {
        my ($self)=@_;
        return [ $self->{filename} ];
    }

    sub set_filename($$)
    {
        my ($self, $filename)=@_;

        delete $cxrwcache{$self->{filename}} if (defined $self->{filename});
        $self->{filename}=$filename;
        $self->{modified}=1;
        $cxrwcache{$filename}=$self;
    }

    sub is_modified($)
    {
        my ($self)=@_;
        return $self->{modified};
    }

    sub write($$)
    {
        my ($self, $filename)=@_;

        my $fh;
        return 0 if (!open($fh, ">", $filename));
        cxlog("CXRWConfig->write($filename)\n");
        my $crlf=($self->{crlf} ? "\r\n" : "\n");
        foreach my $name ("[begin]", @{$self->{section_list}})
        {
            my $section=$self->get_section($name);
            next if (!$section);
            foreach my $line (@{$section->{lines}})
            {
                print $fh @$line[-1], $crlf;
            }
        }
        close($fh);

        return 1;
    }

    sub save($)
    {
        my ($self)=@_;
        if (!$self->{modified})
        {
            my $filename=$self->{filename} || "";
            cxlog("'$filename' not modified -> no need to save\n");
            return 1;
        }
        return 0 if (!$self->{filename});
        my $rc=$self->write($self->{filename});
        $self->{modified}=0 if ($rc);
        return $rc;
    }


    #
    # Merging configuration files
    #

    # FIXME: Merge the configuration merging code from CXUpgradeConfig
    # and switch cxupgrade to using CXRWConfig.
}

return 1;