[X2Go-Commits] [x2goclient] 11/19: debian/: new file "preprocessor.pl".
git-admin at x2go.org
git-admin at x2go.org
Wed May 12 19:28:23 CEST 2021
This is an automated email from the git hooks/post-receive script.
x2go pushed a commit to branch master
in repository x2goclient.
commit 0ea11593ea3c6f62c39b307cb34362aa8977e48f
Author: Mihai Moldovan <ionic at ionic.de>
Date: Thu May 6 19:19:50 2021 +0200
debian/: new file "preprocessor.pl".
Simple text file preprocessor supporting basic condition logic.
---
debian/changelog | 2 +
debian/preprocessor.pl | 814 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 816 insertions(+)
diff --git a/debian/changelog b/debian/changelog
index cb39b66..d2dcc19 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -60,6 +60,8 @@ x2goclient (4.1.2.3-0x2go1) UNRELEASED; urgency=medium
changes.
+ New file "upstream/metadata". Same reasoning as for "watch". Pulled from
Mike's changes.
+ + New file "preprocessor.pl". Simple text file preprocessor supporting
+ basic condition logic.
[ Mike Gabriel ]
* New upstream version (4.1.2.3):
diff --git a/debian/preprocessor.pl b/debian/preprocessor.pl
new file mode 100755
index 0000000..8c8eb39
--- /dev/null
+++ b/debian/preprocessor.pl
@@ -0,0 +1,814 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2021 X2Go Project - https://wiki.x2go.org
+#
+# 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 2 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, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+# Disable some Perl::Critic violations.
+#
+# I like parentheses.
+## no critic (ProhibitParensWithBuiltins)
+#
+# It's recommended not to use the constant pragma, but we don't want to use
+# non-core modules like Readonly.
+## no critic (ValuesAndExpressions::ProhibitConstantPragma)
+
+use strict;
+use warnings;
+
+# Enable full Unicode handling, if possible.
+use 5.012;
+
+# Enable UTF-8 encoded data in the script itself.
+use utf8;
+
+# Make text coding issues fatal.
+use warnings qw (FATAL utf8);
+
+#use X2Go::Utils qw (is_int);
+use English qw (-no_match_vars);
+use Getopt::Long qw (GetOptionsFromArray);
+use Pod::Usage;
+use Storable qw (dclone);
+use Data::Dumper qw (Dumper);
+use Encode qw (encode decode);
+use Encode::Locale;
+use File::Temp qw (tempfile);
+use Scalar::Util qw (openhandle looks_like_number);
+use IO::Handle;
+use File::Copy qw (copy);
+
+# Set up automatic encoding.
+if (-t STDIN) {
+ binmode STDIN, ":encoding(console_in)";
+}
+if (-t STDOUT) {
+ binmode STDOUT, ":encoding(console_out)";
+}
+if (-t STDERR) {
+ binmode STDERR, ":encoding(console_out)";
+}
+
+# Convert data in ARGV.
+exit (Main (map { Encode::decode (locale => $_, (Encode::FB_CROAK | Encode::LEAVE_SRC)); } @ARGV));
+
+BEGIN {
+}
+
+# No code past this point should be getting executed!
+#
+# These are actually supposed to be enums, but since Perl doesn't have a
+# proper way of creating enums (at least not natively), we'll emulate that
+# using list constants.
+#
+# Make sure that each token value is unique and referencing the correct
+# element index in the TOKENS array.
+use constant TOKEN_IF => 0;
+use constant TOKEN_EL => 1;
+use constant TOKEN_FI => 2;
+use constant TOKEN_EQ => 3;
+use constant TOKEN_NE => 4;
+use constant TOKEN_INVALID => 5;
+use constant TOKENS => ( q{@?}, q{@!}, q{@/}, q{==}, q{!=}, q{} );
+
+# Helper function deleting whitespace from the left side of a string.
+#
+# Takes the string to trim as its only parameter.
+#
+# Returns the modified string.
+sub ltrim {
+ my $str = shift;
+
+ $str =~ s/^\s+//;
+
+ return $str;
+}
+
+# Helper function deleting whitespace from the right side of a string.
+#
+# Takes the string to trim as its only parameter.
+#
+# Returns the modified string.
+sub rtrim {
+ my $str = shift;
+
+ $str =~ s/\s+$//;
+
+ return $str;
+}
+
+# Helper function deleting whitespace from both sides of a string.
+#
+# Takes the string to trim as its only parameter.
+#
+# Returns the modified string.
+sub trim {
+ my $str = shift;
+
+ $str = ltrim ($str);
+ $str = rtrim ($str);
+
+ return $str;
+}
+
+# Helper function extracting a token from the given string.
+#
+# Takes a string as its only parameter.
+#
+# Returns the deitected token as an offset/index in the tokens array.
+#
+# On error, returns undef.
+sub fetch_token {
+ my $ret = TOKEN_INVALID;
+
+ my $input = shift;
+
+ if (!(defined ($input))) {
+ print {*STDERR} "No input given to token fetching function, erroring out.\n";
+ $ret = undef;
+ }
+
+ if (defined ($ret)) {
+ ## no critic (ControlStructures::ProhibitCStyleForLoops)
+ for (my $i = 0; $i < scalar ((TOKENS)); ++$i) {
+ ## critic (ControlStructures::ProhibitCStyleForLoops)
+ # Luckily, tokens are non-ambiguous.
+ my $token_length = length ((TOKENS)[$i]);
+ if ((TOKENS)[$i] eq substr ($input, 0, $token_length)) {
+ $ret = $i;
+ last;
+ }
+ }
+ }
+
+ return $ret;
+}
+
+# Helper function parsing a condition and turning it into a boolean value.
+#
+# Takes the (probable) condition as a string, and a boolean value denoting
+# whether debugging is requested or not as its parameters.
+#
+# Returns a boolean value .
+#
+# On error, returns undef.
+sub parse_condition {
+ my $ret = 0;
+ my $error_detected = 0;
+
+ my $condition = shift;
+ my $debug = shift;
+
+ if (!(defined ($condition))) {
+ print {*STDERR} "No condition passed to condition parsing helper, erroring out.\n";
+ $error_detected = 1;
+ }
+
+ if (!($error_detected)) {
+ if (!(defined ($debug))) {
+ print {*STDERR} "No debugging argument passed to parsing helper, erroring out.\n";
+ $error_detected = 1;
+ }
+ }
+
+ if (!($error_detected)) {
+ if (!(length ($condition))) {
+ print {*STDERR} "Empty condition is invalid, erroring out.\n";
+ $error_detected = 1;
+ }
+ }
+
+ my $lhs = undef;
+ my $rhs = undef;
+ my $token = undef;
+ if (!($error_detected)) {
+ # First, extract lhs and remove it from $condition.
+ if ($condition =~ m/^([^\s]+)\s+(.*)$/) {
+ $lhs = $1;
+ $condition = $2;
+ }
+ else {
+ print {*STDERR} "Unable to extract left hand side from condition, erroring out.\n";
+ $error_detected = 1;
+ }
+ }
+
+ if (!($error_detected)) {
+ $token = fetch_token ($condition);
+
+ if (!(defined ($token))) {
+ print {*STDERR} ".\n";
+ $error_detected = 1;
+ }
+ else {
+ if ((TOKEN_EQ == $token) || (TOKEN_NE == $token)) {
+ $condition = substr ($condition, length ((TOKENS)[$token]));
+ }
+ else {
+ print {*STDERR} "Invalid token found in condition, erroring out.\n";
+ $error_detected = 1;
+ }
+ }
+ }
+
+ if (!($error_detected)) {
+ # Everything left must be the rhs.
+ $rhs = ltrim ($condition);
+
+ if (!(length ($rhs))) {
+ print {*STDERR} "No right hand side found while parsing the condition, erroring out.\n";
+ $error_detected = 1;
+ }
+ }
+
+ if (!($error_detected)) {
+ # Now find out if both sides look like integers, in which case we'll use
+ # integer semantics for the condition.
+ # Otherwise, we'll use a string comparison.
+ my $string_comp = 1;
+ if ((Scalar::Util::looks_like_number ($lhs)) && (Scalar::Util::looks_like_number ($rhs))) {
+ $string_comp = 0;
+ }
+
+ # And, finally, do the actual comparison.
+ if ($string_comp) {
+ $ret = ($lhs eq $rhs);
+ }
+ else {
+ $ret = ($lhs == $rhs);
+ }
+
+ # For non-equal, just invert.
+ if (TOKEN_NE == $token) {
+ $ret = (!($ret));
+ }
+ }
+
+ if ($error_detected) {
+ $ret = undef;
+ }
+
+ return $ret;
+}
+
+# Helper function reading a file, parsing each line and writing it to a
+# temporary file handle.
+#
+# Takes a file path, a temporary file handle and a boolean value denoting
+# whether debugging is requested or not as its parameters.
+#
+# Returns a boolean value denoting success (false) or failure (true).
+sub parse_file {
+ my $error_detected = 0;
+
+ my $infile = shift;
+ my $temp_fh = shift;
+ my $debug = shift;
+
+ if (!(defined ($infile))) {
+ print {*STDERR} "No input file passed to parsing helper, erroring out.\n";
+ $error_detected = 1;
+ }
+
+ if (!($error_detected)) {
+ if (!(defined ($temp_fh))) {
+ print {*STDERR} "No file handle passed to parsing helper, erroring out.\n";
+ $error_detected = 1;
+ }
+ }
+
+ if (!($error_detected)) {
+ if (!(defined (openhandle ($temp_fh)))) {
+ print {*STDERR} "Invalid file handle passed to parsing helper, erroring out.\n";
+ $error_detected = 1;
+ }
+ }
+
+ if (!($error_detected)) {
+ if (!(defined ($debug))) {
+ print {*STDERR} "No debugging argument passed to parsing helper, erroring out.\n";
+ $error_detected = 1;
+ }
+ }
+
+ my $read_fh = undef;
+ if (!($error_detected)) {
+ $error_detected = (!(open ($read_fh, q{<}, $infile)));
+
+ if (($error_detected) || (!(defined ($read_fh)))) {
+ print {*STDERR} "Unable to open input file \"${infile}\" for reading in parsing helper, erroring out.\n";
+ }
+ }
+
+ if (!($error_detected)) {
+ my @cond_stack = ( );
+ my $skip = 0;
+ while (!(eof ($read_fh))) {
+ my $line = readline ($read_fh);
+ my $skip_once = 0;
+
+ if ($debug) {
+ print {*STDERR} "Processing line \"${line}\" ... ";
+ }
+
+ my $work = ltrim ($line);
+ my $token = fetch_token ($work);
+
+ if (!(defined ($token))) {
+ print {*STDERR} "Unable to extract token from line \"${line}\", erroring out.\n"
+ }
+ elsif ($debug) {
+ print {*STDERR} q{extracted token: } . $token . "\n";
+ }
+
+ # Check if the token is valid.
+ if (TOKEN_FI == $token) {
+ if (!(scalar (@cond_stack))) {
+ print {*STDERR} "Encountered FI (end of condition) token, but no condition block active, erroring out.\n";
+ $error_detected = 1;
+ last;
+ }
+
+ # Otherwise, it's valid. Let's handle it by removing an onion layer.
+ if ($debug) {
+ print {*STDERR} "Got FI token, removing one layer.\n";
+ }
+
+ # If we're already in a skip section, just decrement the counter.
+ if (1 < $skip) {
+ --$skip;
+ }
+ else {
+ pop (@cond_stack);
+
+ # Condition stack might be empty now, in which case make sure to not
+ # skip anything.
+ if (scalar (@cond_stack)) {
+ $skip = $cond_stack[$#cond_stack];
+ }
+ else {
+ $skip = 0;
+ }
+ }
+
+ $skip_once = 1;
+ }
+
+ if (TOKEN_EL == $token) {
+ if (!(scalar (@cond_stack))) {
+ print {*STDERR} "Encountered EL (else branch) token, but no condition block active, erroring out.\n";
+ $error_detected = 1;
+ last;
+ }
+
+ # Okay, valid.
+ # In case we're in a nested skipping section, do nothing. Otherwise,
+ # handle it by reversing the skip value.
+ if (1 < $skip) {
+ if ($debug) {
+ print {*STDERR} "Got EL token, but we're already in a skipping section, ignoring.\n";
+ }
+ }
+ else {
+ if ($debug) {
+ print {*STDERR} "Got EL token, reversing skip value \"${skip}\".\n";
+ }
+
+ $skip = (!($skip));
+
+ # Also update stack value, so that it's is up-to-date if additional
+ # nesting comes up.
+ $cond_stack[$#cond_stack] = $skip;
+ }
+
+ $skip_once = 1;
+ }
+
+ if (TOKEN_IF == $token) {
+ # IF is always valid. But we'll have to parse it. Unless we're in a
+ # skipping section. In that case, don't parse the condition and keep
+ # skipping, incrementing the skipping counter.
+ if (!($skip)) {
+ $work = substr ($work, length ((TOKENS)[$token]));
+ $work = ltrim ($work);
+ my $cond = parse_condition ($work, $debug);
+
+ if (!(defined ($cond))) {
+ print {*STDERR} "Unable to parse conditional into a boolean value, erroring out.\n";
+ $error_detected = 1;
+ last;
+ }
+
+ $skip = (!($cond));
+ push (@cond_stack, $skip);
+ }
+ else {
+ ++$skip;
+ }
+
+ $skip_once = 1;
+ }
+
+ # Any other token, including TOKEN_INVALID, is actually always valid,
+ # since it is not a preprocessor token.
+ # Sounds weird at first, but those lines will just be passed-through
+ # verbatim.
+ if ((!($skip)) && (!($skip_once))) {
+ if ($debug) {
+ print {*STDERR} "Passing-through line.\n";
+ }
+
+ print $temp_fh "${line}";
+ }
+ }
+
+ if (scalar (@cond_stack)) {
+ print {*STDERR} "Reached end of file, but condition stack not empty - runaway/unterminated condition, erroring out.\n";
+ $error_detected = 1;
+ }
+ }
+
+ close ($read_fh);
+
+ return $error_detected;
+}
+
+# Helper function handling unknown options or ignoring the well-known
+# separator. It scans for options until hitting the first non-option entry.
+#
+# Takes an array reference with unparsed options and a boolean value denoting
+# whether the separating "--" pseudo-option should be skipped or not as its
+# parameters.
+#
+# Returns an array reference containing a boolean value denoting whether a
+# separating "--" pseudo-option has been found *and* skipping it was requested,
+# and the sanitized version of the original array reference.
+#
+# On error, returns undef.
+sub sanitize_program_options {
+ my $ret = undef;
+ my $error_detected = 0;
+ my $found_separator = 0;
+
+ my $args = shift;
+ my $skip_separator = shift;
+
+ if ((!(defined ($args))) || ('ARRAY' ne ref ($args))) {
+ print {*STDERR} "Invalid argument array reference passed to program sanitization helper, erroring out.\n";
+ $error_detected = 1;
+ }
+
+ if (!($error_detected)) {
+ if (!(defined ($skip_separator))) {
+ print {*STDERR} "No skip-separator parameter passed to program sanitization helper, erroring out.\n";
+ $error_detected = 1;
+ }
+ }
+
+ if (!($error_detected)) {
+ $args = Storable::dclone ($args);
+
+ ## no critic (ControlStructures::ProhibitCStyleForLoops)
+ for (my $cur_arg = shift (@{$args}); defined ($cur_arg); $cur_arg = shift (@{$args})) {
+ ## critic (ControlStructures::ProhibitCStyleForLoops)
+ if (q{-} eq substr ($cur_arg, 0, 1)) {
+ # Looks like an option so far. Let's continue scanning.
+
+ if (1 == length ($cur_arg)) {
+ # But isn't a real option. Add back to argument list and stop
+ # processing.
+ unshift (@{$args}, $cur_arg);
+ last;
+ }
+ elsif ((2 == length ($cur_arg)) && (q{-} eq substr ($cur_arg, 1, 1))) {
+ if ($skip_separator) {
+ # Found separating "--" pseudo-option, but skipping requested. Only
+ # set the boolean value for our return value and make sure that we
+ # don't skip another separating pseudo-option if it comes up again
+ # right next to this one.
+ $found_separator = 1;
+ $skip_separator = 0;
+ }
+ else {
+ # Not skipping separating "--" pseudo-option - i.e., we'll treat this
+ # as a non-option.
+ unshift (@{$args}, $cur_arg);
+ last;
+ }
+ }
+ else {
+ # Otherwise this is an actual option.
+ # We either want to error out, if no previous separating "--"
+ # pseudo-option was found, or ignore it.
+ # The weird 0 + (...) construct here is forcing an arithmetic
+ # context. Otherwise, the interpreter might use a string context,
+ # in which the value "0" is dualvar'd to both an arithmetic 0 and
+ # an empty string.
+ my $separator_found = (0 + ((!($skip_separator)) | ($found_separator)));
+ if ($separator_found) {
+ # Put back into array. We'll handle this as not-an-option.
+ unshift (@{$args}, $cur_arg);
+ last;
+ }
+ else {
+ print {*STDERR} q{Unknown option encountered: } . $cur_arg . "; erroring out.\n";
+ $error_detected = 1;
+ last;
+ }
+ }
+ }
+ else {
+ # Definitely not an option, add back to array.
+ unshift (@{$args}, $cur_arg);
+ last;
+ }
+ }
+ }
+
+ if (!($error_detected)) {
+ $ret = [ $found_separator, $args ];
+ }
+
+ return $ret;
+}
+
+# Main function, no code outside of it shall be executed.
+#
+# Expects @ARGV to be passed in.
+## no critic (NamingConventions::Capitalization)
+sub Main {
+## critic (NamingConventions::Capitalization)
+ my @program_arguments = @_;
+ my $error_detected = 0;
+ my $found_separator = 0;
+
+ Getopt::Long::Configure ('gnu_getopt', 'no_auto_abbrev', 'pass_through');
+
+ my $help = 0;
+ my $man = 0;
+ my $debug = 0;
+ Getopt::Long::GetOptionsFromArray (\@program_arguments, 'help|?|h' => \$help,
+ 'man' => \$man,
+ 'debug|d' => \$debug) or Pod::Usage::pod2usage (2);
+
+ if ($help) {
+ Pod::Usage::pod2usage (1);
+ }
+
+ if ($man) {
+ Pod::Usage::pod2usage (-verbose => 2, -exitval => 3);
+ }
+
+ my $sanitized_options = undef;
+
+ if (!($error_detected)) {
+ $sanitized_options = sanitize_program_options (\@program_arguments, (!($found_separator)));
+
+ if (!(defined ($sanitized_options))) {
+ Pod::Usage::pod2usage (-exitval => 'NOEXIT');
+ $error_detected = 4;
+ }
+ }
+
+ my $infile = undef;
+
+ if (!($error_detected)) {
+ if ($debug) {
+ print {*STDERR} 'Sanitized program options string as: ' . Data::Dumper::Dumper ($sanitized_options);
+ }
+
+ # The shift () operations here actually shift the outer array, not the
+ # inner elements.
+ # This can be very confusing.
+ # The return value is an array consisting of a boolean value and an array
+ # reference.
+ #
+ # Thus, shifting once returns the boolean value, while shifting again
+ # returns the options array reference. Crucially, no shift () operation
+ # here modifies the modified options array.
+ $found_separator |= (0 + shift (@{$sanitized_options}));
+ $sanitized_options = shift (@{$sanitized_options});
+ @program_arguments = @{$sanitized_options};
+
+ $infile = shift (@program_arguments);
+
+ if (!(defined ($infile))) {
+ print {*STDERR} "No input file given as an argument, aborting.\n";
+ $error_detected = 5;
+ }
+ }
+
+ if (!($error_detected)) {
+ if ($debug) {
+ print {*STDERR} 'Fetched input file as: ' . Data::Dumper::Dumper (\$infile);
+ }
+
+ if ((!(-f $infile)) || (!(-r $infile))) {
+ print {*STDERR} "Input file \"${infile}\" is not a regular file or readable, aborting.\n";
+ $error_detected = 6;
+ }
+ }
+
+ # In general, we could process the input file line-by-line and cache it in
+ # memory.
+ # That's a bad idea, though, if the file to process is rather big.
+ # Take the easy way out and write to a temporary file instead, which is
+ # memory-friendly and lets the operating system handle caching and the like.
+ my $temp_fh = undef;
+ my $temp_name = undef;
+
+ if (!($error_detected)) {
+ ($temp_fh, $temp_name) = File::Temp::tempfile ('preprocTMP' . 'X' x 16, UNLINK => 1, TMPDIR => 1);
+ $error_detected = parse_file ($infile, $temp_fh, $debug);
+
+ if ($error_detected) {
+ print {*STDERR} "Unable to parse input file \"${infile}\", aborting.\n";
+ $error_detected += 6;
+ }
+ }
+
+ if (!($error_detected)) {
+ # Lastly, copy content of temporary file to original one, but make sure to
+ # flush all data first.
+ $temp_fh->autoflush ();
+
+ File::Copy::copy ($temp_name, $infile);
+ }
+
+ return $error_detected;
+}
+
+__END__
+
+=pod
+
+=head1 NAME
+
+preprocessor.pl - Simple text file preprocessor supporting conditionals
+
+=head1 SYNOPSIS
+
+=over
+
+=item B<preprocessor.pl> B<--help>|B<-h>|B<-?>
+
+=item B<preprocessor.pl> B<--man>
+
+=item B<preprocessor.pl> [B<--debug>|B<-d>] [B<-->] I<file.in>
+
+=back
+
+=head1 DESCRIPTION
+
+=for comment
+Due to parser oddities, breaking a line during an L<dummy|text> formatter code
+right after the separating pipe character will generate broken links.
+Make sure to only break it during the description or, generally, on space
+characters.
+A workaround for this has been proposed in
+https://github.com/Perl/perl5/issues/18305 .
+
+B<preprocessor.pl> is a utility for preprocessing files with a simple,
+hard-coded conditional syntax.
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<--help>|B<-?>|B<-h>
+
+Print a brief help message and exits.
+
+=item B<--man>
+
+Prints the manual page and exits.
+
+=item B<--debug>|B<-d>
+
+Enables noisy debug output.
+
+=back
+
+=head2 CONDITIONAL SYNTAX
+
+This program processes files by evaluating conditions and removing parts that
+evaluate to false.
+
+The supported parser tokens are:
+
+=over 8
+
+=item B<@?>
+
+This token is equivalent to an C<if> statement. A condition that evaluates to a
+boolean value must follow.
+
+=item B<@!>
+
+This token is equivalent to an C<else> statement. The rest of the line will be
+ignored. To this effect, it B<must not> directly be chained with another C<if>
+statement to create an C<elseif> statement.
+
+If you need chaining, start another C<if> statement on a dedicated line after
+the C<else> statement.
+
+=item B<@/>
+
+This token is equivalent to a C<fi> statement. The rest of the line will be
+ignored. This statement terminates a conditional block.
+
+=back
+
+Conditional statements support the following operators:
+
+=over 8
+
+=item I<a> B<==> I<b>
+
+This is a binary operator that checks its two operands for equality.
+
+The semantics are as follows:
+
+=over 2
+
+=item I<a> and I<b> are numbers
+
+The check is being done numerically. Whether arguments are numbers or strings
+is determined via C<Scalar::Util::looks_like_number ()>.
+
+=item otherwise
+
+The check is being done using a string comparison. Note that the locale is
+respected and influences the order of characters.
+
+=back
+
+=item I<a> B<!=> I<b>
+
+This binary operator reverses the boolean value that would be computed using
+the B<==> operator.
+
+=back
+
+=head1 EXAMPLES
+
+Original content:
+
+=over 2
+
+ Constant data
+ @? 10 == 1
+ Dynamic data
+ @? ui != gui
+ is this conditional?
+ @!
+ or this?
+ @/ data discarded
+ and what about this?
+ @!
+ Generated data
+ @? help == me
+ sed -e 's/a/b/g' some.file
+ @!
+ sed -e 's/b/a/g' some.file
+ @/
+ end: data
+ end: transmission
+ @/
+ end: all
+
+=back
+
+Preprocessed content:
+
+=over 2
+
+ Constant data
+ Generated data
+ sed -e 's/b/a/g' some.file
+ end: data
+ end: transmission
+ end: all
+
+=back
+
+=head1 AUTHOR
+
+This manual has been written by
+Mihai Moldovan L<E<lt>ionic at ionic.deE<gt>|mailto:ionic at ionic.de> for the X2Go
+project (L<https://www.x2go.org|https://www.x2go.org>).
+
+=cut
--
Alioth's /home/x2go-admin/maintenancescripts/git/hooks/post-receive-email on /srv/git/code.x2go.org/x2goclient.git
More information about the x2go-commits
mailing list