🐳 chore(工具): 整理

develop
xiaojin 5 years ago
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,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 和端口)

@ -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
}
}

@ -2,6 +2,9 @@
docker stop $(docker ps -a -q) && docker system prune --all --force docker stop $(docker ps -a -q) && docker system prune --all --force
配置中心(apollo)
ELK
代码安全指南: 代码安全指南:
https://github.com/Tencent/secguide https://github.com/Tencent/secguide

@ -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…
Cancel
Save