🐳 chore(工具): 整理
parent
6a8673d95d
commit
15de42b61a
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
.git/
|
|
||||||
.idea/
|
|
||||||
@ -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"]
|
|
||||||
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
docker-compose --env-file .env.dev up -d
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
docker-compose --env-file .env.prod up -d
|
|
||||||
@ -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).
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +1,2 @@
|
|||||||
return "mm"
|
local inspect = require "inspect"
|
||||||
|
return inspect
|
||||||
|
|||||||
@ -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('<table ', self:getId(t), '>')
|
||||||
|
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('<metatable> = ')
|
||||||
|
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
|
||||||
@ -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 = {
|
|
||||||
"<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
|
|
||||||
Loading…
Reference in New Issue