1#!/usr/bin/env fish
2
3# This file must be sourced in fish:
4#
5#   . (which env_parallel.fish)
6#
7# after which 'env_parallel' works
8#
9#
10# Copyright (C) 2016-2021 Ole Tange, http://ole.tange.dk and Free
11# Software Foundation, Inc.
12#
13# This program is free software; you can redistribute it and/or modify
14# it under the terms of the GNU General Public License as published by
15# the Free Software Foundation; either version 3 of the License, or
16# (at your option) any later version.
17#
18# This program is distributed in the hope that it will be useful, but
19# WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21# General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License
24# along with this program; if not, see <http://www.gnu.org/licenses/>
25# or write to the Free Software Foundation, Inc., 51 Franklin St,
26# Fifth Floor, Boston, MA 02110-1301 USA
27#
28# SPDX-FileCopyrightText: 2021 Ole Tange, http://ole.tange.dk and Free Software and Foundation, Inc.
29# SPDX-License-Identifier: GPL-3.0-or-later
30
31# If you are a fisherman feel free to improve the code
32#
33# The code needs to deal with variables like:
34#   set funky (perl -e 'print pack "c*", 2..254')
35#
36# Problem:
37#   Tell the difference between:
38#     set tmp "a'  'b'  'c"
39#     set tmparr1 "a'  'b"  'c'
40#     set tmparr2 'a'  "b'  'c"
41#   The output from `set` is exactly the same.
42# Solution:
43#   for-loop for each variable. Each value is separated with a
44#   separator.
45
46function env_parallel
47  # env_parallel.fish
48
49  # --session
50  perl -e 'exit grep { /^--session/ } @ARGV' -- $argv; or begin;
51    setenv PARALLEL_IGNORED_NAMES (
52        begin;
53	  functions -n
54	  set -n;
55	end | perl -pe 's/\n/,/g';
56    )
57    return 0
58  end;
59  setenv PARALLEL_ENV (
60    begin;
61      set _grep_REGEXP (
62        begin;
63          perl -e '
64	    for(@ARGV){
65                /^_$/ and $next_is_env = 0;
66                $next_is_env and push @envvar, split/,/, $_;
67                $next_is_env = /^--env$/;
68            }
69            $vars = join "|",map { quotemeta $_ } @envvar;
70            print $vars ? "($vars)" : "(.*)";
71            ' -- $argv;
72        end;
73      )
74      # Deal with --env _
75      set _ignore_UNDERSCORE (
76        begin;
77          perl -e '
78            for(@ARGV){
79                $next_is_env and push @envvar, split/,/, $_;
80                $next_is_env=/^--env$/;
81            }
82            if(grep { /^_$/ } @envvar) {
83                if(not open(IN, "<", "$ENV{HOME}/.parallel/ignored_vars")) {
84             	    print STDERR "parallel: Error: ",
85            	    "Run \"parallel --record-env\" in a clean environment first.\n";
86                } else {
87            	    chomp(@ignored_vars = <IN>);
88                }
89            }
90            if($ENV{PARALLEL_IGNORED_NAMES}) {
91                push @ignored_vars, split/,/, $ENV{PARALLEL_IGNORED_NAMES};
92                chomp @ignored_vars;
93            }
94            $vars = join "|",map { quotemeta $_ } @ignored_vars;
95	    print $vars ? "($vars)" : "(,,nO,,VaRs,,)";
96            ' -- $argv;
97        end;
98      )
99
100      # --record-env
101      perl -e 'exit grep { /^--record-env$/ } @ARGV' -- $argv; or begin;
102        begin;
103	  functions -n | perl -pe 's/,/\n/g';
104	  set -n;
105	end | cat > $HOME/.parallel/ignored_vars;
106      end;
107
108      # Export function definitions
109      # Keep the ones from --env
110      # Ignore the ones from ~/.parallel/ignored_vars
111      # Dump each function defition
112      # Replace \001 with \002 because \001 is used by env_parallel
113      # Convert \n to \001
114      functions -n | perl -pe 's/,/\n/g' | \
115        grep -Ev '^(PARALLEL_ENV|PARALLEL_TMP)$' | \
116        grep -E "^$_grep_REGEXP"\$ | grep -vE "^$_ignore_UNDERSCORE"\$ | \
117        while read d; functions $d; end | \
118        perl -pe 's/\001/\002/g and not $printed++ and print STDERR
119          "env_parallel: Warning: ASCII value 1 in functions is not supported\n";
120                  s/\n/\001/g';
121      # Convert scalar vars to fish \XX quoting
122      # Keep the ones from --env
123      # Ignore the ones from ~/.parallel/ignored_vars
124      # Ignore read only vars
125      # Execute 'set' of the content
126      eval (set -L | \
127        grep -Ev '^(PARALLEL_TMP)$' | \
128        grep -E "^$_grep_REGEXP " | grep -vE "^$_ignore_UNDERSCORE " | \
129        perl -ne 'chomp;
130          ($name,$val)=split(/ /,$_,2);
131          $name=~/^(HOME|USER|COLUMNS|FISH_VERSION|LINES|PWD|SHLVL|_|
132                    history|status|version)$|\./x and next;
133          if($val=~/^'"'"'/) { next; }
134          print "set $name \"\$$name\";\n";
135        ')
136      # Generate commands to set scalar variables
137      # Keep the ones from --env
138      # Ignore the ones from ~/.parallel/ignored_vars
139      #
140      begin;
141        for v in (set -n | \
142          grep -Ev '^(PARALLEL_TMP)$' | \
143          grep -E "^$_grep_REGEXP\$" | grep -vE "^$_ignore_UNDERSCORE\$");
144          # Separate variables with the string: \000
145	  # array_name1 val1\0
146	  # array_name1 val2\0
147	  # array_name2 val3\0
148	  # array_name2 val4\0
149          eval "for i in \$$v;
150            echo -n $v \$i;
151    	    perl -e print\\\"\\\\0\\\";
152          end;"
153        end;
154        # A final line to flush the last variable in Perl
155        perl -e print\"\\0\";
156      end | perl -0 -ne '
157        # Remove separator string \0
158        chop;
159	# Split line into name and value
160        ($name,$val)=split(/ /,$_,2);
161        # Ignore read-only vars
162        $name=~/^(HOME|USER|COLUMNS|FISH_VERSION|LINES|PWD|SHLVL|_|
163                  fish_pid|history|hostname|status|version)$/x and next;
164        # Single quote $val
165	if($val =~ /[^-_.+a-z0-9\/]/i) {
166	  $val =~ s/\047/\047"\047"\047/g;  # "-quote single quotes
167  	  $val = "\047$val\047";            # single-quote entire string
168	  $val =~ s/^\047\047|\047\047$//g; # Remove unneeded '' at ends
169	} elsif ($val eq "") {
170	  $val = "\047\047";
171	}
172
173        if($name ne $last and $last) {
174          # The $name is different, so this is a new variable.
175          # Print the last one.
176          # Separate list elements by 2 spaces
177          $"="  ";
178          print "set $last @qval;\n";
179          @qval=();
180        }
181        push @qval,$val;
182        $last=$name;
183        '| \
184        perl -pe 's/\001/\002/g and not $printed++ and print STDERR
185          "env_parallel: Warning: ASCII value 1 in variables is not supported\n";
186          s/\n/\001/g'
187    end;
188    )
189  # If --record-env: exit
190  perl -e 'exit grep { /^--record-env$/ } @ARGV' -- $argv; and parallel $argv;
191  set _parallel_exit_CODE $status
192  set -e PARALLEL_ENV
193  return $_parallel_exit_CODE
194end
195