Skip to content

Commit

Permalink
Fix #13
Browse files Browse the repository at this point in the history
  • Loading branch information
westracer committed Apr 23, 2021
1 parent bdcb8ad commit 4191c0a
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 87 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,7 @@
## 0.3.0-nullsafety.1

* Fix regression related to CLI options (#13)

## 0.3.0-nullsafety.0

* Migrate to null safety
Expand Down
124 changes: 70 additions & 54 deletions lib/src/cli/arguments.dart
Expand Up @@ -121,23 +121,7 @@ class CliArguments {
///
/// Throws [CliArgumentException], if there is an error in arg parsing
/// or if argument has wrong type.
factory CliArguments.fromMap(Map<CliArgument, dynamic> rawArgMap) {
// Validating types
for (final e in _kArgAllowedTypes.entries) {
final arg = e.key;
final argType = rawArgMap[arg].runtimeType;
final allowedTypes = e.value;

if (argType != Null && !allowedTypes.contains(argType)) {
throw CliArgumentException("'${argumentNames[arg]}' argument\'s type "
'must be one of following: $allowedTypes, '
"instead got '$argType'.");
}
}

final map = formatArguments(rawArgMap);
_validate(map);

factory CliArguments.fromMap(Map<CliArgument, dynamic> map) {
return CliArguments(
map[CliArgument.svgDir] as Directory,
map[CliArgument.fontFile] as File,
Expand Down Expand Up @@ -173,7 +157,8 @@ class CliArguments {
/// Throws [CliHelpException], if 'help' option is present.
///
/// Returns an instance of [CliArguments] containing all parsed data.
CliArguments parseArguments(ArgParser argParser, List<String> args) {
Map<CliArgument, dynamic> parseArguments(
ArgParser argParser, List<String> args) {
late final ArgResults argResults;
try {
argResults = argParser.parse(args);
Expand All @@ -194,7 +179,7 @@ CliArguments parseArguments(ArgParser argParser, List<String> args) {
_kPositionalArguments[i]: argResults.rest[i],
};

return CliArguments.fromMap(rawArgMap);
return rawArgMap;
}

MapEntry<CliArgument, dynamic>? _mapConfigKeyEntry(
Expand All @@ -221,7 +206,7 @@ MapEntry<CliArgument, dynamic>? _mapConfigKeyEntry(
///
/// Returns an instance of [CliArguments] containing all parsed data or null,
/// if 'fontify' key is not present in config file.
CliArguments? parseConfig(String config) {
Map<CliArgument, dynamic>? parseConfig(String config) {
final dynamic yamlMap = loadYaml(config);

if (yamlMap is! YamlMap) {
Expand All @@ -238,9 +223,7 @@ CliArguments? parseConfig(String config) {
.map(_mapConfigKeyEntry)
.whereType<MapEntry<CliArgument, dynamic>>();

final argMap = Map<CliArgument, dynamic>.fromEntries(entries);

return CliArguments.fromMap(argMap);
return Map<CliArgument, dynamic>.fromEntries(entries);
}

/// Parses argument list and config file, validates parsed data.
Expand All @@ -250,12 +233,15 @@ CliArguments? parseConfig(String config) {
/// Throws [CliArgumentException], if there is an error in arg parsing.
CliArguments parseArgsAndConfig(ArgParser argParser, List<String> args) {
var parsedArgs = parseArguments(argParser, args);
final dynamic configFile = parsedArgs[CliArgument.configFile];

final defaultConfigList = _kDefaultConfigPathList.map((e) => File(e));
final configList = [parsedArgs.configFile, ...defaultConfigList];
final configList = <String>[
if (configFile is String) configFile,
..._kDefaultConfigPathList
].map((e) => File(e));

for (final configFile in configList) {
if (configFile != null && configFile.existsSync()) {
if (configFile.existsSync()) {
final parsedConfig = parseConfig(configFile.readAsStringSync());

if (parsedConfig != null) {
Expand All @@ -266,34 +252,7 @@ CliArguments parseArgsAndConfig(ArgParser argParser, List<String> args) {
}
}

return parsedArgs;
}

/// Validates CLI arguments.
///
/// Throws [CliArgumentException], if argument is not valid.
void _validate(Map<CliArgument, dynamic> args) {
final svgDir = args[CliArgument.svgDir] as Directory?;
final fontFile = args[CliArgument.fontFile] as File?;
final indent = args[CliArgument.indent] as int?;

if (svgDir == null) {
throw CliArgumentException('The input directory is not specified.');
}

if (fontFile == null) {
throw CliArgumentException('The output font file is not specified.');
}

if (svgDir.statSync().type != FileSystemEntityType.directory) {
throw CliArgumentException(
"The input directory is not a directory or it doesn't exist.");
}

if (indent != null && indent < 0) {
throw CliArgumentException(
'indent must be a non-negative integer, was $indent.');
}
return CliArguments.fromMap(parsedArgs.validateAndFormat());
}

class CliArgumentException implements Exception {
Expand All @@ -307,5 +266,62 @@ class CliArgumentException implements Exception {

class CliHelpException implements Exception {}

extension CliArgumentMapExtension on Map<CliArgument, dynamic> {
/// Validates raw CLI arguments.
///
/// Throws [CliArgumentException], if argument is not valid.
void _validateRaw() {
// Validating types
for (final e in _kArgAllowedTypes.entries) {
final arg = e.key;
final argType = this[arg].runtimeType;
final allowedTypes = e.value;

if (argType != Null && !allowedTypes.contains(argType)) {
throw CliArgumentException("'${argumentNames[arg]}' argument\'s type "
'must be one of following: $allowedTypes, '
"instead got '$argType'.");
}
}
}

/// Validates formatted CLI arguments.
///
/// Throws [CliArgumentException], if argument is not valid.
void _validateFormatted() {
final args = this;

final svgDir = args[CliArgument.svgDir] as Directory?;
final fontFile = args[CliArgument.fontFile] as File?;
final indent = args[CliArgument.indent] as int?;

if (svgDir == null) {
throw CliArgumentException('The input directory is not specified.');
}

if (fontFile == null) {
throw CliArgumentException('The output font file is not specified.');
}

if (svgDir.statSync().type != FileSystemEntityType.directory) {
throw CliArgumentException(
"The input directory is not a directory or it doesn't exist.");
}

if (indent != null && indent < 0) {
throw CliArgumentException(
'indent must be a non-negative integer, was $indent.');
}
}

/// Validates and formats CLI arguments.
///
/// Throws [CliArgumentException], if argument is not valid.
Map<CliArgument, dynamic> validateAndFormat() {
_validateRaw();
return formatArguments(this).._validateFormatted();
}
}

// Ignoring as CLI arguments are dynamically typed
// ignore_for_file: avoid_annotating_with_dynamic
2 changes: 1 addition & 1 deletion pubspec.yaml
@@ -1,5 +1,5 @@
name: fontify
version: 0.3.0-nullsafety.0
version: 0.3.0-nullsafety.1
description: >-
Converts SVG icons to OTF font and generates Flutter-compatible class.
Provides an API and a CLI tool.
Expand Down
76 changes: 44 additions & 32 deletions test/cli_test.dart
@@ -1,15 +1,18 @@
import 'dart:io';

import 'package:args/args.dart';
import 'package:fontify/src/cli/arguments.dart';
import 'package:fontify/src/cli/options.dart';
import 'package:test/test.dart';

void main() {
final _argParser = ArgParser(allowTrailingOptions: true);

group('Arguments', () {
final _argParser = ArgParser(allowTrailingOptions: true);
defineOptions(_argParser);

void expectCliArgumentException(List<String> args) {
expect(() => parseArguments(_argParser, args),
expect(() => parseArgsAndConfig(_argParser, args),
throwsA(const TypeMatcher<CliArgumentException>()));
}

Expand Down Expand Up @@ -51,7 +54,7 @@ void main() {
'--package=test_package',
];

final parsedArgs = parseArguments(_argParser, args);
final parsedArgs = parseArgsAndConfig(_argParser, args);

expect(parsedArgs.svgDir.path, args.first);
expect(parsedArgs.fontFile.path, args[1]);
Expand All @@ -75,7 +78,7 @@ void main() {
'--ignore-shapes',
];

final parsedArgs = parseArguments(_argParser, args);
final parsedArgs = parseArgsAndConfig(_argParser, args);

expect(parsedArgs.svgDir.path, args.first);
expect(parsedArgs.fontFile.path, args[1]);
Expand All @@ -93,7 +96,7 @@ void main() {

test('Help', () {
void expectCliHelpException(List<String> args) {
expect(() => parseArguments(_argParser, args),
expect(() => parseArgsAndConfig(_argParser, args),
throwsA(const TypeMatcher<CliHelpException>()));
}

Expand Down Expand Up @@ -152,8 +155,6 @@ void main() {

test('No arguments and config', () {
const args = [
'./',
'yes',
'--config-file=test/assets/test_config.yaml',
];

Expand All @@ -175,8 +176,17 @@ void main() {
});

group('Config', () {
final _configFile = File('fontify.yaml');

tearDown(_configFile.deleteSync);

CliArguments _parseConfig(String config) {
_configFile.writeAsStringSync(config);
return parseArgsAndConfig(_argParser, []);
}

void expectCliArgumentException(String cfg) {
expect(() => parseConfig(cfg),
expect(() => _parseConfig(cfg),
throwsA(const TypeMatcher<CliArgumentException>()));
}

Expand Down Expand Up @@ -229,7 +239,7 @@ fontify:
});

test('All arguments with non-defaults', () {
final parsedArgs = parseConfig('''
final rawParsedArgs = _parseConfig('''
fontify:
input_svg_dir: ./
output_font_file: generated_font.otf
Expand All @@ -246,38 +256,40 @@ fontify:
recursive: true
verbose: true
''');
final parsedArgs = rawParsedArgs;

expect(parsedArgs?.svgDir.path, './');
expect(parsedArgs?.fontFile.path, 'generated_font.otf');
expect(parsedArgs?.classFile?.path, 'lib/test_font.dart');
expect(parsedArgs?.indent, 4);
expect(parsedArgs?.className, 'MyIcons');
expect(parsedArgs?.fontName, 'My Icons');
expect(parsedArgs?.normalize, isFalse);
expect(parsedArgs?.ignoreShapes, isFalse);
expect(parsedArgs?.recursive, isTrue);
expect(parsedArgs?.verbose, isTrue);
expect(parsedArgs?.fontPackage, 'test_package');
expect(parsedArgs.svgDir.path, './');
expect(parsedArgs.fontFile.path, 'generated_font.otf');
expect(parsedArgs.classFile?.path, 'lib/test_font.dart');
expect(parsedArgs.indent, 4);
expect(parsedArgs.className, 'MyIcons');
expect(parsedArgs.fontName, 'My Icons');
expect(parsedArgs.normalize, isFalse);
expect(parsedArgs.ignoreShapes, isFalse);
expect(parsedArgs.recursive, isTrue);
expect(parsedArgs.verbose, isTrue);
expect(parsedArgs.fontPackage, 'test_package');
});

test('All arguments with defaults', () {
final parsedArgs = parseConfig('''
final rawParsedArgs = _parseConfig('''
fontify:
input_svg_dir: ./
output_font_file: generated_font.otf
''');
final parsedArgs = rawParsedArgs;

expect(parsedArgs?.svgDir.path, './');
expect(parsedArgs?.fontFile.path, 'generated_font.otf');
expect(parsedArgs?.classFile, isNull);
expect(parsedArgs?.indent, null);
expect(parsedArgs?.className, isNull);
expect(parsedArgs?.fontName, isNull);
expect(parsedArgs?.normalize, isNull);
expect(parsedArgs?.ignoreShapes, isNull);
expect(parsedArgs?.recursive, isNull);
expect(parsedArgs?.verbose, isNull);
expect(parsedArgs?.fontPackage, isNull);
expect(parsedArgs.svgDir.path, './');
expect(parsedArgs.fontFile.path, 'generated_font.otf');
expect(parsedArgs.classFile, isNull);
expect(parsedArgs.indent, null);
expect(parsedArgs.className, isNull);
expect(parsedArgs.fontName, isNull);
expect(parsedArgs.normalize, isNull);
expect(parsedArgs.ignoreShapes, isNull);
expect(parsedArgs.recursive, isNull);
expect(parsedArgs.verbose, isNull);
expect(parsedArgs.fontPackage, isNull);
});

test('Type validation', () {
Expand Down

0 comments on commit 4191c0a

Please sign in to comment.