读取 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)).

使用效果如下图所示

解析出ODBC的DSN 解析出ODBC的DSN

现在可以改写源文件http_server.plurl_to_id.pl了,前者在谓词main中调用parse/1来解析配置文件,后者调用current_dsn/1来获取 DSN 以连接数据库。

字典类型

parse中调用atom_json_dict得到的JSONDict是一个字典类型的对象,可以用write来查看它的内容

字典类型的对象 字典类型的对象

图中如_1518_1510这样的项在 SWI-Prolog 中叫做Tag,可以将其视为系统随机生成的类型名,而如mysqldsn这样的位于冒号左侧的就是字典的键、右侧的就是对应的值。这个语法也可以用于输入字典字面量

输入字典字面量 输入字典字面量

命令行选项

尽管在前文中,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处理到,如下图所示

测试cli_parser模块 测试cli_parser模块

最后在谓词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).