Flutter【03】图片输出package依靠关系

打印 上一主题 下一主题

主题 546|帖子 546|积分 1638

环境预备

安装 graphviz
  1. arch -arm64 brew install graphviz
复制代码
项目根目录pubspec.yaml文件内添加
  1. dev_dependencies:
  2. yaml: ^3.1.1
  3. gviz: ^0.4.0
复制代码
执行脚本

项目根目录下添加dart文件,运行main函数
  1. import 'dart:io';
  2. import 'dart:convert';
  3. import 'package:yaml/yaml.dart' as yaml;
  4. import 'package:gviz/gviz.dart';
  5. void main() async {
  6.   final projectPath = await _getProjectPath();
  7.   final file = File('$projectPath/pubspec.yaml');
  8.   final fileContent = file.readAsStringSync();
  9.   final yamlMap = yaml.loadYaml(fileContent) as yaml.YamlMap;
  10.   final appName = yamlMap['name'].toString();
  11.   print('开始 ...');
  12.   final dependencyContent = await _getComponentDependencyTree(
  13.     projectPath: projectPath,
  14.   );
  15.   print('... 开始遍历组件依赖节点');
  16.   print(dependencyContent);
  17.   final dependencyNodes = _traversalComponentDependencyTree(dependencyContent);
  18.   print('... 完成遍历组件依赖节点');
  19.   final graph = Gviz(
  20.     name: appName,
  21.     graphProperties: {
  22.       'pad': '0.5',
  23.       'nodesep': '1',
  24.       'ranksep': '2',
  25.     },
  26.     edgeProperties: {
  27.       'fontcolor': 'gray',
  28.     },
  29.   );
  30.   print('... 开始转换 dot 节点');
  31.   _generateDotByNodes(
  32.     dependencyNodes,
  33.     graph: graph,
  34.     edgeCache: <String>[],
  35.   );
  36.   print('... 完成转换 dot 节点');
  37.   final dotDirectoryPath = '$projectPath/dotGenerateDir';
  38.   final dotDirectory = Directory(dotDirectoryPath);
  39.   if (!dotDirectory.existsSync()) {
  40.     await dotDirectory.create();
  41.     print('... 创建 dotGenerate 文件夹');
  42.   }
  43.   final dotFileName = '$appName.dot';
  44.   final dotPngName = '$appName.png';
  45.   final dotFile = File('$dotDirectoryPath/$dotFileName');
  46.   final dotPngFile = File('$dotDirectoryPath/$dotPngName');
  47.   if (dotFile.existsSync()) {
  48.     await dotFile.delete();
  49.     print('... 删除原有 dot 生成文件');
  50.   }
  51.   if (dotPngFile.existsSync()) {
  52.     await dotPngFile.delete();
  53.     print('... 删除原有 dot 依赖关系图');
  54.   }
  55.   await dotFile.create();
  56.   final dotResult = await dotFile.writeAsString(graph.toString());
  57.   print('dot 文件生成成功: ${dotResult.path}');
  58.   print('... 开始生成 dot png');
  59.   await _runCommand(
  60.     executable: 'dot',
  61.     projectPath: projectPath,
  62.     commandArgs: [
  63.       '$dotDirectoryPath/$dotFileName',
  64.       '-T',
  65.       'png',
  66.       '-o',
  67.       '$dotDirectoryPath/$dotPngName'
  68.     ],
  69.   );
  70.   print('png 文件生成成功:$dotDirectoryPath/$dotPngName');
  71.   await Process.run(
  72.     'open',
  73.     [dotDirectoryPath],
  74.   );
  75. }
  76. const List<String> ignoreDependency = <String>[
  77.   'flutter',
  78.   'flutter_test',
  79.   'flutter_lints',
  80.   'cupertino_icons',
  81.   'gviz',
  82.   'yaml',
  83.   'injectable_generator',
  84.   'build_runner',
  85. ];
  86. Future<String> _getComponentDependencyTree({
  87.   required String projectPath,
  88. }) {
  89.   return _runCommand(
  90.     projectPath: projectPath,
  91.     commandArgs: ['pub', 'deps', '--json'],
  92.   ).then(
  93.         (value) {
  94.       if (value.contains('dependencies:') &&
  95.           value.contains('dev dependencies:')) {
  96.         final start = value.indexOf('dependencies:');
  97.         final end = value.indexOf('dev dependencies:');
  98.         return value.substring(start, end);
  99.       } else {
  100.         return value;
  101.       }
  102.     },
  103.   );
  104. }
  105. List<DependencyNode> _traversalComponentDependencyTree(String dependencyContent) {
  106.   final dependencyJson = jsonDecode(dependencyContent) as Map<String, dynamic>;
  107.   final packages = dependencyJson['packages'] as List<dynamic>;
  108.   final nodeMap = <String, DependencyNode>{};
  109.   for (var package in packages) {
  110.     final node = DependencyNode.fromMap(package);
  111.     nodeMap[node.name] = node;
  112.   }
  113.   final rootNode = nodeMap.values.firstWhere((element) => element.isRootNode);
  114.   void mapDependencies(DependencyNode node, Set<String> visitedNodes) {
  115.     if (visitedNodes.contains(node.name)) {
  116.       return;
  117.     }
  118.     visitedNodes.add(node.name);
  119.     for (final itemName in node.dependencies) {
  120.       if (!ignoreDependency.contains(itemName)) {
  121.         final itemNode = nodeMap[itemName];
  122.         if (itemNode != null) {
  123.           mapDependencies(itemNode, visitedNodes);
  124.           node.children.add(itemNode);
  125.           itemNode.isLevel1Node = false;
  126.         }
  127.       }
  128.     }
  129.   }
  130.   final visitedNodes = <String>{};
  131.   mapDependencies(rootNode, visitedNodes);
  132.   // 使用新的 rebuildDependencyTree 函数来创建一个没有重复依赖的新树
  133.   DependencyNode newRootNode = rebuildDependencyTree(rootNode, Set<String>());
  134.   return [newRootNode];
  135. }
  136. DependencyNode rebuildDependencyTree(DependencyNode originalNode, Set<String> seenDependencies) {
  137.   // 创建一个新的节点,复制原始节点的属性
  138.   DependencyNode newNode = DependencyNode(
  139.     name: originalNode.name,
  140.     version: originalNode.version,
  141.     kind: originalNode.kind,
  142.     source: originalNode.source,
  143.     dependencies: originalNode.dependencies,
  144.   );
  145.   newNode.isLevel1Node = originalNode.isLevel1Node;
  146.   // 如果这个节点已经被处理过,直接返回新节点(没有子节点)
  147.   if (seenDependencies.contains(newNode.name)) {
  148.     return newNode;
  149.   }
  150.   // 将这个节点添加到已处理集合中
  151.   seenDependencies.add(newNode.name);
  152.   // 处理子节点
  153.   for (var childNode in originalNode.children) {
  154.     if (!ignoreDependency.contains(childNode.name)) {
  155.       var newChildNode = rebuildDependencyTree(childNode, seenDependencies);
  156.       newNode.children.add(newChildNode);
  157.     }
  158.   }
  159.   return newNode;
  160. }
  161. Future<String> _getProjectPath() async {
  162.   final originProjectPath = await Process.run(
  163.     'pwd',
  164.     [],
  165.   );
  166.   final projectPath = (originProjectPath.stdout as String).replaceAll(
  167.     '\n',
  168.     '',
  169.   );
  170.   return projectPath;
  171. }
  172. void _generateDotByNodes(
  173.     List<DependencyNode> nodes, {
  174.       required Gviz graph,
  175.       required List<String> edgeCache,
  176.     }) {
  177.   if (nodes.isEmpty) {
  178.     return;
  179.   }
  180.   for (int index = 0; index < nodes.length; index++) {
  181.     final itemNode = nodes[index];
  182.     final from = '${itemNode.name}\n${itemNode.version}';
  183.     if (!graph.nodeExists(from)) {
  184.       graph.addNode(
  185.         from,
  186.         properties: {
  187.           'color': 'black',
  188.           'shape': 'rectangle',
  189.           'margin': '1,0.8',
  190.           'penwidth': '7',
  191.           'style': 'filled',
  192.           'fillcolor': 'gray',
  193.           'fontsize': itemNode.isLevel1Node ? '60' : '55',
  194.         },
  195.       );
  196.     }
  197.     final toArr = itemNode.children.map((e) => '${e.name}\n${e.version}').toList();
  198.     for (var element in toArr) {
  199.       final edgeKey = '$from-$element';
  200.       if (!edgeCache.contains(edgeKey)) {
  201.         graph.addEdge(
  202.           from,
  203.           element,
  204.           properties: {
  205.             'penwidth': '2',
  206.             'style': 'dashed',
  207.             'arrowed': 'vee',
  208.           },
  209.         );
  210.         edgeCache.add(edgeKey);
  211.       }
  212.     }
  213.     _generateDotByNodes(
  214.       itemNode.children,
  215.       graph: graph,
  216.       edgeCache: edgeCache,
  217.     );
  218.   }
  219. }
  220. Future<String> _runCommand({
  221.   String executable = 'flutter',
  222.   required String projectPath,
  223.   required List<String> commandArgs,
  224. }) {
  225.   return Process.run(
  226.     executable,
  227.     commandArgs,
  228.     runInShell: true,
  229.     workingDirectory: projectPath,
  230.   ).then((result) => result.stdout as String);
  231. }
  232. class DependencyNode {
  233.   final String name;
  234.   final String version;
  235.   final String kind;
  236.   final String source;
  237.   final List<String> dependencies;
  238.   final children = <DependencyNode>[];
  239.   bool isLevel1Node = true;
  240.   factory DependencyNode.fromMap(Map<String, dynamic> map) {
  241.     return DependencyNode(
  242.       name: map['name'] as String,
  243.       version: map['version'] as String,
  244.       kind: map['kind'] as String,
  245.       source: map['source'] as String,
  246.       dependencies: (map['dependencies'] as List<dynamic>)
  247.           .map((e) => e as String)
  248.           .toList(),
  249.     );
  250.   }
  251.   bool get isRootNode => kind == 'root';
  252.   @override
  253.   bool operator ==(Object other) =>
  254.       identical(this, other) ||
  255.           other is DependencyNode &&
  256.               runtimeType == other.runtimeType &&
  257.               name == other.name;
  258.   @override
  259.   int get hashCode => name.hashCode;
  260.   DependencyNode({
  261.     required this.name,
  262.     required this.version,
  263.     required this.kind,
  264.     required this.source,
  265.     required this.dependencies,
  266.   });
  267. }
复制代码
输出示例




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

杀鸡焉用牛刀

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表