From 15de42b61a47c48df8b4bab90a8cb1d552fd9e07 Mon Sep 17 00:00:00 2001 From: xiaojin Date: Wed, 18 Aug 2021 10:48:55 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=B3=20chore(=E5=B7=A5=E5=85=B7):=20?= =?UTF-8?q?=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/apollo/.env.dev | 17 - docker/apollo/.env.prod | 17 - docker/apollo/.gitignore | 2 - docker/apollo/README.md | 32 - docker/apollo/apollo-docker-compose.yml | 79 -- docker/apollo/startup_dev.sh | 3 - docker/apollo/startup_prod.sh | 3 - .../elasticsearch-logstash-kibana/README.md | 58 -- .../docker-compose.yml | 50 -- .../logstash/pipeline/logstash-nginx.config | 30 - docs/README.md | 3 + framework/lualib/3rd/misc/dump.lua | 3 +- framework/lualib/3rd/misc/inspect.lua | 348 +++++++++ framework/lualib/3rd/misc/mm.lua | 724 ------------------ 14 files changed, 353 insertions(+), 1016 deletions(-) delete mode 100755 docker/apollo/.env.dev delete mode 100755 docker/apollo/.env.prod delete mode 100755 docker/apollo/.gitignore delete mode 100755 docker/apollo/README.md delete mode 100755 docker/apollo/apollo-docker-compose.yml delete mode 100755 docker/apollo/startup_dev.sh delete mode 100755 docker/apollo/startup_prod.sh delete mode 100644 docker/elasticsearch-logstash-kibana/README.md delete mode 100644 docker/elasticsearch-logstash-kibana/docker-compose.yml delete mode 100644 docker/elasticsearch-logstash-kibana/logstash/pipeline/logstash-nginx.config create mode 100644 framework/lualib/3rd/misc/inspect.lua delete mode 100644 framework/lualib/3rd/misc/mm.lua diff --git a/docker/apollo/.env.dev b/docker/apollo/.env.dev deleted file mode 100755 index ee4cd57..0000000 --- a/docker/apollo/.env.dev +++ /dev/null @@ -1,17 +0,0 @@ -# for compose only -COMPOSE_PROJECT_NAME=apollo-all-in-one -COMPOSE_FILE=apollo-docker-compose.yml -# common for apollo -SPRING_DATASOURCE_USERNAME=root -SPRING_DATASOURCE_PASSWORD=1234567 -EUREKA_INSTANCE_IP_ADDRESS=192.168.1.249 -# for apollo portal only -DEV_META=http://192.168.1.249:8073 -PROD_META=http://192.168.1.249:8073 -# other user defines -IMAGE_TAG=1.7.1 -SPRING_DATASOURCE_CONFIG_URL=jdbc:mysql://192.168.1.249:3306/ApolloConfigDB?characterEncoding=utf8 -SPRING_DATASOURCE_PORTAL_URL=jdbc:mysql://192.168.1.249:3306/ApolloPortalDB?characterEncoding=utf8 -CONFIG_SERVER_PORT=8073 -ADMIN_SERVER_PORT=8072 -PORTAL_SERVER_PORT=8071 diff --git a/docker/apollo/.env.prod b/docker/apollo/.env.prod deleted file mode 100755 index c274db2..0000000 --- a/docker/apollo/.env.prod +++ /dev/null @@ -1,17 +0,0 @@ -# for compose only -COMPOSE_PROJECT_NAME=apollo-all-in-one -COMPOSE_FILE=apollo-docker-compose.yml -# common for apollo -SPRING_DATASOURCE_USERNAME=root -SPRING_DATASOURCE_PASSWORD=123456 -EUREKA_INSTANCE_IP_ADDRESS=172.16.141.109 -# for apollo portal only -DEV_META=http://172.16.141.109:8083 -PROD_META=http://172.16.141.109:8083 -# other user defines -IMAGE_TAG=1.7.1 -SPRING_DATASOURCE_CONFIG_URL=jdbc:mysql://rm-xxx.mysql.rds.aliyuncs.com:3306/ApolloConfigDB?characterEncoding=utf8 -SPRING_DATASOURCE_PORTAL_URL=jdbc:mysql://rm-xxx.mysql.rds.aliyuncs.com:3306/ApolloPortalDB?characterEncoding=utf8 -CONFIG_SERVER_PORT=8083 -ADMIN_SERVER_PORT=8093 -PORTAL_SERVER_PORT=8071 diff --git a/docker/apollo/.gitignore b/docker/apollo/.gitignore deleted file mode 100755 index 17c41f3..0000000 --- a/docker/apollo/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.git/ -.idea/ \ No newline at end of file diff --git a/docker/apollo/README.md b/docker/apollo/README.md deleted file mode 100755 index 73f8a10..0000000 --- a/docker/apollo/README.md +++ /dev/null @@ -1,32 +0,0 @@ -## apollo-all-in-one-for-docker -docker 一键部署 Apollo 分布式配置中心(简化部署流程和运维复杂性:直接使用官方开源镜像,在 docker-compose 的环境下运行 ./startup_{profile}.sh 即可) -注:以下演示以 docker-compose 版本 1.25.0 为准 - -#### 使用 Apollo 至少要开三个服务,在容器化部署的过程中也踩了一些坑,于是自己动手写了个自动化部署运维脚本,解决了一些问题: -1. 一键部署(提前准备好各个环境的配置,如线下线上); -2. 使用 docker-compose + wait_for_it.sh 实现根据服务依赖关系顺序启动; -3. 在容器默认 bridge 网络模式下,解决 eureka 注册中心的 ip 问题 - -#### 使用说明(以 V1.7.1 版本为例) -###### 第一步:初始化数据库 -* https://github.com/ctripcorp/apollo/blob/v1.7.1/scripts/sql/apolloconfigdb.sql -* https://github.com/ctripcorp/apollo/blob/v1.7.1/scripts/sql/apolloportaldb.sql -* 更新注册中心地址: -``` -update ApolloConfigDB.serverconfig set `value` = 'http://172.16.141.109:8761/eureka/,http://172.16.141.110:8761/eureka/,http://172.16.141.111:8761/eureka/' where `key` = 'eureka.service.url'; -``` - -###### 第二步:更改各环境的配置 .env.{profile} - -###### 第三步:对应于各环境的启动脚本 startup_{profile}.sh - -#### 关于注册中心 -###### 默认值(数据库表:ApolloConfigDB.serverconfig) -```http://localhost:8080/eureka/``` - -###### 使用内置的注册中心 -* 在 .env.{profile} 设置环境变量 APOLLO_EUREKA_SERVER_ENABLED=true -* 更改数据库配置的注册中心地址,如:http://{宿主机 ip}:{configserver 端口}/eureka/ - -###### 使用外部的注册中心(默认支持) -* 直接更改数据库配置为外部的注册中心地址即可(当前服务所在的宿主机必须能够访问到的 ip 和端口) diff --git a/docker/apollo/apollo-docker-compose.yml b/docker/apollo/apollo-docker-compose.yml deleted file mode 100755 index 972df47..0000000 --- a/docker/apollo/apollo-docker-compose.yml +++ /dev/null @@ -1,79 +0,0 @@ -# usage of testing config: docker-compose --env-file .env.dev config -# usage of run: docker-compose --env-file .env.dev up -d -version: "3" -networks: - common-network: - driver: bridge -services: - apollo-configservice: - image: "apolloconfig/apollo-configservice:${IMAGE_TAG:-1.7.1}" - container_name: apollo-configservice - restart: always - hostname: apollo-configservice - networks: - common-network: - aliases: - - configservice - expose: - - ${CONFIG_SERVER_PORT} - ports: - - ${CONFIG_SERVER_PORT}:${CONFIG_SERVER_PORT} - volumes: - - "/tmp/apollologs:/opt/logs" - environment: - - SPRING_DATASOURCE_URL=${SPRING_DATASOURCE_CONFIG_URL} - - SPRING_DATASOURCE_USERNAME - - SPRING_DATASOURCE_PASSWORD - - EUREKA_INSTANCE_IP_ADDRESS - - APOLLO_EUREKA_SERVER_ENABLED=${APOLLO_EUREKA_SERVER_ENABLED:-false} - - SERVER_PORT=${CONFIG_SERVER_PORT} - apollo-adminservice: - image: "apolloconfig/apollo-adminservice:${IMAGE_TAG:-1.7.1}" - container_name: apollo-adminservice - restart: always - hostname: apollo-adminservice - networks: - common-network: - aliases: - - adminservice - expose: - - ${ADMIN_SERVER_PORT} - ports: - - ${ADMIN_SERVER_PORT}:${ADMIN_SERVER_PORT} - volumes: - - "/tmp/apollologs:/opt/logs" - environment: - - SPRING_DATASOURCE_URL=${SPRING_DATASOURCE_CONFIG_URL} - - SPRING_DATASOURCE_USERNAME - - SPRING_DATASOURCE_PASSWORD - - EUREKA_INSTANCE_IP_ADDRESS - - SERVER_PORT=${ADMIN_SERVER_PORT} - apollo-portal: - image: "apolloconfig/apollo-portal:${IMAGE_TAG:-1.7.1}" - container_name: apollo-portal - restart: always - hostname: apollo-portal - networks: - common-network: - aliases: - - portal - depends_on: - - apollo-configservice - - apollo-adminservice - expose: - - ${PORTAL_SERVER_PORT} - ports: - - ${PORTAL_SERVER_PORT}:${PORTAL_SERVER_PORT} - volumes: - - "/tmp/apollologs:/opt/logs" - environment: - - SPRING_DATASOURCE_URL=${SPRING_DATASOURCE_PORTAL_URL} - - SPRING_DATASOURCE_USERNAME - - SPRING_DATASOURCE_PASSWORD - - EUREKA_INSTANCE_IP_ADDRESS - - SERVER_PORT=${PORTAL_SERVER_PORT} - - APOLLO_PORTAL_ENVS=dev,prod - - DEV_META - - PROD_META - command: ["/bin/sh", "-c", "[[ ! -f wait-for-it.sh ]] && wget https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh && chmod +x wait-for-it.sh; ./wait-for-it.sh apollo-configservice:${CONFIG_SERVER_PORT} --timeout=300 --strict -- ./wait-for-it.sh apollo-adminservice:${ADMIN_SERVER_PORT} --timeout=300 --strict -- /apollo-portal/scripts/startup.sh"] - diff --git a/docker/apollo/startup_dev.sh b/docker/apollo/startup_dev.sh deleted file mode 100755 index c501d48..0000000 --- a/docker/apollo/startup_dev.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -docker-compose --env-file .env.dev up -d diff --git a/docker/apollo/startup_prod.sh b/docker/apollo/startup_prod.sh deleted file mode 100755 index f3cf5c0..0000000 --- a/docker/apollo/startup_prod.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -docker-compose --env-file .env.prod up -d diff --git a/docker/elasticsearch-logstash-kibana/README.md b/docker/elasticsearch-logstash-kibana/README.md deleted file mode 100644 index 62b4ec4..0000000 --- a/docker/elasticsearch-logstash-kibana/README.md +++ /dev/null @@ -1,58 +0,0 @@ -## Compose sample application -### Elasticsearch, Logstash, and Kibana (ELK) in single-node - -Project structure: -``` -. -└── docker-compose.yml -``` - -[_docker-compose.yml_](docker-compose.yml) -``` -services: - elasticsearch: - image: elasticsearch:7.8.0 - ... - logstash: - image: logstash:7.8.0 - ... - kibana: - image: kibana:7.8.0 - ... -``` - -## Deploy with docker-compose - -``` -$ docker-compose up -d -Creating network "elasticsearch-logstash-kibana_elastic" with driver "bridge" -Creating es ... done -Creating log ... done -Creating kib ... done -``` - -## Expected result - -Listing containers must show three containers running and the port mapping as below: -``` -$ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -173f0634ed33 logstash:7.8.0 "/usr/local/bin/dock…" 43 seconds ago Up 41 seconds 0.0.0.0:5000->5000/tcp, 0.0.0.0:5044->5044/tcp, 0.0.0.0:9600->9600/tcp, 0.0.0.0:5000->5000/udp log -b448fd3e9b30 kibana:7.8.0 "/usr/local/bin/dumb…" 43 seconds ago Up 42 seconds 0.0.0.0:5601->5601/tcp kib -366d358fb03d elasticsearch:7.8.0 "/tini -- /usr/local…" 43 seconds ago Up 42 seconds (healthy) 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp es -``` - -After the application starts, navigate to below links in your web browser: - -* Elasticsearch: [`http://localhost:9200`](http://localhost:9200) -* Logstash: [`http://localhost:9600`](http://localhost:9600) -* Kibana: [`http://localhost:5601`](http://localhost:5601) - -Stop and remove the containers -``` -$ docker-compose down -``` - -## Attribution - -The [example Nginx logs](https://github.com/docker/awesome-compose/tree/master/elasticsearch-logstash-kibana/logstash/nginx.log) are copied from [here](https://github.com/elastic/examples/blob/master/Common%20Data%20Formats/nginx_json_logs/nginx_json_logs). diff --git a/docker/elasticsearch-logstash-kibana/docker-compose.yml b/docker/elasticsearch-logstash-kibana/docker-compose.yml deleted file mode 100644 index 560212a..0000000 --- a/docker/elasticsearch-logstash-kibana/docker-compose.yml +++ /dev/null @@ -1,50 +0,0 @@ -version: '3.8' - -services: - elasticsearch: - image: elasticsearch:7.8.0 - container_name: es - environment: - discovery.type: single-node - ES_JAVA_OPTS: "-Xms512m -Xmx512m" - ports: - - "9200:9200" - - "9300:9300" - healthcheck: - test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"] - interval: 10s - timeout: 10s - retries: 3 - networks: - - elastic - logstash: - image: logstash:7.8.0 - container_name: log - environment: - discovery.seed_hosts: logstash - LS_JAVA_OPTS: "-Xms512m -Xmx512m" - volumes: - - ./logstash/pipeline/logstash-nginx.config:/usr/share/logstash/pipeline/logstash-nginx.config - - ./logstash/nginx.log:/home/nginx.log - ports: - - "5000:5000/tcp" - - "5000:5000/udp" - - "5044:5044" - - "9600:9600" - depends_on: - - elasticsearch - networks: - - elastic - command: logstash -f /usr/share/logstash/pipeline/logstash-nginx.config - kibana: - image: kibana:7.8.0 - container_name: kib - ports: - - "5601:5601" - depends_on: - - elasticsearch - networks: - - elastic -networks: - elastic: - driver: bridge diff --git a/docker/elasticsearch-logstash-kibana/logstash/pipeline/logstash-nginx.config b/docker/elasticsearch-logstash-kibana/logstash/pipeline/logstash-nginx.config deleted file mode 100644 index 0e7d7ed..0000000 --- a/docker/elasticsearch-logstash-kibana/logstash/pipeline/logstash-nginx.config +++ /dev/null @@ -1,30 +0,0 @@ -input { - file { - path => "/home/nginx.log" - start_position => "beginning" - sincedb_path => "/dev/null" - } -} - -filter { - json { - source => "message" - } - geoip { - source => "remote_ip" - } - useragent { - source => "agent" - target => "useragent" - } -} - -output { - elasticsearch { - hosts => ["http://es:9200"] - index => "nginx" - } - stdout { - codec => rubydebug - } -} diff --git a/docs/README.md b/docs/README.md index 6d787b6..0973851 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,6 +2,9 @@ docker stop $(docker ps -a -q) && docker system prune --all --force +配置中心(apollo) +ELK + 代码安全指南: https://github.com/Tencent/secguide diff --git a/framework/lualib/3rd/misc/dump.lua b/framework/lualib/3rd/misc/dump.lua index 317c71f..bf43b15 100644 --- a/framework/lualib/3rd/misc/dump.lua +++ b/framework/lualib/3rd/misc/dump.lua @@ -1 +1,2 @@ -return "mm" \ No newline at end of file +local inspect = require "inspect" +return inspect diff --git a/framework/lualib/3rd/misc/inspect.lua b/framework/lualib/3rd/misc/inspect.lua new file mode 100644 index 0000000..98f0af0 --- /dev/null +++ b/framework/lualib/3rd/misc/inspect.lua @@ -0,0 +1,348 @@ +local inspect = { + _VERSION = 'inspect.lua 3.1.0', + _URL = 'http://github.com/kikito/inspect.lua', + _DESCRIPTION = 'human-readable representations of tables', +} + +local tostring = tostring + +inspect.KEY = setmetatable({}, { + __tostring = function() + return 'inspect.KEY' + end, +}) +inspect.METATABLE = setmetatable({}, { + __tostring = function() + return 'inspect.METATABLE' + end, +}) + +local function rawpairs(t) + return next, t, nil +end + +-- Apostrophizes the string if it has quotes, but not aphostrophes +-- Otherwise, it returns a regular quoted string +local function smartQuote(str) + if str:match('"') and not str:match("'") then + return "'" .. str .. "'" + end + return '"' .. str:gsub('"', '\\"') .. '"' +end + +-- \a => '\\a', \0 => '\\0', 31 => '\31' +local shortControlCharEscapes = { + ["\a"] = "\\a", + ["\b"] = "\\b", + ["\f"] = "\\f", + ["\n"] = "\\n", + ["\r"] = "\\r", + ["\t"] = "\\t", + ["\v"] = "\\v", +} +local longControlCharEscapes = {} -- \a => nil, \0 => \000, 31 => \031 +for i = 0, 31 do + local ch = string.char(i) + if not shortControlCharEscapes[ch] then + shortControlCharEscapes[ch] = "\\" .. i + longControlCharEscapes[ch] = string.format("\\%03d", i) + end +end + +local function escape(str) + return (str:gsub("\\", "\\\\"):gsub("(%c)%f[0-9]", longControlCharEscapes):gsub("%c", shortControlCharEscapes)) +end + +local function isIdentifier(str) + return type(str) == 'string' and str:match("^[_%a][_%a%d]*$") +end + +local function isSequenceKey(k, sequenceLength) + return type(k) == 'number' and 1 <= k and k <= sequenceLength and math.floor(k) == k +end + +local defaultTypeOrders = { + ['number'] = 1, + ['boolean'] = 2, + ['string'] = 3, + ['table'] = 4, + ['function'] = 5, + ['userdata'] = 6, + ['thread'] = 7, +} + +local function sortKeys(a, b) + local ta, tb = type(a), type(b) + + -- strings and numbers are sorted numerically/alphabetically + if ta == tb and (ta == 'string' or ta == 'number') then + return a < b + end + + local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb] + -- Two default types are compared according to the defaultTypeOrders table + if dta and dtb then + return defaultTypeOrders[ta] < defaultTypeOrders[tb] + elseif dta then + return true -- default types before custom ones + elseif dtb then + return false -- custom types after default ones + end + + -- custom types are sorted out alphabetically + return ta < tb +end + +-- For implementation reasons, the behavior of rawlen & # is "undefined" when +-- tables aren't pure sequences. So we implement our own # operator. +local function getSequenceLength(t) + local len = 1 + local v = rawget(t, len) + while v ~= nil do + len = len + 1 + v = rawget(t, len) + end + return len - 1 +end + +local function getNonSequentialKeys(t) + local keys, keysLength = {}, 0 + local sequenceLength = getSequenceLength(t) + for k, _ in rawpairs(t) do + if not isSequenceKey(k, sequenceLength) then + keysLength = keysLength + 1 + keys[keysLength] = k + end + end + table.sort(keys, sortKeys) + return keys, keysLength, sequenceLength +end + +local function countTableAppearances(t, tableAppearances) + tableAppearances = tableAppearances or {} + + if type(t) == 'table' then + if not tableAppearances[t] then + tableAppearances[t] = 1 + for k, v in rawpairs(t) do + countTableAppearances(k, tableAppearances) + countTableAppearances(v, tableAppearances) + end + countTableAppearances(getmetatable(t), tableAppearances) + else + tableAppearances[t] = tableAppearances[t] + 1 + end + end + + return tableAppearances +end + +local copySequence = function(s) + local copy, len = {}, #s + for i = 1, len do + copy[i] = s[i] + end + return copy, len +end + +local function makePath(path, ...) + local keys = {...} + local newPath, len = copySequence(path) + for i = 1, #keys do + newPath[len + i] = keys[i] + end + return newPath +end + +local function processRecursive(process, item, path, visited) + if item == nil then + return nil + end + if visited[item] then + return visited[item] + end + + local processed = process(item, path) + if type(processed) == 'table' then + local processedCopy = {} + visited[item] = processedCopy + local processedKey + + for k, v in rawpairs(processed) do + processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) + if processedKey ~= nil then + processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + end + end + + local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) + if type(mt) ~= 'table' then + mt = nil + end -- ignore not nil/table __metatable field + setmetatable(processedCopy, mt) + processed = processedCopy + end + return processed +end + +------------------------------------------------------------------- + +local Inspector = {} +local Inspector_mt = { + __index = Inspector, +} + +function Inspector:puts(...) + local args = {...} + local buffer = self.buffer + local len = #buffer + for i = 1, #args do + len = len + 1 + buffer[len] = args[i] + end +end + +function Inspector:down(f) + self.level = self.level + 1 + f() + self.level = self.level - 1 +end + +function Inspector:tabify() + self:puts(self.newline, string.rep(self.indent, self.level)) +end + +function Inspector:alreadyVisited(v) + return self.ids[v] ~= nil +end + +function Inspector:getId(v) + local id = self.ids[v] + if not id then + local tv = type(v) + id = (self.maxIds[tv] or 0) + 1 + self.maxIds[tv] = id + self.ids[v] = id + end + return tostring(id) +end + +function Inspector:putKey(k) + if isIdentifier(k) then + return self:puts(k) + end + self:puts("[") + self:putValue(k) + self:puts("]") +end + +function Inspector:putTable(t) + if t == inspect.KEY or t == inspect.METATABLE then + self:puts(tostring(t)) + elseif self:alreadyVisited(t) then + self:puts('') + elseif self.level >= self.depth then + self:puts('{...}') + else + if self.tableAppearances[t] > 1 then + self:puts('<', self:getId(t), '>') + end + + local nonSequentialKeys, nonSequentialKeysLength, sequenceLength = getNonSequentialKeys(t) + local mt = getmetatable(t) + + self:puts('{') + self:down(function() + local count = 0 + for i = 1, sequenceLength do + if count > 0 then + self:puts(',') + end + self:puts(' ') + self:putValue(t[i]) + count = count + 1 + end + + for i = 1, nonSequentialKeysLength do + local k = nonSequentialKeys[i] + if count > 0 then + self:puts(',') + end + self:tabify() + self:putKey(k) + self:puts(' = ') + self:putValue(t[k]) + count = count + 1 + end + + if type(mt) == 'table' then + if count > 0 then + self:puts(',') + end + self:tabify() + self:puts(' = ') + self:putValue(mt) + end + end) + + if nonSequentialKeysLength > 0 or type(mt) == 'table' then -- result is multi-lined. Justify closing } + self:tabify() + elseif sequenceLength > 0 then -- array tables have one extra space before closing } + self:puts(' ') + end + + self:puts('}') + end +end + +function Inspector:putValue(v) + local tv = type(v) + + if tv == 'string' then + self:puts(smartQuote(escape(v))) + elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or tv == 'cdata' or tv == 'ctype' then + self:puts(tostring(v)) + elseif tv == 'table' then + self:putTable(v) + else + self:puts('<', tv, ' ', self:getId(v), '>') + end +end + +------------------------------------------------------------------- + +function inspect.inspect(root, options) + options = options or {} + + local depth = options.depth or math.huge + local newline = options.newline or '\n' + local indent = options.indent or ' ' + local process = options.process + + if process then + root = processRecursive(process, root, {}, {}) + end + + local inspector = setmetatable({ + depth = depth, + level = 0, + buffer = {}, + ids = {}, + maxIds = {}, + newline = newline, + indent = indent, + tableAppearances = countTableAppearances(root), + }, Inspector_mt) + + inspector:putValue(root) + + return table.concat(inspector.buffer) +end + +setmetatable(inspect, { + __call = function(_, ...) + return inspect.inspect(...) + end, +}) + +return inspect diff --git a/framework/lualib/3rd/misc/mm.lua b/framework/lualib/3rd/misc/mm.lua deleted file mode 100644 index b51911f..0000000 --- a/framework/lualib/3rd/misc/mm.lua +++ /dev/null @@ -1,724 +0,0 @@ --- https://github.com/nenofite/mm - --- Terminal color (and formatting) codes. -local C = { - e = '\27[0m', -- reset - - -- Text attributes. - br = '\27[1m', -- bright - di = '\27[2m', -- dim - it = '\27[3m', -- italics - un = '\27[4m', -- underscore - bl = '\27[5m', -- blink - re = '\27[7m', -- reverse - hi = '\27[8m', -- hidden - - -- Text colors. - k = '\27[30m', -- black - r = '\27[31m', -- red - g = '\27[32m', -- green - y = '\27[33m', -- yellow - b = '\27[34m', -- blue - m = '\27[35m', -- magenta - c = '\27[36m', -- cyan - w = '\27[37m', -- white - - -- Background colors. - _k = '\27[40m', -- black - _r = '\27[41m', -- red - _g = '\27[42m', -- green - _y = '\27[43m', -- yellow - _b = '\27[44m', -- blue - _m = '\27[45m', -- magenta - _c = '\27[46m', -- cyan - _w = '\27[47m', -- white -} - --- If we're on Windows, set all colors to empty strings so we don't spam the --- output with meaningless escape codes. -local ON_WINDOWS = string.find(package.path, '\\') ~= nil -if ON_WINDOWS then - for k, v in pairs(C) do - C[k] = '' - end -end - -local METATABLE = { - "", - colors = C.it .. C.y, -} -local INDENT = " " - --- The default sequence separator. -local SEP = " " - --- The open and close brackets can be any piece (notably, a sequence with --- colors). The separator must be a plain string. -local BOPEN, BSEP, BCLOSE = 1, 2, 3 - --- The default frame brackets and separator. -local BRACKETS = {{ - "{", - colors = C.br, -}, ",", { - "}", - colors = C.br, -}} - -local STR_HALF = 30 -local MAX_STR_LEN = STR_HALF * 2 - --- Names to use for named references. The order is important; these are aligned --- with the colors in `NAME_COLORS`. -local NAMES = {"Cherry", "Apple", "Lemon", "Blueberry", "Jam", "Cream", "Rhubarb", "Lime", "Butter", "Grape", - "Pomegranate", "Sugar", "Cinnamon", "Avocado", "Honey"} - --- Colors to use for named references. Don't use black nor white. -local NAME_COLORS = {C.r, C.g, C.y, C.b, C.m, C.c} - --- Reserved Lua keywords as a convenient look-up table. -local RESERVED = { - ['and'] = true, - ['break'] = true, - ['do'] = true, - ['else'] = true, - ['elseif'] = true, - ['end'] = true, - ['false'] = true, - ['for'] = true, - ['function'] = true, - ['goto'] = true, - ['if'] = true, - ['in'] = true, - ['local'] = true, - ['nil'] = true, - ['not'] = true, - ['or'] = true, - ['repeat'] = true, - ['return'] = true, - ['then'] = true, - ['true'] = true, - ['until'] = true, - ['while'] = true, -} - --- --- Namers --- - -local function new_namer() - local index = 1 - local suffix = 1 - local color_index = 1 - - return function() - -- Pick the name. - local result = NAMES[index] - if suffix > 1 then - result = result .. " " .. tostring(suffix) - end - - index = index + 1 - if index > #NAMES then - index = 1 - suffix = suffix + 1 - end - - -- Pick the color. - local color = NAME_COLORS[color_index] - - color_index = color_index + 1 - if color_index > #NAME_COLORS then - color_index = 1 - end - - return { - result, - colors = C.un .. color, - } - end -end - --- --- Context --- - -local function new_context() - return { - occur = {}, - named = {}, - next_name = new_namer(), - - prev_indent = '', - next_indent = INDENT, - line_len = 0, - max_width = 78, - - result = '', - } -end - --- --- Translating into pieces --- - --- Translaters take any Lua value and create pieces to represent them. --- --- Some values should only be serialized once, both to prevent cycles and to --- prevent redundancy. Or in other cases, these values cannot be serialized --- (such as functions) but if they appear multiple times we want to express --- that they are the same. --- --- When a translater encounters such a value for the first time, it is --- registered in the context in `occur`. The value is wrapped in a plain table --- with the `id` field pointing to the original value. If the value is --- serializable, such as a table, then the the `def` field contains the piece --- to display. If it is unserializable or it is not the first time this value --- has occurred, the `def` field is nil. --- --- In the cleaning stage, these `id` fields are replaced with their names. If a --- `def` field is present, then a sequence is generated to define the name with --- the piece. - -local translaters = {} -local translate, ident_friendly - -function translate(val, ctx) - -- Try to find a type-specific translater. - local by_type = translaters[type(val)] - - if by_type then - -- If there is a type-specific translater, call it. - return by_type(val, ctx) - end - - -- Otherwise perform the default translation. - - -- Check whether we've already encountered this value. - if ctx.occur[val] then - -- We have; give it a name if we haven't already. - if not ctx.named[val] then - ctx.named[val] = ctx.next_name() - end - - -- Return the value as a reference. - return { - id = val, - } - else - -- We haven't; mark it as encountered. - ctx.occur[val] = true - - -- Return the value as a definition. - return { - id = val, - def = tostring(val), - } - end -end - -translaters['function'] = function(val, ctx) - -- Check whether we've already encountered this function. - if ctx.occur[val] then - -- We have; give it a name if we haven't already. - if not ctx.named[val] then - ctx.named[val] = ctx.next_name() - end - else - -- We haven't; mark it as encountered. - ctx.occur[val] = true - end - - -- Return the unserialized function. - return { - id = val, - } -end - -function translaters.table(val, ctx) - -- Check whether we've already encountered this table. - if ctx.occur[val] then - -- We have; give it a name if we haven't already. - if not ctx.named[val] then - ctx.named[val] = ctx.next_name() - end - - -- Return the unserialized table. - return { - id = val, - } - else - -- We haven't; mark it as encountered. - ctx.occur[val] = true - - -- Construct the frame for this table. - local result = { - bracket = BRACKETS, - } - - -- The equals-sign between key and value. - local eq = { - "=", - colors = C.di, - } - - -- Represent the metatable, if present. - local mt = getmetatable(val) - if mt then - -- Translate the metatable. - mt = translate(mt, ctx) - table.insert(result, {METATABLE, eq, mt}) - end - - -- Represent the contents. - for k, v in pairs(val) do - -- If it is a string key which can be represented without quotes, leave - -- it plain. - if ident_friendly(k) then - -- Leave the key as it is. - k = { - k, - colors = C.m, - } - else - -- Otherwise translate the key. - k = translate(k, ctx) - end - - -- Translate the value. - v = translate(v, ctx) - - table.insert(result, {k, eq, v}) - end - - -- Wrap the result with its id. - return { - id = val, - def = result, - } - end -end - -function translaters.string(val, ctx) - if #val <= MAX_STR_LEN then - -- The string is short enough; display it all. - local a = string.format('%q', val) - a = string.gsub(a, '\n', 'n') - - return { - a, - colors = C.g, - } - else - -- The string is too long. Only show the start and end. - local a = string.format('%q', string.sub(val, 1, STR_HALF)) - a = string.gsub(a, '\n', 'n') - local b = string.format('%q', string.sub(val, -STR_HALF)) - b = string.gsub(b, '\n', 'n') - - return { - a, - { - "...", - colors = C.di, - }, - b, - colors = C.g, - sep = '', - tight = true, - } - end -end - -function translaters.number(val, ctx) - return { - tostring(val), - colors = C.m .. C.br, - } -end - --- Check whether a value can be represented as a Lua identifier, without the --- need for quotes or translation. --- --- If the value is not a string, this immediately returns false. Otherwise, the --- string must be a valid Lua name: a sequence of letters, digits, and --- underscores that doesn't start with a digit and isn't a reserved keyword. --- --- See http://www.lua.org/manual/5.3/manual.html#3.1 -function ident_friendly(val) - -- The value must be a string. - if type(val) ~= 'string' then - return false - end - - if string.find(val, '^[_%a][_%a%d]*$') then - -- The value is a Lua name; check if it is reserved. - if RESERVED[val] then - -- The value is a resreved keyword. - return false - else - -- The value is a valid name. - return true - end - else - -- The value is not a Lua name. - return false - end -end - --- --- Cleaning pieces --- - -local function clean(piece, ctx) - if type(piece) == 'table' then - -- Check if it's an id reference. - if piece.id then - local name = ctx.named[piece.id] - local def = piece.def - - -- Check whether it has been given a name. - if name then - local header = { - "<", - type(piece.id), - " ", - name, - ">", - colors = C.it, - sep = '', - tight = true, - } - -- Named. Check whether the reference has a definition. - if def then - -- Create a sequence defining the name to the definition. - return {header, { - "is", - colors = C.di, - }, clean(piece.def, ctx)} - else - -- Show just the name. - return header - end - else - -- No name. Check whether the reference has a definition. - if def then - -- Display the definition without any header. - return clean(piece.def, ctx) - else - -- Display just the type. - return { - "<", - type(piece.id), - ">", - colors = C.it, - sep = '', - tight = true, - } - end - end - - -- Check if it's a frame. - elseif piece.bracket then - -- Clean each child. - for i, child in ipairs(piece) do - piece[i] = clean(child, ctx) - end - return piece - - -- Otherwise it's a sequence. - else - -- Clean each child. - for i, child in ipairs(piece) do - piece[i] = clean(child, ctx) - end - return piece - end - else - -- It's a plain value, not a table; no cleaning is needed. - return piece - end -end - --- --- Displaying pieces --- - --- Pieces are either frames (with brackets), sequences (no brackets), or --- strings. - --- Frames are displayed either short-form as { a = 1 } or long-form as --- { --- a = 1 --- }. - --- Declare all the local functions first, so they can refer to each other. -local min_len, display, display_frame, display_sequence, display_string, display_frame_short, display_frame_long, - newline, newline_no_indent, write, write_nolength, space_here, space_newline - --- Dispatch based on the piece's type. -function display(piece, ctx) - if type(piece) == 'string' then - -- String. - return display_string(piece, ctx) - elseif piece.bracket then - -- Frame. - return display_frame(piece, ctx) - else - -- Sequence. - return display_sequence(piece, ctx) - end -end - --- Display a frame. -function display_frame(frame, ctx) - if #frame == 0 then - -- If the frame is empty, just display the brackets. - local str = { - frame.bracket[BOPEN], - frame.bracket[BCLOSE], - sep = '', - tight = true, - } - return display(str, ctx) - end - - local ml = min_len(frame) - - -- Try to fit the frame short-form on this line. - if ml <= space_here(ctx) then - return display_frame_short(frame, ctx) - - -- Otherwise try to fit it short-form on the next line. - elseif ml <= space_newline(ctx) then - newline(ctx) - return display_frame_short(frame, ctx) - - -- Otherwise display it long-form. - else - return display_frame_long(frame, ctx) - end -end - -function display_frame_short(frame, ctx) - -- Short-form frames never wrap onto new lines, so we don't need to do any - -- length checking (it's already been done for us). - - -- Write the open bracket. - display(frame.bracket[BOPEN], ctx) - write(" ", ctx) - - -- Display the first child. - display(frame[1], ctx) - - -- Display the remaining children. - for i = 2, #frame do - local child = frame[i] - - -- Write the separator. - write(frame.bracket[BSEP], ctx) - write(" ", ctx) - - -- Display the child. - display(child, ctx) - end - - -- Write the close bracket. - write(" ", ctx) - display(frame.bracket[BCLOSE], ctx) -end - -function display_frame_long(frame, ctx) - -- Remember the original value of next_indent. - local old_old_indent = ctx.prev_indent - local old_indent = ctx.next_indent - - -- Display the open bracket. - display(frame.bracket[BOPEN], ctx) - - -- Increase the indentation. - ctx.prev_indent = old_indent - ctx.next_indent = old_indent .. INDENT - - -- For all but the last child... - for i = 1, #frame - 1 do - local child = frame[i] - - -- Start a new line with old indentation. - newline_no_indent(ctx) - write(old_indent, ctx) - - -- Display the child. - display(child, ctx) - - -- Write the separator. - write(frame.bracket[BSEP], ctx) - end - - -- For the last child... - do - local child = frame[#frame] - - -- Start a new line with old indentation. - newline_no_indent(ctx) - write(old_indent, ctx) - - -- Display the child. - display(child, ctx) - -- No separator. - end - - -- Write the close bracket. - newline_no_indent(ctx) - write(old_old_indent, ctx) - display(frame.bracket[BCLOSE], ctx) - - -- Return to the old indentation. - ctx.prev_indent = old_old_indent - ctx.next_indent = old_indent -end - -function display_sequence(piece, ctx) - if #piece > 0 then - -- Check if this is a tight sequence. - if piece.tight then - -- Try to fit the entire sequence on one line. - local ml = min_len(piece, ctx) - - -- If it won't fit here, but it would fit on the next line, then write it - -- on the next line; otherwise, write it here. - if ml > space_here(ctx) and ml <= space_newline(ctx) then - newline(ctx) - end - end - - -- Apply the colors, if given. - if piece.colors then - write_nolength(piece.colors, ctx) - end - - -- Display the first child. - display(piece[1], ctx) - - -- For each following children: - for i = 2, #piece do - local child = piece[i] - - -- Apply the colors, if given. - if piece.colors then - write_nolength(piece.colors, ctx) - end - - -- Write a separator. - write(piece.sep or SEP, ctx) - - -- Then display the child. - display(child, ctx) - end - - -- Reset the colors. - if piece.colors then - write_nolength(C.e, ctx) - end - end -end - -function display_string(piece, ctx) - local ml = min_len(piece) - - -- If it won't fit here, but it would fit on the next line, then write it on - -- the next line; otherwise, write it here. - if ml > space_here(ctx) and ml <= space_newline(ctx) then - newline(ctx) - end - - write(piece, ctx) -end - --- The minimum length to display this piece, if it is placed all on one line. -function min_len(piece, ctx) - -- For strings, simply return their length. - if type(piece) == 'string' then - return #piece - end - - -- Otherwise, we have some calculations to do. - local result = 0 - - if piece.bracket then - -- This is a frame. - - -- If it's an empty frame, just the open and close brackets. - if #piece == 0 then - return min_len(piece.bracket[BOPEN]) + min_len(piece.bracket[BCLOSE]) - end - - -- Open and close brackets, plus a space for each. - result = result + min_len(piece.bracket[BOPEN]) + min_len(piece.bracket[BCLOSE]) + 2 - - -- A separator between each item, plus a space for each. - result = result + (#piece - 1) * (#piece.bracket[BSEP] + 1) - else - -- This is a sequence. - - -- If it's an empty sequence, then nothing. - if #piece == 0 then - return 0 - end - - -- A single separator between each item. - result = result + (#piece - 1) * #(piece.sep or SEP) - end - - -- For both frames and sequences: - -- Find the minimum length of each child. - for _, child in ipairs(piece) do - result = result + min_len(child, ctx) - end - - return result -end - -function newline(ctx) - ctx.result = ctx.result .. "\n" - ctx.line_len = 0 - write(ctx.next_indent, ctx) -end - -function newline_no_indent(ctx) - ctx.result = ctx.result .. "\n" - ctx.line_len = 0 -end - -function write(str, ctx) - ctx.result = ctx.result .. str - ctx.line_len = ctx.line_len + #str -end - -function write_nolength(str, ctx) - ctx.result = ctx.result .. str -end - -function space_here(ctx) - return math.max(0, ctx.max_width - ctx.line_len) -end - -function space_newline(ctx) - return math.max(0, ctx.max_width - #ctx.next_indent) -end - --- --- Main function --- - -return function(val) - if val == nil then - return nil - else - local ctx = new_context() - local piece = translate(val, ctx) - piece = clean(piece, ctx) - display(piece, ctx) - return (C.e .. ctx.result .. C.e) - end -end