前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter TolyUI 框架#07 | 案例解析与管理

Flutter TolyUI 框架#07 | 案例解析与管理

作者头像
张风捷特烈
发布2024-06-12 08:26:43
1220
发布2024-06-12 08:26:43
举报

《Flutter TolyUI 框架》系列前言:

TolyUI张风捷特烈 打造的 Fluter 全平台应用开发 UI 框架。具备 全平台组件化源码开放响应式 四大特点。可以帮助开发者迅速构建具有响应式全平台应用软件:

开源地址: github.com/TolyFx/toly…

image.png
image.png

该系列将详细介绍 TolyUI 框架使用方式、框架开发过程中的技术知识、设计理念、难题解决等。


一、为什么需要解析管理

TolyUI 中每个组件有若干个介绍的案例,随着组件的增加,介绍的节点的维护成了一个非常繁杂的事。如下所示,一个介绍节点包括 标题介绍代码内容 四个部分:

image.png
image.png

1. 目前遇到的问题

之前案例展示的信息内容通过 Map 对象进行维护,如下红框中是 BreadcrumbDemo1 的介绍信息。这种维护方式,任何部分的变更都需要及时同步,比如视图代码变化了,展示的代码信息如果没及时更新。使用者就会产生困惑:

image.png
image.png

靠手动来维护这些数据,正在变得越来越复杂。为了 TolyUI 更好的发展,我需要寻找一条可以自动解析和管理介绍文本信息的方式。也是文想要向大家分享的内容。


2. 解析和生成

面对当前的维护困境,我给出的方案是: 解析文件自动生成代码。首先需要明确,当前解析的目标以及想要生成的内容。解析的目标自然是对当前案例代码的介绍信息。自动生成的内容有三个部分:

  • [1]. 之前手动维护的 displayNodes 将是生成代码的核心内容。放在 node.g.dart 文件中。
  • [2]. 案例的展示代码属于大文本,并没有必要全部放入映射中占据内存。所以会将其抓取到 assets 资源文件之下,点击时按需加载。
  • [3]. 案例最终想要以组件的形式展示在界面上,节点数据以字符串作为标识,通过 widget_display_map.g.dart 来维护标识与具体组件间的映射关系。
image.png
image.png

3. 注解与信息维护

巧妇难为无米之炊 ,现在最重要的问题是:在哪里,如何向案例提供描述信息? 由于 Dart 即将支持宏编程,所以我决定采用 注解 的方式来维护某个案例的介绍数据。如下所示: @DisplayNode 是我自定义的注解类,包含标题和描述两个字段:

image.png
image.png

到这里,解析生成的路线基本就确定了:

  • [1]. 遍历案例文件夹,当文件内容有 @DisplayNode 注解时,通过正则解析文件,收录数据。
  • [2]. 通过解析收录的数据,操作文件生成对应代码。
  • [3]. 解析过程中提取案例代码到资源文件。

二、案例文件的解析逻辑

NodeMeta 是解析过程中承载数据的核心对象,每个案例文件将解析成一个 NodeMeta 对象。其中变化代码字符串、案例文件路径、案例名称、展示信息四个数据内容:

代码语言:javascript
复制
class NodeMeta {
  final String code;
  final String name;
  final String filePath;
  final DisplayNode display;

  const NodeMeta({
    required this.filePath,
    required this.code,
    required this.name,
    required this.display,
  });

1. 提取案例文件信息

拿上面的 CardDemo1 为例,该文件中已经包含了 NodeMeta 对象的所有信息数据。现在关键在于如何解析文本内容,生成 NodeMeta 对象。

image.png
image.png

想要匹配文本中的关键信息,很容易想到可以使用 正则表达式 。比如想要抓取注解的字符串内容,可以通过 @DisplayNode(.|\s)*?\) 进行匹配:

image.png
image.png

抓取到 DisplayNode 配置的字符串之后,可以继续通过正则表达式来匹配对应字段的数据。如下所示,匹配其中 title 对应的字符串信息:

image.png
image.png

通过 class (?<name>\w+)(.|\s)* 匹配第一个以 class 开头的文字及其之后的所有字符串。并且 class 后的类名通过 name 组名获取:

image.png
image.png

2. 单文件解析类 DisplayFileParser

一个园林中有很多树,想着为所有树木修葺是一件很复杂的事。但修葺一棵树就比较简单,而且修葺的任务是类似的工作,一棵修的好,那么其他的都只是时间问题。解析也是一样,一开始应该着眼于个体,做好第一个任务。

如下,定义 DisplayFileParser 类负责解析 path 对应的文件内容,通过 parser 方法异步解析,生成 NodeMeta 对象:

代码语言:javascript
复制
class DisplayFileParser {
  final String path;

  static final RegExp _codeRegex = RegExp(r'class (?<name>\w+)(.|\s)*');
  static final RegExp _displayRegex = RegExp(r'@DisplayNode(.|\s)*?\)');

  const DisplayFileParser(this.path);
  
  Future<NodeMeta?> parser() async {...}

在解析前,可以先基于一些既定规则,过滤一下不必要的文件读取和解析。比如这里所有的案例文件名都会包含 _demo 字符串。另外,读取内容中不包含 @DisplayNode( 的文件表示不需要解析。最终通过 _parserContent 方法处理具体的解析逻辑:

代码语言:javascript
复制
Future<NodeMeta?> parser() async {
  if (!path.contains('_demo')) return null;
  File file = File(path);
  String content = await file.readAsString();
  bool hasDisplay = content.contains('@DisplayNode(');
  if (!hasDisplay) return null;
  return _parserContent(path, content);
}

_parserContent 方法中基于两个正则表达式匹配得到数据,从而创建 NodeMeta 对象。其中 DisplayNode.fromString 构造方法是基于字符串,来匹配创建 DisplayNode 对象:

代码语言:javascript
复制
NodeMeta _parserContent(String filePath, String content) {
  RegExpMatch? codeMatch = _codeRegex.firstMatch(content);
  String? code = codeMatch?.group(0);
  String? name = codeMatch?.namedGroup('name');
  String? display = _displayRegex.firstMatch(content)?.group(0);
  return NodeMeta(
    filePath: filePath,
    name: name ?? '',
    code: code ?? '',
    display: DisplayNode.fromString(display ?? ''),
  );
}

如下所示,这样就可以解析某个案例文件,通过正则匹配得到关键的数据内容:

image.png
image.png

3. 遍历解析与收集 NodeMeta 结果

所有的案例代码都放在了项目中的 widgets 文件夹下,接下来需要遍历文件夹,来逐一解析内容。得到每个案例文件对应的 NodeMeta 数据集:

image.png
image.png

下面代码中,通过 parserDir 方法遍历一个文件夹中的文件,处理解析逻辑。并将解析的结果放入 displayMap 中。由于一个组件有若干个案例,所以这里通过 Map<String, List<NodeMeta>> 记录一个组件对应的节点列表信息。解析完后,数据如下所示:

image.png
image.png
代码语言:javascript
复制
void main() async {
  String widgetPath = path.join(Directory.current.path, 'lib', 'view', 'widgets');
  Directory widgetDir = Directory(widgetPath);

  Map<String, List<NodeMeta>> displayMap = {};
  await parserDir(widgetDir, displayMap);
}

Future<void> parserDir(Directory dir, Map<String, List<NodeMeta>> displayMap) async {
  List<NodeMeta> displays = [];
  List<FileSystemEntity> entity = dir.listSync();
  for (FileSystemEntity e in entity) {
    if (e is File) {
      NodeMeta? ret = await DisplayFileParser(e.path).parser();
      if (ret != null) {
        displays.add(ret);
        ret.saveCode();
      }
    } else if (e is Directory) {
      await parserDir(e, displayMap);
    }
  }
  if (displays.isNotEmpty) {
    displayMap[path.basename(dir.path)] = displays;
  }
}

三、使用结果生成代码

上面已经完成了对案例代码的解析,得到了所有期望获取的数据。接下来就是基于这些数据,创建并写入代码文件,完成案例代码的自动维护。


1. 代码生成的格式

代码生成的核心是 node.g.dart ,其中 queryDisplayNodes 方法可以通过组件名称得到对应的案例列表数据。注意这里使用的是 switch 进行匹配,并不是将所有的数据通过 Map 全部加入到内存中。这种运行时的取用,可以降低内存的使用,特别是对于案例介绍这样的大量数据。

image.png
image.png

另外,这里将每个组件对应的案例列表数据拆散成 独立文件。通过 partpart of 关键字建立文件间的关系。将独立文件在逻辑上视为 node.g.dart 的一部分。单独分离文件的目的在于:让代码逻辑结构更加清晰,另外,单个大文件在多人协作时更容易产生冲突。

image.png
image.png

2.组件名到组件的映射

在案例介绍的信息中,记录着 String 类型的案例组件名,但在展示时需要将组件名映射为具体的组件。由于解析过程中,所有案例的组件名都可以收集到,因此可以自动生成 widgetDisplayMap 的映射关系,将字符串映射为对应的组件:

image.png
image.png

在视图层的使用中,通过组件标识调用 queryDisplayNodes 查找案例信息列表。然后遍历列表,根据案例组件的字符串名称,基于 widgetDisplayMap 得到对应的组件:

image.png
image.png

3. 代码生成逻辑

代码本质上也是字符串,基于解析得到的 displayMap 数据,我们可以通过字符串拼接得到代码字符串,然后写入到指定的文件中。这样就完成了用代码写代码的目的:通过 FileGen类来维护代码生成的逻辑,其中依赖解析后的数据对象 displayMap,通过构造函数传入:

代码语言:javascript
复制
class FileGen {
  final Map<String, List<NodeMeta>> displayMap;

  FileGen(this.displayMap);

代码的生成听起来好像挺高大上的,但本质上也只是一个基于模版的填词游戏。如下是 node.g.dart 的文件模版,需要结合 displayMap 中的数据,将 partcontent 的两处内容添到指定位置:

代码语言:javascript
复制
  String nodeTemplate(String content, String part) {
    return """
/// ===================================================
/// Power By 张风捷特烈 --- Generated file. Do not edit.
/// github: https://github.com/toly1994328
/// ===================================================
$part
Map<String, dynamic>  queryDisplayNodes(String name){
  return switch(name){
$content    _ => {},
  };
}
    """;
  }

其中的内容,只需要遍历 displayMap 映射元素,拼接呈成目标字符串即可。如下代码在 nodePartsnodeContents 分别表示 node.g.dart 头部引入的部分和中间的具体内容字符串列表。生成代码字符串之后,写入对应文件中,将完成代码的生成任务:

代码语言:javascript
复制
Future<void> genNode(String outPath) async {
  List<String> nodeParts = [];
  List<String> nodeContents = [];
  File file = File(outPath);
  displayMap.forEach((k, v) {
    Map<String, dynamic> items = {};
    nodeParts.add("part '$k.g.dart';\n");
    nodeContents.add('    "$k" => _${k}Data,\n');
    ///...
  });
  String content = nodeTemplate(nodeContents.join(), nodeParts.join());
  await file.writeAsString(content);
}

单个组件对应的节点列表文件也是类似,定义模版之后,遍历映射关系,向其中插入期望的字符串,得到代码:

image.png
image.png
代码语言:javascript
复制
  String singleNodeTemplate(String content, String name) {
    content = content.replaceAll(r'$', r'\$');
    return """/// ===================================================
/// Power By 张风捷特烈 --- Generated file. Do not edit.
/// github: https://github.com/toly1994328
/// ===================================================

part of 'node.g.dart';

Map<String, dynamic> get _${name}Data => $content;""";
  }

4. 基于命令行工具使用生成器

到这里,已经完成了解析和代码生成的逻辑,以后任何的代码或描述信息的改动,或者新增组件案例介绍。只要运行一下工具就可以自动生成代码,同步所有的更新内容。从而大大简化了书写和维护案例介绍的 劳动成本

虽然现在已经挺好用了,但是作为 dart 文件来执行会比较麻烦,还需要手动点击运行。期间的编译、运行会耗个十几秒,也不是非常优雅。

之前在 《Flutter 知识集锦 | Dart 开发命令行工具》 一文中介绍过,Dart 文件可以作为打包为命令行工具,进行使用。所以为了更好地使用工具来生成代码,我将这个代码解析生成器集成到 toly 命令行工具中:

image.png
image.png

也就是说,当案例信息有任何变化,我只需要在命令行输入 toly ui ,就可以在 100ms 内完成代码生成来更新所有的案例信息。

image.png
image.png

工具可以让人从枯燥的繁杂任务中解脱出来,特别是重复性的有明确规则的任务。联合收割机、卡车、电饭锅,优秀的工具能更精准、迅速且正确地完成特定任务,从而可以大大提升生产的效率。希望 tolyui 中对于案例的解析管理,能让你对工具的使用有所启发。那本就到这里,谢谢观看 ~


四、小结

到这里 TolyUI 就完成了一个可以灵活定制的下拉菜单 TolyDropMenu。目前为止,TolyUI 已经完成了响应式布局和反馈模块的核心功能。导航模块也完成了三个非常重要的组件,下一步会继续对导航模块进行开发,敬请期待 ~

image.png
image.png

感谢你关注 tolyui 的成长,如果喜欢,也希望你能在 github 中点赞支持~

github 开源地址: github.com/TolyFx/toly… TolyUI 官方案例演示网站:toly1994.com/ui

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、为什么需要解析管理
    • 1. 目前遇到的问题
      • 2. 解析和生成
        • 3. 注解与信息维护
        • 二、案例文件的解析逻辑
          • 1. 提取案例文件信息
            • 2. 单文件解析类 DisplayFileParser
              • 3. 遍历解析与收集 NodeMeta 结果
              • 三、使用结果生成代码
                • 1. 代码生成的格式
                  • 2.组件名到组件的映射
                    • 3. 代码生成逻辑
                      • 4. 基于命令行工具使用生成器
                      • 四、小结
                      相关产品与服务
                      命令行工具
                      腾讯云命令行工具 TCCLI 是管理腾讯云资源的统一工具。使用腾讯云命令行工具,您可以快速调用腾讯云 API 来管理您的腾讯云资源。此外,您还可以基于腾讯云的命令行工具来做自动化和脚本处理,以更多样的方式进行组合和重用。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档