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