flowy_svg.dart 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import 'dart:async';
  2. import 'dart:developer';
  3. import 'dart:io';
  4. import 'package:args/args.dart';
  5. import 'package:path/path.dart' as path;
  6. import 'options.dart';
  7. const languageKeywords = [
  8. 'abstract',
  9. 'else',
  10. 'import',
  11. 'show',
  12. 'as',
  13. 'enum',
  14. 'static',
  15. 'assert',
  16. 'export',
  17. 'interface',
  18. 'super',
  19. 'async',
  20. 'extends',
  21. 'is',
  22. 'switch',
  23. 'await',
  24. 'extension',
  25. 'late',
  26. 'sync',
  27. 'base',
  28. 'external',
  29. 'library',
  30. 'this',
  31. 'break',
  32. 'factory',
  33. 'mixin',
  34. 'throw',
  35. 'case',
  36. 'false',
  37. 'new',
  38. 'true',
  39. 'catch',
  40. 'final',
  41. 'variable',
  42. 'null',
  43. 'try',
  44. 'class',
  45. 'final',
  46. 'class',
  47. 'on',
  48. 'typedef',
  49. 'const',
  50. 'finally',
  51. 'operator',
  52. 'var',
  53. 'continue',
  54. 'for',
  55. 'part',
  56. 'void',
  57. 'covariant',
  58. 'Function',
  59. 'required',
  60. 'when',
  61. 'default',
  62. 'get',
  63. 'rethrow',
  64. 'while',
  65. 'deferred',
  66. 'hide',
  67. 'return',
  68. 'with',
  69. 'do',
  70. 'if',
  71. 'sealed',
  72. 'yield',
  73. 'dynamic',
  74. 'implements',
  75. 'set',
  76. ];
  77. void main(List<String> args) {
  78. if (_isHelpCommand(args)) {
  79. _printHelperDisplay();
  80. } else {
  81. generateSvgData(_generateOption(args));
  82. }
  83. }
  84. bool _isHelpCommand(List<String> args) {
  85. return args.length == 1 && (args[0] == '--help' || args[0] == '-h');
  86. }
  87. void _printHelperDisplay() {
  88. final parser = _generateArgParser(null);
  89. log(parser.usage);
  90. }
  91. Options _generateOption(List<String> args) {
  92. final generateOptions = Options();
  93. _generateArgParser(generateOptions).parse(args);
  94. return generateOptions;
  95. }
  96. ArgParser _generateArgParser(Options? generateOptions) {
  97. final parser = ArgParser()
  98. ..addOption(
  99. 'source-dir',
  100. abbr: 'S',
  101. defaultsTo: '/assets/flowy_icons',
  102. callback: (String? x) => generateOptions!.sourceDir = x,
  103. help: 'Folder containing localization files',
  104. )
  105. ..addOption(
  106. 'output-dir',
  107. abbr: 'O',
  108. defaultsTo: '/lib/generated',
  109. callback: (String? x) => generateOptions!.outputDir = x,
  110. help: 'Output folder stores for the generated file',
  111. )
  112. ..addOption(
  113. 'name',
  114. abbr: 'N',
  115. defaultsTo: 'flowy_svgs.g.dart',
  116. callback: (String? x) => generateOptions!.outputFile = x,
  117. help: 'The name of the output file that this tool will generate',
  118. );
  119. return parser;
  120. }
  121. Directory source(Options options) => Directory(
  122. [
  123. Directory.current.path,
  124. Directory.fromUri(
  125. Uri.file(
  126. options.sourceDir!,
  127. windows: Platform.isWindows,
  128. ),
  129. ).path,
  130. ].join(),
  131. );
  132. File output(Options options) => File(
  133. [
  134. Directory.current.path,
  135. Directory.fromUri(
  136. Uri.file(options.outputDir!, windows: Platform.isWindows),
  137. ).path,
  138. Platform.pathSeparator,
  139. File.fromUri(
  140. Uri.file(
  141. options.outputFile!,
  142. windows: Platform.isWindows,
  143. ),
  144. ).path,
  145. ].join(),
  146. );
  147. /// generates the svg data
  148. Future<void> generateSvgData(Options options) async {
  149. // the source directory that this is targeting
  150. final src = source(options);
  151. // the output directory that this is targeting
  152. final out = output(options);
  153. var files = await dirContents(src);
  154. files = files.where((f) => f.path.contains('.svg')).toList();
  155. await generate(files, out, options);
  156. }
  157. /// List the contents of the directory
  158. Future<List<FileSystemEntity>> dirContents(Directory dir) {
  159. final files = <FileSystemEntity>[];
  160. final completer = Completer<List<FileSystemEntity>>();
  161. dir.list(recursive: true).listen(
  162. files.add,
  163. onDone: () => completer.complete(files),
  164. );
  165. return completer.future;
  166. }
  167. /// Generate the abstract class for the FlowySvg data.
  168. Future<void> generate(
  169. List<FileSystemEntity> files,
  170. File output,
  171. Options options,
  172. ) async {
  173. final generated = File(output.path);
  174. // create the output file if it doesn't exist
  175. if (!generated.existsSync()) {
  176. generated.createSync(recursive: true);
  177. }
  178. // content of the generated file
  179. final builder = StringBuffer()..writeln(prelude);
  180. files.whereType<File>().forEach(
  181. (element) => builder.writeln(lineFor(element, options)),
  182. );
  183. builder.writeln(postlude);
  184. generated.writeAsStringSync(builder.toString());
  185. }
  186. String lineFor(File file, Options options) {
  187. final name = varNameFor(file, options);
  188. return " static const $name = FlowySvgData('${pathFor(file)}');";
  189. }
  190. String pathFor(File file) {
  191. final relative = path.relative(file.path, from: Directory.current.path);
  192. final uri = Uri.file(relative);
  193. return uri.toFilePath(windows: false);
  194. }
  195. String varNameFor(File file, Options options) {
  196. final from = source(options).path;
  197. final relative = Uri.file(path.relative(file.path, from: from));
  198. final parts = relative.pathSegments;
  199. final cleaned = parts.map(clean).toList();
  200. var simplified = cleaned.reversed
  201. // join all cleaned path segments with an underscore
  202. .join('_')
  203. // there are some cases where the segment contains a dart reserved keyword
  204. // in this case, the path will be suffixed with an underscore which means
  205. // there will be a double underscore, so we have to replace the double
  206. // underscore with one underscore
  207. .replaceAll(RegExp('_+'), '_');
  208. // rename icon based on relative path folder name (16x, 24x, etc.)
  209. for (final key in sizeMap.keys) {
  210. simplified = simplified.replaceAll(key, sizeMap[key]!);
  211. }
  212. return simplified;
  213. }
  214. const sizeMap = {r'$16x': 's', r'$24x': 'm', r'$32x': 'lg', r'$40x': 'xl'};
  215. /// cleans the path segment before rejoining the path into a variable name
  216. String clean(String segment) {
  217. final cleaned = segment
  218. // replace all dashes with underscores (dash is invalid in
  219. // a variable name)
  220. .replaceAll('-', '_')
  221. // replace all spaces with an underscore
  222. .replaceAll(RegExp(r'\s+'), '_')
  223. // replace all file extensions with an empty string
  224. .replaceAll(RegExp(r'\.[^.]*$'), '')
  225. // convert everything to lower case
  226. .toLowerCase();
  227. if (languageKeywords.contains(cleaned)) {
  228. return '${cleaned}_';
  229. } else if (cleaned.startsWith(RegExp('[0-9]'))) {
  230. return '\$$cleaned';
  231. }
  232. return cleaned;
  233. }
  234. /// The prelude for the generated file
  235. const prelude = '''
  236. // DO NOT EDIT. This code is generated by the flowy_svg script
  237. // import the widget with from this package
  238. import 'package:flowy_svg/flowy_svg.dart';
  239. // export as convenience to the programmer
  240. export 'package:flowy_svg/flowy_svg.dart';
  241. /// A class to easily list all the svgs in the app
  242. class FlowySvgs {''';
  243. /// The postlude for the generated file
  244. const postlude = '''
  245. }
  246. ''';