Added new command-line option `--compiler-opts`. (#192)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index abc7d7b..319671a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 2.2.5
+- Added new command line flag `--compiler-opts` to the command line tool.
+
 # 2.2.4
 - Fix `sort: true` not working.
 - Fix extra `//` or `///` in comments when using `comments -> style`: `full`.
diff --git a/README.md b/README.md
index 9f516e4..5eb0103 100644
--- a/README.md
+++ b/README.md
@@ -135,12 +135,18 @@
   </tr>
   <tr>
     <td>compiler-opts</td>
-    <td>Pass compiler options to clang.</td>
+    <td>Pass compiler options to clang. You can also pass
+    these via the command line tool.</td>
     <td>
 
 ```yaml
 compiler-opts: '-I/usr/lib/llvm-9/include/'
 ```
+and/or via the command line -
+```bash
+dart run ffigen --compiler-opts "-I/headers
+-L 'path/to/folder name/file'"
+```
   </td>
   </tr>
   <tr>
diff --git a/lib/src/config_provider/config.dart b/lib/src/config_provider/config.dart
index cf083df..434137e 100644
--- a/lib/src/config_provider/config.dart
+++ b/lib/src/config_provider/config.dart
@@ -125,6 +125,17 @@
     return configspecs;
   }
 
+  /// Add compiler options for clang. If [highPriority] is true these are added
+  /// to the front of the list.
+  void addCompilerOpts(String compilerOpts, {bool highPriority = false}) {
+    if (highPriority) {
+      _compilerOpts.insertAll(
+          0, compilerOptsToList(compilerOpts)); // Inserts at the front.
+    } else {
+      _compilerOpts.addAll(compilerOptsToList(compilerOpts));
+    }
+  }
+
   /// Checks if there are nested [key] in [map].
   bool _checkKeyInYaml(List<String> key, YamlMap map) {
     dynamic last = map;
diff --git a/lib/src/config_provider/spec_utils.dart b/lib/src/config_provider/spec_utils.dart
index 0351d8c..692f921 100644
--- a/lib/src/config_provider/spec_utils.dart
+++ b/lib/src/config_provider/spec_utils.dart
@@ -103,8 +103,25 @@
   return true;
 }
 
+final _quoteMatcher = RegExp(r'''^["'](.*)["']$''', dotAll: true);
+final _cmdlineArgMatcher = RegExp(r'''['"](\\"|[^"])*?['"]|[^ ]+''');
+List<String> compilerOptsToList(String compilerOpts) {
+  final list = <String>[];
+  _cmdlineArgMatcher.allMatches(compilerOpts).forEach((element) {
+    var match = element.group(0);
+    if (match != null) {
+      if (quiver.matchesFull(_quoteMatcher, match)) {
+        match = _quoteMatcher.allMatches(match).first.group(1)!;
+      }
+      list.add(match);
+    }
+  });
+
+  return list;
+}
+
 List<String> compilerOptsExtractor(dynamic value) =>
-    (value as String).split(' ');
+    compilerOptsToList(value as String);
 
 bool compilerOptsValidator(List<String> name, dynamic value) =>
     checkType<String>(name, value);
diff --git a/lib/src/executables/ffigen.dart b/lib/src/executables/ffigen.dart
index 14d1388..12dd868 100644
--- a/lib/src/executables/ffigen.dart
+++ b/lib/src/executables/ffigen.dart
@@ -14,6 +14,18 @@
 final _logger = Logger('ffigen.ffigen');
 final _ansi = Ansi(Ansi.terminalSupportsAnsi);
 
+const compilerOpts = 'compiler-opts';
+const conf = 'config';
+const help = 'help';
+const verbose = 'verbose';
+const pubspecName = 'pubspec.yaml';
+const configKey = 'ffigen';
+const logAll = 'all';
+const logFine = 'fine';
+const logInfo = 'info';
+const logWarning = 'warning';
+const logSevere = 'severe';
+
 String successPen(String str) {
   return '${_ansi.green}$str${_ansi.none}';
 }
@@ -50,19 +62,27 @@
 
 Config getConfig(ArgResults result) {
   _logger.info('Running in ${Directory.current}');
+  Config config;
 
-  if (result.wasParsed('config')) {
-    return getConfigFromCustomYaml(result['config'] as String);
+  // Parse config from yaml.
+  if (result.wasParsed(conf)) {
+    config = getConfigFromCustomYaml(result[conf] as String);
   } else {
-    return getConfigFromPubspec();
+    config = getConfigFromPubspec();
   }
+
+  // Add compiler options from command line.
+  if (result.wasParsed(compilerOpts)) {
+    _logger.fine('Passed compiler opts - "${result[compilerOpts]}"');
+    config.addCompilerOpts((result[compilerOpts] as String),
+        highPriority: true);
+  }
+
+  return config;
 }
 
 /// Extracts configuration from pubspec file.
 Config getConfigFromPubspec() {
-  final pubspecName = 'pubspec.yaml';
-  final configKey = 'ffigen';
-
   final pubspecFile = File(pubspecName);
 
   if (!pubspecFile.existsSync()) {
@@ -107,33 +127,37 @@
   parser.addSeparator(
       'FFIGEN: Generate dart bindings from C header files\nUsage:');
   parser.addOption(
-    'config',
-    help: 'path to Yaml file containing configurations if not in pubspec.yaml',
+    conf,
+    help: 'Path to Yaml file containing configurations if not in pubspec.yaml',
   );
   parser.addOption(
-    'verbose',
+    verbose,
     abbr: 'v',
-    defaultsTo: 'info',
+    defaultsTo: logInfo,
     allowed: [
-      'all',
-      'fine',
-      'info',
-      'warning',
-      'severe',
+      logAll,
+      logFine,
+      logInfo,
+      logWarning,
+      logSevere,
     ],
   );
   parser.addFlag(
-    'help',
+    help,
     abbr: 'h',
-    help: 'prints this usage',
+    help: 'Prints this usage',
     negatable: false,
   );
+  parser.addOption(
+    compilerOpts,
+    help: 'Compiler options for clang. (E.g --$compilerOpts "-I/headers -W")',
+  );
 
   ArgResults results;
   try {
     results = parser.parse(args);
 
-    if (results.wasParsed('help')) {
+    if (results.wasParsed(help)) {
       print(parser.usage);
       exit(0);
     }
@@ -148,25 +172,25 @@
 
 /// Sets up the logging level and printing.
 void setupLogger(ArgResults result) {
-  if (result.wasParsed('verbose')) {
-    switch (result['verbose'] as String?) {
-      case 'all':
+  if (result.wasParsed(verbose)) {
+    switch (result[verbose] as String?) {
+      case logAll:
         // Logs everything, the entire AST touched by our parser.
         Logger.root.level = Level.ALL;
         break;
-      case 'fine':
+      case logFine:
         // Logs AST parts relevant to user (i.e those included in filters).
         Logger.root.level = Level.FINE;
         break;
-      case 'info':
+      case logInfo:
         // Logs relevant info for general user (default).
         Logger.root.level = Level.INFO;
         break;
-      case 'warning':
+      case logWarning:
         // Logs warnings for relevant stuff.
         Logger.root.level = Level.WARNING;
         break;
-      case 'severe':
+      case logSevere:
         // Logs severe warnings and errors.
         Logger.root.level = Level.SEVERE;
         break;
diff --git a/pubspec.yaml b/pubspec.yaml
index e986dfd..31048c4 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@
 # BSD-style license that can be found in the LICENSE file.
 
 name: ffigen
-version: 2.2.4
+version: 2.2.5
 homepage: https://github.com/dart-lang/ffigen
 description: Generator for FFI bindings, using LibClang to parse C header files.
 
diff --git a/test/config_tests/compiler_opts_test.dart b/test/config_tests/compiler_opts_test.dart
new file mode 100644
index 0000000..a3aa117
--- /dev/null
+++ b/test/config_tests/compiler_opts_test.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:ffigen/src/code_generator.dart';
+import 'package:ffigen/src/config_provider/spec_utils.dart';
+import 'package:test/test.dart';
+
+late Library actual, expected;
+
+void main() {
+  group('compiler_opts_test', () {
+    test('Compiler Opts', () {
+      final opts =
+          '''--option value "in double quotes" 'in single quotes'  -tab=separated''';
+      final list = compilerOptsToList(opts);
+      expect(
+        list,
+        <String>[
+          '--option',
+          'value',
+          'in double quotes',
+          'in single quotes',
+          '-tab=separated',
+        ],
+      );
+    });
+  });
+}
diff --git a/test/test_coverage.dart b/test/test_coverage.dart
index 9f61cde..88623ba 100644
--- a/test/test_coverage.dart
+++ b/test/test_coverage.dart
@@ -7,14 +7,20 @@
     as collision_tests_decl_decl_collision_test;
 import 'collision_tests/reserved_keyword_collision_test.dart'
     as collision_tests_reserved_keyword_collision_test;
+import 'config_tests/compiler_opts_test.dart'
+    as config_tests_compiler_opts_test;
 import 'example_tests/cjson_example_test.dart'
     as example_tests_cjson_example_test;
 import 'example_tests/libclang_example_test.dart'
     as example_tests_libclang_example_test;
 import 'example_tests/simple_example_test.dart'
     as example_tests_simple_example_test;
+import 'header_parser_tests/comment_markup_test.dart'
+    as header_parser_tests_comment_markup_test;
 import 'header_parser_tests/dart_handle_test.dart'
     as header_parser_tests_dart_handle_test;
+import 'header_parser_tests/forward_decl_test.dart'
+    as header_parser_tests_forward_decl_test;
 import 'header_parser_tests/function_n_struct_test.dart'
     as header_parser_tests_function_n_struct_test;
 import 'header_parser_tests/functions_test.dart'
@@ -27,6 +33,8 @@
     as header_parser_tests_native_func_typedef_test;
 import 'header_parser_tests/nested_parsing_test.dart'
     as header_parser_tests_nested_parsing_test;
+import 'header_parser_tests/opaque_dependencies_test.dart'
+    as header_parser_tests_opaque_dependencies_test;
 import 'header_parser_tests/typedef_test.dart'
     as header_parser_tests_typedef_test;
 import 'header_parser_tests/unnamed_enums_test.dart'
@@ -43,13 +51,17 @@
   example_tests_libclang_example_test.main();
   collision_tests_decl_decl_collision_test.main();
   collision_tests_reserved_keyword_collision_test.main();
+  config_tests_compiler_opts_test.main();
+  header_parser_tests_comment_markup_test.main();
   header_parser_tests_dart_handle_test.main();
+  header_parser_tests_forward_decl_test.main();
   header_parser_tests_functions_test.main();
   header_parser_tests_globals_test.main();
   header_parser_tests_macros_test.main();
   header_parser_tests_function_n_struct_test.main();
   header_parser_tests_native_func_typedef_test.main();
   header_parser_tests_nested_parsing_test.main();
+  header_parser_tests_opaque_dependencies_test.main();
   header_parser_tests_typedef_test.main();
   header_parser_tests_unnamed_enums_test.main();
   native_test_native_test.main();