读取 JSON 配置文件
序言
在前一篇文章中,连接数据库的配置是写在代码中的,在谓词url_to_id
中重复出现了两次。在本文中,会将配置从源文件中剥离出来,改为在程序启动的时候从 JSON 格式的配置文件中读取。
配置文件及其解析
新增一个配置文件config.json
,其内容如下:
{
"mysql": {
"dsn": "DRIVER={/usr/local/mysql-connector-odbc-8.0.33-macos13-x86-64bit/lib/libmyodbc8a.so};String Types=Unicode;password=1234567;port=3306;server=localhost;user=shorten"
}
}
接着新增一个源文件config.pl
,它负责实现一个用于解析 JSON 文件的模块
:- module(config, [current_dsn/1, parse/1]).
:- dynamic current_dsn/1. % 声明一个动态谓词,用于存储解析好的 ODBC DSN 字符串。
:- use_module(library(http/json), [atom_json_dict/3]).
:- use_module(library(readutil), [read_file_to_string/3]).
parse(ConfigPath) :-
read_file_to_string(ConfigPath, String, []),
% 按照 JSON 格式反序列化为字典类型的数据。
atom_json_dict(String, JSONDict, []),
% 将 DSN 字符串存储在谓词中,这样外部就可以通过 current_dsn/1 获取。
asserta(current_dsn(JSONDict.mysql.dsn)).
使用效果如下图所示
现在可以改写源文件http_server.pl
和url_to_id.pl
了,前者在谓词main
中调用parse/1
来解析配置文件,后者调用current_dsn/1
来获取 DSN 以连接数据库。
字典类型
在parse
中调用atom_json_dict
得到的JSONDict
是一个字典类型的对象,可以用write
来查看它的内容
图中如_1518
、_1510
这样的项在 SWI-Prolog 中叫做Tag
,可以将其视为系统随机生成的类型名,而如mysql
、dsn
这样的位于冒号左侧的就是字典的键、右侧的就是对应的值。这个语法也可以用于输入字典字面量
命令行选项
尽管在前文中,ODBC DSN 已经被放到了配置文件中,但是配置文件的路径仍然是写在源文件中的(节选如下)
main(_) :-
% 解析配置文件。
parse("./config.json"),
一个更好的做法是将配置文件的路径通过命令行选项传给程序,为此这里用到了谓词opt_arguments
来解析命令行选项。新增一个模块cli_parser.pl
来负责解析命令行选项
:- module(cli_parser, [extract_config_path/2, parse_argv/1]).
:- use_module(library(optparse), [opt_arguments/3]).
extract_config_path([], _) :- fail.
extract_config_path([config_path(ConfigPath) | _], ConfigPath).
extract_config_path([_ | Opts], ConfigPath) :- extract_config_path(Opts, ConfigPath).
parse_argv(Opts) :-
OptsSpec = [
[
opt(config_path), % opt_arguments 要求每个命令行选项都必须指定 opt。
shortflags([c]), % 短的选项名称,使用时为 -c。
type(atom) % 传入的值为字符串,因此必须指定类型为 atom。
]
],
opt_arguments(OptsSpec, Opts, _).
要测试这个模块,可以在启动swipl
时输入两个连字符--
,然后再跟着要被解析的命令行选项,如-c '/Users/liutos/Projects/shorten/tutorials/config.json'
。这样一来,--
之后的部分都将可以被opt_arguments
处理到,如下图所示
最后在谓词main
中调用这两个新的功能即可
main(_) :-
% 解析命令行选项。
parse_argv(Opts),
% 取出配置文件路径。
extract_config_path(Opts, ConfigPath),
% 解析配置文件。
parse(ConfigPath),
http_handler('/api/lengthen', lengthen_url, [methods([get])]),
http_handler('/api/shorten', shorten_url, [methods([post])]),
http_server([port(8082)]),
sleep(100000000).