001package net.filebot.format;
002
003import static java.util.Collections.*;
004import static java.util.stream.Collectors.*;
005import static net.filebot.Settings.*;
006import static net.filebot.util.RegularExpressions.*;
007
008import java.io.File;
009import java.io.FileNotFoundException;
010import java.util.Collection;
011import java.util.LinkedHashMap;
012import java.util.List;
013import java.util.Map;
014import java.util.Objects;
015import java.util.regex.Pattern;
016import java.util.stream.Stream;
017
018import com.sun.jna.Platform;
019
020import groovy.lang.Closure;
021import groovy.lang.Script;
022import groovy.util.XmlSlurper;
023import net.filebot.ApplicationFolder;
024import net.filebot.platform.mac.MacAppUtilities;
025import net.filebot.util.FileUtilities;
026
027/**
028 * Global functions available in the {@link ExpressionFormat}
029 */
030public class ExpressionFormatFunctions {
031
032        /*
033         * General helpers and utilities
034         */
035
036        public static Object call(Script context, Object object) {
037                if (object instanceof Closure) {
038                        try {
039                                return call(context, ((Closure) object).call());
040                        } catch (Exception e) {
041                                return null;
042                        }
043                }
044
045                if (isEmptyValue(context, object)) {
046                        return null;
047                }
048
049                return object;
050        }
051
052        public static boolean isEmptyValue(Script context, Object object) {
053                // treat empty string as null
054                if (object instanceof CharSequence && object.toString().isEmpty()) {
055                        return true;
056                }
057
058                // treat empty list as null
059                if (object instanceof Collection && ((Collection) object).isEmpty()) {
060                        return true;
061                }
062
063                return false;
064        }
065
066        public static Object any(Script context, Object c1, Object c2, Object... cN) {
067                return stream(context, c1, c2, cN).findFirst().orElse(null);
068        }
069
070        public static List<Object> allOf(Script context, Object c1, Object c2, Object... cN) {
071                return stream(context, c1, c2, cN).collect(toList());
072        }
073
074        public static String concat(Script context, Object c1, Object c2, Object... cN) {
075                return stream(context, c1, c2, cN).map(Objects::toString).collect(joining());
076        }
077
078        private static Stream<Object> stream(Script context, Object c1, Object c2, Object... cN) {
079                return Stream.concat(Stream.of(c1, c2), Stream.of(cN)).map(c -> call(context, c)).filter(Objects::nonNull);
080        }
081
082        /*
083         * Unix Shell / Windows PowerShell utilities
084         */
085
086        public static String quote(Script context, Object c1, Object... cN) {
087                return Platform.isWindows() ? quotePowerShell(context, c1, cN) : quoteBash(context, c1, cN);
088        }
089
090        public static String quoteBash(Script context, Object c1, Object... cN) {
091                return stream(context, c1, null, cN).map(Objects::toString).map(s -> "'" + s.replace("'", "'\"'\"'") + "'").collect(joining(" "));
092        }
093
094        public static String quotePowerShell(Script context, Object c1, Object... cN) {
095                return stream(context, c1, null, cN).map(Objects::toString).map(s -> "@'\n" + s + "\n'@").collect(joining(" "));
096        }
097
098        /*
099         * I/O utilities
100         */
101
102        public static Map<String, String> csv(Script context, Object path) throws Exception {
103                Pattern[] delimiter = { TAB, SEMICOLON };
104                Map<String, String> map = new LinkedHashMap<String, String>();
105                for (String line : readLines(context, path)) {
106                        for (Pattern d : delimiter) {
107                                String[] field = d.split(line, 2);
108                                if (field.length >= 2) {
109                                        map.put(field[0].trim(), field[1].trim());
110                                        break;
111                                }
112                        }
113                }
114                return map;
115        }
116
117        public static List<String> readLines(Script context, Object path) throws Exception {
118                return FileUtilities.readLines(resolve(context, path));
119        }
120
121        public static Object readXml(Script context, Object path) throws Exception {
122                return new XmlSlurper().parse(resolve(context, path));
123        }
124
125        public static File getUserFile(Script context, Object path) throws Exception {
126                File f = path instanceof File ? (File) path : new File(path.toString());
127
128                if (!f.isAbsolute()) {
129                        f = ApplicationFolder.UserHome.resolve(f.getPath());
130                }
131
132                if (isMacSandbox()) {
133                        MacAppUtilities.askUnlockFolders(null, singleton(f));
134                }
135
136                if (!f.exists()) {
137                        throw new FileNotFoundException("File not found: " + f);
138                }
139
140                return f;
141        }
142
143        public static Object include(Script context, Object path) throws Exception {
144                return context.evaluate(resolve(context, path));
145        }
146
147        public static File resolve(Script context, Object path) throws Exception {
148                File include = path instanceof File ? (File) path : new File(path.toString());
149
150                // resolve relative path relative to current script file
151                if (!include.isAbsolute()) {
152                        String script = context.getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
153                        if (!GROOVY_SCRIPT_CODE_BASE.equals(script)) {
154                                return getUserFile(context, new File(new File(script).getParentFile(), include.getPath()));
155                        }
156                }
157
158                return getUserFile(context, include);
159        }
160
161        private static final String GROOVY_SCRIPT_CODE_BASE = "/groovy/script";
162
163}