', '2023-07-10', 1, 8, '推荐一个支持谷歌插件,微软插件的手机浏览器~', 'https://pic.imgdb.cn/item/640f214ef144a01007e4e317.jpg');
-- ----------------------------
-- Table structure for category
@@ -116,7 +116,7 @@ CREATE TABLE `comment` (
-- ----------------------------
-- Records of comment
-- ----------------------------
-INSERT INTO `comment` VALUES (49, 13, 26, '可以,666', 0, '2023-07-10 20:31:59', 1, 5, '2023-07-10 20:31:59', 0);
+INSERT INTO `comment` VALUES (49, 13, 26, '可以', 0, '2023-07-10 20:31:59', 1, 5, '2023-07-10 20:31:59', 0);
-- ----------------------------
-- Table structure for githubinfo
@@ -141,7 +141,7 @@ CREATE TABLE `githubinfo` (
`isVaild` int NULL DEFAULT NULL COMMENT '是否有效',
PRIMARY KEY (`id`) USING BTREE,
INDEX `projectId_info`(`projectId` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 27 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+) ENGINE = InnoDB AUTO_INCREMENT = 28 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of githubinfo
@@ -150,6 +150,46 @@ INSERT INTO `githubinfo` VALUES (23, 27, 'https://avatars.githubusercontent.com/
INSERT INTO `githubinfo` VALUES (24, 26, 'https://avatars.githubusercontent.com/u/3266363?v=4', 'awesome-macos-screensavers', 'https://starchart.cc/agarrharr/awesome-macos-screensavers.svg', 3180, '无', 'true', 80, 4, 'User', 'master', 129, '无', '0', 0);
INSERT INTO `githubinfo` VALUES (25, 42, 'https://avatars.githubusercontent.com/u/65466868?v=4', 'devpod', 'https://starchart.cc/loft-sh/devpod.svg', 3233, 'Go', 'true', 21, 69, 'Organization', 'main', 137, 'MPL-2.0', '0', 0);
INSERT INTO `githubinfo` VALUES (26, 43, 'https://avatars.githubusercontent.com/u/69631?v=4', 'redex', 'https://starchart.cc/facebook/redex.svg', 5854, 'C++', 'true', 229, 70, 'Organization', 'main', 671, 'MIT', '0', 0);
+INSERT INTO `githubinfo` VALUES (27, 32, 'https://avatars.githubusercontent.com/u/65466868?v=4', 'devpod', 'https://starchart.cc/loft-sh/devpod.svg', 3239, 'Go', 'true', 21, 69, 'Organization', 'main', 137, 'MPL-2.0', '0', 0);
+
+-- ----------------------------
+-- Table structure for onefile
+-- ----------------------------
+DROP TABLE IF EXISTS `onefile`;
+CREATE TABLE `onefile` (
+ `id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
+ `oneFile_Name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '名称',
+ `language` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '语言',
+ `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述',
+ `userId` int NULL DEFAULT NULL COMMENT '作者ID',
+ `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '访问地址',
+ `lookNum` int NULL DEFAULT NULL COMMENT '观看数',
+ `Content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '内容',
+ `isValid` int NULL DEFAULT NULL COMMENT '是否有效',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 19 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Records of onefile
+-- ----------------------------
+INSERT INTO `onefile` VALUES (1, 'tinyhttpd.c', 'C', '不到 500 行的超轻量型 HTTP Server,可以用来理解服务器程序的原理和本质', 12, 'https://github.com/EZLippi/Tinyhttpd', 1111, '/* J. David\'s webserver */\r\n/* This is a simple webserver.\r\n * Created November 1999 by J. David Blackstone.\r\n * CSE 4344 (Network concepts), Prof. Zeigler\r\n * University of Texas at Arlington\r\n */\r\n/* This program compiles for Sparc Solaris 2.6.\r\n * To compile for Linux:\r\n * 1) Comment out the #include line.\r\n * 2) Comment out the line that defines the variable newthread.\r\n * 3) Comment out the two lines that run pthread_create().\r\n * 4) Uncomment the line that runs accept_request().\r\n * 5) Remove -lsocket from the Makefile.\r\n */\r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n\r\n#define ISspace(x) isspace((int)(x))\r\n\r\n#define SERVER_STRING \"Server: jdbhttpd/0.1.0\\r\\n\"\r\n#define STDIN 0\r\n#define STDOUT 1\r\n#define STDERR 2\r\n\r\nvoid accept_request(void *);\r\nvoid bad_request(int);\r\nvoid cat(int, FILE *);\r\nvoid cannot_execute(int);\r\nvoid error_die(const char *);\r\nvoid execute_cgi(int, const char *, const char *, const char *);\r\nint get_line(int, char *, int);\r\nvoid headers(int, const char *);\r\nvoid not_found(int);\r\nvoid serve_file(int, const char *);\r\nint startup(u_short *);\r\nvoid unimplemented(int);\r\n\r\n/**********************************************************************/\r\n/* A request has caused a call to accept() on the server port to\r\n * return. Process the request appropriately.\r\n * Parameters: the socket connected to the client */\r\n/**********************************************************************/\r\nvoid accept_request(void *arg)\r\n{\r\n int client = (intptr_t)arg;\r\n char buf[1024];\r\n size_t numchars;\r\n char method[255];\r\n char url[255];\r\n char path[512];\r\n size_t i, j;\r\n struct stat st;\r\n int cgi = 0; /* becomes true if server decides this is a CGI\r\n * program */\r\n char *query_string = NULL;\r\n\r\n numchars = get_line(client, buf, sizeof(buf));\r\n i = 0; j = 0;\r\n while (!ISspace(buf[i]) && (i < sizeof(method) - 1))\r\n {\r\n method[i] = buf[i];\r\n i++;\r\n }\r\n j=i;\r\n method[i] = \'\\0\';\r\n\r\n if (strcasecmp(method, \"GET\") && strcasecmp(method, \"POST\"))\r\n {\r\n unimplemented(client);\r\n return;\r\n }\r\n\r\n if (strcasecmp(method, \"POST\") == 0)\r\n cgi = 1;\r\n\r\n i = 0;\r\n while (ISspace(buf[j]) && (j < numchars))\r\n j++;\r\n while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars))\r\n {\r\n url[i] = buf[j];\r\n i++; j++;\r\n }\r\n url[i] = \'\\0\';\r\n\r\n if (strcasecmp(method, \"GET\") == 0)\r\n {\r\n query_string = url;\r\n while ((*query_string != \'?\') && (*query_string != \'\\0\'))\r\n query_string++;\r\n if (*query_string == \'?\')\r\n {\r\n cgi = 1;\r\n *query_string = \'\\0\';\r\n query_string++;\r\n }\r\n }\r\n\r\n sprintf(path, \"htdocs%s\", url);\r\n if (path[strlen(path) - 1] == \'/\')\r\n strcat(path, \"index.html\");\r\n if (stat(path, &st) == -1) {\r\n while ((numchars > 0) && strcmp(\"\\n\", buf)) /* read & discard headers */\r\n numchars = get_line(client, buf, sizeof(buf));\r\n not_found(client);\r\n }\r\n else\r\n {\r\n if ((st.st_mode & S_IFMT) == S_IFDIR)\r\n strcat(path, \"/index.html\");\r\n if ((st.st_mode & S_IXUSR) ||\r\n (st.st_mode & S_IXGRP) ||\r\n (st.st_mode & S_IXOTH) )\r\n cgi = 1;\r\n if (!cgi)\r\n serve_file(client, path);\r\n else\r\n execute_cgi(client, path, method, query_string);\r\n }\r\n\r\n close(client);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Inform the client that a request it has made has a problem.\r\n * Parameters: client socket */\r\n/**********************************************************************/\r\nvoid bad_request(int client)\r\n{\r\n char buf[1024];\r\n\r\n sprintf(buf, \"HTTP/1.0 400 BAD REQUEST\\r\\n\");\r\n send(client, buf, sizeof(buf), 0);\r\n sprintf(buf, \"Content-type: text/html\\r\\n\");\r\n send(client, buf, sizeof(buf), 0);\r\n sprintf(buf, \"\\r\\n\");\r\n send(client, buf, sizeof(buf), 0);\r\n sprintf(buf, \"
Your browser sent a bad request, \");\r\n send(client, buf, sizeof(buf), 0);\r\n sprintf(buf, \"such as a POST without a Content-Length.\\r\\n\");\r\n send(client, buf, sizeof(buf), 0);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Put the entire contents of a file out on a socket. This function\r\n * is named after the UNIX \"cat\" command, because it might have been\r\n * easier just to do something like pipe, fork, and exec(\"cat\").\r\n * Parameters: the client socket descriptor\r\n * FILE pointer for the file to cat */\r\n/**********************************************************************/\r\nvoid cat(int client, FILE *resource)\r\n{\r\n char buf[1024];\r\n\r\n fgets(buf, sizeof(buf), resource);\r\n while (!feof(resource))\r\n {\r\n send(client, buf, strlen(buf), 0);\r\n fgets(buf, sizeof(buf), resource);\r\n }\r\n}\r\n\r\n/**********************************************************************/\r\n/* Inform the client that a CGI script could not be executed.\r\n * Parameter: the client socket descriptor. */\r\n/**********************************************************************/\r\nvoid cannot_execute(int client)\r\n{\r\n char buf[1024];\r\n\r\n sprintf(buf, \"HTTP/1.0 500 Internal Server Error\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"Content-type: text/html\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"
Error prohibited CGI execution.\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Print out an error message with perror() (for system errors; based\r\n * on value of errno, which indicates system call errors) and exit the\r\n * program indicating an error. */\r\n/**********************************************************************/\r\nvoid error_die(const char *sc)\r\n{\r\n perror(sc);\r\n exit(1);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Execute a CGI script. Will need to set environment variables as\r\n * appropriate.\r\n * Parameters: client socket descriptor\r\n * path to the CGI script */\r\n/**********************************************************************/\r\nvoid execute_cgi(int client, const char *path,\r\n const char *method, const char *query_string)\r\n{\r\n char buf[1024];\r\n int cgi_output[2];\r\n int cgi_input[2];\r\n pid_t pid;\r\n int status;\r\n int i;\r\n char c;\r\n int numchars = 1;\r\n int content_length = -1;\r\n\r\n buf[0] = \'A\'; buf[1] = \'\\0\';\r\n if (strcasecmp(method, \"GET\") == 0)\r\n while ((numchars > 0) && strcmp(\"\\n\", buf)) /* read & discard headers */\r\n numchars = get_line(client, buf, sizeof(buf));\r\n else if (strcasecmp(method, \"POST\") == 0) /*POST*/\r\n {\r\n numchars = get_line(client, buf, sizeof(buf));\r\n while ((numchars > 0) && strcmp(\"\\n\", buf))\r\n {\r\n buf[15] = \'\\0\';\r\n if (strcasecmp(buf, \"Content-Length:\") == 0)\r\n content_length = atoi(&(buf[16]));\r\n numchars = get_line(client, buf, sizeof(buf));\r\n }\r\n if (content_length == -1) {\r\n bad_request(client);\r\n return;\r\n }\r\n }\r\n else/*HEAD or other*/\r\n {\r\n }\r\n\r\n\r\n if (pipe(cgi_output) < 0) {\r\n cannot_execute(client);\r\n return;\r\n }\r\n if (pipe(cgi_input) < 0) {\r\n cannot_execute(client);\r\n return;\r\n }\r\n\r\n if ( (pid = fork()) < 0 ) {\r\n cannot_execute(client);\r\n return;\r\n }\r\n sprintf(buf, \"HTTP/1.0 200 OK\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n if (pid == 0) /* child: CGI script */\r\n {\r\n char meth_env[255];\r\n char query_env[255];\r\n char length_env[255];\r\n\r\n dup2(cgi_output[1], STDOUT);\r\n dup2(cgi_input[0], STDIN);\r\n close(cgi_output[0]);\r\n close(cgi_input[1]);\r\n sprintf(meth_env, \"REQUEST_METHOD=%s\", method);\r\n putenv(meth_env);\r\n if (strcasecmp(method, \"GET\") == 0) {\r\n sprintf(query_env, \"QUERY_STRING=%s\", query_string);\r\n putenv(query_env);\r\n }\r\n else { /* POST */\r\n sprintf(length_env, \"CONTENT_LENGTH=%d\", content_length);\r\n putenv(length_env);\r\n }\r\n execl(path, NULL);\r\n exit(0);\r\n } else { /* parent */\r\n close(cgi_output[1]);\r\n close(cgi_input[0]);\r\n if (strcasecmp(method, \"POST\") == 0)\r\n for (i = 0; i < content_length; i++) {\r\n recv(client, &c, 1, 0);\r\n write(cgi_input[1], &c, 1);\r\n }\r\n while (read(cgi_output[0], &c, 1) > 0)\r\n send(client, &c, 1, 0);\r\n\r\n close(cgi_output[0]);\r\n close(cgi_input[1]);\r\n waitpid(pid, &status, 0);\r\n }\r\n}\r\n\r\n/**********************************************************************/\r\n/* Get a line from a socket, whether the line ends in a newline,\r\n * carriage return, or a CRLF combination. Terminates the string read\r\n * with a null character. If no newline indicator is found before the\r\n * end of the buffer, the string is terminated with a null. If any of\r\n * the above three line terminators is read, the last character of the\r\n * string will be a linefeed and the string will be terminated with a\r\n * null character.\r\n * Parameters: the socket descriptor\r\n * the buffer to save the data in\r\n * the size of the buffer\r\n * Returns: the number of bytes stored (excluding null) */\r\n/**********************************************************************/\r\nint get_line(int sock, char *buf, int size)\r\n{\r\n int i = 0;\r\n char c = \'\\0\';\r\n int n;\r\n\r\n while ((i < size - 1) && (c != \'\\n\'))\r\n {\r\n n = recv(sock, &c, 1, 0);\r\n /* DEBUG printf(\"%02X\\n\", c); */\r\n if (n > 0)\r\n {\r\n if (c == \'\\r\')\r\n {\r\n n = recv(sock, &c, 1, MSG_PEEK);\r\n /* DEBUG printf(\"%02X\\n\", c); */\r\n if ((n > 0) && (c == \'\\n\'))\r\n recv(sock, &c, 1, 0);\r\n else\r\n c = \'\\n\';\r\n }\r\n buf[i] = c;\r\n i++;\r\n }\r\n else\r\n c = \'\\n\';\r\n }\r\n buf[i] = \'\\0\';\r\n\r\n return(i);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Return the informational HTTP headers about a file. */\r\n/* Parameters: the socket to print the headers on\r\n * the name of the file */\r\n/**********************************************************************/\r\nvoid headers(int client, const char *filename)\r\n{\r\n char buf[1024];\r\n (void)filename; /* could use filename to determine file type */\r\n\r\n strcpy(buf, \"HTTP/1.0 200 OK\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n strcpy(buf, SERVER_STRING);\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"Content-Type: text/html\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n strcpy(buf, \"\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Give a client a 404 not found status message. */\r\n/**********************************************************************/\r\nvoid not_found(int client)\r\n{\r\n char buf[1024];\r\n\r\n sprintf(buf, \"HTTP/1.0 404 NOT FOUND\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, SERVER_STRING);\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"Content-Type: text/html\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"
Not Found\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"
The server could not fulfill\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"your request because the resource specified\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"is unavailable or nonexistent.\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Send a regular file to the client. Use headers, and report\r\n * errors to client if they occur.\r\n * Parameters: a pointer to a file structure produced from the socket\r\n * file descriptor\r\n * the name of the file to serve */\r\n/**********************************************************************/\r\nvoid serve_file(int client, const char *filename)\r\n{\r\n FILE *resource = NULL;\r\n int numchars = 1;\r\n char buf[1024];\r\n\r\n buf[0] = \'A\'; buf[1] = \'\\0\';\r\n while ((numchars > 0) && strcmp(\"\\n\", buf)) /* read & discard headers */\r\n numchars = get_line(client, buf, sizeof(buf));\r\n\r\n resource = fopen(filename, \"r\");\r\n if (resource == NULL)\r\n not_found(client);\r\n else\r\n {\r\n headers(client, filename);\r\n cat(client, resource);\r\n }\r\n fclose(resource);\r\n}\r\n\r\n/**********************************************************************/\r\n/* This function starts the process of listening for web connections\r\n * on a specified port. If the port is 0, then dynamically allocate a\r\n * port and modify the original port variable to reflect the actual\r\n * port.\r\n * Parameters: pointer to variable containing the port to connect on\r\n * Returns: the socket */\r\n/**********************************************************************/\r\nint startup(u_short *port)\r\n{\r\n int httpd = 0;\r\n int on = 1;\r\n struct sockaddr_in name;\r\n\r\n httpd = socket(PF_INET, SOCK_STREAM, 0);\r\n if (httpd == -1)\r\n error_die(\"socket\");\r\n memset(&name, 0, sizeof(name));\r\n name.sin_family = AF_INET;\r\n name.sin_port = htons(*port);\r\n name.sin_addr.s_addr = htonl(INADDR_ANY);\r\n if ((setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0) \r\n { \r\n error_die(\"setsockopt failed\");\r\n }\r\n if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)\r\n error_die(\"bind\");\r\n if (*port == 0) /* if dynamically allocating a port */\r\n {\r\n socklen_t namelen = sizeof(name);\r\n if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)\r\n error_die(\"getsockname\");\r\n *port = ntohs(name.sin_port);\r\n }\r\n if (listen(httpd, 5) < 0)\r\n error_die(\"listen\");\r\n return(httpd);\r\n}\r\n\r\n/**********************************************************************/\r\n/* Inform the client that the requested web method has not been\r\n * implemented.\r\n * Parameter: the client socket */\r\n/**********************************************************************/\r\nvoid unimplemented(int client)\r\n{\r\n char buf[1024];\r\n\r\n sprintf(buf, \"HTTP/1.0 501 Method Not Implemented\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, SERVER_STRING);\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"Content-Type: text/html\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"\\r\\n\");\r\n send(client, buf, strlen(buf), 0);\r\n sprintf(buf, \"
\r\n \r\n\r\n\r\n\r\n', 1);
+INSERT INTO `onefile` VALUES (10, 'ascii-cam', 'HTML', '把视频图像转成 ascii \r\n', 12, 'https://github.com/521xueweihan/OneFile/blob/main/src/html/ascii-cam.html', 2600, '\r\n\r\n\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n A S C I I C A M\r\n \r\n\r\n\r\n\r\n
\r\n\r\n _n_|_|_,_\r\n |===.-.===|\r\n | ((_)) |\r\n \'===\'-\'===\'\r\nA S C I I C A M\r\n点击“允许”使用摄像头\r\n
\r\n\r\n
Allow access to camera to use
\r\n\r\n
\r\n
\r\n \r\n \r\n \r\n
\r\n
\r\n\r\n \r\n \r\n \r\n\r\n\r\n', 1);
+INSERT INTO `onefile` VALUES (11, 'the-super-tiny-compiler', 'JavaScript', '人人都能看懂的微型编译器', 12, 'https://github.com/jamiebuilds/the-super-tiny-compiler', 9100, '\'use strict\';\r\n\r\n/**\r\n * TTTTTTTTTTTTTTTTTTTTTTTHHHHHHHHH HHHHHHHHHEEEEEEEEEEEEEEEEEEEEEE\r\n * T:::::::::::::::::::::TH:::::::H H:::::::HE::::::::::::::::::::E\r\n * T:::::::::::::::::::::TH:::::::H H:::::::HE::::::::::::::::::::E\r\n * T:::::TT:::::::TT:::::THH::::::H H::::::HHEE::::::EEEEEEEEE::::E\r\n * TTTTTT T:::::T TTTTTT H:::::H H:::::H E:::::E EEEEEE\r\n * T:::::T H:::::H H:::::H E:::::E\r\n * T:::::T H::::::HHHHH::::::H E::::::EEEEEEEEEE\r\n * T:::::T H:::::::::::::::::H E:::::::::::::::E\r\n * T:::::T H:::::::::::::::::H E:::::::::::::::E\r\n * T:::::T H::::::HHHHH::::::H E::::::EEEEEEEEEE\r\n * T:::::T H:::::H H:::::H E:::::E\r\n * T:::::T H:::::H H:::::H E:::::E EEEEEE\r\n * TT:::::::TT HH::::::H H::::::HHEE::::::EEEEEEEE:::::E\r\n * T:::::::::T H:::::::H H:::::::HE::::::::::::::::::::E\r\n * T:::::::::T H:::::::H H:::::::HE::::::::::::::::::::E\r\n * TTTTTTTTTTT HHHHHHHHH HHHHHHHHHEEEEEEEEEEEEEEEEEEEEEE\r\n *\r\n * SSSSSSSSSSSSSSS UUUUUUUU UUUUUUUUPPPPPPPPPPPPPPPPP EEEEEEEEEEEEEEEEEEEEEERRRRRRRRRRRRRRRRR\r\n * SS:::::::::::::::SU::::::U U::::::UP::::::::::::::::P E::::::::::::::::::::ER::::::::::::::::R\r\n * S:::::SSSSSS::::::SU::::::U U::::::UP::::::PPPPPP:::::P E::::::::::::::::::::ER::::::RRRRRR:::::R\r\n * S:::::S SSSSSSSUU:::::U U:::::UUPP:::::P P:::::PEE::::::EEEEEEEEE::::ERR:::::R R:::::R\r\n * S:::::S U:::::U U:::::U P::::P P:::::P E:::::E EEEEEE R::::R R:::::R\r\n * S:::::S U:::::U U:::::U P::::P P:::::P E:::::E R::::R R:::::R\r\n * S::::SSSS U:::::U U:::::U P::::PPPPPP:::::P E::::::EEEEEEEEEE R::::RRRRRR:::::R\r\n * SS::::::SSSSS U:::::U U:::::U P:::::::::::::PP E:::::::::::::::E R:::::::::::::RR\r\n * SSS::::::::SS U:::::U U:::::U P::::PPPPPPPPP E:::::::::::::::E R::::RRRRRR:::::R\r\n * SSSSSS::::S U:::::U U:::::U P::::P E::::::EEEEEEEEEE R::::R R:::::R\r\n * S:::::S U:::::U U:::::U P::::P E:::::E R::::R R:::::R\r\n * S:::::S U::::::U U::::::U P::::P E:::::E EEEEEE R::::R R:::::R\r\n * SSSSSSS S:::::S U:::::::UUU:::::::U PP::::::PP EE::::::EEEEEEEE:::::ERR:::::R R:::::R\r\n * S::::::SSSSSS:::::S UU:::::::::::::UU P::::::::P E::::::::::::::::::::ER::::::R R:::::R\r\n * S:::::::::::::::SS UU:::::::::UU P::::::::P E::::::::::::::::::::ER::::::R R:::::R\r\n * SSSSSSSSSSSSSSS UUUUUUUUU PPPPPPPPPP EEEEEEEEEEEEEEEEEEEEEERRRRRRRR RRRRRRR\r\n *\r\n * TTTTTTTTTTTTTTTTTTTTTTTIIIIIIIIIINNNNNNNN NNNNNNNNYYYYYYY YYYYYYY\r\n * T:::::::::::::::::::::TI::::::::IN:::::::N N::::::NY:::::Y Y:::::Y\r\n * T:::::::::::::::::::::TI::::::::IN::::::::N N::::::NY:::::Y Y:::::Y\r\n * T:::::TT:::::::TT:::::TII::::::IIN:::::::::N N::::::NY::::::Y Y::::::Y\r\n * TTTTTT T:::::T TTTTTT I::::I N::::::::::N N::::::NYYY:::::Y Y:::::YYY\r\n * T:::::T I::::I N:::::::::::N N::::::N Y:::::Y Y:::::Y\r\n * T:::::T I::::I N:::::::N::::N N::::::N Y:::::Y:::::Y\r\n * T:::::T I::::I N::::::N N::::N N::::::N Y:::::::::Y\r\n * T:::::T I::::I N::::::N N::::N:::::::N Y:::::::Y\r\n * T:::::T I::::I N::::::N N:::::::::::N Y:::::Y\r\n * T:::::T I::::I N::::::N N::::::::::N Y:::::Y\r\n * T:::::T I::::I N::::::N N:::::::::N Y:::::Y\r\n * TT:::::::TT II::::::IIN::::::N N::::::::N Y:::::Y\r\n * T:::::::::T I::::::::IN::::::N N:::::::N YYYY:::::YYYY\r\n * T:::::::::T I::::::::IN::::::N N::::::N Y:::::::::::Y\r\n * TTTTTTTTTTT IIIIIIIIIINNNNNNNN NNNNNNN YYYYYYYYYYYYY\r\n *\r\n * CCCCCCCCCCCCC OOOOOOOOO MMMMMMMM MMMMMMMMPPPPPPPPPPPPPPPPP IIIIIIIIIILLLLLLLLLLL EEEEEEEEEEEEEEEEEEEEEERRRRRRRRRRRRRRRRR\r\n * CCC::::::::::::C OO:::::::::OO M:::::::M M:::::::MP::::::::::::::::P I::::::::IL:::::::::L E::::::::::::::::::::ER::::::::::::::::R\r\n * CC:::::::::::::::C OO:::::::::::::OO M::::::::M M::::::::MP::::::PPPPPP:::::P I::::::::IL:::::::::L E::::::::::::::::::::ER::::::RRRRRR:::::R\r\n * C:::::CCCCCCCC::::CO:::::::OOO:::::::OM:::::::::M M:::::::::MPP:::::P P:::::PII::::::IILL:::::::LL EE::::::EEEEEEEEE::::ERR:::::R R:::::R\r\n * C:::::C CCCCCCO::::::O O::::::OM::::::::::M M::::::::::M P::::P P:::::P I::::I L:::::L E:::::E EEEEEE R::::R R:::::R\r\n * C:::::C O:::::O O:::::OM:::::::::::M M:::::::::::M P::::P P:::::P I::::I L:::::L E:::::E R::::R R:::::R\r\n * C:::::C O:::::O O:::::OM:::::::M::::M M::::M:::::::M P::::PPPPPP:::::P I::::I L:::::L E::::::EEEEEEEEEE R::::RRRRRR:::::R\r\n * C:::::C O:::::O O:::::OM::::::M M::::M M::::M M::::::M P:::::::::::::PP I::::I L:::::L E:::::::::::::::E R:::::::::::::RR\r\n * C:::::C O:::::O O:::::OM::::::M M::::M::::M M::::::M P::::PPPPPPPPP I::::I L:::::L E:::::::::::::::E R::::RRRRRR:::::R\r\n * C:::::C O:::::O O:::::OM::::::M M:::::::M M::::::M P::::P I::::I L:::::L E::::::EEEEEEEEEE R::::R R:::::R\r\n * C:::::C O:::::O O:::::OM::::::M M:::::M M::::::M P::::P I::::I L:::::L E:::::E R::::R R:::::R\r\n * C:::::C CCCCCCO::::::O O::::::OM::::::M MMMMM M::::::M P::::P I::::I L:::::L LLLLLL E:::::E EEEEEE R::::R R:::::R\r\n * C:::::CCCCCCCC::::CO:::::::OOO:::::::OM::::::M M::::::MPP::::::PP II::::::IILL:::::::LLLLLLLLL:::::LEE::::::EEEEEEEE:::::ERR:::::R R:::::R\r\n * CC:::::::::::::::C OO:::::::::::::OO M::::::M M::::::MP::::::::P I::::::::IL::::::::::::::::::::::LE::::::::::::::::::::ER::::::R R:::::R\r\n * CCC::::::::::::C OO:::::::::OO M::::::M M::::::MP::::::::P I::::::::IL::::::::::::::::::::::LE::::::::::::::::::::ER::::::R R:::::R\r\n * CCCCCCCCCCCCC OOOOOOOOO MMMMMMMM MMMMMMMMPPPPPPPPPP IIIIIIIIIILLLLLLLLLLLLLLLLLLLLLLLLEEEEEEEEEEEEEEEEEEEEEERRRRRRRR RRRRRRR\r\n *\r\n * =======================================================================================================================================================================\r\n * =======================================================================================================================================================================\r\n * =======================================================================================================================================================================\r\n * =======================================================================================================================================================================\r\n */\r\n\r\n/**\r\n * 今天我们要一起写一个编译器。 可能和之前的编译器不太一样,因为这个编译器实在是太 mini 了!迷你到除去注视意外,整个文件大约只有 200 行实际代码。\r\n * 首先我们将把一些类似 lisp 的函数调用编译成类似 C 的函数调用。如果您不熟悉其中之一,那就先来一个简单快速的介绍:\r\n * 如果我们有两个函数 `add` 和 `subtract` 他们会写成这样:\r\n *\r\n * LISP C\r\n *\r\n * 2 + 2 (add 2 2) add(2, 2)\r\n * 4 - 2 (subtract 4 2) subtract(4, 2)\r\n * 2 + (4 - 2) (add 2 (subtract 4 2)) add(2, subtract(4, 2))\r\n *\r\n * 这个对比容易理解吧?这正是我们要编译的。 虽然这不是完整的 LISP 或 C 语法,但它足以展示现代编译器的许多主要部分。\r\n */\r\n\r\n/**\r\n * 大多数编译器分为三个主要阶段:解析、转换和代码生成:\r\n *\r\n * 1. 解析:将原始代码转化为更抽象的代码表示\r\n *\r\n * 2. 转换:采用抽象表示并进行操作来执行编译器希望它执行的任何操作\r\n *\r\n * 3. 代码生成:把经过转换表示的代码转换为新代码\r\n\r\n/**\r\n * 解析\r\n * -------\r\n *\r\n * 解析通常分为两个阶段:词法分析和句法分析\r\n *\r\n * 1. 词法分析:获取原始代码并通过一种称为标记器(或词法分析器 Tokenizer)的东西将其拆分为一种称为标记(Token)的东西。\r\n * 标记是一个数组,它描述了一个独立的语法片段。这些片段可以是数字、标签、标点符号、运算符等等。\r\n *\r\n * 2. 语法分析:获取之前生成的标记(Token),并把它们转换成一种抽象的表示,这种抽象的表示描述了代码语句中的每一个片段以及它们之间的关系。\r\n * 这被称为中间表示(intermediate representation)或抽象语法树(Abstract Syntax Tree, 缩写为AST)。\r\n * AST是一个深度嵌套的对象,用一种更容易处理的方式代表了代码本身,也能给我们更多信息。\r\n *\r\n * 对于下面这个句法:\r\n *\r\n * (add 2 (subtract 4 2))\r\n *\r\n * 标记可能看起来像这样:\r\n *\r\n * [\r\n * { type: \'paren\', value: \'(\' },\r\n * { type: \'name\', value: \'add\' },\r\n * { type: \'number\', value: \'2\' },\r\n * { type: \'paren\', value: \'(\' },\r\n * { type: \'name\', value: \'subtract\' },\r\n * { type: \'number\', value: \'4\' },\r\n * { type: \'number\', value: \'2\' },\r\n * { type: \'paren\', value: \')\' },\r\n * { type: \'paren\', value: \')\' },\r\n * ]\r\n *\r\n * 然后一个抽象语法树(AST)可能看起来像这样:\r\n *\r\n * {\r\n * type: \'Program\',\r\n * body: [{\r\n * type: \'CallExpression\',\r\n * name: \'add\',\r\n * params: [{\r\n * type: \'NumberLiteral\',\r\n * value: \'2\',\r\n * }, {\r\n * type: \'CallExpression\',\r\n * name: \'subtract\',\r\n * params: [{\r\n * type: \'NumberLiteral\',\r\n * value: \'4\',\r\n * }, {\r\n * type: \'NumberLiteral\',\r\n * value: \'2\',\r\n * }]\r\n * }]\r\n * }]\r\n * }\r\n */\r\n\r\n/**\r\n * 转换\r\n * --------------\r\n *\r\n * 编译器的下一个阶段是转换。同样的,获取了 AST 之后再对其进行更改。\r\n * 它可以用相同的语言操作 AST,也可以将其翻译成一种全新的语言。\r\n *\r\n * 让我们来看看如何转换 AST。\r\n *\r\n * 你可能会注意到我们的 AST 中的元素看起来非常相似。\r\n * 这些对象都有具有类型属性。这些中的每一个都称为 AST 的节点。\r\n * 这些节点在树上定义了属性并描述了树上的每一个独立的部分。\r\n *\r\n * 我们可以有一个“NumberLiteral”的节点:\r\n *\r\n * {\r\n * type: \'NumberLiteral\',\r\n * value: \'2\',\r\n * }\r\n *\r\n * 或者可能是“CallExpression”的一个节点:\r\n *\r\n * {\r\n * type: \'CallExpression\',\r\n * name: \'subtract\',\r\n * params: [...嵌套节点放在这里...],\r\n * }\r\n *\r\n * 在转换 AST 的时候,我们可以通过新增、删除、替换属性来操作节点,我们可以新增节点、删除节点,\r\n * 或者我们可以在原有的 AST 结构保持不变的状态下创建一个基于它的新的 AST。\r\n *\r\n * 由于我们的目标是一种新的语言,所以我们将要专注于创造一个完全新的 AST 来配合这个特定的语言。\r\n *\r\n * 遍历\r\n * ---------\r\n *\r\n * 为了浏览所有的这些节点,我们需要遍历它们。\r\n * 这个遍历过程首先是深度遍历。\r\n *\r\n * {\r\n * type: \'Program\',\r\n * body: [{\r\n * type: \'CallExpression\',\r\n * name: \'add\',\r\n * params: [{\r\n * type: \'NumberLiteral\',\r\n * value: \'2\'\r\n * }, {\r\n * type: \'CallExpression\',\r\n * name: \'subtract\',\r\n * params: [{\r\n * type: \'NumberLiteral\',\r\n * value: \'4\'\r\n * }, {\r\n * type: \'NumberLiteral\',\r\n * value: \'2\'\r\n * }]\r\n * }]\r\n * }]\r\n * }\r\n *\r\n * 对于上面那个 AST,我们会:\r\n *\r\n * 1.Program-首先从 AST 的顶层开始\r\n * 2. CallExpression(add)-移动到程序主体的第一个元素\r\n * 3. NumberLiteral(2)-移动到 NumberLiteral 参数的第一个元素\r\n * 4. CallExpression (subtract) - 移动到 CallExpression 参数的第二个元素\r\n * 5. NumberLiteral (4) - 移动到 CallExpression 参数的第一个元素\r\n * 6. NumberLiteral (2) - 移动到 CallExpression 参数的第二个元素\r\n *\r\n * 如果我们直接操作这个 AST,而不是创造一个单独的 AST,我们很有可能会在这里引入各种抽象。\r\n * 但是仅仅访问树中的每个节点对于我们来说想做和能做的事情已经很多了。\r\n *\r\n * 使用访问(visiting)这个词是因为这是一种模式,代表在对象结构内对元素进行操作。\r\n *\r\n * 访问者(Visitors)\r\n * --------\r\n *\r\n * 这里的基本思想是我们将创建一个“访问者”对象,该对象具有接受不同节点类型的方法。\r\n *\r\n * var visitor = {\r\n * NumberLiteral() {},\r\n * CallExpression() {},\r\n * };\r\n *\r\n * 当我们遍历我们的 AST 时,只要我们“进入”一个匹配类型的节点,我们就会调用这个“访问者”的方法。\r\n * 为了使他能够有效,我们还将传递这个节点和它对父节点的引用。\r\n *\r\n * var visitor = {\r\n * NumberLiteral(node, parent) {},\r\n * CallExpression(node, parent) {},\r\n * };\r\n *\r\n * 但是,也存在在“退出”时调用事物的可能性。想象一下我们之前列表形式的树结构:\r\n *\r\n * - Program\r\n * - CallExpression\r\n * - NumberLiteral\r\n * - CallExpression\r\n * - NumberLiteral\r\n * - NumberLiteral\r\n *\r\n * 当我们向下遍历时,我们将到达有死胡同的分支。当我们完成树的每个分支时就“退出”它。\r\n * \r\n * 所以顺着树往下走时,我们可以“进入”每个节点,而当往上走时就“退出”。\r\n *\r\n * -> Program (enter)\r\n * -> CallExpression (enter)\r\n * -> Number Literal (enter)\r\n * <- Number Literal (exit)\r\n * -> Call Expression (enter)\r\n * -> Number Literal (enter)\r\n * <- Number Literal (exit)\r\n * -> Number Literal (enter)\r\n * <- Number Literal (exit)\r\n * <- CallExpression (exit)\r\n * <- CallExpression (exit)\r\n * <- Program (exit)\r\n *\r\n * 为了支持这点,我们的“访问者”的最终形式应该是这样:\r\n *\r\n * var visitor = {\r\n * NumberLiteral: {\r\n * enter(node, parent) {},\r\n * exit(node, parent) {},\r\n * }\r\n * };\r\n */\r\n\r\n/**\r\n * 代码生成\r\n * ---------------\r\n *\r\n * 编译器的最后一步是代码生成。有时候编译器会做一些和转换重叠的事情,但是大部分情况下,\r\n * 代码生成只是意味着将我们的 AST 取出并进行字符串化。\r\n *\r\n * 代码生成器有几种不同的工作方式,一些编译器直接将之前的标记重复使用,\r\n * 另一些会创建代码的单独表示,这样他们就可以线性的打印节点,\r\n * 但是据我所知大部分编译器将会使用我们刚刚创建的相同的 AST,这是我们需要重点关注的。\r\n *\r\n * 实际上,我们的代码生成器知道如何“打印” AST 的所有不同节点类型,\r\n * 并且它将递归调用自身来打印嵌套节点,直到所有内容都打印成一长串代码。\r\n */\r\n\r\n/**\r\n * 就是这样!这就是编译器中所有不同的部分。\r\n * 现在这并不是说每个编译器看起来都和我在这里描述的完全一样。\r\n * 编译器有许多不同的用途,它们可能比我说的这些步骤更加的详细。\r\n *\r\n * 但是现在你应该对大多数编译器有了一个大致的了解。\r\n * 现在我已经解释了所有这些,你们都可以编写自己的编译器了,对吧?\r\n * \r\n * 好了开个玩笑,让我来帮助你从这里慢慢编写一个简单的编译器吧!\r\n */\r\n\r\n/**\r\n * ============================================================================\r\n * (/^▽^)/\r\n * THE TOKENIZER!\r\n * ============================================================================\r\n */\r\n\r\n/**\r\n * 我们将从解析的第一阶段开始:词法分析和标记器。\r\n * 我们只要将我们的代码字符串分解成一个标记数组:\r\n *\r\n * (add 2 (subtract 4 2)) => [{ type: \'paren\', value: \'(\' }, ...]\r\n */\r\n\r\n// 我们从接收一个代码输入字符串(input)开始,并且我们还要做两件事...\r\nfunction tokenizer(input) {\r\n \r\n // `current` 变量就像一个指光标一样让我们可以在代码中追踪我们的位置\r\n let current = 0;\r\n \r\n // `tokens` 数组用来存我们的标记\r\n let tokens = [];\r\n \r\n // 首先创建一个`while`循环,在循环里面我们可以将`current`变量增加为我们想要的值\r\n // 做这一步的原因是因为我们想要在一个循环里多次增加`current`变量的值,因为我们的标记的长度不确定\r\n while (current < input.length) {\r\n \r\n // 我们还将在 `input` 中存储 `current` 字符\r\n let char = input[current];\r\n \r\n // 我们要检查的第一件事是一个左括号。 这稍后将用于 `CallExpression` 但现在我们只关心字符。\r\n // 检查是否有一个左括号:\r\n if (char === \'(\') {\r\n \r\n // 如果有,我们会存一个类型为 `paren` 的新标记到数组,并将值设置为一个左括号。\r\n tokens.push({\r\n type: \'paren\',\r\n value: \'(\',\r\n });\r\n \r\n // `current`自增\r\n current++;\r\n \r\n // 然后继续进入下一次循环。\r\n continue;\r\n }\r\n \r\n // 接下去检查右括号, 像上面一样\r\n if (char === \')\') {\r\n tokens.push({\r\n type: \'paren\',\r\n value: \')\',\r\n });\r\n current++;\r\n continue;\r\n }\r\n \r\n // 接下去我们检查空格,这对于我们来说就是为了知道字符的分割,但是并不需要存储为标记。\r\n \r\n // 所以我们来检查是否有空格的存在,如果存在,就继续下一次循环,做除了存储到标记数组之外的其他操作即可\r\n let WHITESPACE = /\\s/;\r\n if (WHITESPACE.test(char)) {\r\n current++;\r\n continue;\r\n }\r\n \r\n // **首先我们从表达式的值中来入手,这里简单举例子就只检查了两种类型的值\r\n // **一种是数字类型,一种是字符串类型\r\n \r\n // 接下去的标记类型是数字. 这和我们之前看到的不太一样\r\n // 因为数字可以是任何数量的字符,我们希望将整个字符序列捕获称为一个标记\r\n //\r\n // (add 123 456)\r\n // ^^^ ^^^\r\n // 只有两个单独的标记\r\n //\r\n // 因此,当我们遇到序列中的第一个数字时,我们就开始了\r\n let NUMBERS = /[0-9]/;\r\n if (NUMBERS.test(char)) {\r\n \r\n // 我们将创建一个`value`字符串,并把字符推送给他\r\n let value = \'\';\r\n \r\n // 然后我们将遍历序列中的每个字符,直到遇到一个不是数字的字符\r\n // 将每个作为数字的字符推到我们的 `value` 并随着我们去增加 `current`\r\n // 这样我们就能拿到一个完整的数字字符串,例如上面的 123 和 456,而不是单独的 1 2 3 4 5 6\r\n while (NUMBERS.test(char)) {\r\n value += char;\r\n char = input[++current];\r\n }\r\n \r\n // 接着我们把数字放到标记数组中,用数字类型来描述区分它\r\n tokens.push({ type: \'number\', value });\r\n \r\n // 继续外层的下一次循环\r\n continue;\r\n }\r\n \r\n // 我们还将在我们的语言中添加对字符串的支持,可以是任何\r\n // 用双引号 (\") 括起来的文本。\r\n //\r\n // (concat \"foo\" \"bar\")\r\n // ^^^ ^^^ 字符串标记\r\n //\r\n // 从检查开头的双引号开始:\r\n if (char === \'\"\') {\r\n // 保留一个 `value` 变量来构建我们的字符串标记。\r\n let value = \'\';\r\n \r\n // 我们将跳过编辑中开头的双引号\r\n char = input[++current];\r\n \r\n // 然后我们将遍历每个字符,直到我们到达另一个双引号\r\n while (char !== \'\"\') {\r\n value += char;\r\n char = input[++current];\r\n }\r\n \r\n // 跳过相对应闭合的双引号.\r\n char = input[++current];\r\n \r\n // 把我们的字符串标记添加到标记数组中\r\n tokens.push({ type: \'string\', value });\r\n \r\n continue;\r\n }\r\n \r\n // 最后一种标记的类型是名称。这是一个字母序列而不是数字,这是我们 lisp 语法中的函数名称\r\n //\r\n // (add 2 4)\r\n // ^^^\r\n // 名称标记\r\n //\r\n let LETTERS = /[a-z]/i;\r\n if (LETTERS.test(char)) {\r\n let value = \'\';\r\n \r\n // 同样,我们遍历所有,并将它们完整的存到`value`变量中\r\n while (LETTERS.test(char)) {\r\n value += char;\r\n char = input[++current];\r\n }\r\n \r\n // 并把这种名称类型的标记存到标记数组中,继续循环\r\n tokens.push({ type: \'name\', value });\r\n \r\n continue;\r\n }\r\n \r\n // 如果到最后我们还没有匹配到一个字符,那就抛出一个异常错误并完全退出\r\n throw new TypeError(\'I dont know what this character is: \' + char);\r\n }\r\n \r\n // 在标记器执行结束后,直接返回标记数组即可\r\n return tokens;\r\n}\r\n\r\n/**\r\n * ============================================================================\r\n * ヽ/❀o ل͜ o\\ノ\r\n * THE PARSER!!!\r\n * ============================================================================\r\n */\r\n\r\n/**\r\n * 对于解析器来说,我们要把我们的标记数组变成一个 AST:\r\n *\r\n * [{ type: \'paren\', value: \'(\' }, ...] => { type: \'Program\', body: [...] }\r\n */\r\n\r\n// 因此我们先定义一个`parser`函数用来接收我们的`tokens`数组\r\nfunction parser(tokens) {\r\n\r\n // 同样的我们保持一个`current`变量当作光标来供我们使用\r\n let current = 0;\r\n\r\n // 但是这一次,我们我们将使用递归来替代之前使用的 whie 循环\r\n // 先定义一个`walk`函数\r\n function walk() {\r\n\r\n // 在 walk 函数里面,我们首先获取`current`标记\r\n let token = tokens[current];\r\n\r\n // 我们将把每种类型的标记分成不同的代码路径\r\n // 先从`number`类型的标记开始.\r\n\r\n // 首先先检查一下是否有`number`标签.\r\n if (token.type === \'number\') {\r\n\r\n // 如果找到一个,就增加`current`.\r\n current++;\r\n\r\n // 然后我们就能返回一个新的叫做`NumberLiteral`的 AST 节点,并赋值\r\n return {\r\n type: \'NumberLiteral\',\r\n value: token.value,\r\n };\r\n }\r\n\r\n // 对于字符串来说,也是和上面数字一样的操作。新增一个`StringLiteral`节点\r\n if (token.type === \'string\') {\r\n current++;\r\n\r\n return {\r\n type: \'StringLiteral\',\r\n value: token.value,\r\n };\r\n }\r\n\r\n // 接下去我们要寻找调用的表达式(CallExpressions)\r\n // 从找到第一个左括号开始\r\n if (\r\n token.type === \'paren\' &&\r\n token.value === \'(\'\r\n ) {\r\n\r\n // 我们将增加`current`来跳过这个插入语,因为在 AST 树中我们并不关心这个括号\r\n token = tokens[++current];\r\n\r\n // 我们创建一个类型为“CallExpression”的基本节点,并把当前标记的值设置到 name 字段上\r\n // 因为左括号的下一个标记就是这个函数的名字\r\n let node = {\r\n type: \'CallExpression\',\r\n name: token.value,\r\n params: [],\r\n };\r\n\r\n // 继续增加`current`来跳过这个名称标记\r\n token = tokens[++current];\r\n\r\n // 现在我们要遍历每一个标记,找出其中是`CallExpression`的`params`,直到遇到右括号\r\n\r\n // 现在这就是递归发挥作用的地方。我们将使用递归来解决事情而不是尝试去解析可能无限嵌套的节点集\r\n //\r\n // 为了解释这个,我们先用 lisp 代码做演示\r\n // \r\n // 可以看到`add` 的参数是一个数字,还有一个包含自己数字的嵌套`CallExpression`\r\n // (add 2 (subtract 4 2))\r\n //\r\n // 你也将会注意到,在我们的标记数组里,我们有很多右括号\r\n //\r\n // [\r\n // { type: \'paren\', value: \'(\' },\r\n // { type: \'name\', value: \'add\' },\r\n // { type: \'number\', value: \'2\' },\r\n // { type: \'paren\', value: \'(\' },\r\n // { type: \'name\', value: \'subtract\' },\r\n // { type: \'number\', value: \'4\' },\r\n // { type: \'number\', value: \'2\' },\r\n // { type: \'paren\', value: \')\' }, <<< 右括号\r\n // { type: \'paren\', value: \')\' }, <<< 右括号\r\n // ]\r\n //\r\n // 我们将依赖嵌套的`walk`函数来增加我们的`current`变量来超过任何嵌套的`CallExpression`\r\n // 所以我们创建一个`while`循环持续到遇到一个`type`是\'paren\'并且`value`是右括号的标记\r\n while (\r\n (token.type !== \'paren\') ||\r\n (token.type === \'paren\' && token.value !== \')\')\r\n ) {\r\n // 我们调用`walk`方法,这个方法会返回一个`node`节点\r\n // 我们把这个节点存到我们的`node.params`中去\r\n node.params.push(walk());\r\n token = tokens[current];\r\n }\r\n\r\n // 我们最后一次增加`current`变量来跳过右括号\r\n current++;\r\n\r\n // 返回node节点\r\n return node;\r\n }\r\n\r\n // 同样的,如果我们到这里都没有辨认出标记的类型,就抛出一个异常\r\n throw new TypeError(token.type);\r\n }\r\n\r\n // 现在,我们将要创建我们的 AST 树,他有一个根节点叫做`Program`节点\r\n let ast = {\r\n type: \'Program\',\r\n body: [],\r\n };\r\n\r\n // 并且启动我们的`walk`函数,把节点推到我们的`ast.body`数组\r\n // 我们之所以在一个循环里做这些事是因为我们的程序可以有一个接一个的`CallExpression`而不是嵌套的\r\n //\r\n // (add 2 2)\r\n // (subtract 4 2)\r\n //\r\n while (current < tokens.length) {\r\n ast.body.push(walk());\r\n }\r\n\r\n // 最后返回我们得到的 AST 结构\r\n return ast;\r\n}\r\n\r\n/**\r\n * ============================================================================\r\n * ⌒(❀>◞౪◟<❀)⌒\r\n * THE TRAVERSER!!!\r\n * ============================================================================\r\n */\r\n\r\n/**\r\n * 现在我们已经有了一个 AST 树。然后我们想要通过访问者(visitor)来访问不同的节点。\r\n * 无论什么时候只要遇到能匹配类型的节点,就需要调用访问者的方法,像这样:\r\n *\r\n * traverse(ast, {\r\n * Program: {\r\n * enter(node, parent) {\r\n * // ...\r\n * },\r\n * exit(node, parent) {\r\n * // ...\r\n * },\r\n * },\r\n *\r\n * CallExpression: {\r\n * enter(node, parent) {\r\n * // ...\r\n * },\r\n * exit(node, parent) {\r\n * // ...\r\n * },\r\n * },\r\n *\r\n * NumberLiteral: {\r\n * enter(node, parent) {\r\n * // ...\r\n * },\r\n * exit(node, parent) {\r\n * // ...\r\n * },\r\n * },\r\n * });\r\n */\r\n\r\n// 所以我们定义了一个接收一个 AST 和一个访问者的遍历器函数\r\n// 在里面我们还将定义两个函数...\r\nfunction traverser(ast, visitor) {\r\n\r\n // 一个`traverseArray`函数,一个`traverseNode`函数\r\n // traverseArray 函数来迭代数组,并且调用 traverseNode 函数\r\n function traverseArray(array, parent) {\r\n array.forEach(child => {\r\n traverseNode(child, parent);\r\n });\r\n }\r\n\r\n // traverseNode 函数将接受两个参数:node 节点和他的 parent 节点\r\n // 这样他就可以将两者都传递给我们的访问者方法(visitor)\r\n function traverseNode(node, parent) {\r\n\r\n // 我们首先从匹配`type`开始,来检测访问者方法是否存在。访问者方法就是(enter 和 exit)\r\n let methods = visitor[node.type];\r\n\r\n // 如果这个节点类型有`enter`方法,我们将调用这个函数,并且传入当前节点和他的父节点\r\n if (methods && methods.enter) {\r\n methods.enter(node, parent);\r\n }\r\n\r\n // 接下去我们将按当前节点类型来进行拆分,以便于对子节点数组进行遍历,处理到每一个子节点\r\n switch (node.type) {\r\n\r\n // 首先从最高的`Program`层开始。因为 Program 节点有一个名叫 body 的属性,里面包含了节点数组\r\n // 我们调用`traverseArray`来向下遍历它们\r\n //\r\n // 请记住,`traverseArray`会依次调用`traverseNode`,所以这棵树将会被递归遍历\r\n case \'Program\':\r\n traverseArray(node.body, node);\r\n break;\r\n\r\n // 接下去我们对`CallExpression`做相同的事情,然后遍历`params`属性\r\n case \'CallExpression\':\r\n traverseArray(node.params, node);\r\n break;\r\n\r\n // 对于`NumberLiteral`和`StringLiteral`的情况,由于没有子节点,所以直接 break 即可\r\n case \'NumberLiteral\':\r\n case \'StringLiteral\':\r\n break;\r\n\r\n // 接着,如果我们没有匹配到上面的节点类型,就抛出一个异常错误\r\n default:\r\n throw new TypeError(node.type);\r\n }\r\n\r\n // 如果这个节点类型里面有一个`exit`方法,我们就调用它,并且传入当前节点和他的父节点\r\n if (methods && methods.exit) {\r\n methods.exit(node, parent);\r\n }\r\n }\r\n\r\n // 代码最后,我们调用`traverseNode`来启动遍历,传入之前的 AST 树,由于 AST 树最开始\r\n // 的点没有父节点,所以我们直接传入 null 就好\r\n traverseNode(ast, null);\r\n}\r\n\r\n/**\r\n * ============================================================================\r\n * ⁽(◍˃̵͈̑ᴗ˂̵͈̑)⁽\r\n * THE TRANSFORMER!!!\r\n * ============================================================================\r\n */\r\n\r\n/**\r\n * 下一步就是转换器了。我们的转换器将获取我们构建的 AST 并将其传递给我们的遍历器函数,并创建一个新的 AST:\r\n *\r\n * ----------------------------------------------------------------------------\r\n * Original AST | Transformed AST\r\n * ----------------------------------------------------------------------------\r\n * { | {\r\n * type: \'Program\', | type: \'Program\',\r\n * body: [{ | body: [{\r\n * type: \'CallExpression\', | type: \'ExpressionStatement\',\r\n * name: \'add\', | expression: {\r\n * params: [{ | type: \'CallExpression\',\r\n * type: \'NumberLiteral\', | callee: {\r\n * value: \'2\' | type: \'Identifier\',\r\n * }, { | name: \'add\'\r\n * type: \'CallExpression\', | },\r\n * name: \'subtract\', | arguments: [{\r\n * params: [{ | type: \'NumberLiteral\',\r\n * type: \'NumberLiteral\', | value: \'2\'\r\n * value: \'4\' | }, {\r\n * }, { | type: \'CallExpression\',\r\n * type: \'NumberLiteral\', | callee: {\r\n * value: \'2\' | type: \'Identifier\',\r\n * }] | name: \'subtract\'\r\n * }] | },\r\n * }] | arguments: [{\r\n * } | type: \'NumberLiteral\',\r\n * | value: \'4\'\r\n * ---------------------------------- | }, {\r\n * | type: \'NumberLiteral\',\r\n * | value: \'2\'\r\n * | }]\r\n * (sorry the other one is longer.) | }\r\n * | }\r\n * | }]\r\n * | }\r\n * ----------------------------------------------------------------------------\r\n */\r\n\r\n// 这就是我们的转换器函数,他接受一个 lisp 格式的 ast 树。\r\nfunction transformer(ast) {\r\n\r\n // 我们创建一个像之前的 AST 树一样的新的 AST 树,也有一个 program 节点。\r\n let newAst = {\r\n type: \'Program\',\r\n body: [],\r\n };\r\n\r\n // 接下去我们要使用一些 hack 的小技巧\r\n // 首先我们在父节点上使用`context`的属性,这样我们就可以把节点放到其父节点的`context`上\r\n // 通常你会有比这个更好的抽象方法,但是为了我们的目标能实现,这个方法相对简单些\r\n //\r\n // 需要注意的是,这个上下文(context)属性只是用来对比新旧 ast 的\r\n ast._context = newAst.body;\r\n\r\n // 在这里调用遍历器函数并传入我们的旧的 AST 树和访问者方法\r\n traverser(ast, {\r\n\r\n // 第一个访问者方法是 NumberLiteral \r\n NumberLiteral: {\r\n // 我们将在 enter 方法里面访问他们\r\n enter(node, parent) {\r\n // 我们将创建一个也叫做`NumberLiteral`的新节点,并放到父节点的上下文中去\r\n parent._context.push({\r\n type: \'NumberLiteral\',\r\n value: node.value,\r\n });\r\n },\r\n },\r\n\r\n // 接下去是 `StringLiteral`\r\n StringLiteral: {\r\n enter(node, parent) {\r\n parent._context.push({\r\n type: \'StringLiteral\',\r\n value: node.value,\r\n });\r\n },\r\n },\r\n\r\n // 然后, `CallExpression`,这里相对比较复杂一点\r\n CallExpression: {\r\n enter(node, parent) {\r\n\r\n // 首先我们创建一个叫`CallExpression`的节点,它带有表示嵌套的标识符“Identifier”\r\n let expression = {\r\n type: \'CallExpression\',\r\n callee: {\r\n type: \'Identifier\',\r\n name: node.name,\r\n },\r\n arguments: [],\r\n };\r\n\r\n // 接下去我们在原有的`CallExpression`节点(node)上定义一个新的上下文\r\n // 他将引用 `expression`的参数(arguments),这样就可以利用这个数组了\r\n node._context = expression.arguments;\r\n\r\n // 之后我们将检查父节点是否是`CallExpression`类型\r\n // 如果不是的话\r\n if (parent.type !== \'CallExpression\') {\r\n\r\n // 我们将用`ExpressionStatement`来包裹`CallExpression`节点\r\n // 我们这样做的原因是因为单独存在的`CallExpression`在 js 中也可以被当作是声明语句\r\n expression = {\r\n type: \'ExpressionStatement\',\r\n expression: expression,\r\n };\r\n }\r\n\r\n // 最后我们把我们的`CallExpression`(可能是被包起来的)放到父节点的上下文中去\r\n parent._context.push(expression);\r\n },\r\n }\r\n });\r\n\r\n // 在转换器方法的最后,我们就能返回我们刚创建的新的 AST 树了\r\n return newAst;\r\n}\r\n\r\n/**\r\n * ============================================================================\r\n * ヾ(〃^∇^)ノ♪\r\n * THE CODE GENERATOR!!!!\r\n * ============================================================================\r\n */\r\n\r\n/**\r\n * 现在让我们来到我们最后的一个阶段:代码生成。\r\n * 代码生成器将递归的调用自己来打印树的每一个节点,最后输出一个巨大的字符串。\r\n */\r\n\r\n function codeGenerator(node) {\r\n\r\n // 还是按照节点的类型来进行分解操作\r\n switch (node.type) {\r\n\r\n // 如果我们有`Program`节点,我们将映射`body`中的每个节点\r\n // 并且通过代码生成器来运行他们,用换行符将他们连接起来\r\n case \'Program\':\r\n return node.body.map(codeGenerator)\r\n .join(\'\\n\');\r\n\r\n // 对于`ExpressionStatement`,我们将在嵌套表达式上调用代码生成器,并添加一个分号\r\n case \'ExpressionStatement\':\r\n return (\r\n codeGenerator(node.expression) +\r\n \';\' // << 这是因为保持代码的统一性(用正确的方式编写代码)\r\n );\r\n\r\n // 对于`CallExpression`我们将打印`callee`, 新增一个左括号\r\n // 然后映射每一个`arguments`数组的节点,并用代码生成器执行,每一个节点运行完之后加上逗号\r\n // 最后增加一个右括号\r\n case \'CallExpression\':\r\n return (\r\n codeGenerator(node.callee) +\r\n \'(\' +\r\n node.arguments.map(codeGenerator)\r\n .join(\', \') +\r\n \')\'\r\n );\r\n\r\n // 对于`Identifier`直接返回`node`的名字就好.\r\n case \'Identifier\':\r\n return node.name;\r\n\r\n // 对于`NumberLiteral`直接返回`node`的值就好.\r\n case \'NumberLiteral\':\r\n return node.value;\r\n\r\n // 对于`StringLiteral`,在`node`的值的周围添加双引号.\r\n case \'StringLiteral\':\r\n return \'\"\' + node.value + \'\"\';\r\n\r\n // 如果没有匹配到节点的类型,就抛出异常\r\n default:\r\n throw new TypeError(node.type);\r\n }\r\n}\r\n\r\n/**\r\n * ============================================================================\r\n * (۶* ‘ヮ’)۶”\r\n * !!!!!!!!THE COMPILER!!!!!!!!\r\n * ============================================================================\r\n */\r\n\r\n/**\r\n * 最后! 我们创建了 `compiler` 函数。 在这里,我们将把管道的每个部分连接在一起。\r\n *\r\n * 1. input => tokenizer => tokens\r\n * 2. tokens => parser => ast\r\n * 3. ast => transformer => newAst\r\n * 4. newAst => generator => output\r\n */\r\n\r\nfunction compiler(input) {\r\n let tokens = tokenizer(input);\r\n let ast = parser(tokens);\r\n let newAst = transformer(ast);\r\n let output = codeGenerator(newAst);\r\n\r\n // 简单的返回输出\r\n return output;\r\n}\r\n\r\n/**\r\n * ============================================================================\r\n * (๑˃̵ᴗ˂̵)و\r\n * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!YOU MADE IT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\r\n * ============================================================================\r\n */\r\n\r\n// 这里把所有的东西都导出...\r\nmodule.exports = {\r\n tokenizer,\r\n parser,\r\n traverser,\r\n transformer,\r\n codeGenerator,\r\n compiler,\r\n};', 1);
+INSERT INTO `onefile` VALUES (12, 'pico', 'JavaScript', '200 行实现的面部识别库', 12, 'https://github.com/nenadmarkus/picojs', 1100, '/* This library is released under the MIT license, see https://github.com/nenadmarkus/picojs */\r\npico = {}\r\n\r\npico.unpack_cascade = function(bytes)\r\n{\r\n //\r\n const dview = new DataView(new ArrayBuffer(4));\r\n /*\r\n we skip the first 8 bytes of the cascade file\r\n (cascade version number and some data used during the learning process)\r\n */\r\n let p = 8;\r\n /*\r\n read the depth (size) of each tree first: a 32-bit signed integer\r\n */\r\n dview.setUint8(0, bytes[p+0]), dview.setUint8(1, bytes[p+1]), dview.setUint8(2, bytes[p+2]), dview.setUint8(3, bytes[p+3]);\r\n const tdepth = dview.getInt32(0, true);\r\n p = p + 4\r\n /*\r\n next, read the number of trees in the cascade: another 32-bit signed integer\r\n */\r\n dview.setUint8(0, bytes[p+0]), dview.setUint8(1, bytes[p+1]), dview.setUint8(2, bytes[p+2]), dview.setUint8(3, bytes[p+3]);\r\n const ntrees = dview.getInt32(0, true);\r\n p = p + 4\r\n /*\r\n read the actual trees and cascade thresholds\r\n */\r\n const tcodes_ls = [];\r\n const tpreds_ls = [];\r\n const thresh_ls = [];\r\n for(let t=0; t> 0; // \'>>0\' transforms this number to int\r\n\r\n for(let i=0; i> 8\' here to perform an integer division: this seems important for performance\r\n idx = 2*idx + (pixels[((r+tcodes[root + 4*idx + 0]*s) >> 8)*ldim+((c+tcodes[root + 4*idx + 1]*s) >> 8)]<=pixels[((r+tcodes[root + 4*idx + 2]*s) >> 8)*ldim+((c+tcodes[root + 4*idx + 3]*s) >> 8)]);\r\n\r\n o = o + tpreds[pow2tdepth*i + idx-pow2tdepth];\r\n\r\n if(o<=thresh[i])\r\n return -1;\r\n\r\n root += 4*pow2tdepth;\r\n }\r\n return o - thresh[ntrees-1];\r\n }\r\n /*\r\n we\'re done\r\n */\r\n return classify_region;\r\n}\r\n\r\npico.run_cascade = function(image, classify_region, params)\r\n{\r\n const pixels = image.pixels;\r\n const nrows = image.nrows;\r\n const ncols = image.ncols;\r\n const ldim = image.ldim;\r\n\r\n const shiftfactor = params.shiftfactor;\r\n const minsize = params.minsize;\r\n const maxsize = params.maxsize;\r\n const scalefactor = params.scalefactor;\r\n\r\n let scale = minsize;\r\n const detections = [];\r\n\r\n while(scale<=maxsize)\r\n {\r\n const step = Math.max(shiftfactor*scale, 1) >> 0; // \'>>0\' transforms this number to int\r\n const offset = (scale/2 + 1) >> 0;\r\n\r\n for(let r=offset; r<=nrows-offset; r+=step)\r\n for(let c=offset; c<=ncols-offset; c+=step)\r\n {\r\n const q = classify_region(r, c, scale, pixels, ldim);\r\n if (q > 0.0)\r\n detections.push([r, c, scale, q]);\r\n }\r\n \r\n scale = scale*scalefactor;\r\n }\r\n\r\n return detections;\r\n}\r\n\r\npico.cluster_detections = function(dets, iouthreshold)\r\n{\r\n /*\r\n sort detections by their score\r\n */\r\n dets = dets.sort(function(a, b) {\r\n return b[3] - a[3];\r\n });\r\n /*\r\n this helper function calculates the intersection over union for two detections\r\n */\r\n function calculate_iou(det1, det2)\r\n {\r\n // unpack the position and size of each detection\r\n const r1=det1[0], c1=det1[1], s1=det1[2];\r\n const r2=det2[0], c2=det2[1], s2=det2[2];\r\n // calculate detection overlap in each dimension\r\n const overr = Math.max(0, Math.min(r1+s1/2, r2+s2/2) - Math.max(r1-s1/2, r2-s2/2));\r\n const overc = Math.max(0, Math.min(c1+s1/2, c2+s2/2) - Math.max(c1-s1/2, c2-s2/2));\r\n // calculate and return IoU\r\n return overr*overc/(s1*s1+s2*s2-overr*overc);\r\n }\r\n /*\r\n do clustering through non-maximum suppression\r\n */\r\n const assignments = new Array(dets.length).fill(0);\r\n const clusters = [];\r\n for(let i=0; iiouthreshold)\r\n {\r\n assignments[j] = 1;\r\n r = r + dets[j][0];\r\n c = c + dets[j][1];\r\n s = s + dets[j][2];\r\n q = q + dets[j][3];\r\n n = n + 1;\r\n }\r\n // make a cluster representative\r\n clusters.push([r/n, c/n, s/n, q]);\r\n }\r\n }\r\n\r\n return clusters;\r\n}\r\n\r\npico.instantiate_detection_memory = function(size)\r\n{\r\n /*\r\n initialize a circular buffer of `size` elements\r\n */\r\n let n = 0;\r\n const memory = [];\r\n for(let i=0; itextElements($text);\r\n\r\n # convert to markup\r\n $markup = $this->elements($Elements);\r\n\r\n # trim line breaks\r\n $markup = trim($markup, \"\\n\");\r\n\r\n return $markup;\r\n }\r\n\r\n protected function textElements($text)\r\n {\r\n # make sure no definitions are set\r\n $this->DefinitionData = array();\r\n\r\n # standardize line breaks\r\n $text = str_replace(array(\"\\r\\n\", \"\\r\"), \"\\n\", $text);\r\n\r\n # remove surrounding line breaks\r\n $text = trim($text, \"\\n\");\r\n\r\n # split text into lines\r\n $lines = explode(\"\\n\", $text);\r\n\r\n # iterate through lines to identify blocks\r\n return $this->linesElements($lines);\r\n }\r\n\r\n #\r\n # Setters\r\n #\r\n\r\n function setBreaksEnabled($breaksEnabled)\r\n {\r\n $this->breaksEnabled = $breaksEnabled;\r\n\r\n return $this;\r\n }\r\n\r\n protected $breaksEnabled;\r\n\r\n function setMarkupEscaped($markupEscaped)\r\n {\r\n $this->markupEscaped = $markupEscaped;\r\n\r\n return $this;\r\n }\r\n\r\n protected $markupEscaped;\r\n\r\n function setUrlsLinked($urlsLinked)\r\n {\r\n $this->urlsLinked = $urlsLinked;\r\n\r\n return $this;\r\n }\r\n\r\n protected $urlsLinked = true;\r\n\r\n function setSafeMode($safeMode)\r\n {\r\n $this->safeMode = (bool) $safeMode;\r\n\r\n return $this;\r\n }\r\n\r\n protected $safeMode;\r\n\r\n function setStrictMode($strictMode)\r\n {\r\n $this->strictMode = (bool) $strictMode;\r\n\r\n return $this;\r\n }\r\n\r\n protected $strictMode;\r\n\r\n protected $safeLinksWhitelist = array(\r\n \'http://\',\r\n \'https://\',\r\n \'ftp://\',\r\n \'ftps://\',\r\n \'mailto:\',\r\n \'tel:\',\r\n \'data:image/png;base64,\',\r\n \'data:image/gif;base64,\',\r\n \'data:image/jpeg;base64,\',\r\n \'irc:\',\r\n \'ircs:\',\r\n \'git:\',\r\n \'ssh:\',\r\n \'news:\',\r\n \'steam:\',\r\n );\r\n\r\n #\r\n # Lines\r\n #\r\n\r\n protected $BlockTypes = array(\r\n \'#\' => array(\'Header\'),\r\n \'*\' => array(\'Rule\', \'List\'),\r\n \'+\' => array(\'List\'),\r\n \'-\' => array(\'SetextHeader\', \'Table\', \'Rule\', \'List\'),\r\n \'0\' => array(\'List\'),\r\n \'1\' => array(\'List\'),\r\n \'2\' => array(\'List\'),\r\n \'3\' => array(\'List\'),\r\n \'4\' => array(\'List\'),\r\n \'5\' => array(\'List\'),\r\n \'6\' => array(\'List\'),\r\n \'7\' => array(\'List\'),\r\n \'8\' => array(\'List\'),\r\n \'9\' => array(\'List\'),\r\n \':\' => array(\'Table\'),\r\n \'<\' => array(\'Comment\', \'Markup\'),\r\n \'=\' => array(\'SetextHeader\'),\r\n \'>\' => array(\'Quote\'),\r\n \'[\' => array(\'Reference\'),\r\n \'_\' => array(\'Rule\'),\r\n \'`\' => array(\'FencedCode\'),\r\n \'|\' => array(\'Table\'),\r\n \'~\' => array(\'FencedCode\'),\r\n );\r\n\r\n # ~\r\n\r\n protected $unmarkedBlockTypes = array(\r\n \'Code\',\r\n );\r\n\r\n #\r\n # Blocks\r\n #\r\n\r\n protected function lines(array $lines)\r\n {\r\n return $this->elements($this->linesElements($lines));\r\n }\r\n\r\n protected function linesElements(array $lines)\r\n {\r\n $Elements = array();\r\n $CurrentBlock = null;\r\n\r\n foreach ($lines as $line)\r\n {\r\n if (chop($line) === \'\')\r\n {\r\n if (isset($CurrentBlock))\r\n {\r\n $CurrentBlock[\'interrupted\'] = (isset($CurrentBlock[\'interrupted\'])\r\n ? $CurrentBlock[\'interrupted\'] + 1 : 1\r\n );\r\n }\r\n\r\n continue;\r\n }\r\n\r\n while (($beforeTab = strstr($line, \"\\t\", true)) !== false)\r\n {\r\n $shortage = 4 - mb_strlen($beforeTab, \'utf-8\') % 4;\r\n\r\n $line = $beforeTab\r\n . str_repeat(\' \', $shortage)\r\n . substr($line, strlen($beforeTab) + 1)\r\n ;\r\n }\r\n\r\n $indent = strspn($line, \' \');\r\n\r\n $text = $indent > 0 ? substr($line, $indent) : $line;\r\n\r\n # ~\r\n\r\n $Line = array(\'body\' => $line, \'indent\' => $indent, \'text\' => $text);\r\n\r\n # ~\r\n\r\n if (isset($CurrentBlock[\'continuable\']))\r\n {\r\n $methodName = \'block\' . $CurrentBlock[\'type\'] . \'Continue\';\r\n $Block = $this->$methodName($Line, $CurrentBlock);\r\n\r\n if (isset($Block))\r\n {\r\n $CurrentBlock = $Block;\r\n\r\n continue;\r\n }\r\n else\r\n {\r\n if ($this->isBlockCompletable($CurrentBlock[\'type\']))\r\n {\r\n $methodName = \'block\' . $CurrentBlock[\'type\'] . \'Complete\';\r\n $CurrentBlock = $this->$methodName($CurrentBlock);\r\n }\r\n }\r\n }\r\n\r\n # ~\r\n\r\n $marker = $text[0];\r\n\r\n # ~\r\n\r\n $blockTypes = $this->unmarkedBlockTypes;\r\n\r\n if (isset($this->BlockTypes[$marker]))\r\n {\r\n foreach ($this->BlockTypes[$marker] as $blockType)\r\n {\r\n $blockTypes []= $blockType;\r\n }\r\n }\r\n\r\n #\r\n # ~\r\n\r\n foreach ($blockTypes as $blockType)\r\n {\r\n $Block = $this->{\"block$blockType\"}($Line, $CurrentBlock);\r\n\r\n if (isset($Block))\r\n {\r\n $Block[\'type\'] = $blockType;\r\n\r\n if ( ! isset($Block[\'identified\']))\r\n {\r\n if (isset($CurrentBlock))\r\n {\r\n $Elements[] = $this->extractElement($CurrentBlock);\r\n }\r\n\r\n $Block[\'identified\'] = true;\r\n }\r\n\r\n if ($this->isBlockContinuable($blockType))\r\n {\r\n $Block[\'continuable\'] = true;\r\n }\r\n\r\n $CurrentBlock = $Block;\r\n\r\n continue 2;\r\n }\r\n }\r\n\r\n # ~\r\n\r\n if (isset($CurrentBlock) and $CurrentBlock[\'type\'] === \'Paragraph\')\r\n {\r\n $Block = $this->paragraphContinue($Line, $CurrentBlock);\r\n }\r\n\r\n if (isset($Block))\r\n {\r\n $CurrentBlock = $Block;\r\n }\r\n else\r\n {\r\n if (isset($CurrentBlock))\r\n {\r\n $Elements[] = $this->extractElement($CurrentBlock);\r\n }\r\n\r\n $CurrentBlock = $this->paragraph($Line);\r\n\r\n $CurrentBlock[\'identified\'] = true;\r\n }\r\n }\r\n\r\n # ~\r\n\r\n if (isset($CurrentBlock[\'continuable\']) and $this->isBlockCompletable($CurrentBlock[\'type\']))\r\n {\r\n $methodName = \'block\' . $CurrentBlock[\'type\'] . \'Complete\';\r\n $CurrentBlock = $this->$methodName($CurrentBlock);\r\n }\r\n\r\n # ~\r\n\r\n if (isset($CurrentBlock))\r\n {\r\n $Elements[] = $this->extractElement($CurrentBlock);\r\n }\r\n\r\n # ~\r\n\r\n return $Elements;\r\n }\r\n\r\n protected function extractElement(array $Component)\r\n {\r\n if ( ! isset($Component[\'element\']))\r\n {\r\n if (isset($Component[\'markup\']))\r\n {\r\n $Component[\'element\'] = array(\'rawHtml\' => $Component[\'markup\']);\r\n }\r\n elseif (isset($Component[\'hidden\']))\r\n {\r\n $Component[\'element\'] = array();\r\n }\r\n }\r\n\r\n return $Component[\'element\'];\r\n }\r\n\r\n protected function isBlockContinuable($Type)\r\n {\r\n return method_exists($this, \'block\' . $Type . \'Continue\');\r\n }\r\n\r\n protected function isBlockCompletable($Type)\r\n {\r\n return method_exists($this, \'block\' . $Type . \'Complete\');\r\n }\r\n\r\n #\r\n # Code\r\n\r\n protected function blockCode($Line, $Block = null)\r\n {\r\n if (isset($Block) and $Block[\'type\'] === \'Paragraph\' and ! isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n if ($Line[\'indent\'] >= 4)\r\n {\r\n $text = substr($Line[\'body\'], 4);\r\n\r\n $Block = array(\r\n \'element\' => array(\r\n \'name\' => \'pre\',\r\n \'element\' => array(\r\n \'name\' => \'code\',\r\n \'text\' => $text,\r\n ),\r\n ),\r\n );\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockCodeContinue($Line, $Block)\r\n {\r\n if ($Line[\'indent\'] >= 4)\r\n {\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n $Block[\'element\'][\'element\'][\'text\'] .= str_repeat(\"\\n\", $Block[\'interrupted\']);\r\n\r\n unset($Block[\'interrupted\']);\r\n }\r\n\r\n $Block[\'element\'][\'element\'][\'text\'] .= \"\\n\";\r\n\r\n $text = substr($Line[\'body\'], 4);\r\n\r\n $Block[\'element\'][\'element\'][\'text\'] .= $text;\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockCodeComplete($Block)\r\n {\r\n return $Block;\r\n }\r\n\r\n #\r\n # Comment\r\n\r\n protected function blockComment($Line)\r\n {\r\n if ($this->markupEscaped or $this->safeMode)\r\n {\r\n return;\r\n }\r\n\r\n if (strpos($Line[\'text\'], \'\') !== false)\r\n {\r\n $Block[\'closed\'] = true;\r\n }\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockCommentContinue($Line, array $Block)\r\n {\r\n if (isset($Block[\'closed\']))\r\n {\r\n return;\r\n }\r\n\r\n $Block[\'element\'][\'rawHtml\'] .= \"\\n\" . $Line[\'body\'];\r\n\r\n if (strpos($Line[\'text\'], \'-->\') !== false)\r\n {\r\n $Block[\'closed\'] = true;\r\n }\r\n\r\n return $Block;\r\n }\r\n\r\n #\r\n # Fenced Code\r\n\r\n protected function blockFencedCode($Line)\r\n {\r\n $marker = $Line[\'text\'][0];\r\n\r\n $openerLength = strspn($Line[\'text\'], $marker);\r\n\r\n if ($openerLength < 3)\r\n {\r\n return;\r\n }\r\n\r\n $infostring = trim(substr($Line[\'text\'], $openerLength), \"\\t \");\r\n\r\n if (strpos($infostring, \'`\') !== false)\r\n {\r\n return;\r\n }\r\n\r\n $Element = array(\r\n \'name\' => \'code\',\r\n \'text\' => \'\',\r\n );\r\n\r\n if ($infostring !== \'\')\r\n {\r\n /**\r\n * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes\r\n * Every HTML element may have a class attribute specified.\r\n * The attribute, if specified, must have a value that is a set\r\n * of space-separated tokens representing the various classes\r\n * that the element belongs to.\r\n * [...]\r\n * The space characters, for the purposes of this specification,\r\n * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),\r\n * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and\r\n * U+000D CARRIAGE RETURN (CR).\r\n */\r\n $language = substr($infostring, 0, strcspn($infostring, \" \\t\\n\\f\\r\"));\r\n\r\n $Element[\'attributes\'] = array(\'class\' => \"language-$language\");\r\n }\r\n\r\n $Block = array(\r\n \'char\' => $marker,\r\n \'openerLength\' => $openerLength,\r\n \'element\' => array(\r\n \'name\' => \'pre\',\r\n \'element\' => $Element,\r\n ),\r\n );\r\n\r\n return $Block;\r\n }\r\n\r\n protected function blockFencedCodeContinue($Line, $Block)\r\n {\r\n if (isset($Block[\'complete\']))\r\n {\r\n return;\r\n }\r\n\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n $Block[\'element\'][\'element\'][\'text\'] .= str_repeat(\"\\n\", $Block[\'interrupted\']);\r\n\r\n unset($Block[\'interrupted\']);\r\n }\r\n\r\n if (($len = strspn($Line[\'text\'], $Block[\'char\'])) >= $Block[\'openerLength\']\r\n and chop(substr($Line[\'text\'], $len), \' \') === \'\'\r\n ) {\r\n $Block[\'element\'][\'element\'][\'text\'] = substr($Block[\'element\'][\'element\'][\'text\'], 1);\r\n\r\n $Block[\'complete\'] = true;\r\n\r\n return $Block;\r\n }\r\n\r\n $Block[\'element\'][\'element\'][\'text\'] .= \"\\n\" . $Line[\'body\'];\r\n\r\n return $Block;\r\n }\r\n\r\n protected function blockFencedCodeComplete($Block)\r\n {\r\n return $Block;\r\n }\r\n\r\n #\r\n # Header\r\n\r\n protected function blockHeader($Line)\r\n {\r\n $level = strspn($Line[\'text\'], \'#\');\r\n\r\n if ($level > 6)\r\n {\r\n return;\r\n }\r\n\r\n $text = trim($Line[\'text\'], \'#\');\r\n\r\n if ($this->strictMode and isset($text[0]) and $text[0] !== \' \')\r\n {\r\n return;\r\n }\r\n\r\n $text = trim($text, \' \');\r\n\r\n $Block = array(\r\n \'element\' => array(\r\n \'name\' => \'h\' . $level,\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => $text,\r\n \'destination\' => \'elements\',\r\n )\r\n ),\r\n );\r\n\r\n return $Block;\r\n }\r\n\r\n #\r\n # List\r\n\r\n protected function blockList($Line, array $CurrentBlock = null)\r\n {\r\n list($name, $pattern) = $Line[\'text\'][0] <= \'-\' ? array(\'ul\', \'[*+-]\') : array(\'ol\', \'[0-9]{1,9}+[.\\)]\');\r\n\r\n if (preg_match(\'/^(\'.$pattern.\'([ ]++|$))(.*+)/\', $Line[\'text\'], $matches))\r\n {\r\n $contentIndent = strlen($matches[2]);\r\n\r\n if ($contentIndent >= 5)\r\n {\r\n $contentIndent -= 1;\r\n $matches[1] = substr($matches[1], 0, -$contentIndent);\r\n $matches[3] = str_repeat(\' \', $contentIndent) . $matches[3];\r\n }\r\n elseif ($contentIndent === 0)\r\n {\r\n $matches[1] .= \' \';\r\n }\r\n\r\n $markerWithoutWhitespace = strstr($matches[1], \' \', true);\r\n\r\n $Block = array(\r\n \'indent\' => $Line[\'indent\'],\r\n \'pattern\' => $pattern,\r\n \'data\' => array(\r\n \'type\' => $name,\r\n \'marker\' => $matches[1],\r\n \'markerType\' => ($name === \'ul\' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),\r\n ),\r\n \'element\' => array(\r\n \'name\' => $name,\r\n \'elements\' => array(),\r\n ),\r\n );\r\n $Block[\'data\'][\'markerTypeRegex\'] = preg_quote($Block[\'data\'][\'markerType\'], \'/\');\r\n\r\n if ($name === \'ol\')\r\n {\r\n $listStart = ltrim(strstr($matches[1], $Block[\'data\'][\'markerType\'], true), \'0\') ?: \'0\';\r\n\r\n if ($listStart !== \'1\')\r\n {\r\n if (\r\n isset($CurrentBlock)\r\n and $CurrentBlock[\'type\'] === \'Paragraph\'\r\n and ! isset($CurrentBlock[\'interrupted\'])\r\n ) {\r\n return;\r\n }\r\n\r\n $Block[\'element\'][\'attributes\'] = array(\'start\' => $listStart);\r\n }\r\n }\r\n\r\n $Block[\'li\'] = array(\r\n \'name\' => \'li\',\r\n \'handler\' => array(\r\n \'function\' => \'li\',\r\n \'argument\' => !empty($matches[3]) ? array($matches[3]) : array(),\r\n \'destination\' => \'elements\'\r\n )\r\n );\r\n\r\n $Block[\'element\'][\'elements\'] []= & $Block[\'li\'];\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockListContinue($Line, array $Block)\r\n {\r\n if (isset($Block[\'interrupted\']) and empty($Block[\'li\'][\'handler\'][\'argument\']))\r\n {\r\n return null;\r\n }\r\n\r\n $requiredIndent = ($Block[\'indent\'] + strlen($Block[\'data\'][\'marker\']));\r\n\r\n if ($Line[\'indent\'] < $requiredIndent\r\n and (\r\n (\r\n $Block[\'data\'][\'type\'] === \'ol\'\r\n and preg_match(\'/^[0-9]++\'.$Block[\'data\'][\'markerTypeRegex\'].\'(?:[ ]++(.*)|$)/\', $Line[\'text\'], $matches)\r\n ) or (\r\n $Block[\'data\'][\'type\'] === \'ul\'\r\n and preg_match(\'/^\'.$Block[\'data\'][\'markerTypeRegex\'].\'(?:[ ]++(.*)|$)/\', $Line[\'text\'], $matches)\r\n )\r\n )\r\n ) {\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n $Block[\'li\'][\'handler\'][\'argument\'] []= \'\';\r\n\r\n $Block[\'loose\'] = true;\r\n\r\n unset($Block[\'interrupted\']);\r\n }\r\n\r\n unset($Block[\'li\']);\r\n\r\n $text = isset($matches[1]) ? $matches[1] : \'\';\r\n\r\n $Block[\'indent\'] = $Line[\'indent\'];\r\n\r\n $Block[\'li\'] = array(\r\n \'name\' => \'li\',\r\n \'handler\' => array(\r\n \'function\' => \'li\',\r\n \'argument\' => array($text),\r\n \'destination\' => \'elements\'\r\n )\r\n );\r\n\r\n $Block[\'element\'][\'elements\'] []= & $Block[\'li\'];\r\n\r\n return $Block;\r\n }\r\n elseif ($Line[\'indent\'] < $requiredIndent and $this->blockList($Line))\r\n {\r\n return null;\r\n }\r\n\r\n if ($Line[\'text\'][0] === \'[\' and $this->blockReference($Line))\r\n {\r\n return $Block;\r\n }\r\n\r\n if ($Line[\'indent\'] >= $requiredIndent)\r\n {\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n $Block[\'li\'][\'handler\'][\'argument\'] []= \'\';\r\n\r\n $Block[\'loose\'] = true;\r\n\r\n unset($Block[\'interrupted\']);\r\n }\r\n\r\n $text = substr($Line[\'body\'], $requiredIndent);\r\n\r\n $Block[\'li\'][\'handler\'][\'argument\'] []= $text;\r\n\r\n return $Block;\r\n }\r\n\r\n if ( ! isset($Block[\'interrupted\']))\r\n {\r\n $text = preg_replace(\'/^[ ]{0,\'.$requiredIndent.\'}+/\', \'\', $Line[\'body\']);\r\n\r\n $Block[\'li\'][\'handler\'][\'argument\'] []= $text;\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockListComplete(array $Block)\r\n {\r\n if (isset($Block[\'loose\']))\r\n {\r\n foreach ($Block[\'element\'][\'elements\'] as &$li)\r\n {\r\n if (end($li[\'handler\'][\'argument\']) !== \'\')\r\n {\r\n $li[\'handler\'][\'argument\'] []= \'\';\r\n }\r\n }\r\n }\r\n\r\n return $Block;\r\n }\r\n\r\n #\r\n # Quote\r\n\r\n protected function blockQuote($Line)\r\n {\r\n if (preg_match(\'/^>[ ]?+(.*+)/\', $Line[\'text\'], $matches))\r\n {\r\n $Block = array(\r\n \'element\' => array(\r\n \'name\' => \'blockquote\',\r\n \'handler\' => array(\r\n \'function\' => \'linesElements\',\r\n \'argument\' => (array) $matches[1],\r\n \'destination\' => \'elements\',\r\n )\r\n ),\r\n );\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockQuoteContinue($Line, array $Block)\r\n {\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n if ($Line[\'text\'][0] === \'>\' and preg_match(\'/^>[ ]?+(.*+)/\', $Line[\'text\'], $matches))\r\n {\r\n $Block[\'element\'][\'handler\'][\'argument\'] []= $matches[1];\r\n\r\n return $Block;\r\n }\r\n\r\n if ( ! isset($Block[\'interrupted\']))\r\n {\r\n $Block[\'element\'][\'handler\'][\'argument\'] []= $Line[\'text\'];\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n #\r\n # Rule\r\n\r\n protected function blockRule($Line)\r\n {\r\n $marker = $Line[\'text\'][0];\r\n\r\n if (substr_count($Line[\'text\'], $marker) >= 3 and chop($Line[\'text\'], \" $marker\") === \'\')\r\n {\r\n $Block = array(\r\n \'element\' => array(\r\n \'name\' => \'hr\',\r\n ),\r\n );\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n #\r\n # Setext\r\n\r\n protected function blockSetextHeader($Line, array $Block = null)\r\n {\r\n if ( ! isset($Block) or $Block[\'type\'] !== \'Paragraph\' or isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n if ($Line[\'indent\'] < 4 and chop(chop($Line[\'text\'], \' \'), $Line[\'text\'][0]) === \'\')\r\n {\r\n $Block[\'element\'][\'name\'] = $Line[\'text\'][0] === \'=\' ? \'h1\' : \'h2\';\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n #\r\n # Markup\r\n\r\n protected function blockMarkup($Line)\r\n {\r\n if ($this->markupEscaped or $this->safeMode)\r\n {\r\n return;\r\n }\r\n\r\n if (preg_match(\'/^<[\\/]?+(\\w*)(?:[ ]*+\'.$this->regexHtmlAttribute.\')*+[ ]*+(\\/)?>/\', $Line[\'text\'], $matches))\r\n {\r\n $element = strtolower($matches[1]);\r\n\r\n if (in_array($element, $this->textLevelElements))\r\n {\r\n return;\r\n }\r\n\r\n $Block = array(\r\n \'name\' => $matches[1],\r\n \'element\' => array(\r\n \'rawHtml\' => $Line[\'text\'],\r\n \'autobreak\' => true,\r\n ),\r\n );\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n protected function blockMarkupContinue($Line, array $Block)\r\n {\r\n if (isset($Block[\'closed\']) or isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n $Block[\'element\'][\'rawHtml\'] .= \"\\n\" . $Line[\'body\'];\r\n\r\n return $Block;\r\n }\r\n\r\n #\r\n # Reference\r\n\r\n protected function blockReference($Line)\r\n {\r\n if (strpos($Line[\'text\'], \']\') !== false\r\n and preg_match(\'/^\\[(.+?)\\]:[ ]*+(\\S+?)>?(?:[ ]+[\"\\\'(](.+)[\"\\\')])?[ ]*+$/\', $Line[\'text\'], $matches)\r\n ) {\r\n $id = strtolower($matches[1]);\r\n\r\n $Data = array(\r\n \'url\' => $matches[2],\r\n \'title\' => isset($matches[3]) ? $matches[3] : null,\r\n );\r\n\r\n $this->DefinitionData[\'Reference\'][$id] = $Data;\r\n\r\n $Block = array(\r\n \'element\' => array(),\r\n );\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n #\r\n # Table\r\n\r\n protected function blockTable($Line, array $Block = null)\r\n {\r\n if ( ! isset($Block) or $Block[\'type\'] !== \'Paragraph\' or isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n if (\r\n strpos($Block[\'element\'][\'handler\'][\'argument\'], \'|\') === false\r\n and strpos($Line[\'text\'], \'|\') === false\r\n and strpos($Line[\'text\'], \':\') === false\r\n or strpos($Block[\'element\'][\'handler\'][\'argument\'], \"\\n\") !== false\r\n ) {\r\n return;\r\n }\r\n\r\n if (chop($Line[\'text\'], \' -:|\') !== \'\')\r\n {\r\n return;\r\n }\r\n\r\n $alignments = array();\r\n\r\n $divider = $Line[\'text\'];\r\n\r\n $divider = trim($divider);\r\n $divider = trim($divider, \'|\');\r\n\r\n $dividerCells = explode(\'|\', $divider);\r\n\r\n foreach ($dividerCells as $dividerCell)\r\n {\r\n $dividerCell = trim($dividerCell);\r\n\r\n if ($dividerCell === \'\')\r\n {\r\n return;\r\n }\r\n\r\n $alignment = null;\r\n\r\n if ($dividerCell[0] === \':\')\r\n {\r\n $alignment = \'left\';\r\n }\r\n\r\n if (substr($dividerCell, - 1) === \':\')\r\n {\r\n $alignment = $alignment === \'left\' ? \'center\' : \'right\';\r\n }\r\n\r\n $alignments []= $alignment;\r\n }\r\n\r\n # ~\r\n\r\n $HeaderElements = array();\r\n\r\n $header = $Block[\'element\'][\'handler\'][\'argument\'];\r\n\r\n $header = trim($header);\r\n $header = trim($header, \'|\');\r\n\r\n $headerCells = explode(\'|\', $header);\r\n\r\n if (count($headerCells) !== count($alignments))\r\n {\r\n return;\r\n }\r\n\r\n foreach ($headerCells as $index => $headerCell)\r\n {\r\n $headerCell = trim($headerCell);\r\n\r\n $HeaderElement = array(\r\n \'name\' => \'th\',\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => $headerCell,\r\n \'destination\' => \'elements\',\r\n )\r\n );\r\n\r\n if (isset($alignments[$index]))\r\n {\r\n $alignment = $alignments[$index];\r\n\r\n $HeaderElement[\'attributes\'] = array(\r\n \'style\' => \"text-align: $alignment;\",\r\n );\r\n }\r\n\r\n $HeaderElements []= $HeaderElement;\r\n }\r\n\r\n # ~\r\n\r\n $Block = array(\r\n \'alignments\' => $alignments,\r\n \'identified\' => true,\r\n \'element\' => array(\r\n \'name\' => \'table\',\r\n \'elements\' => array(),\r\n ),\r\n );\r\n\r\n $Block[\'element\'][\'elements\'] []= array(\r\n \'name\' => \'thead\',\r\n );\r\n\r\n $Block[\'element\'][\'elements\'] []= array(\r\n \'name\' => \'tbody\',\r\n \'elements\' => array(),\r\n );\r\n\r\n $Block[\'element\'][\'elements\'][0][\'elements\'] []= array(\r\n \'name\' => \'tr\',\r\n \'elements\' => $HeaderElements,\r\n );\r\n\r\n return $Block;\r\n }\r\n\r\n protected function blockTableContinue($Line, array $Block)\r\n {\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n if (count($Block[\'alignments\']) === 1 or $Line[\'text\'][0] === \'|\' or strpos($Line[\'text\'], \'|\'))\r\n {\r\n $Elements = array();\r\n\r\n $row = $Line[\'text\'];\r\n\r\n $row = trim($row);\r\n $row = trim($row, \'|\');\r\n\r\n preg_match_all(\'/(?:(\\\\\\\\[|])|[^|`]|`[^`]++`|`)++/\', $row, $matches);\r\n\r\n $cells = array_slice($matches[0], 0, count($Block[\'alignments\']));\r\n\r\n foreach ($cells as $index => $cell)\r\n {\r\n $cell = trim($cell);\r\n\r\n $Element = array(\r\n \'name\' => \'td\',\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => $cell,\r\n \'destination\' => \'elements\',\r\n )\r\n );\r\n\r\n if (isset($Block[\'alignments\'][$index]))\r\n {\r\n $Element[\'attributes\'] = array(\r\n \'style\' => \'text-align: \' . $Block[\'alignments\'][$index] . \';\',\r\n );\r\n }\r\n\r\n $Elements []= $Element;\r\n }\r\n\r\n $Element = array(\r\n \'name\' => \'tr\',\r\n \'elements\' => $Elements,\r\n );\r\n\r\n $Block[\'element\'][\'elements\'][1][\'elements\'] []= $Element;\r\n\r\n return $Block;\r\n }\r\n }\r\n\r\n #\r\n # ~\r\n #\r\n\r\n protected function paragraph($Line)\r\n {\r\n return array(\r\n \'type\' => \'Paragraph\',\r\n \'element\' => array(\r\n \'name\' => \'p\',\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => $Line[\'text\'],\r\n \'destination\' => \'elements\',\r\n ),\r\n ),\r\n );\r\n }\r\n\r\n protected function paragraphContinue($Line, array $Block)\r\n {\r\n if (isset($Block[\'interrupted\']))\r\n {\r\n return;\r\n }\r\n\r\n $Block[\'element\'][\'handler\'][\'argument\'] .= \"\\n\".$Line[\'text\'];\r\n\r\n return $Block;\r\n }\r\n\r\n #\r\n # Inline Elements\r\n #\r\n\r\n protected $InlineTypes = array(\r\n \'!\' => array(\'Image\'),\r\n \'&\' => array(\'SpecialCharacter\'),\r\n \'*\' => array(\'Emphasis\'),\r\n \':\' => array(\'Url\'),\r\n \'<\' => array(\'UrlTag\', \'EmailTag\', \'Markup\'),\r\n \'[\' => array(\'Link\'),\r\n \'_\' => array(\'Emphasis\'),\r\n \'`\' => array(\'Code\'),\r\n \'~\' => array(\'Strikethrough\'),\r\n \'\\\\\' => array(\'EscapeSequence\'),\r\n );\r\n\r\n # ~\r\n\r\n protected $inlineMarkerList = \'!*_&[:<`~\\\\\';\r\n\r\n #\r\n # ~\r\n #\r\n\r\n public function line($text, $nonNestables = array())\r\n {\r\n return $this->elements($this->lineElements($text, $nonNestables));\r\n }\r\n\r\n protected function lineElements($text, $nonNestables = array())\r\n {\r\n # standardize line breaks\r\n $text = str_replace(array(\"\\r\\n\", \"\\r\"), \"\\n\", $text);\r\n\r\n $Elements = array();\r\n\r\n $nonNestables = (empty($nonNestables)\r\n ? array()\r\n : array_combine($nonNestables, $nonNestables)\r\n );\r\n\r\n # $excerpt is based on the first occurrence of a marker\r\n\r\n while ($excerpt = strpbrk($text, $this->inlineMarkerList))\r\n {\r\n $marker = $excerpt[0];\r\n\r\n $markerPosition = strlen($text) - strlen($excerpt);\r\n\r\n $Excerpt = array(\'text\' => $excerpt, \'context\' => $text);\r\n\r\n foreach ($this->InlineTypes[$marker] as $inlineType)\r\n {\r\n # check to see if the current inline type is nestable in the current context\r\n\r\n if (isset($nonNestables[$inlineType]))\r\n {\r\n continue;\r\n }\r\n\r\n $Inline = $this->{\"inline$inlineType\"}($Excerpt);\r\n\r\n if ( ! isset($Inline))\r\n {\r\n continue;\r\n }\r\n\r\n # makes sure that the inline belongs to \"our\" marker\r\n\r\n if (isset($Inline[\'position\']) and $Inline[\'position\'] > $markerPosition)\r\n {\r\n continue;\r\n }\r\n\r\n # sets a default inline position\r\n\r\n if ( ! isset($Inline[\'position\']))\r\n {\r\n $Inline[\'position\'] = $markerPosition;\r\n }\r\n\r\n # cause the new element to \'inherit\' our non nestables\r\n\r\n\r\n $Inline[\'element\'][\'nonNestables\'] = isset($Inline[\'element\'][\'nonNestables\'])\r\n ? array_merge($Inline[\'element\'][\'nonNestables\'], $nonNestables)\r\n : $nonNestables\r\n ;\r\n\r\n # the text that comes before the inline\r\n $unmarkedText = substr($text, 0, $Inline[\'position\']);\r\n\r\n # compile the unmarked text\r\n $InlineText = $this->inlineText($unmarkedText);\r\n $Elements[] = $InlineText[\'element\'];\r\n\r\n # compile the inline\r\n $Elements[] = $this->extractElement($Inline);\r\n\r\n # remove the examined text\r\n $text = substr($text, $Inline[\'position\'] + $Inline[\'extent\']);\r\n\r\n continue 2;\r\n }\r\n\r\n # the marker does not belong to an inline\r\n\r\n $unmarkedText = substr($text, 0, $markerPosition + 1);\r\n\r\n $InlineText = $this->inlineText($unmarkedText);\r\n $Elements[] = $InlineText[\'element\'];\r\n\r\n $text = substr($text, $markerPosition + 1);\r\n }\r\n\r\n $InlineText = $this->inlineText($text);\r\n $Elements[] = $InlineText[\'element\'];\r\n\r\n foreach ($Elements as &$Element)\r\n {\r\n if ( ! isset($Element[\'autobreak\']))\r\n {\r\n $Element[\'autobreak\'] = false;\r\n }\r\n }\r\n\r\n return $Elements;\r\n }\r\n\r\n #\r\n # ~\r\n #\r\n\r\n protected function inlineText($text)\r\n {\r\n $Inline = array(\r\n \'extent\' => strlen($text),\r\n \'element\' => array(),\r\n );\r\n\r\n $Inline[\'element\'][\'elements\'] = self::pregReplaceElements(\r\n $this->breaksEnabled ? \'/[ ]*+\\n/\' : \'/(?:[ ]*+\\\\\\\\|[ ]{2,}+)\\n/\',\r\n array(\r\n array(\'name\' => \'br\'),\r\n array(\'text\' => \"\\n\"),\r\n ),\r\n $text\r\n );\r\n\r\n return $Inline;\r\n }\r\n\r\n protected function inlineCode($Excerpt)\r\n {\r\n $marker = $Excerpt[\'text\'][0];\r\n\r\n if (preg_match(\'/^([\'.$marker.\']++)[ ]*+(.+?)[ ]*+(? strlen($matches[0]),\r\n \'element\' => array(\r\n \'name\' => \'code\',\r\n \'text\' => $text,\r\n ),\r\n );\r\n }\r\n }\r\n\r\n protected function inlineEmailTag($Excerpt)\r\n {\r\n $hostnameLabel = \'[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\';\r\n\r\n $commonMarkEmail = \'[a-zA-Z0-9.!#$%&\\\'*+\\/=?^_`{|}~-]++@\'\r\n . $hostnameLabel . \'(?:\\.\' . $hostnameLabel . \')*\';\r\n\r\n if (strpos($Excerpt[\'text\'], \'>\') !== false\r\n and preg_match(\"/^<((mailto:)?$commonMarkEmail)>/i\", $Excerpt[\'text\'], $matches)\r\n ){\r\n $url = $matches[1];\r\n\r\n if ( ! isset($matches[2]))\r\n {\r\n $url = \"mailto:$url\";\r\n }\r\n\r\n return array(\r\n \'extent\' => strlen($matches[0]),\r\n \'element\' => array(\r\n \'name\' => \'a\',\r\n \'text\' => $matches[1],\r\n \'attributes\' => array(\r\n \'href\' => $url,\r\n ),\r\n ),\r\n );\r\n }\r\n }\r\n\r\n protected function inlineEmphasis($Excerpt)\r\n {\r\n if ( ! isset($Excerpt[\'text\'][1]))\r\n {\r\n return;\r\n }\r\n\r\n $marker = $Excerpt[\'text\'][0];\r\n\r\n if ($Excerpt[\'text\'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt[\'text\'], $matches))\r\n {\r\n $emphasis = \'strong\';\r\n }\r\n elseif (preg_match($this->EmRegex[$marker], $Excerpt[\'text\'], $matches))\r\n {\r\n $emphasis = \'em\';\r\n }\r\n else\r\n {\r\n return;\r\n }\r\n\r\n return array(\r\n \'extent\' => strlen($matches[0]),\r\n \'element\' => array(\r\n \'name\' => $emphasis,\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => $matches[1],\r\n \'destination\' => \'elements\',\r\n )\r\n ),\r\n );\r\n }\r\n\r\n protected function inlineEscapeSequence($Excerpt)\r\n {\r\n if (isset($Excerpt[\'text\'][1]) and in_array($Excerpt[\'text\'][1], $this->specialCharacters))\r\n {\r\n return array(\r\n \'element\' => array(\'rawHtml\' => $Excerpt[\'text\'][1]),\r\n \'extent\' => 2,\r\n );\r\n }\r\n }\r\n\r\n protected function inlineImage($Excerpt)\r\n {\r\n if ( ! isset($Excerpt[\'text\'][1]) or $Excerpt[\'text\'][1] !== \'[\')\r\n {\r\n return;\r\n }\r\n\r\n $Excerpt[\'text\']= substr($Excerpt[\'text\'], 1);\r\n\r\n $Link = $this->inlineLink($Excerpt);\r\n\r\n if ($Link === null)\r\n {\r\n return;\r\n }\r\n\r\n $Inline = array(\r\n \'extent\' => $Link[\'extent\'] + 1,\r\n \'element\' => array(\r\n \'name\' => \'img\',\r\n \'attributes\' => array(\r\n \'src\' => $Link[\'element\'][\'attributes\'][\'href\'],\r\n \'alt\' => $Link[\'element\'][\'handler\'][\'argument\'],\r\n ),\r\n \'autobreak\' => true,\r\n ),\r\n );\r\n\r\n $Inline[\'element\'][\'attributes\'] += $Link[\'element\'][\'attributes\'];\r\n\r\n unset($Inline[\'element\'][\'attributes\'][\'href\']);\r\n\r\n return $Inline;\r\n }\r\n\r\n protected function inlineLink($Excerpt)\r\n {\r\n $Element = array(\r\n \'name\' => \'a\',\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => null,\r\n \'destination\' => \'elements\',\r\n ),\r\n \'nonNestables\' => array(\'Url\', \'Link\'),\r\n \'attributes\' => array(\r\n \'href\' => null,\r\n \'title\' => null,\r\n ),\r\n );\r\n\r\n $extent = 0;\r\n\r\n $remainder = $Excerpt[\'text\'];\r\n\r\n if (preg_match(\'/\\[((?:[^][]++|(?R))*+)\\]/\', $remainder, $matches))\r\n {\r\n $Element[\'handler\'][\'argument\'] = $matches[1];\r\n\r\n $extent += strlen($matches[0]);\r\n\r\n $remainder = substr($remainder, $extent);\r\n }\r\n else\r\n {\r\n return;\r\n }\r\n\r\n if (preg_match(\'/^[(]\\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+(\"[^\"]*+\"|\\\'[^\\\']*+\\\'))?\\s*+[)]/\', $remainder, $matches))\r\n {\r\n $Element[\'attributes\'][\'href\'] = $matches[1];\r\n\r\n if (isset($matches[2]))\r\n {\r\n $Element[\'attributes\'][\'title\'] = substr($matches[2], 1, - 1);\r\n }\r\n\r\n $extent += strlen($matches[0]);\r\n }\r\n else\r\n {\r\n if (preg_match(\'/^\\s*\\[(.*?)\\]/\', $remainder, $matches))\r\n {\r\n $definition = strlen($matches[1]) ? $matches[1] : $Element[\'handler\'][\'argument\'];\r\n $definition = strtolower($definition);\r\n\r\n $extent += strlen($matches[0]);\r\n }\r\n else\r\n {\r\n $definition = strtolower($Element[\'handler\'][\'argument\']);\r\n }\r\n\r\n if ( ! isset($this->DefinitionData[\'Reference\'][$definition]))\r\n {\r\n return;\r\n }\r\n\r\n $Definition = $this->DefinitionData[\'Reference\'][$definition];\r\n\r\n $Element[\'attributes\'][\'href\'] = $Definition[\'url\'];\r\n $Element[\'attributes\'][\'title\'] = $Definition[\'title\'];\r\n }\r\n\r\n return array(\r\n \'extent\' => $extent,\r\n \'element\' => $Element,\r\n );\r\n }\r\n\r\n protected function inlineMarkup($Excerpt)\r\n {\r\n if ($this->markupEscaped or $this->safeMode or strpos($Excerpt[\'text\'], \'>\') === false)\r\n {\r\n return;\r\n }\r\n\r\n if ($Excerpt[\'text\'][1] === \'/\' and preg_match(\'/^<\\/\\w[\\w-]*+[ ]*+>/s\', $Excerpt[\'text\'], $matches))\r\n {\r\n return array(\r\n \'element\' => array(\'rawHtml\' => $matches[0]),\r\n \'extent\' => strlen($matches[0]),\r\n );\r\n }\r\n\r\n if ($Excerpt[\'text\'][1] === \'!\' and preg_match(\'/^/s\', $Excerpt[\'text\'], $matches))\r\n {\r\n return array(\r\n \'element\' => array(\'rawHtml\' => $matches[0]),\r\n \'extent\' => strlen($matches[0]),\r\n );\r\n }\r\n\r\n if ($Excerpt[\'text\'][1] !== \' \' and preg_match(\'/^<\\w[\\w-]*+(?:[ ]*+\'.$this->regexHtmlAttribute.\')*+[ ]*+\\/?>/s\', $Excerpt[\'text\'], $matches))\r\n {\r\n return array(\r\n \'element\' => array(\'rawHtml\' => $matches[0]),\r\n \'extent\' => strlen($matches[0]),\r\n );\r\n }\r\n }\r\n\r\n protected function inlineSpecialCharacter($Excerpt)\r\n {\r\n if (substr($Excerpt[\'text\'], 1, 1) !== \' \' and strpos($Excerpt[\'text\'], \';\') !== false\r\n and preg_match(\'/^&(#?+[0-9a-zA-Z]++);/\', $Excerpt[\'text\'], $matches)\r\n ) {\r\n return array(\r\n \'element\' => array(\'rawHtml\' => \'&\' . $matches[1] . \';\'),\r\n \'extent\' => strlen($matches[0]),\r\n );\r\n }\r\n\r\n return;\r\n }\r\n\r\n protected function inlineStrikethrough($Excerpt)\r\n {\r\n if ( ! isset($Excerpt[\'text\'][1]))\r\n {\r\n return;\r\n }\r\n\r\n if ($Excerpt[\'text\'][1] === \'~\' and preg_match(\'/^~~(?=\\S)(.+?)(?<=\\S)~~/\', $Excerpt[\'text\'], $matches))\r\n {\r\n return array(\r\n \'extent\' => strlen($matches[0]),\r\n \'element\' => array(\r\n \'name\' => \'del\',\r\n \'handler\' => array(\r\n \'function\' => \'lineElements\',\r\n \'argument\' => $matches[1],\r\n \'destination\' => \'elements\',\r\n )\r\n ),\r\n );\r\n }\r\n }\r\n\r\n protected function inlineUrl($Excerpt)\r\n {\r\n if ($this->urlsLinked !== true or ! isset($Excerpt[\'text\'][2]) or $Excerpt[\'text\'][2] !== \'/\')\r\n {\r\n return;\r\n }\r\n\r\n if (strpos($Excerpt[\'context\'], \'http\') !== false\r\n and preg_match(\'/\\bhttps?+:[\\/]{2}[^\\s<]+\\b\\/*+/ui\', $Excerpt[\'context\'], $matches, PREG_OFFSET_CAPTURE)\r\n ) {\r\n $url = $matches[0][0];\r\n\r\n $Inline = array(\r\n \'extent\' => strlen($matches[0][0]),\r\n \'position\' => $matches[0][1],\r\n \'element\' => array(\r\n \'name\' => \'a\',\r\n \'text\' => $url,\r\n \'attributes\' => array(\r\n \'href\' => $url,\r\n ),\r\n ),\r\n );\r\n\r\n return $Inline;\r\n }\r\n }\r\n\r\n protected function inlineUrlTag($Excerpt)\r\n {\r\n if (strpos($Excerpt[\'text\'], \'>\') !== false and preg_match(\'/^<(\\w++:\\/{2}[^ >]++)>/i\', $Excerpt[\'text\'], $matches))\r\n {\r\n $url = $matches[1];\r\n\r\n return array(\r\n \'extent\' => strlen($matches[0]),\r\n \'element\' => array(\r\n \'name\' => \'a\',\r\n \'text\' => $url,\r\n \'attributes\' => array(\r\n \'href\' => $url,\r\n ),\r\n ),\r\n );\r\n }\r\n }\r\n\r\n # ~\r\n\r\n protected function unmarkedText($text)\r\n {\r\n $Inline = $this->inlineText($text);\r\n return $this->element($Inline[\'element\']);\r\n }\r\n\r\n #\r\n # Handlers\r\n #\r\n\r\n protected function handle(array $Element)\r\n {\r\n if (isset($Element[\'handler\']))\r\n {\r\n if (!isset($Element[\'nonNestables\']))\r\n {\r\n $Element[\'nonNestables\'] = array();\r\n }\r\n\r\n if (is_string($Element[\'handler\']))\r\n {\r\n $function = $Element[\'handler\'];\r\n $argument = $Element[\'text\'];\r\n unset($Element[\'text\']);\r\n $destination = \'rawHtml\';\r\n }\r\n else\r\n {\r\n $function = $Element[\'handler\'][\'function\'];\r\n $argument = $Element[\'handler\'][\'argument\'];\r\n $destination = $Element[\'handler\'][\'destination\'];\r\n }\r\n\r\n $Element[$destination] = $this->{$function}($argument, $Element[\'nonNestables\']);\r\n\r\n if ($destination === \'handler\')\r\n {\r\n $Element = $this->handle($Element);\r\n }\r\n\r\n unset($Element[\'handler\']);\r\n }\r\n\r\n return $Element;\r\n }\r\n\r\n protected function handleElementRecursive(array $Element)\r\n {\r\n return $this->elementApplyRecursive(array($this, \'handle\'), $Element);\r\n }\r\n\r\n protected function handleElementsRecursive(array $Elements)\r\n {\r\n return $this->elementsApplyRecursive(array($this, \'handle\'), $Elements);\r\n }\r\n\r\n protected function elementApplyRecursive($closure, array $Element)\r\n {\r\n $Element = call_user_func($closure, $Element);\r\n\r\n if (isset($Element[\'elements\']))\r\n {\r\n $Element[\'elements\'] = $this->elementsApplyRecursive($closure, $Element[\'elements\']);\r\n }\r\n elseif (isset($Element[\'element\']))\r\n {\r\n $Element[\'element\'] = $this->elementApplyRecursive($closure, $Element[\'element\']);\r\n }\r\n\r\n return $Element;\r\n }\r\n\r\n protected function elementApplyRecursiveDepthFirst($closure, array $Element)\r\n {\r\n if (isset($Element[\'elements\']))\r\n {\r\n $Element[\'elements\'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element[\'elements\']);\r\n }\r\n elseif (isset($Element[\'element\']))\r\n {\r\n $Element[\'element\'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element[\'element\']);\r\n }\r\n\r\n $Element = call_user_func($closure, $Element);\r\n\r\n return $Element;\r\n }\r\n\r\n protected function elementsApplyRecursive($closure, array $Elements)\r\n {\r\n foreach ($Elements as &$Element)\r\n {\r\n $Element = $this->elementApplyRecursive($closure, $Element);\r\n }\r\n\r\n return $Elements;\r\n }\r\n\r\n protected function elementsApplyRecursiveDepthFirst($closure, array $Elements)\r\n {\r\n foreach ($Elements as &$Element)\r\n {\r\n $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element);\r\n }\r\n\r\n return $Elements;\r\n }\r\n\r\n protected function element(array $Element)\r\n {\r\n if ($this->safeMode)\r\n {\r\n $Element = $this->sanitiseElement($Element);\r\n }\r\n\r\n # identity map if element has no handler\r\n $Element = $this->handle($Element);\r\n\r\n $hasName = isset($Element[\'name\']);\r\n\r\n $markup = \'\';\r\n\r\n if ($hasName)\r\n {\r\n $markup .= \'<\' . $Element[\'name\'];\r\n\r\n if (isset($Element[\'attributes\']))\r\n {\r\n foreach ($Element[\'attributes\'] as $name => $value)\r\n {\r\n if ($value === null)\r\n {\r\n continue;\r\n }\r\n\r\n $markup .= \" $name=\\\"\".self::escape($value).\'\"\';\r\n }\r\n }\r\n }\r\n\r\n $permitRawHtml = false;\r\n\r\n if (isset($Element[\'text\']))\r\n {\r\n $text = $Element[\'text\'];\r\n }\r\n // very strongly consider an alternative if you\'re writing an\r\n // extension\r\n elseif (isset($Element[\'rawHtml\']))\r\n {\r\n $text = $Element[\'rawHtml\'];\r\n\r\n $allowRawHtmlInSafeMode = isset($Element[\'allowRawHtmlInSafeMode\']) && $Element[\'allowRawHtmlInSafeMode\'];\r\n $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;\r\n }\r\n\r\n $hasContent = isset($text) || isset($Element[\'element\']) || isset($Element[\'elements\']);\r\n\r\n if ($hasContent)\r\n {\r\n $markup .= $hasName ? \'>\' : \'\';\r\n\r\n if (isset($Element[\'elements\']))\r\n {\r\n $markup .= $this->elements($Element[\'elements\']);\r\n }\r\n elseif (isset($Element[\'element\']))\r\n {\r\n $markup .= $this->element($Element[\'element\']);\r\n }\r\n else\r\n {\r\n if (!$permitRawHtml)\r\n {\r\n $markup .= self::escape($text, true);\r\n }\r\n else\r\n {\r\n $markup .= $text;\r\n }\r\n }\r\n\r\n $markup .= $hasName ? \'\' . $Element[\'name\'] . \'>\' : \'\';\r\n }\r\n elseif ($hasName)\r\n {\r\n $markup .= \' />\';\r\n }\r\n\r\n return $markup;\r\n }\r\n\r\n protected function elements(array $Elements)\r\n {\r\n $markup = \'\';\r\n\r\n $autoBreak = true;\r\n\r\n foreach ($Elements as $Element)\r\n {\r\n if (empty($Element))\r\n {\r\n continue;\r\n }\r\n\r\n $autoBreakNext = (isset($Element[\'autobreak\'])\r\n ? $Element[\'autobreak\'] : isset($Element[\'name\'])\r\n );\r\n // (autobreak === false) covers both sides of an element\r\n $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext;\r\n\r\n $markup .= ($autoBreak ? \"\\n\" : \'\') . $this->element($Element);\r\n $autoBreak = $autoBreakNext;\r\n }\r\n\r\n $markup .= $autoBreak ? \"\\n\" : \'\';\r\n\r\n return $markup;\r\n }\r\n\r\n # ~\r\n\r\n protected function li($lines)\r\n {\r\n $Elements = $this->linesElements($lines);\r\n\r\n if ( ! in_array(\'\', $lines)\r\n and isset($Elements[0]) and isset($Elements[0][\'name\'])\r\n and $Elements[0][\'name\'] === \'p\'\r\n ) {\r\n unset($Elements[0][\'name\']);\r\n }\r\n\r\n return $Elements;\r\n }\r\n\r\n #\r\n # AST Convenience\r\n #\r\n\r\n /**\r\n * Replace occurrences $regexp with $Elements in $text. Return an array of\r\n * elements representing the replacement.\r\n */\r\n protected static function pregReplaceElements($regexp, $Elements, $text)\r\n {\r\n $newElements = array();\r\n\r\n while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE))\r\n {\r\n $offset = $matches[0][1];\r\n $before = substr($text, 0, $offset);\r\n $after = substr($text, $offset + strlen($matches[0][0]));\r\n\r\n $newElements[] = array(\'text\' => $before);\r\n\r\n foreach ($Elements as $Element)\r\n {\r\n $newElements[] = $Element;\r\n }\r\n\r\n $text = $after;\r\n }\r\n\r\n $newElements[] = array(\'text\' => $text);\r\n\r\n return $newElements;\r\n }\r\n\r\n #\r\n # Deprecated Methods\r\n #\r\n\r\n function parse($text)\r\n {\r\n $markup = $this->text($text);\r\n\r\n return $markup;\r\n }\r\n\r\n protected function sanitiseElement(array $Element)\r\n {\r\n static $goodAttribute = \'/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/\';\r\n static $safeUrlNameToAtt = array(\r\n \'a\' => \'href\',\r\n \'img\' => \'src\',\r\n );\r\n\r\n if ( ! isset($Element[\'name\']))\r\n {\r\n unset($Element[\'attributes\']);\r\n return $Element;\r\n }\r\n\r\n if (isset($safeUrlNameToAtt[$Element[\'name\']]))\r\n {\r\n $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element[\'name\']]);\r\n }\r\n\r\n if ( ! empty($Element[\'attributes\']))\r\n {\r\n foreach ($Element[\'attributes\'] as $att => $val)\r\n {\r\n # filter out badly parsed attribute\r\n if ( ! preg_match($goodAttribute, $att))\r\n {\r\n unset($Element[\'attributes\'][$att]);\r\n }\r\n # dump onevent attribute\r\n elseif (self::striAtStart($att, \'on\'))\r\n {\r\n unset($Element[\'attributes\'][$att]);\r\n }\r\n }\r\n }\r\n\r\n return $Element;\r\n }\r\n\r\n protected function filterUnsafeUrlInAttribute(array $Element, $attribute)\r\n {\r\n foreach ($this->safeLinksWhitelist as $scheme)\r\n {\r\n if (self::striAtStart($Element[\'attributes\'][$attribute], $scheme))\r\n {\r\n return $Element;\r\n }\r\n }\r\n\r\n $Element[\'attributes\'][$attribute] = str_replace(\':\', \'%3A\', $Element[\'attributes\'][$attribute]);\r\n\r\n return $Element;\r\n }\r\n\r\n #\r\n # Static Methods\r\n #\r\n\r\n protected static function escape($text, $allowQuotes = false)\r\n {\r\n return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, \'UTF-8\');\r\n }\r\n\r\n protected static function striAtStart($string, $needle)\r\n {\r\n $len = strlen($needle);\r\n\r\n if ($len > strlen($string))\r\n {\r\n return false;\r\n }\r\n else\r\n {\r\n return strtolower(substr($string, 0, $len)) === strtolower($needle);\r\n }\r\n }\r\n\r\n static function instance($name = \'default\')\r\n {\r\n if (isset(self::$instances[$name]))\r\n {\r\n return self::$instances[$name];\r\n }\r\n\r\n $instance = new static();\r\n\r\n self::$instances[$name] = $instance;\r\n\r\n return $instance;\r\n }\r\n\r\n private static $instances = array();\r\n\r\n #\r\n # Fields\r\n #\r\n\r\n protected $DefinitionData;\r\n\r\n #\r\n # Read-Only\r\n\r\n protected $specialCharacters = array(\r\n \'\\\\\', \'`\', \'*\', \'_\', \'{\', \'}\', \'[\', \']\', \'(\', \')\', \'>\', \'#\', \'+\', \'-\', \'.\', \'!\', \'|\', \'~\'\r\n );\r\n\r\n protected $StrongRegex = array(\r\n \'*\' => \'/^[*]{2}((?:\\\\\\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s\',\r\n \'_\' => \'/^__((?:\\\\\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us\',\r\n );\r\n\r\n protected $EmRegex = array(\r\n \'*\' => \'/^[*]((?:\\\\\\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s\',\r\n \'_\' => \'/^_((?:\\\\\\\\_|[^_]|__[^_]*__)+?)_(?!_)\\b/us\',\r\n );\r\n\r\n protected $regexHtmlAttribute = \'[a-zA-Z_:][\\w:.-]*+(?:\\s*+=\\s*+(?:[^\"\\\'=<>`\\s]+|\"[^\"]*+\"|\\\'[^\\\']*+\\\'))?+\';\r\n\r\n protected $voidElements = array(\r\n \'area\', \'base\', \'br\', \'col\', \'command\', \'embed\', \'hr\', \'img\', \'input\', \'link\', \'meta\', \'param\', \'source\',\r\n );\r\n\r\n protected $textLevelElements = array(\r\n \'a\', \'br\', \'bdo\', \'abbr\', \'blink\', \'nextid\', \'acronym\', \'basefont\',\r\n \'b\', \'em\', \'big\', \'cite\', \'small\', \'spacer\', \'listing\',\r\n \'i\', \'rp\', \'del\', \'code\', \'strike\', \'marquee\',\r\n \'q\', \'rt\', \'ins\', \'font\', \'strong\',\r\n \'s\', \'tt\', \'kbd\', \'mark\',\r\n \'u\', \'xm\', \'sub\', \'nobr\',\r\n \'sup\', \'ruby\',\r\n \'var\', \'span\',\r\n \'wbr\', \'time\',\r\n );\r\n}', 1);
+INSERT INTO `onefile` VALUES (14, 'httpstat', 'Python', '用更优雅的方式展示 curl 结果的命令行工具', 12, 'https://github.com/reorx/httpstat', 8000, '#!/usr/bin/env python\r\n# coding: utf-8\r\n# References:\r\n# man curl\r\n# https://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\r\n# https://curl.haxx.se/libcurl/c/easy_getinfo_options.html\r\n# http://blog.kenweiner.com/2014/11/http-request-timings-with-curl.html\r\n\r\nfrom __future__ import print_function\r\n\r\nimport os\r\nimport json\r\nimport sys\r\nimport logging\r\nimport tempfile\r\nimport subprocess\r\n\r\n\r\n__version__ = \'1.3.1\'\r\n\r\n\r\nPY3 = sys.version_info >= (3,)\r\n\r\nif PY3:\r\n xrange = range\r\n\r\n\r\n# Env class is copied from https://github.com/reorx/getenv/blob/master/getenv.py\r\nclass Env(object):\r\n prefix = \'HTTPSTAT\'\r\n _instances = []\r\n\r\n def __init__(self, key):\r\n self.key = key.format(prefix=self.prefix)\r\n Env._instances.append(self)\r\n\r\n def get(self, default=None):\r\n return os.environ.get(self.key, default)\r\n\r\n\r\nENV_SHOW_BODY = Env(\'{prefix}_SHOW_BODY\')\r\nENV_SHOW_IP = Env(\'{prefix}_SHOW_IP\')\r\nENV_SHOW_SPEED = Env(\'{prefix}_SHOW_SPEED\')\r\nENV_SAVE_BODY = Env(\'{prefix}_SAVE_BODY\')\r\nENV_CURL_BIN = Env(\'{prefix}_CURL_BIN\')\r\nENV_METRICS_ONLY = Env(\'{prefix}_METRICS_ONLY\')\r\nENV_DEBUG = Env(\'{prefix}_DEBUG\')\r\n\r\n\r\ncurl_format = \"\"\"{\r\n\"time_namelookup\": %{time_namelookup},\r\n\"time_connect\": %{time_connect},\r\n\"time_appconnect\": %{time_appconnect},\r\n\"time_pretransfer\": %{time_pretransfer},\r\n\"time_redirect\": %{time_redirect},\r\n\"time_starttransfer\": %{time_starttransfer},\r\n\"time_total\": %{time_total},\r\n\"speed_download\": %{speed_download},\r\n\"speed_upload\": %{speed_upload},\r\n\"remote_ip\": \"%{remote_ip}\",\r\n\"remote_port\": \"%{remote_port}\",\r\n\"local_ip\": \"%{local_ip}\",\r\n\"local_port\": \"%{local_port}\"\r\n}\"\"\"\r\n\r\nhttps_template = \"\"\"\r\n DNS Lookup TCP Connection TLS Handshake Server Processing Content Transfer\r\n[ {a0000} | {a0001} | {a0002} | {a0003} | {a0004} ]\r\n | | | | |\r\n namelookup:{b0000} | | | |\r\n connect:{b0001} | | |\r\n pretransfer:{b0002} | |\r\n starttransfer:{b0003} |\r\n total:{b0004}\r\n\"\"\"[1:]\r\n\r\nhttp_template = \"\"\"\r\n DNS Lookup TCP Connection Server Processing Content Transfer\r\n[ {a0000} | {a0001} | {a0003} | {a0004} ]\r\n | | | |\r\n namelookup:{b0000} | | |\r\n connect:{b0001} | |\r\n starttransfer:{b0003} |\r\n total:{b0004}\r\n\"\"\"[1:]\r\n\r\n\r\n# Color code is copied from https://github.com/reorx/python-terminal-color/blob/master/color_simple.py\r\nISATTY = sys.stdout.isatty()\r\n\r\n\r\ndef make_color(code):\r\n def color_func(s):\r\n if not ISATTY:\r\n return s\r\n tpl = \'\\x1b[{}m{}\\x1b[0m\'\r\n return tpl.format(code, s)\r\n return color_func\r\n\r\n\r\nred = make_color(31)\r\ngreen = make_color(32)\r\nyellow = make_color(33)\r\nblue = make_color(34)\r\nmagenta = make_color(35)\r\ncyan = make_color(36)\r\n\r\nbold = make_color(1)\r\nunderline = make_color(4)\r\n\r\ngrayscale = {(i - 232): make_color(\'38;5;\' + str(i)) for i in xrange(232, 256)}\r\n\r\n\r\ndef quit(s, code=0):\r\n if s is not None:\r\n print(s)\r\n sys.exit(code)\r\n\r\n\r\ndef print_help():\r\n help = \"\"\"\r\nUsage: httpstat URL [CURL_OPTIONS]\r\n httpstat -h | --help\r\n httpstat --version\r\n\r\nArguments:\r\n URL url to request, could be with or without `http(s)://` prefix\r\n\r\nOptions:\r\n CURL_OPTIONS any curl supported options, except for -w -D -o -S -s,\r\n which are already used internally.\r\n -h --help show this screen.\r\n --version show version.\r\n\r\nEnvironments:\r\n HTTPSTAT_SHOW_BODY Set to `true` to show response body in the output,\r\n note that body length is limited to 1023 bytes, will be\r\n truncated if exceeds. Default is `false`.\r\n HTTPSTAT_SHOW_IP By default httpstat shows remote and local IP/port address.\r\n Set to `false` to disable this feature. Default is `true`.\r\n HTTPSTAT_SHOW_SPEED Set to `true` to show download and upload speed.\r\n Default is `false`.\r\n HTTPSTAT_SAVE_BODY By default httpstat stores body in a tmp file,\r\n set to `false` to disable this feature. Default is `true`\r\n HTTPSTAT_CURL_BIN Indicate the curl bin path to use. Default is `curl`\r\n from current shell $PATH.\r\n HTTPSTAT_DEBUG Set to `true` to see debugging logs. Default is `false`\r\n\"\"\"[1:-1]\r\n print(help)\r\n\r\n\r\ndef main():\r\n args = sys.argv[1:]\r\n if not args:\r\n print_help()\r\n quit(None, 0)\r\n\r\n # get envs\r\n show_body = \'true\' in ENV_SHOW_BODY.get(\'false\').lower()\r\n show_ip = \'true\' in ENV_SHOW_IP.get(\'true\').lower()\r\n show_speed = \'true\'in ENV_SHOW_SPEED.get(\'false\').lower()\r\n save_body = \'true\' in ENV_SAVE_BODY.get(\'true\').lower()\r\n curl_bin = ENV_CURL_BIN.get(\'curl\')\r\n metrics_only = \'true\' in ENV_METRICS_ONLY.get(\'false\').lower()\r\n is_debug = \'true\' in ENV_DEBUG.get(\'false\').lower()\r\n\r\n # configure logging\r\n if is_debug:\r\n log_level = logging.DEBUG\r\n else:\r\n log_level = logging.INFO\r\n logging.basicConfig(level=log_level)\r\n lg = logging.getLogger(\'httpstat\')\r\n\r\n # log envs\r\n lg.debug(\'Envs:\\n%s\', \'\\n\'.join(\' {}={}\'.format(i.key, i.get(\'\')) for i in Env._instances))\r\n lg.debug(\'Flags: %s\', dict(\r\n show_body=show_body,\r\n show_ip=show_ip,\r\n show_speed=show_speed,\r\n save_body=save_body,\r\n curl_bin=curl_bin,\r\n is_debug=is_debug,\r\n ))\r\n\r\n # get url\r\n url = args[0]\r\n if url in [\'-h\', \'--help\']:\r\n print_help()\r\n quit(None, 0)\r\n elif url == \'--version\':\r\n print(\'httpstat {}\'.format(__version__))\r\n quit(None, 0)\r\n\r\n curl_args = args[1:]\r\n\r\n # check curl args\r\n exclude_options = [\r\n \'-w\', \'--write-out\',\r\n \'-D\', \'--dump-header\',\r\n \'-o\', \'--output\',\r\n \'-s\', \'--silent\',\r\n ]\r\n for i in exclude_options:\r\n if i in curl_args:\r\n quit(yellow(\'Error: {} is not allowed in extra curl args\'.format(i)), 1)\r\n\r\n # tempfile for output\r\n bodyf = tempfile.NamedTemporaryFile(delete=False)\r\n bodyf.close()\r\n\r\n headerf = tempfile.NamedTemporaryFile(delete=False)\r\n headerf.close()\r\n\r\n # run cmd\r\n cmd_env = os.environ.copy()\r\n cmd_env.update(\r\n LC_ALL=\'C\',\r\n )\r\n cmd_core = [curl_bin, \'-w\', curl_format, \'-D\', headerf.name, \'-o\', bodyf.name, \'-s\', \'-S\']\r\n cmd = cmd_core + curl_args + [url]\r\n lg.debug(\'cmd: %s\', cmd)\r\n p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=cmd_env)\r\n out, err = p.communicate()\r\n if PY3:\r\n out, err = out.decode(), err.decode()\r\n lg.debug(\'out: %s\', out)\r\n\r\n # print stderr\r\n if p.returncode == 0:\r\n if err:\r\n print(grayscale[16](err))\r\n else:\r\n _cmd = list(cmd)\r\n _cmd[2] = \'\'\r\n _cmd[4] = \'\'\r\n _cmd[6] = \'\'\r\n print(\'> {}\'.format(\' \'.join(_cmd)))\r\n quit(yellow(\'curl error: {}\'.format(err)), p.returncode)\r\n\r\n # parse output\r\n try:\r\n d = json.loads(out)\r\n except ValueError as e:\r\n print(yellow(\'Could not decode json: {}\'.format(e)))\r\n print(\'curl result:\', p.returncode, grayscale[16](out), grayscale[16](err))\r\n quit(None, 1)\r\n\r\n # convert time_ metrics from seconds to milliseconds\r\n for k in d:\r\n if k.startswith(\'time_\'):\r\n v = d[k]\r\n # Convert time_ values to milliseconds in int\r\n if isinstance(v, float):\r\n # Before 7.61.0, time values are represented as seconds in float\r\n d[k] = int(v * 1000)\r\n elif isinstance(v, int):\r\n # Starting from 7.61.0, libcurl uses microsecond in int\r\n # to return time values, references:\r\n # https://daniel.haxx.se/blog/2018/07/11/curl-7-61-0/\r\n # https://curl.se/bug/?i=2495\r\n d[k] = int(v / 1000)\r\n else:\r\n raise TypeError(\'{} value type is invalid: {}\'.format(k, type(v)))\r\n\r\n # calculate ranges\r\n d.update(\r\n range_dns=d[\'time_namelookup\'],\r\n range_connection=d[\'time_connect\'] - d[\'time_namelookup\'],\r\n range_ssl=d[\'time_pretransfer\'] - d[\'time_connect\'],\r\n range_server=d[\'time_starttransfer\'] - d[\'time_pretransfer\'],\r\n range_transfer=d[\'time_total\'] - d[\'time_starttransfer\'],\r\n )\r\n\r\n # print json if metrics_only is enabled\r\n if metrics_only:\r\n print(json.dumps(d, indent=2))\r\n quit(None, 0)\r\n\r\n # ip\r\n if show_ip:\r\n s = \'Connected to {}:{} from {}:{}\'.format(\r\n cyan(d[\'remote_ip\']), cyan(d[\'remote_port\']),\r\n d[\'local_ip\'], d[\'local_port\'],\r\n )\r\n print(s)\r\n print()\r\n\r\n # print header & body summary\r\n with open(headerf.name, \'r\') as f:\r\n headers = f.read().strip()\r\n # remove header file\r\n lg.debug(\'rm header file %s\', headerf.name)\r\n os.remove(headerf.name)\r\n\r\n for loop, line in enumerate(headers.split(\'\\n\')):\r\n if loop == 0:\r\n p1, p2 = tuple(line.split(\'/\'))\r\n print(green(p1) + grayscale[14](\'/\') + cyan(p2))\r\n else:\r\n pos = line.find(\':\')\r\n print(grayscale[14](line[:pos + 1]) + cyan(line[pos + 1:]))\r\n\r\n print()\r\n\r\n # body\r\n if show_body:\r\n body_limit = 1024\r\n with open(bodyf.name, \'r\') as f:\r\n body = f.read().strip()\r\n body_len = len(body)\r\n\r\n if body_len > body_limit:\r\n print(body[:body_limit] + cyan(\'...\'))\r\n print()\r\n s = \'{} is truncated ({} out of {})\'.format(green(\'Body\'), body_limit, body_len)\r\n if save_body:\r\n s += \', stored in: {}\'.format(bodyf.name)\r\n print(s)\r\n else:\r\n print(body)\r\n else:\r\n if save_body:\r\n print(\'{} stored in: {}\'.format(green(\'Body\'), bodyf.name))\r\n\r\n # remove body file\r\n if not save_body:\r\n lg.debug(\'rm body file %s\', bodyf.name)\r\n os.remove(bodyf.name)\r\n\r\n # print stat\r\n if url.startswith(\'https://\'):\r\n template = https_template\r\n else:\r\n template = http_template\r\n\r\n # colorize template first line\r\n tpl_parts = template.split(\'\\n\')\r\n tpl_parts[0] = grayscale[16](tpl_parts[0])\r\n template = \'\\n\'.join(tpl_parts)\r\n\r\n def fmta(s):\r\n return cyan(\'{:^7}\'.format(str(s) + \'ms\'))\r\n\r\n def fmtb(s):\r\n return cyan(\'{:<7}\'.format(str(s) + \'ms\'))\r\n\r\n stat = template.format(\r\n # a\r\n a0000=fmta(d[\'range_dns\']),\r\n a0001=fmta(d[\'range_connection\']),\r\n a0002=fmta(d[\'range_ssl\']),\r\n a0003=fmta(d[\'range_server\']),\r\n a0004=fmta(d[\'range_transfer\']),\r\n # b\r\n b0000=fmtb(d[\'time_namelookup\']),\r\n b0001=fmtb(d[\'time_connect\']),\r\n b0002=fmtb(d[\'time_pretransfer\']),\r\n b0003=fmtb(d[\'time_starttransfer\']),\r\n b0004=fmtb(d[\'time_total\']),\r\n )\r\n print()\r\n print(stat)\r\n\r\n # speed, originally bytes per second\r\n if show_speed:\r\n print(\'speed_download: {:.1f} KiB/s, speed_upload: {:.1f} KiB/s\'.format(\r\n d[\'speed_download\'] / 1024, d[\'speed_upload\'] / 1024))\r\n\r\n\r\nif __name__ == \'__main__\':\r\n main()', 1);
+INSERT INTO `onefile` VALUES (15, 'py2sec', 'Python', '一款轻量级跨平台 Python “加密”、加速的脚本工具', 12, 'https://github.com/cckuailong/py2sec', 8600, '# -*- coding: utf-8 -*-\r\nimport getopt\r\nimport os\r\nimport platform\r\nimport shutil\r\nimport subprocess\r\nimport sys\r\n\r\n\r\nclass OptionsOfBuild():\r\n def __init__(self):\r\n self.pyVer = \'\'\r\n self.fileName = \'\'\r\n self.rootName = \'\'\r\n self.excludeFiles = []\r\n self.nthread = \'1\'\r\n self.quiet = \"False\"\r\n self.release = False\r\n\r\n\r\nisWindows = True if platform.system() == \'Windows\' else False\r\n\r\npy2sec_version = (\'0\', \'3\', \'1\')\r\n\r\nHELP_TEXT = \'\'\'\r\npy2sec is a Cross-Platform, Fast and Flexible tool to compile the .py to .so(Linux and Mac) or .pdy(Win).\r\nYou can use it to hide the source code of py\r\nIt can be called by the main func as \"from module import * \"\r\npy2sec can be used in the environment of python2 or python3\r\n\r\nUsage: python py2sec.py [options] ...\r\n\r\nOptions:\r\n -v, --version Show the version of the py2sec\r\n -h, --help Show the help info\r\n -p, --python Python version, default is based on the version of python you bind with command \"python\" \r\n Example: -p 3 (means you tends to encrypt python3)\r\n -d, --directory Directory of your project (if use -d, you encrypt the whole directory)\r\n -f, --file File to be transfered (if use -f, you only encrypt one file)\r\n -m, --maintain List the file or the directory you don\'t want to transfer\r\n Note: The directories should be suffix by path separate char (\'\\\\\' in Windows or \'/\'), \r\n and must be the relative path to -d\'s value\r\n Example: -m setup.py,mod/__init__.py,exclude_dir/\r\n -x, --nthread number of parallel thread to build jobs\r\n -q --quiet Quiet Mode, Default: False\r\n -r --release Release Mode, clear all the tmp files, only output the result, Default: False\r\n\r\n\r\nExample:\r\n python py2sec.py -f test.py\r\n python py2sec.py -f example/test1.py -r\r\n python py2sec.py -d example/ -m test1.py,bbb/\r\n\r\n # some OS use command \"python3\" to run python3, like Ubuntu, you can use -p to solve it\r\n python3 py2sec.py -p 3 -d example/\r\n\'\'\'\r\n\r\nbuildingScript_fileName = \'tmp_py2sec_build.py\'\r\nbuildingScript_template_fileName = \'py2sec_build.py.template\'\r\n\r\n\r\ndef getFiles_inDir(dir_path,\r\n includeSubfolder=True,\r\n path_type=0,\r\n ext_names=\"*\"):\r\n \'\'\'获得指定目录下的所有文件,\r\n :param dir_path: 指定的目录路径\r\n :param includeSubfolder: 是否包含子文件夹里的文件,默认 True\r\n :param path_type: 返回的文件路径形式\r\n 0 绝对路径,默认值\r\n 1 相对路径\r\n 2 文件名\r\n :param ext_names: \"*\" | string | list\r\n 可以指定文件扩展名类型,支持以列表形式指定多个扩展名。默认为 \"*\",即所有扩展名。\r\n 举例:\".txt\" 或 [\".jpg\",\".png\"]\r\n\r\n :return: 以 yield 方式返回结果\r\n\r\n Updated:\r\n 2020-04-21\r\n Author:\r\n nodewee (https://nodewee.github.io)\r\n \'\'\'\r\n\r\n # ext_names\r\n if type(ext_names) is str:\r\n if ext_names != \"*\":\r\n ext_names = [ext_names]\r\n # lower ext name letters\r\n if type(ext_names) is list:\r\n for i in range(len(ext_names)):\r\n ext_names[i] = ext_names[i].lower()\r\n\r\n def willKeep_thisFile_by_ExtName(file_name):\r\n if type(ext_names) is list:\r\n if file_name[0] == \'.\':\r\n file_ext = file_name\r\n else:\r\n file_ext = os.path.splitext(file_name)[1]\r\n #\r\n if file_ext.lower() not in ext_names:\r\n return False\r\n else:\r\n return True\r\n return True\r\n\r\n if includeSubfolder:\r\n len_of_inpath = len(dir_path)\r\n for root, dirs, files in os.walk(dir_path):\r\n for file_name in files:\r\n if not willKeep_thisFile_by_ExtName(file_name):\r\n continue\r\n if path_type == 0: # absolute path\r\n yield os.path.join(root, file_name)\r\n elif path_type == 1: # relative path\r\n yield os.path.join(\r\n root[len_of_inpath:].lstrip(os.path.sep), file_name)\r\n else: # filen ame\r\n yield file_name\r\n else:\r\n for file_name in os.listdir(dir_path):\r\n filepath = os.path.join(dir_path, file_name)\r\n if os.path.isfile(filepath):\r\n #\r\n if not willKeep_thisFile_by_ExtName(file_name):\r\n continue\r\n #\r\n if path_type == 0:\r\n yield filepath\r\n else:\r\n yield file_name\r\n\r\n\r\ndef makeDirs(dirpath):\r\n \'\'\'\r\n 创建目录\r\n 支持多级目录,若目录已存在自动忽略\r\n Updated: 2020-02-27\r\n Author: nodewee (https://nodewee.github.io)\r\n \'\'\'\r\n\r\n dirpath = dirpath.strip().rstrip(os.path.sep)\r\n\r\n if dirpath:\r\n if not os.path.exists(dirpath):\r\n os.makedirs(dirpath)\r\n\r\n\r\ndef getCommandOptions(opts):\r\n try:\r\n options, _ = getopt.getopt(sys.argv[1:], \"vhp:d:f:m:x:qr\", [\r\n \"version\", \"help\", \"python=\", \"directory=\", \"file=\", \"maintain=\",\r\n \"nthread=\", \"quiet\", \"release\"\r\n ])\r\n except getopt.GetoptError:\r\n print(\"Get options Error\")\r\n print(HELP_TEXT)\r\n sys.exit(1)\r\n\r\n for key, value in options:\r\n if key in [\"-h\", \"--help\"]:\r\n print(HELP_TEXT)\r\n sys.exit(0)\r\n elif key in [\"-v\", \"--version\"]:\r\n print(\"py2sec version {0}\".format(\'.\'.join(py2sec_version)))\r\n sys.exit(0)\r\n elif key in [\"-p\", \"--python\"]:\r\n opts.pyVer = value\r\n elif key in [\"-d\", \"--directory\"]:\r\n if opts.fileName:\r\n print(\"Canceled. Do not use -d -f at the same time\")\r\n sys.exit(1)\r\n if value[-1] == \'/\':\r\n opts.rootName = value[:-1]\r\n else:\r\n opts.rootName = value\r\n elif key in [\"-f\", \"--file\"]:\r\n if opts.rootName:\r\n print(\"Canceled. Do not use -d -f at the same time\")\r\n sys.exit(1)\r\n opts.fileName = value\r\n elif key in [\"-m\", \"--maintain\"]:\r\n for path_assign in value.split(\",\"):\r\n if not path_assign[-1:] in [\'/\', \'\\\\\']: # if last char is not a path sep, consider it\'s assign a file\r\n opts.excludeFiles.append(path_assign)\r\n else: # assign a dir\r\n assign_dir = path_assign.strip(\'/\').strip(\'\\\\\')\r\n tmp_dir = os.path.join(opts.rootName, assign_dir)\r\n files = getFiles_inDir(dir_path=tmp_dir,\r\n includeSubfolder=True,\r\n path_type=1)\r\n #\r\n for file in files:\r\n fpath = os.path.join(assign_dir, file)\r\n opts.excludeFiles.append(fpath)\r\n\r\n elif key in [\"-x\", \"--nthread\"]:\r\n opts.nthread = value\r\n elif key in [\"-q\", \"--quiet\"]:\r\n opts.quiet = \"True\"\r\n elif key in [\"-r\", \"--release\"]:\r\n opts.release = True\r\n\r\n return opts\r\n\r\n\r\ndef getEncryptFileList(opts):\r\n will_compile_files = []\r\n\r\n #\r\n if opts.rootName != \'\':\r\n if not os.path.exists(opts.rootName):\r\n print(\"No such Directory, please check or use the Absolute Path\")\r\n sys.exit(1)\r\n\r\n #\r\n pyfiles = getFiles_inDir(dir_path=opts.rootName,\r\n includeSubfolder=True,\r\n path_type=1,\r\n ext_names=\'.py\')\r\n # exclude __init__.py file\r\n pyfiles = [pyfile for pyfile in pyfiles if not pyfile.endswith(\'__init__.py\')]\r\n # filter maintain files\r\n tmp_files = list(set(pyfiles) - set(opts.excludeFiles))\r\n will_compile_files = []\r\n for file in tmp_files:\r\n will_compile_files.append(os.path.join(opts.rootName, file))\r\n\r\n #\r\n if opts.fileName != \'\':\r\n if opts.fileName.endswith(\".py\"):\r\n will_compile_files.append(opts.fileName)\r\n else:\r\n print(\"Make sure you give the right name of py file\")\r\n\r\n return will_compile_files\r\n\r\n\r\ndef genSetup(opts, will_compile_files):\r\n if os.path.exists(buildingScript_fileName):\r\n os.remove(buildingScript_fileName)\r\n with open(buildingScript_template_fileName, \"r\") as f:\r\n template = f.read()\r\n files = \'\", r\"\'.join(will_compile_files)\r\n cont = template % (files, opts.pyVer, opts.nthread, opts.quiet)\r\n with open(buildingScript_fileName, \"w\") as f:\r\n f.write(cont)\r\n\r\n\r\ndef clearBuildFolders():\r\n if os.path.isdir(\"build\"):\r\n shutil.rmtree(\"build\")\r\n if os.path.isdir(\"tmp_build\"):\r\n shutil.rmtree(\"tmp_build\")\r\n if os.path.isdir(\"result\"):\r\n shutil.rmtree(\"result\")\r\n\r\n\r\ndef clearTmpFiles():\r\n if os.path.isdir(\"build\"):\r\n shutil.rmtree(\"build\")\r\n if os.path.isdir(\"tmp_build\"):\r\n shutil.rmtree(\"tmp_build\")\r\n if os.path.isfile(\"tmp_py2sec_build.py\"):\r\n os.remove(\"tmp_py2sec_build.py\")\r\n\r\n\r\ndef pyEncrypt(opts):\r\n # prepare folders\r\n makeDirs(\'build\')\r\n makeDirs(\'tmp_build\')\r\n\r\n if opts.quiet == \"True\":\r\n log = \"> log.txt\"\r\n else:\r\n log = \"\"\r\n cmd = \" {0} build_ext {1}\".format(buildingScript_fileName, log)\r\n if opts.pyVer == \'\':\r\n cmd = \'python\' + cmd\r\n else:\r\n cmd = \'python\' + opts.pyVer + cmd\r\n if not isWindows:\r\n print(\'> pyEncrypt\')\r\n print(cmd)\r\n p = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT)\r\n code = p.wait()\r\n if code:\r\n print(\"\\nPy2Sec Encrypt Encounter Error\")\r\n sys.exit(1)\r\n\r\n\r\ndef genProject(opts, will_compile_files):\r\n makeDirs(\'result\')\r\n for file in getFiles_inDir(\'build\', True, 1, [\'.so\', \'.pyd\']):\r\n src_path = os.path.join(\'build\', file)\r\n mid_path = os.path.sep.join(file.split(os.path.sep)[1:-1])\r\n file_name_parts = os.path.basename(src_path).split(\'.\')\r\n file_name = \'.\'.join([file_name_parts[0]] + file_name_parts[-1:])\r\n dest_path = os.path.join(\'result\', mid_path, file_name)\r\n makeDirs(os.path.dirname(dest_path))\r\n shutil.copy(src_path, dest_path)\r\n # 非编译文件靠谱至生成库路径\r\n not_compile_files = get_not_compile_files(opts, will_compile_files)\r\n for not_compile_file in not_compile_files:\r\n dest_path = os.path.join(\'result\', not_compile_file)\r\n filepath, filename = os.path.split(dest_path)\r\n makeDirs(filepath)\r\n shutil.copyfile(not_compile_file, dest_path)\r\n\r\n if opts.release:\r\n clearTmpFiles()\r\n print(\"\\nPy2Sec Encrypt Finished\")\r\n\r\n\r\ndef get_not_compile_files(opts, will_compile_files):\r\n \"\"\"获取非编译文件\"\"\"\r\n files = getFiles_inDir(dir_path=opts.rootName,\r\n includeSubfolder=True,\r\n path_type=1,\r\n ext_names=\'*\')\r\n files = [os.path.join(opts.rootName, file) for file in files if not file.endswith(\'.pyc\')]\r\n not_compile_files = list(set(files) - set(will_compile_files))\r\n return not_compile_files\r\n\r\n\r\nif __name__ == \"__main__\":\r\n opts = getCommandOptions(OptionsOfBuild())\r\n will_compile_files = getEncryptFileList(opts)\r\n clearBuildFolders()\r\n if not isWindows:\r\n genSetup(opts, will_compile_files)\r\n pyEncrypt(opts)\r\n else: # Windows OS\r\n for file in will_compile_files:\r\n genSetup(opts, [file])\r\n pyEncrypt(opts)\r\n\r\n genProject(opts, will_compile_files)', 1);
+INSERT INTO `onefile` VALUES (16, 'tomato-clock', 'Python', 'Python 写的命令行番茄工作法定时器', 12, 'https://github.com/coolcode/tomato-clock', 8400, '#!/usr/bin/env python3\r\n# -*- coding: utf-8 -*-\r\n\r\n# Pomodoro 番茄工作法 https://en.wikipedia.org/wiki/Pomodoro_Technique\r\n# ====== 🍅 Tomato Clock =======\r\n# ./tomato.py # start a 25 minutes tomato clock + 5 minutes break\r\n# ./tomato.py -t # start a 25 minutes tomato clock\r\n# ./tomato.py -t # start a minutes tomato clock\r\n# ./tomato.py -b # take a 5 minutes break\r\n# ./tomato.py -b # take a minutes break\r\n# ./tomato.py -h # help\r\n\r\n\r\nimport sys\r\nimport time\r\nimport subprocess\r\n\r\nWORK_MINUTES = 25\r\nBREAK_MINUTES = 5\r\n\r\n\r\ndef main():\r\n try:\r\n if len(sys.argv) <= 1:\r\n print(f\'🍅 tomato {WORK_MINUTES} minutes. Ctrl+C to exit\')\r\n tomato(WORK_MINUTES, \'It is time to take a break\')\r\n print(f\'🛀 break {BREAK_MINUTES} minutes. Ctrl+C to exit\')\r\n tomato(BREAK_MINUTES, \'It is time to work\')\r\n\r\n elif sys.argv[1] == \'-t\':\r\n minutes = int(sys.argv[2]) if len(sys.argv) > 2 else WORK_MINUTES\r\n print(f\'🍅 tomato {minutes} minutes. Ctrl+C to exit\')\r\n tomato(minutes, \'It is time to take a break\')\r\n\r\n elif sys.argv[1] == \'-b\':\r\n minutes = int(sys.argv[2]) if len(sys.argv) > 2 else BREAK_MINUTES\r\n print(f\'🛀 break {minutes} minutes. Ctrl+C to exit\')\r\n tomato(minutes, \'It is time to work\')\r\n\r\n elif sys.argv[1] == \'-h\':\r\n help()\r\n\r\n else:\r\n help()\r\n\r\n except KeyboardInterrupt:\r\n print(\'\\n👋 goodbye\')\r\n except Exception as ex:\r\n print(ex)\r\n exit(1)\r\n\r\n\r\ndef tomato(minutes, notify_msg):\r\n start_time = time.perf_counter()\r\n while True:\r\n diff_seconds = int(round(time.perf_counter() - start_time))\r\n left_seconds = minutes * 60 - diff_seconds\r\n if left_seconds <= 0:\r\n print(\'\')\r\n break\r\n\r\n countdown = \'{}:{} ⏰\'.format(int(left_seconds / 60), int(left_seconds % 60))\r\n duration = min(minutes, 25)\r\n progressbar(diff_seconds, minutes * 60, duration, countdown)\r\n time.sleep(1)\r\n\r\n notify_me(notify_msg)\r\n\r\n\r\ndef progressbar(curr, total, duration=10, extra=\'\'):\r\n frac = curr / total\r\n filled = round(frac * duration)\r\n print(\'\\r\', \'🍅\' * filled + \'--\' * (duration - filled), \'[{:.0%}]\'.format(frac), extra, end=\'\')\r\n\r\n\r\ndef notify_me(msg):\r\n \'\'\'\r\n # macos desktop notification\r\n terminal-notifier -> https://github.com/julienXX/terminal-notifier#download\r\n terminal-notifier -message \r\n\r\n # ubuntu desktop notification\r\n notify-send\r\n\r\n # voice notification\r\n say -v \r\n lang options:\r\n - Daniel: British English\r\n - Ting-Ting: Mandarin\r\n - Sin-ji: Cantonese\r\n \'\'\'\r\n\r\n print(msg)\r\n try:\r\n if sys.platform == \'darwin\':\r\n # macos desktop notification\r\n subprocess.run([\'terminal-notifier\', \'-title\', \'🍅\', \'-message\', msg])\r\n subprocess.run([\'say\', \'-v\', \'Daniel\', msg])\r\n elif sys.platform.startswith(\'linux\'):\r\n # ubuntu desktop notification\r\n subprocess.Popen([\"notify-send\", \'🍅\', msg])\r\n else:\r\n # windows?\r\n # TODO: windows notification\r\n pass\r\n\r\n except:\r\n # skip the notification error\r\n pass\r\n\r\n\r\ndef help():\r\n appname = sys.argv[0]\r\n appname = appname if appname.endswith(\'.py\') else \'tomato\' # tomato is pypi package\r\n print(\'====== 🍅 Tomato Clock =======\')\r\n print(f\'{appname} # start a {WORK_MINUTES} minutes tomato clock + {BREAK_MINUTES} minutes break\')\r\n print(f\'{appname} -t # start a {WORK_MINUTES} minutes tomato clock\')\r\n print(f\'{appname} -t # start a minutes tomato clock\')\r\n print(f\'{appname} -b # take a {BREAK_MINUTES} minutes break\')\r\n print(f\'{appname} -b # take a minutes break\')\r\n print(f\'{appname} -h # help\')\r\n\r\n\r\nif __name__ == \"__main__\":\r\n main()', 1);
+INSERT INTO `onefile` VALUES (17, 'share', 'Python', '基于 HTTP 协议的文件分享工具', 12, 'https://github.com/beavailable/share', 8200, '#!/bin/env python3\r\nimport sys\r\nimport signal\r\nimport os\r\nimport argparse\r\nimport functools\r\nfrom http.server import ThreadingHTTPServer, BaseHTTPRequestHandler, HTTPStatus\r\nfrom http import cookies\r\nfrom urllib import parse\r\nimport html\r\nimport mimetypes\r\nimport shutil\r\nimport base64\r\nimport time\r\nimport io\r\nimport stat\r\nimport re\r\nimport socket\r\n\r\n\r\nclass ShareServer(ThreadingHTTPServer):\r\n\r\n def __init__(self, *args, **kwargs):\r\n if is_windows():\r\n self._print_error = self._print_error_windows\r\n else:\r\n self._print_error = self._print_error_unix\r\n super().__init__(*args, **kwargs)\r\n\r\n def handle_error(self, request, client_address):\r\n year, month, day, hh, mm, ss, x, y, z = time.localtime()\r\n t, value, traceback = sys.exc_info()\r\n self._print_error(f\'{year:04}/{month:02}/{day:02} {hh:02}:{mm:02}:{ss:02} - {client_address[0]}:{client_address[1]} - {t.__name__}: {value}\')\r\n\r\n def _print_error_windows(self, msg):\r\n sys.stderr.write(f\'{msg}\\n\')\r\n\r\n def _print_error_unix(self, msg):\r\n sys.stderr.write(f\'\\033[33m{msg}\\033[0m\\n\')\r\n\r\n\r\nclass BaseHandler(BaseHTTPRequestHandler):\r\n\r\n protocol_version = \'HTTP/1.1\'\r\n ico = base64.b64decode(\'AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAAAABgAAAAAQAgAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAFxszwhcbM8U3GzPGZxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8ZnGzPFNxszwhcbM8AQAAAAAAAAAAAAAAAAAAAAAAAAAAcbM8B3GzPGxxszzccbM8/HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPxxszzccbM8bHGzPAcAAAAAAAAAAAAAAAAAAAAAcbM8bHGzPPZxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM89nGzPGwAAAAAAAAAAAAAAABxszwhcbM823GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNtxszwhAAAAAAAAAABxszxTcbM8+3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPtxszxTAAAAAAAAAABxszxmcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxmAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8+3GzPOFxszzHcbM8x3GzPOFxszz8cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzccbM8bHGzPCJxszwMcbM8DXGzPCJxszxscbM83XGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNNxszw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcbM8OHGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM88XGzPE0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPE5xszzycbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8s3GzPAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAlxszy0cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8fQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszx+cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8bgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszxvcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz+cbM8cwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszyFcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPOFxszx4cbM8FQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPBBxszzCcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz0cbM8onGzPC9xszwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPGZxszz5cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP5xszzccbM8mXGzPHFxszxxcbM8mHGzPNxxszz+cbM8/XGzPMdxszxScbM8CAAAAAAAAAAAcbM8AXGzPDJxszxDcbM8BAAAAAAAAAAAAAAAAAAAAABxszwDcbM8V3GzPOZxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8+3GzPKBxszwkcbM8AQAAAAAAAAAAcbM8AXGzPCVxszyTcbM8fHGzPBcAAAAAAAAAAAAAAABxszwXcbM8enGzPORxszzvcbM8lXGzPEJxszwkcbM8JHGzPENxszyVcbM873GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8oHGzPA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszwEcbM8AgAAAAAAAAAAcbM8B3GzPFFxszzGcbM8/XGzPP9xszz/cbM8/3GzPPVxszzlcbM85XGzPPVxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzdcbM8JwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAFxszwucbM8oXGzPPRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszyacbM8AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPFRxszzicbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPHRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPHRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszyacbM8AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPFNxszzicbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzdcbM8JwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAFxszwucbM8oHGzPPNxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8n3GzPA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszwEcbM8AgAAAAAAAAAAcbM8B3GzPFFxszzGcbM8/XGzPP9xszz/cbM8/3GzPPVxszzlcbM85XGzPPVxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8+3GzPKBxszwkcbM8AQAAAAAAAAAAcbM8AXGzPCVxszyTcbM8fHGzPBcAAAAAAAAAAAAAAABxszwWcbM8enGzPORxszzvcbM8lXGzPEJxszwkcbM8JHGzPENxszyVcbM873GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP5xszzccbM8mXGzPHFxszxxcbM8mHGzPNxxszz+cbM8/XGzPMdxszxScbM8CAAAAAAAAAAAcbM8AXGzPDJxszxCcbM8BAAAAAAAAAAAAAAAAAAAAABxszwDcbM8VnGzPOZxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz0cbM8onGzPC9xszwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPGZxszz5cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPOJxszx4cbM8FQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPBBxszzCcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz+cbM8cwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszyFcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8bgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszxvcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8fQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszx+cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8s3GzPAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAlxszy0cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM88XGzPE0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPE5xszzycbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNNxszw3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcbM8OHGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzccbM8bHGzPCJxszwMcbM8DXGzPCJxszxscbM83XGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8+3GzPOFxszzHcbM8x3GzPOFxszz8cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxncbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxnAAAAAAAAAABxszxmcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszxmAAAAAAAAAABxszxTcbM8+3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPtxszxTAAAAAAAAAABxszwhcbM823GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNtxszwhAAAAAAAAAAAAAAAAcbM8bHGzPPZxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM89nGzPGwAAAAAAAAAAAAAAAAAAAAAcbM8B3GzPGxxszzccbM8/HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPxxszzccbM8bHGzPAcAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAFxszwhcbM8U3GzPGZxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8Z3GzPGdxszxncbM8ZnGzPFNxszwhcbM8AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////wAA////////AADwAAAAAA8AAOAAAAAABwAAwAAAAAADAADAAAAAAAMAAMAAAAAAAwAAwAAAAAADAADAAAAAAAMAAMAAAAfgAwAAwAAAD/ADAADAAAAf+AMAAMAAAB/4AwAAwAAAP/wDAADAAAA//AMAAMAAAD/4AwAAwAAAf/gDAADAAAD/+AMAAMABg//wAwAAwAfv48ADAADAD/+AAAMAAMAf/gAAAwAAwB/8AAADAADAP/wAAAMAAMA//AAAAwAAwB/8AAADAADAH/4AAAMAAMAP/4AAAwAAwAfv48ADAADAAYP/8AMAAMAAAP/4AwAAwAAAf/gDAADAAAA/+AMAAMAAAD/8AwAAwAAAP/wDAADAAAAf+AMAAMAAAB/4AwAAwAAAD/ADAADAAAAH4AMAAMAAAAAAAwAAwAAAAAADAADAAAAAAAMAAMAAAAAAAwAAwAAAAAADAADgAAAAAAcAAPAAAAAADwAA////////AAD///////8AACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPAFxszwOcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8DnGzPAEAAAAAAAAAAAAAAABxszwHcbM8ZHGzPMFxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzBcbM8ZHGzPAcAAAAAAAAAAHGzPGRxszz2cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz2cbM8ZAAAAABxszwNcbM8v3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszy/cbM8DXGzPBhxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNRxszwYcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPlxszzdcbM80HGzPOdxszz+cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzjcbM8Z3GzPBxxszwRcbM8K3GzPJRxszz4cbM8/3GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM883GzPFcAAAAAAAAAAAAAAAAAAAAAcbM8CXGzPJhxszz/cbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszy8cbM8CwAAAAAAAAAAAAAAAAAAAAAAAAAAcbM8NHGzPO1xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPJwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszwacbM823GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz2cbM8ewAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPCpxszzncbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPxxszz+cbM8/3GzPP9xszz9cbM8yHGzPFJxszwIAAAAAAAAAAAAAAAAAAAAAAAAAABxszwBcbM8fXGzPP9xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNZxszx6cbM8UXGzPGRxszy3cbM84HGzPHxxszwXAAAAAHGzPBlxszxtcbM8QnGzPAhxszwCcbM8EXGzPG1xszztcbM8/3GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszzKcbM8KwAAAAAAAAAAAAAAAHGzPBBxszwkcbM8AnGzPAhxszxTcbM8yHGzPP1xszztcbM8unGzPKlxszzLcbM8+HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM893GzPFIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszwscbM8o3GzPPRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszzfcbM8HgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPJlxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPN9xszweAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcbM8mXGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM893GzPFIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszwscbM8o3GzPPRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8ynGzPCsAAAAAAAAAAAAAAABxszwQcbM8JHGzPAJxszwIcbM8U3GzPMhxszz9cbM87XGzPLpxszypcbM8y3GzPPhxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM81nGzPHlxszxRcbM8ZHGzPLdxszzgcbM8fHGzPBcAAAAAcbM8GXGzPG1xszxCcbM8CHGzPAJxszwRcbM8bXGzPO1xszz/cbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPxxszz+cbM8/3GzPP9xszz9cbM8yHGzPFNxszwIAAAAAAAAAAAAAAAAAAAAAAAAAABxszwBcbM8fXGzPP9xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM89nGzPHsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxszwqcbM853GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHGzPBpxszzbcbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszy8cbM8CwAAAAAAAAAAAAAAAAAAAAAAAAAAcbM8NHGzPO1xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwZcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPNxszxXAAAAAAAAAAAAAAAAAAAAAHGzPAlxszyYcbM8/3GzPP9xszz/cbM8/3GzPP9xszzUcbM8GXGzPBlxszzUcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPONxszxncbM8HHGzPBFxszwrcbM8lHGzPPhxszz/cbM8/3GzPP9xszz/cbM8/3GzPNRxszwZcbM8GXGzPNRxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPlxszzdcbM80HGzPOdxszz+cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM81HGzPBlxszwYcbM81HGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszzUcbM8GHGzPA1xszy/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPL9xszwNAAAAAHGzPGRxszz2cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz2cbM8ZAAAAAAAAAAAcbM8B3GzPGRxszzBcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM81HGzPNRxszzUcbM8wXGzPGRxszwHAAAAAAAAAAAAAAAAcbM8AXGzPA5xszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwZcbM8GXGzPBlxszwOcbM8AQAAAAAAAAAA/////+AAAAfAAAADgAAAAYAAAAGAAAABgAAeAYAAPwGAAD+BgAA/gYAAf4GAAP+BgHP/AYD/gAGB/gABgfwAAYH8AAGB/gABgP+AAYBz/wGAAP+BgAB/gYAAP4GAAD+BgAA/AYAAHgGAAAABgAAAAYAAAAHAAAAD4AAAB/////8oAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAcbM8CnGzPFxxszyIcbM8iHGzPIhxszyIcbM8iHGzPIhxszyIcbM8iHGzPIhxszyIcbM8iHGzPIhxszxccbM8CnGzPFxxszzxcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM88XGzPFxxszyHcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz3cbM83XGzPO5xszz/cbM8/3GzPP9xszyHcbM8iHGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz1cbM8cXGzPBpxszxDcbM82HGzPP9xszz/cbM8iHGzPIhxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8y3GzPBEAAAAAAAAAAHGzPIdxszz/cbM8/3GzPIhxszyIcbM8/3GzPP9xszz/cbM8+3GzPPRxszz+cbM85XGzPG1xszwFAAAAAHGzPAVxszygcbM8/3GzPP9xszyIcbM8iHGzPP9xszz/cbM86HGzPGhxszw8cbM8bXGzPD1xszxXcbM8mXGzPGVxszyRcbM883GzPP9xszz/cbM8iHGzPIhxszz/cbM8/3GzPJEAAAAAAAAAAHGzPAVxszyPcbM893GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPIhxszyIcbM8/3GzPP9xszyRAAAAAAAAAABxszwFcbM8j3GzPPdxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszyIcbM8iHGzPP9xszz/cbM86HGzPGhxszw8cbM8bXGzPD1xszxXcbM8mXGzPGVxszyRcbM883GzPP9xszz/cbM8iHGzPIhxszz/cbM8/3GzPP9xszz7cbM89HGzPP5xszzlcbM8bXGzPAUAAAAAcbM8BXGzPKBxszz/cbM8/3GzPIhxszyIcbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPMtxszwRAAAAAAAAAABxszyHcbM8/3GzPP9xszyIcbM8iHGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz1cbM8cXGzPBpxszxDcbM82HGzPP9xszz/cbM8iHGzPIdxszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPdxszzdcbM87nGzPP9xszz/cbM8/3GzPIdxszxccbM88XGzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPP9xszz/cbM8/3GzPPFxszxccbM8CnGzPFxxszyIcbM8iHGzPIhxszyIcbM8iHGzPIhxszyIcbM8iHGzPIhxszyIcbM8iHGzPIhxszxccbM8CsADAACAAQAAAAAAAABwAAAAcAAAAPAAAA+gAAAOAAAADgAAAA+gAAAA8AAAAHAAAABwAAAAAAAAgAEAAMADAAA=\')\r\n\r\n def __init__(self, *args, password=None):\r\n self._hostname = socket.gethostname()\r\n self._password = password\r\n super().__init__(*args)\r\n\r\n def do_GET(self):\r\n if self.path == \'/favicon.ico\':\r\n self.send_response(HTTPStatus.OK)\r\n self.send_content_length(len(BaseHandler.ico))\r\n self.send_content_type(\'image/x-icon\')\r\n self.end_headers()\r\n self.wfile.write(self.ico)\r\n return\r\n if not self._password or self._validate_password():\r\n self.do_get()\r\n return\r\n self.respond_ok(self._build_html_for_password())\r\n\r\n def do_POST(self):\r\n if not self._password or self._validate_password():\r\n self.do_post()\r\n return\r\n content_length = self.headers[\'Content-Length\']\r\n if not content_length or not content_length.isdecimal():\r\n self.respond_bad_request()\r\n return\r\n content_length = int(content_length)\r\n if content_length > 100:\r\n self.respond_bad_request()\r\n return\r\n data = self.rfile.read(content_length).decode()\r\n data = parse.unquote_plus(data)\r\n if data != f\'password={self._password}\':\r\n self.respond_redirect(self.path)\r\n return\r\n self.respond_redirect_cookie(self.path, f\'password={parse.quote_plus(self._password)}; path=/\')\r\n\r\n def do_get(self):\r\n self.respond_method_not_allowed()\r\n\r\n def do_post(self):\r\n self.respond_method_not_allowed()\r\n\r\n def do_multipart(self, save_dir, redirect_location):\r\n content_length = self.headers[\'Content-Length\']\r\n if not content_length or not content_length.isdecimal():\r\n self.respond_bad_request()\r\n return\r\n content_length = int(content_length)\r\n if not self._has_freespace(content_length):\r\n self.respond_internal_server_error()\r\n return\r\n content_type = self.headers[\'Content-Type\']\r\n if not content_type:\r\n self.respond_bad_request()\r\n return\r\n boundary = self._parse_boundary(content_type)\r\n if not boundary:\r\n self.respond_bad_request()\r\n return\r\n try:\r\n parser = MultipartParser(self.rfile, boundary, content_length)\r\n while parser.has_next():\r\n name = parser.next_name()\r\n if name != \'file\':\r\n self.respond_bad_request()\r\n return\r\n filename = parser.next_filename()\r\n if not filename:\r\n self.respond_bad_request()\r\n return\r\n os.makedirs(save_dir, exist_ok=True)\r\n save_dir = save_dir.rstrip(\'/\\\\\')\r\n with open(f\'{save_dir}/{filename}\', \'wb\') as f:\r\n parser.write_next_to(f)\r\n except MultipartError:\r\n self.respond_bad_request()\r\n except PermissionError:\r\n self.respond_forbidden()\r\n else:\r\n self.respond_redirect(redirect_location)\r\n\r\n def _validate_password(self):\r\n cookie = cookies.SimpleCookie(self.headers[\'Cookie\'])\r\n password = cookie.get(\'password\')\r\n return password and parse.unquote_plus(password.value) == self._password\r\n\r\n def _build_html_for_password(self):\r\n builder = HtmlBuilder()\r\n builder.start_head()\r\n builder.start_title()\r\n builder.append(self._hostname)\r\n builder.end_title()\r\n builder.start_style()\r\n builder.append(\'.container{height: 80%; display: flex; align-items: center; justify-content: center;}\')\r\n builder.end_style()\r\n builder.end_head()\r\n builder.start_body()\r\n builder.append(\'
\')\r\n builder.append(f\'{html.escape(self._hostname)}\')\r\n p = \'\'\r\n for name in path.split(\'/\'):\r\n if name:\r\n p = f\'{p}/{name}\'\r\n builder.append(f\' / {html.escape(name)}\')\r\n builder.append(\'
\')\r\n if self._upload:\r\n builder.append(\'\')\r\n builder.append(f\'\')\r\n builder.append(\'