├── LICENSE.md ├── README.md ├── CONTRIBUTING.md ├── .gitattributes ├── .gitignore └── src ├── build_validator.js ├── replace.py ├── build.bat ├── de_debug.py ├── features.py ├── string_compress.py └── all_in_one.py /LICENSE.md: -------------------------------------------------------------------------------- 1 | whatever -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [https://dnsev-h.github.io/eze/](https://dnsev-h.github.io/eze/) -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | * Make sure your script is up to date 2 | * Make sure your userscript manager is up to date 3 | * Make sure your browser is up to date 4 | * File a bug report if problems persist -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | 45 | # Other 46 | *.pyc 47 | -------------------------------------------------------------------------------- /src/build_validator.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | var fs = require("fs"); 5 | 6 | var main = function () { 7 | // Input check 8 | if (process.argv.length <= 2) { 9 | process.stderr.write("No input files detected\n"); 10 | return -1; 11 | } 12 | 13 | var i, src, filename; 14 | for (i = 2; i < process.argv.length; ++i) { 15 | // Read file 16 | filename = process.argv[i]; 17 | try { 18 | src = fs.readFileSync(filename, { encoding: "utf-8", flag: "r" }); 19 | } 20 | catch (e) { 21 | process.stderr.write("Exception reading file \"" + filename + "\":\n" + e + "\n"); 22 | return -2; 23 | } 24 | 25 | // Bad type (?) 26 | if (typeof(src) !== "string") { 27 | process.stderr.write("File \"" + filename + "\" read did not return a string\n"); 28 | return -3; 29 | } 30 | 31 | // Wrap 32 | src = '"use strict";(function(){' + src + '});'; 33 | 34 | // Eval 35 | try { 36 | eval(src); 37 | } 38 | catch (e) { 39 | process.stderr.write("Exception while executing \"" + filename + "\":\n" + e + "\n"); 40 | return -4; 41 | } 42 | } 43 | 44 | // Okay 45 | return 0; 46 | }; 47 | 48 | if (require.main === module) process.exit(main() || 0); 49 | 50 | })(); 51 | 52 | -------------------------------------------------------------------------------- /src/replace.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import re, os, sys, json, shutil, base64, all_in_one; 3 | 4 | 5 | def replace_pattern(match, replacement_data): 6 | valid_patterns = [ "metadata" ]; 7 | 8 | if (match.group(1) in valid_patterns): 9 | # Increase count 10 | replacement_data["count"] += 1; 11 | 12 | # Replacement 13 | r = json.dumps(replacement_data["metadata_new"], separators=(',', ':')); 14 | r = re.sub(r"^\{|\}$", "", r); 15 | 16 | # Modify 17 | return r; 18 | else: 19 | # Same 20 | return m.group(0); 21 | 22 | 23 | def main(): 24 | f = open(sys.argv[1], "rb"); 25 | source = f.read(); 26 | f.close(); 27 | 28 | # Get metadata 29 | source_lines = source.splitlines(); 30 | for i in range(len(source_lines)): source_lines[i] = source_lines[i].rstrip(); 31 | metadata = all_in_one.get_meta(source_lines)[0]; 32 | 33 | # Split metadata 34 | metadata_new = {}; 35 | for m_type in metadata: 36 | for m_entry in m_type: 37 | if (m_entry[0] not in metadata_new): 38 | metadata_new[m_entry[0]] = []; 39 | metadata_new[m_entry[0]].append(m_entry[1]); 40 | 41 | # Un-array 42 | is_array = [ "grant" , "include" , "require" ]; 43 | for m in metadata_new: 44 | if (len(metadata_new[m]) == 1 and (m not in is_array)): 45 | metadata_new[m] = metadata_new[m][0]; 46 | 47 | # Replace 48 | p = re.compile(r"\/\*\{(.+?)\}\*\/"); 49 | replacement_data = { 50 | "count": 0, 51 | "metadata_new": metadata_new 52 | }; 53 | source_new = p.sub(lambda m: replace_pattern(m, replacement_data), source); 54 | 55 | # Write 56 | f = open(sys.argv[2], "wb"); 57 | f.write(source_new); 58 | f.close(); 59 | 60 | # Okay 61 | return 0; 62 | 63 | 64 | 65 | if (__name__ == "__main__"): sys.exit(main()); 66 | -------------------------------------------------------------------------------- /src/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | color 3 | 4 | 5 | 6 | :: Compile 7 | call :compile "" "" || goto :error 8 | 9 | 10 | 11 | :: Done 12 | goto :eof 13 | 14 | 15 | 16 | :: Compile 17 | :compile 18 | 19 | set VERSION=%1 20 | set VERSION_FULL=%2 21 | set DEST_META=..\builds\eze%VERSION%.meta.js 22 | set DEST=..\builds\eze%VERSION%.user.js 23 | 24 | call :get_features %* 25 | 26 | :: Meta building 27 | python all_in_one.py eze.dev.user.js eze.meta.js -nosep -meta -version %VERSION_FULL% 28 | 29 | :: Main 30 | python all_in_one.py eze.dev.user.js eze.user.js -nosep -version %VERSION_FULL% 31 | python replace.py eze.user.js eze.build1.user.js 32 | python de_debug.py eze.build1.user.js eze.build2.user.js 33 | python features.py eze.build2.user.js eze.build3.user.js %FEATURES% 34 | python string_compress.py eze.build3.user.js eze.build4.user.js 35 | 36 | node build_validator.js eze.build4.user.js || exit /B 1 37 | 38 | :: Delete, copy, and cleanup 39 | del %DEST_META% > NUL 2> NUL 40 | del %DEST% > NUL 2> NUL 41 | 42 | copy eze.meta.js ..\builds\eze%VERSION%.meta.js > NUL 2> NUL 43 | copy eze.build4.user.js ..\builds\eze%VERSION%.user.js > NUL 2> NUL 44 | 45 | del eze.meta.js > NUL 2> NUL 46 | 47 | del eze.user.js > NUL 2> NUL 48 | del eze.build1.user.js > NUL 2> NUL 49 | del eze.build2.user.js > NUL 2> NUL 50 | del eze.build3.user.js > NUL 2> NUL 51 | del eze.build4.user.js > NUL 2> NUL 52 | 53 | exit /B 0 54 | 55 | goto :eof 56 | 57 | 58 | 59 | :: Get features from arguments 60 | :get_features 61 | 62 | set FEATURES= 63 | shift 64 | shift 65 | 66 | if "%~1" neq "" ( 67 | set FEATURES=%1 68 | shift 69 | ) 70 | 71 | :get_features_loop 72 | if "%~1" neq "" ( 73 | set FEATURES=%FEATURES% %1 74 | shift 75 | goto :get_features_loop 76 | ) 77 | 78 | goto :eof 79 | 80 | 81 | 82 | :: Error 83 | :error 84 | color c 85 | echo Something went wrong while building 86 | goto :eof 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/de_debug.py: -------------------------------------------------------------------------------- 1 | import os, sys; 2 | from all_in_one import Parser; 3 | 4 | input = os.path.abspath(sys.argv[1]); 5 | output = os.path.abspath(sys.argv[2]); 6 | 7 | if (input == output): 8 | sys.stderr.write("Input and output are the same"); 9 | sys.exit(-1); 10 | 11 | 12 | 13 | # Read 14 | f = open(input, "rb"); 15 | s = f.read(); 16 | f.close(); 17 | 18 | 19 | 20 | # Process 21 | add_depth = 0; 22 | p = Parser(s); 23 | tokens = []; 24 | wrap_positions = []; 25 | debug_removals = 0; 26 | while (True): 27 | t = p.get_token(); 28 | 29 | if (t[0] == Parser.COMMENT_BLOCK): 30 | if (t[2] == "/**/"): 31 | if (add_depth == 0): 32 | debug_removals += 1; 33 | tokens.append(t[1]); 34 | add_depth += 1; 35 | elif (t[2] == "/**/"): 36 | add_depth -= 1; 37 | if (add_depth < 0): add_depth = 0; 38 | else: 39 | if (add_depth == 0): 40 | tokens.append(t[1]); 41 | tokens.append(t[2]); 42 | else: 43 | if (add_depth == 0): 44 | tokens.append(t[1]); 45 | tokens.append(t[2]); 46 | if (t[2] == "_w"): 47 | wrap_positions.append(len(tokens) - 3); 48 | 49 | if (t[0] is None): break; 50 | 51 | 52 | 53 | # Remove wrap functions 54 | match = [ "." , "_w" , "(" , ")" ]; 55 | wrapper_removals = 0; 56 | for p in wrap_positions: 57 | if (p < 1): continue; 58 | 59 | try: 60 | m = 0; 61 | for i in range(p, p + 4 * 2, 2): 62 | if (tokens[i] != match[m]): break; 63 | m += 1; 64 | 65 | if (m == len(match)): 66 | wrapper_removals += 1; 67 | for i in range(p, p + 4 * 2): 68 | tokens[i] = ""; 69 | except IndexError: 70 | pass; 71 | 72 | 73 | 74 | # Output 75 | s_out = "".join(tokens); 76 | f = open(output, "wb"); 77 | f.write(s_out); 78 | f.close(); 79 | 80 | 81 | 82 | # Done 83 | sys.stdout.write("Removed {0:d} debug segment(s)\n".format(debug_removals)); 84 | sys.stdout.write("Removed {0:d} wrapper functions(s)\n".format(wrapper_removals)); 85 | sys.exit(0); 86 | -------------------------------------------------------------------------------- /src/features.py: -------------------------------------------------------------------------------- 1 | import os, re, sys; 2 | from all_in_one import Parser; 3 | 4 | input = os.path.abspath(sys.argv[1]); 5 | output = os.path.abspath(sys.argv[2]); 6 | 7 | if (input == output): 8 | sys.stderr.write("Input and output are the same"); 9 | sys.exit(-1); 10 | 11 | 12 | 13 | # Read 14 | f = open(input, "rb"); 15 | s = f.read(); 16 | f.close(); 17 | 18 | 19 | # Features 20 | features = sys.argv[2:]; 21 | feature_stack = []; 22 | removed_features = {}; 23 | kept_features = {}; 24 | 25 | 26 | # Process 27 | re_feature = re.compile(r"/\*<(/)?feature:([^>]*)>\*/"); 28 | p = Parser(s); 29 | tokens = []; 30 | enabled = True; 31 | loop = True; 32 | while (loop): 33 | t = p.get_token(); 34 | loop = (t[0] is not None); 35 | 36 | if (t[0] == Parser.COMMENT_BLOCK): 37 | m = re_feature.match(t[2]); 38 | if (m is not None): 39 | t = None; 40 | tag = m.group(2); 41 | if (m.group(1) is None): 42 | # Opener 43 | feature_stack.append([ tag , enabled ]); 44 | if (tag in features): 45 | if (tag not in kept_features): kept_features[tag] = 0; 46 | kept_features[tag] += 1; 47 | else: 48 | enabled = False; 49 | if (tag not in removed_features): removed_features[tag] = 0; 50 | removed_features[tag] += 1; 51 | else: 52 | # Closer 53 | if (len(feature_stack) > 0 and feature_stack[-1][0] == tag): 54 | pass; 55 | else: 56 | sys.stderr.write("Mismatched feature tag\n"); 57 | sys.exit(-1); 58 | enabled = feature_stack.pop()[1]; 59 | 60 | if (enabled and t is not None): 61 | tokens.append(t[1]); 62 | tokens.append(t[2]); 63 | 64 | 65 | 66 | # Output 67 | s_out = "".join(tokens); 68 | f = open(output, "wb"); 69 | f.write(s_out); 70 | f.close(); 71 | 72 | 73 | 74 | # Done 75 | def feature_info(map, label1, label2): 76 | keys = map.keys(); 77 | keys.sort(key=lambda k: k.lower()); 78 | if (len(keys) == 0): 79 | sys.stdout.write("{0:s}\n".format(label1)); 80 | else: 81 | sys.stdout.write("{0:s}\n".format(label2)); 82 | for k in keys: 83 | v = map[k]; 84 | sys.stdout.write(" {0:s}: {1:d} section{2:s}\n".format(k, v, "" if (v == 1) else "s")); 85 | 86 | feature_info(kept_features, "No features kept", "Features kept"); 87 | feature_info(removed_features, "No features removed", "Features removed"); 88 | 89 | sys.exit(0); 90 | -------------------------------------------------------------------------------- /src/string_compress.py: -------------------------------------------------------------------------------- 1 | import os, re, sys; 2 | from all_in_one import Parser; 3 | 4 | input = os.path.abspath(sys.argv[1]); 5 | output = os.path.abspath(sys.argv[2]); 6 | 7 | if (input == output): 8 | sys.stderr.write("Input and output are the same"); 9 | sys.exit(-1); 10 | 11 | 12 | 13 | # Read 14 | f = open(input, "rb"); 15 | s = f.read(); 16 | f.close(); 17 | 18 | 19 | 20 | # Join string tokens 21 | def escape_quote(string, quote): 22 | i = 0; 23 | escaped = False; 24 | string_len = len(string); 25 | last_pos = 0; 26 | parts = []; 27 | 28 | while (i < string_len): 29 | c = string[i]; 30 | if (escaped): 31 | escaped = False; 32 | if (c == "\n"): 33 | parts.append(string[last_pos : i]); 34 | last_pos = i + 1; 35 | elif (c == "\r"): 36 | parts.append(string[last_pos : i]); 37 | if (i + 1 < string_len and string[i + 1] == "\n"): 38 | i += 1; 39 | last_pos = i + 1; 40 | else: 41 | if (c == "\\"): 42 | escaped = True; 43 | elif (c == quote): 44 | parts.append(string[last_pos : i]); 45 | parts.append("\\"); 46 | last_pos = i; 47 | 48 | # Next 49 | i += 1; 50 | 51 | if (len(parts) > 0): 52 | parts.append(string[last_pos : ]); 53 | return "".join(parts); 54 | else: 55 | return string; 56 | 57 | def join_string_tokens(strings, joiner): 58 | # Find most common quote 59 | counts = {}; 60 | quote = '"'; 61 | 62 | if (len(strings) > 0): 63 | for s in strings: 64 | c = s[0]; 65 | if (c not in counts): counts[c] = 1; 66 | else: counts[c] += 1; 67 | 68 | counts = counts.items(); 69 | counts.sort(key=lambda k: k[1]); 70 | quote = counts[0][0]; 71 | quote = '"'; 72 | 73 | # Modify joiner 74 | if (joiner[0] == quote): 75 | joiner = joiner[1:-1]; 76 | else: 77 | joiner = escape_quote(joiner[1:-1], quote); 78 | 79 | # Join 80 | full = [ quote ]; 81 | 82 | first = True; 83 | for s in strings: 84 | if (first): 85 | first = False; 86 | else: 87 | full.append(joiner); 88 | 89 | if (s[0] == quote): 90 | full.append(s[1:-1]); 91 | else: 92 | full.append(escape_quote(s[1:-1], quote)); 93 | 94 | full.append(quote); 95 | return "".join(full); 96 | 97 | 98 | 99 | # Process 100 | p = Parser(s); 101 | tokens = []; 102 | loop = True; 103 | string_state = 0; 104 | string_tokens_all = []; 105 | string_tokens = []; 106 | string_token_middle = None; 107 | string_token_first_whitespace = None; 108 | pre_was_string = False; 109 | open_comment_blocks = 0; 110 | re_newline = re.compile("\r\n?|\n"); 111 | while (loop): 112 | t = p.get_token(); 113 | loop = (t[0] is not None); 114 | 115 | if (string_state <= 0): 116 | if (t[0] == Parser.SYMBOL and t[2] == "["): 117 | string_token_first_whitespace = t[1]; 118 | string_tokens_all.extend([ t[1], t[2] ]); 119 | string_state = 1; 120 | pre_was_string = False; 121 | open_comment_blocks = 0; 122 | t = None; 123 | 124 | elif (string_state == -2): 125 | if (t[0] == Parser.COMMENT): 126 | # skip 127 | if (t[2][:3] == "//}"): 128 | t = None; 129 | open_comment_blocks -= 1; 130 | if (open_comment_blocks <= 0): 131 | string_state = 0; 132 | if (t is not None and re_newline.search(t[1] + t[2]) is not None): 133 | string_state = 0; 134 | 135 | elif (string_state == 1): 136 | # Potentially forming a string 137 | string_tokens_all.extend([ t[1], t[2] ]); 138 | if (t[0] == Parser.COMMENT or t[0] == Parser.COMMENT_BLOCK): 139 | # skip 140 | start = t[2][:3]; 141 | if (start == "//{"): 142 | open_comment_blocks += 1; 143 | elif (start == "//}"): 144 | open_comment_blocks -= 1; 145 | if (open_comment_blocks < 0): open_comment_blocks = 0; 146 | elif (t[0] == Parser.STRING): 147 | pre_was_string = True; 148 | string_tokens.append(t[2]); 149 | elif (t[0] == Parser.SYMBOL): 150 | if (t[2] == "]"): 151 | string_state = 2; 152 | elif (t[2] == ","): 153 | if (not pre_was_string): 154 | # Not a string 155 | string_state = -1; 156 | else: 157 | # Not a string 158 | string_state = -1; 159 | else: 160 | # Not a string 161 | string_state = -1; 162 | 163 | t = None; 164 | 165 | else: 166 | # Look for .join("") 167 | string_tokens_all.extend([ t[1], t[2] ]); 168 | if (string_state == 2): # . 169 | if (t[2] == "."): 170 | string_state = 3; 171 | else: 172 | string_state = -1; 173 | 174 | t = None; 175 | elif (string_state == 3): # join 176 | if (t[2] == "join"): 177 | string_state = 4; 178 | else: 179 | string_state = -1; 180 | 181 | t = None; 182 | elif (string_state == 4): # ( 183 | if (t[2] == "("): 184 | string_state = 5; 185 | else: 186 | string_state = -1; 187 | 188 | t = None; 189 | elif (string_state == 5): # string 190 | if (t[0] == Parser.STRING): 191 | string_token_middle = t[2]; 192 | string_state = 6; 193 | else: 194 | string_state = -1; 195 | 196 | t = None; 197 | elif (string_state == 6): # ) 198 | if (t[2] == ")"): 199 | # Complete 200 | tokens.append(string_token_first_whitespace); 201 | tokens.append(join_string_tokens(string_tokens, string_token_middle)); 202 | string_tokens_all = []; 203 | string_tokens = []; 204 | string_state = 0; 205 | if (open_comment_blocks > 0): 206 | string_state = -2; 207 | else: 208 | string_state = -1; 209 | 210 | t = None; 211 | 212 | 213 | if (t is not None): 214 | tokens.append(t[1]); 215 | tokens.append(t[2]); 216 | elif (string_state == -1): 217 | tokens.extend(string_tokens_all); 218 | string_tokens_all = []; 219 | string_tokens = []; 220 | string_state = 0; 221 | 222 | 223 | 224 | 225 | # Output 226 | s_out = "".join(tokens); 227 | f = open(output, "wb"); 228 | f.write(s_out); 229 | f.close(); 230 | 231 | 232 | 233 | # Done 234 | sys.exit(0); 235 | -------------------------------------------------------------------------------- /src/all_in_one.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import re, os, sys; 3 | 4 | 5 | 6 | class Parser: 7 | __operators = ( 8 | ">>=" , "<<=" , "===" , "!==" , ">>>" , 9 | ">>" , "<<" , "++" , "--" , "==" , "!=" , "<=" , ">=" , "&&" , "||" , "+=" , "-=" , "*=" , "/=" , "%=" , "|=" , "&=" , "^=" , 10 | "=" , "+" , "-" , "*" , "/" , "%" , "&" , "|" , "^" , ">" , "<" , "!" , "~" , "." , "[" , "]" , "(" , ")" , "{" , "}" , "?" , ":" , "," , ";" 11 | ); 12 | __regex_symbols = ( 13 | ">>=" , "<<=" , "===" , "!==" , ">>>" , 14 | ">>" , "<<" , "++" , "--" , "==" , "!=" , "<=" , ">=" , "&&" , "||" , "+=" , "-=" , "*=" , "/=" , "%=" , "|=" , "&=" , "^=" , 15 | "=" , "+" , "-" , "*" , "/" , "%" , "&" , "|" , "^" , ">" , "<" , "!" , "~" , "." , "[" , "(" , "{" , "?" , ":" , "," , ";" 16 | ); 17 | WORD = 0; 18 | NUMBER = 1; 19 | SYMBOL = 2; 20 | STRING = 3; 21 | COMMENT = 4; 22 | COMMENT_BLOCK = 5; 23 | REGEX = 6; 24 | 25 | @staticmethod 26 | def is_alphanumeric(ch): 27 | ch = ord(ch.lower()); 28 | return (ch >= ord('a') and ch <= ord('z')) or (ch >= ord('0') and ch <= ord('9')) or ch == ord('_') or ch == ord('$'); 29 | @staticmethod 30 | def is_alphabetic(ch): 31 | ch = ord(ch.lower()); 32 | return (ch >= ord('a') and ch <= ord('z')) or ch == ord('_') or ch == ord('$'); 33 | @staticmethod 34 | def is_numeric(ch): 35 | ch = ord(ch.lower()); 36 | return (ch >= ord('0') and ch <= ord('9')); 37 | 38 | def __init__(self, src): 39 | self.src = src; 40 | self.pos = 0; 41 | self.token_type_pre = None; 42 | self.token_str_pre = None; 43 | 44 | def get_token(self): 45 | # Remove whitespace 46 | whitespace = ""; 47 | while (self.pos < len(self.src) and ord(self.src[self.pos]) <= 32): 48 | whitespace += self.src[self.pos]; 49 | self.pos += 1; 50 | # Done? 51 | if (self.pos >= len(self.src)): 52 | self.token_type_pre = None; 53 | self.token_str_pre = None; 54 | return ( None , whitespace , "" ); 55 | 56 | # Get type 57 | type = None; 58 | if (self.src[self.pos] == '"' or self.src[self.pos] == '\''): 59 | type = Parser.STRING; 60 | elif (self.src[self.pos] == '/' and self.pos + 1 < len(self.src) and (self.src[self.pos + 1] == '/' or self.src[self.pos + 1] == '*')): 61 | if (self.src[self.pos + 1] == '*'): 62 | type = Parser.COMMENT_BLOCK; 63 | else: 64 | type = Parser.COMMENT; 65 | elif (self.src[self.pos] == '/' and (self.token_type_pre == Parser.SYMBOL and self.token_str_pre in Parser.__regex_symbols)): # not perfect, but should work 66 | type = Parser.REGEX; 67 | elif (Parser.is_alphabetic(self.src[self.pos])): 68 | type = Parser.WORD; 69 | elif (Parser.is_numeric(self.src[self.pos])): 70 | type = Parser.NUMBER; 71 | else: 72 | if (self.src[self.pos] == '.' and self.pos + 1 < len(self.src) and Parser.is_numeric(self.src[self.pos + 1])): 73 | type = Parser.NUMBER; 74 | else: 75 | type = Parser.SYMBOL; 76 | 77 | # Parse based on type 78 | literal = self.src[self.pos]; 79 | self.pos += 1; 80 | if (type == Parser.WORD): 81 | # Parse 82 | while (self.pos < len(self.src) and Parser.is_alphanumeric(self.src[self.pos])): 83 | literal += self.src[self.pos]; 84 | self.pos += 1; 85 | elif (type == Parser.NUMBER): 86 | # Parse 87 | while (self.pos < len(self.src) and Parser.is_alphanumeric(self.src[self.pos])): 88 | literal += self.src[self.pos]; 89 | self.pos += 1; 90 | elif (type == Parser.SYMBOL): 91 | # Currently only works for a maximum of length=3 operators 92 | if (self.pos + 1 < len(self.src) and (literal + self.src[self.pos] + self.src[self.pos + 1]) in Parser.__operators): 93 | literal += self.src[self.pos] + self.src[self.pos + 1]; 94 | self.pos += 2; 95 | elif (self.pos < len(self.src) and (literal + self.src[self.pos]) in Parser.__operators): 96 | literal += self.src[self.pos]; 97 | self.pos += 1; 98 | elif (type == Parser.STRING): 99 | # Init 100 | escape = False; 101 | quote = literal; 102 | # Parse 103 | while (self.pos < len(self.src)): 104 | literal += self.src[self.pos]; 105 | if (escape): 106 | escape = False; 107 | else: 108 | if (self.src[self.pos] == '\\'): 109 | escape = True; 110 | elif (self.src[self.pos] == quote or self.src[self.pos] == '\n'): 111 | self.pos += 1; 112 | break; 113 | self.pos += 1; 114 | elif (type == Parser.REGEX): 115 | # Init 116 | escape = False; 117 | quote = literal; 118 | # Parse 119 | while (self.pos < len(self.src)): 120 | literal += self.src[self.pos]; 121 | if (escape): 122 | escape = False; 123 | else: 124 | if (self.src[self.pos] == '\\'): 125 | escape = True; 126 | elif (self.src[self.pos] == quote or self.src[self.pos] == '\n'): 127 | self.pos += 1; 128 | break; 129 | self.pos += 1; 130 | # Flags 131 | while (self.pos < len(self.src)): 132 | if (ord(self.src[self.pos]) >= ord('a') and ord(self.src[self.pos]) <= ord('z')): 133 | literal += self.src[self.pos]; 134 | self.pos += 1; 135 | else: 136 | break; 137 | elif (type == Parser.COMMENT): 138 | # Init 139 | literal += self.src[self.pos]; 140 | self.pos += 1; 141 | # Parse 142 | while (self.pos < len(self.src)): 143 | literal += self.src[self.pos]; 144 | self.pos += 1; 145 | if (self.src[self.pos - 1] == '\n'): 146 | if (self.pos >= 2 and self.src[self.pos - 2] == '\\'): 147 | # Escaped 148 | pass; 149 | elif (self.pos >= 3 and self.src[self.pos - 2] == '\r' and self.src[self.pos - 3] == '\\'): 150 | # Escaped 151 | pass; 152 | else: 153 | break; 154 | elif (type == Parser.COMMENT_BLOCK): 155 | # Init 156 | literal += self.src[self.pos]; 157 | self.pos += 1; 158 | was_star = False; 159 | # Parse 160 | while (self.pos < len(self.src)): 161 | literal += self.src[self.pos]; 162 | self.pos += 1; 163 | if (was_star and self.src[self.pos - 1] == '/'): break; 164 | was_star = (self.src[self.pos - 1] == '*'); 165 | else: # if (type == Parser.PP_CMD): 166 | # Init 167 | # Parse 168 | while (self.pos < len(self.src)): 169 | # Comment check 170 | if (self.pos + 1 < len(self.src) and self.src[self.pos] == '/' and (self.src[self.pos + 1] == '/' or self.src[self.pos + 1] == '*')): 171 | break; 172 | # Append 173 | literal += self.src[self.pos]; 174 | self.pos += 1; 175 | # Endlines 176 | if (self.src[self.pos - 1] == '\n'): 177 | if (self.pos >= 2 and self.src[self.pos - 2] == '\\'): 178 | # Escaped 179 | pass; 180 | elif (self.pos >= 3 and self.src[self.pos - 2] == '\r' and self.src[self.pos - 3] == '\\'): 181 | # Escaped 182 | pass; 183 | else: 184 | break; 185 | 186 | # Return 187 | if (type != Parser.COMMENT and type != Parser.COMMENT_BLOCK): 188 | self.token_type_pre = type; 189 | self.token_str_pre = literal; 190 | return ( type , whitespace , literal ); 191 | 192 | def join(self, token1, token2, newline, pre_whitespace): 193 | # Ignore 194 | if (token2[0] == Parser.COMMENT_BLOCK): 195 | return [ None , "" ]; 196 | if (token2[0] == Parser.COMMENT): 197 | return [ None , token2[1] + "\n" ]; 198 | 199 | # First token 200 | if (token1 == None): return token2[2]; 201 | 202 | # Newlines 203 | if ((pre_whitespace + token2[1]).rfind("\n") >= 0): 204 | pos = token2[1].rfind("\n"); 205 | return newline + token2[1][pos + 1 : ] + token2[2]; 206 | 207 | # Spacing 208 | space = " "; 209 | if ((token1[0] == Parser.WORD or token1[0] == Parser.NUMBER) and (token2[0] == Parser.WORD or token2[0] == Parser.NUMBER) and (self.is_alphanumeric(token1[2][-1]) and self.is_alphanumeric(token2[2][0]))): 210 | # Separate words and regex 211 | return " " + token2[2]; 212 | if ((token1[0] == Parser.REGEX) and (token2[0] == Parser.WORD or token2[0] == Parser.NUMBER)): 213 | # Separate words and regex 214 | return " " + token2[2]; 215 | if (token1[0] == Parser.SYMBOL and token2[0] == Parser.SYMBOL): 216 | for i in range(len(token2[2])): 217 | if ((token1[2] + token2[2][ : i + 1]) in Parser.__operators): 218 | # Separate operators; 219 | return " " + token2[2]; 220 | # Since this isn't doing smart parsing, do this instead 221 | #if (token1[0] == Parser.SYMBOL and (token1[2] == ")" or token1[2] == "]" or token1[2] == "}") and token2[1].find("\n") >= 0): 222 | # return "\n" + token2[2]; 223 | 224 | # Done 225 | return token2[2]; 226 | 227 | 228 | def write_compressed(source, out, t_pre, newline): 229 | p = Parser(source); 230 | add = ""; 231 | while (True): 232 | t = p.get_token(); 233 | if (t[0] == None): break; 234 | s = p.join(t_pre, t, newline, add); 235 | if (s[0] != None): 236 | add = ""; 237 | out.write(s); 238 | t_pre = t; 239 | else: 240 | add += s[1]; 241 | 242 | return t_pre; 243 | 244 | 245 | def get_meta(source): 246 | # Find headers 247 | metadata_labels = [ "UserScript" , "Meta" ]; 248 | metadata_types = [ 0 , 0 ]; 249 | metadata = []; 250 | i = 0; 251 | for label in range(len(metadata_labels)): 252 | metadata.append([]); 253 | in_header = False; 254 | while (i < len(source)): 255 | s = re.split(r"\s", source[i]); 256 | if (s[0][:2] == "//"): 257 | # Comment line 258 | pos = source[i].find("//"); 259 | data = source[i][pos + 2 : ].strip(); 260 | if (in_header): 261 | if (data == ("==/" + metadata_labels[label] + "==")): 262 | i += 1; 263 | break; 264 | elif (metadata_types[label] == 1): 265 | metadata[label].append(source[i]); 266 | elif (data[0] == "@"): 267 | # Parse the option 268 | s = re.split(r"\s", data); 269 | param = s[0][1:]; 270 | value = data[1 + len(param) : ].strip(); 271 | metadata[label].append((param , value)); 272 | elif (data == ("==" + metadata_labels[label] + "==")): in_header = True; 273 | else: break; 274 | elif (metadata_types[label] == 1): 275 | metadata[label].append(source[i]); 276 | else: break; 277 | i += 1; 278 | source_line_first = i; 279 | 280 | return ( metadata , source_line_first ); 281 | 282 | 283 | def main(): 284 | # Usage 285 | if (len(sys.argv) < 3): 286 | print "Usage:" 287 | print " " + sys.argv[0] + " input_userscript.js output_filename.js"; 288 | return -1; 289 | 290 | # Input/output files 291 | input = sys.argv[1]; 292 | output = sys.argv[2]; 293 | argv_remainder = sys.argv[3:]; 294 | shrink = ("-min" in argv_remainder); 295 | meta_only = ("-meta" in argv_remainder); 296 | no_separators = ("-nosep" in argv_remainder); 297 | dev_replace = ""; 298 | if ("-version" in argv_remainder): 299 | i = argv_remainder.index("-version") + 1; 300 | if (i < len(argv_remainder)): 301 | dev_replace = argv_remainder[i]; 302 | 303 | if (input.lower() == output.lower()): 304 | print "Error: input and output scripts cannot be the same"; 305 | return -1; 306 | output_meta = output.replace(".user.js", ".meta.js"); 307 | output_target = output.replace(".meta.js", ".user.js"); 308 | 309 | # Read input 310 | f = open(input, "rb"); 311 | source = f.read().splitlines(); 312 | f.close(); 313 | for i in range(len(source)): source[i] = source[i].rstrip(); 314 | 315 | # Find headers 316 | metadata_labels = [ "UserScript" , "Meta" ]; 317 | metadata = get_meta(source); 318 | source_line_first = metadata[1]; 319 | metadata = metadata[0]; 320 | 321 | # Setup 322 | out = open(output, "wb"); # sys.stdout; 323 | padding = 0; 324 | newline = "\n"; 325 | for i in range(len(metadata[0])): padding = max(padding, len(metadata[0][i][0])); 326 | for i in range(len(metadata[1])): padding = max(padding, len(metadata[1][i][0])); 327 | padding = padding + 1; 328 | requires = []; 329 | 330 | # Metadata 331 | out.write("// ==" + metadata_labels[0] + "==" + newline); 332 | for i in range(len(metadata[0])): 333 | key = metadata[0][i][0]; 334 | val = metadata[0][i][1]; 335 | if (key != "require"): 336 | # Remove "(dev)" tag from title 337 | if (key == "name"): 338 | val = re.compile(r"\s*\(dev\)\s*$", re.I).sub(dev_replace, val); 339 | out.write("// @" + key + (" " * (padding - len(key))) + val + newline); 340 | else: 341 | requires.append(val.rsplit("/", 1)[-1]); 342 | for i in range(len(metadata[1])): 343 | out.write("// @" + metadata[1][i][0] + (" " * (padding - len(metadata[1][i][0]))) + metadata[1][i][1].replace("{{target}}", output_target).replace("{{meta}}", output_meta) + newline); 344 | out.write("// ==/" + metadata_labels[0] + "==" + newline); 345 | 346 | # Done 347 | if (meta_only): 348 | out.close(); 349 | return 0; 350 | 351 | if (not shrink): 352 | if (not no_separators): 353 | out.write(newline + newline); 354 | else: 355 | out.write("// For license information, check the individual files" + newline); 356 | 357 | # Include requirements 358 | t_pre = None; 359 | for i in range(len(requires)): 360 | f = open(requires[i], "rb"); 361 | require_source = f.read(); 362 | f.close(); 363 | 364 | if (shrink): 365 | t_pre = write_compressed(require_source, out, t_pre, newline); 366 | out.write(newline); 367 | else: 368 | if (not no_separators): 369 | out.write(("/" * 80) + newline); 370 | out.write("//{ " + requires[i] + newline); 371 | out.write(("/" * 80) + newline); 372 | 373 | require_source = require_source.splitlines(); 374 | for j in range(len(require_source)): 375 | out.write(require_source[j].rstrip() + newline); 376 | 377 | if (not no_separators): 378 | out.write(("/" * 80) + newline); 379 | out.write("//} /" + requires[i] + newline); 380 | out.write(("/" * 80) + newline + newline + newline); 381 | 382 | # Main source 383 | if (shrink): 384 | t_pre = write_compressed(newline.join(source[source_line_first : ]), out, t_pre, newline); 385 | out.write(newline); 386 | else: 387 | if (not no_separators): 388 | out.write(("/" * 80) + newline); 389 | out.write("//{ Userscript" + newline); 390 | out.write(("/" * 80) + newline); 391 | 392 | for i in range(source_line_first, len(source)): 393 | out.write(source[i].rstrip() + newline); 394 | 395 | if (not no_separators): 396 | out.write(("/" * 80) + newline); 397 | out.write("//} /Userscript" + newline); 398 | out.write(("/" * 80) + newline + newline); 399 | 400 | # Done 401 | out.close(); 402 | return 0; 403 | 404 | 405 | # Run 406 | if (__name__ == "__main__"): sys.exit(main()); 407 | 408 | --------------------------------------------------------------------------------