Regexp based Member renaming (#66)

Closes #28 
- Added Member renaming using `member-rename` subkey in declarations.
- Renames struct/enum members and function parameter names using regexp/full matching.
- Added/Updated renaming tests.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ba4e384..21853fb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 - Updated header config. Header `entry-points` and `include-directives` are now specified under `headers` key. Glob syntax is allowed.
 - Updated declaration `include`/`exclude` config. These are now specified as a list.
 - Added Regexp based declaration renaming using `rename` subkey.
+- Added Regexp based member renaming for structs, enums and functions using `member-rename` subkey. `prefix` and `prefix-replacement` subkeys have been removed.
 
 # 0.1.5
 - Added support for parsing macros and anonymous unnamed enums. These are generated as top level constants.
diff --git a/README.md b/README.md
index ed8ff82..477a0e9 100644
--- a/README.md
+++ b/README.md
@@ -112,11 +112,17 @@
     - [a-z][a-zA-Z0-9]* # Matches using regexp.
     - prefix.* # '.' matches any character.
     - someFuncName # Matches with exact name
-    - anotherName # full names have higher priority.
+    - anotherName # Full names have higher priority.
   rename:
     'clang_(.*)': '$1' # Regexp groups based replacement.
     'clang_dispose': 'dispose' # full name matches have higher priority.
     '_(.*)': '$1' # Removes '_' from beginning of a name.
+enums:
+  member-rename:
+    '(.*)': # Matches any enum.
+      '_(.*)': '$1' # Removes '_' from beginning enum member name.
+    'CXTypeKind': # Full names have higher priority.
+      'CXType(.*)': '$1' # $1 keeps only the 1st group i.e '(.*)'.
     </code></pre></td>
   </tr>
   <tr>
diff --git a/lib/src/code_generator/enum_class.dart b/lib/src/code_generator/enum_class.dart
index 5a96c39..1a383ce 100644
--- a/lib/src/code_generator/enum_class.dart
+++ b/lib/src/code_generator/enum_class.dart
@@ -72,8 +72,14 @@
 
 /// Represents a single value in an enum.
 class EnumConstant {
+  final String originalName;
   final String dartDoc;
   final String name;
   final int value;
-  const EnumConstant({@required this.name, @required this.value, this.dartDoc});
+  const EnumConstant({
+    String originalName,
+    @required this.name,
+    @required this.value,
+    this.dartDoc,
+  }) : originalName = originalName ?? name;
 }
diff --git a/lib/src/code_generator/func.dart b/lib/src/code_generator/func.dart
index 62194c1..0e24fb1 100644
--- a/lib/src/code_generator/func.dart
+++ b/lib/src/code_generator/func.dart
@@ -147,8 +147,10 @@
 
 /// Represents a Function's parameter.
 class Parameter {
+  final String originalName;
   String name;
   final Type type;
 
-  Parameter({this.name, @required this.type});
+  Parameter({String originalName, this.name, @required this.type})
+      : originalName = originalName ?? name;
 }
diff --git a/lib/src/code_generator/struc.dart b/lib/src/code_generator/struc.dart
index dec4c2c..24e6727 100644
--- a/lib/src/code_generator/struc.dart
+++ b/lib/src/code_generator/struc.dart
@@ -151,10 +151,16 @@
 
 class Member {
   final String dartDoc;
+  final String originalName;
   final String name;
   final Type type;
 
-  const Member({@required this.name, @required this.type, this.dartDoc});
+  const Member({
+    String originalName,
+    @required this.name,
+    @required this.type,
+    this.dartDoc,
+  }) : originalName = originalName ?? name;
 }
 
 // Helper bindings for struct array.
diff --git a/lib/src/config_provider/config_types.dart b/lib/src/config_provider/config_types.dart
index 302e69a..b699239 100644
--- a/lib/src/config_provider/config_types.dart
+++ b/lib/src/config_provider/config_types.dart
@@ -95,15 +95,22 @@
 class Declaration {
   final Includer _includer;
   final Renamer _renamer;
+  final MemberRenamer _memberRenamer;
 
   Declaration({
     Includer includer,
     Renamer renamer,
+    MemberRenamer memberRenamer,
   })  : _includer = includer ?? Includer(),
-        _renamer = renamer ?? Renamer();
+        _renamer = renamer ?? Renamer(),
+        _memberRenamer = memberRenamer ?? MemberRenamer();
 
   /// Applies renaming and returns the result.
-  String renameUsingConfig(String name) => _renamer.renameUsingConfig(name);
+  String renameUsingConfig(String name) => _renamer.rename(name);
+
+  /// Applies member renaming and returns the result.
+  String renameMemberUsingConfig(String declaration, String member) =>
+      _memberRenamer.rename(declaration, member);
 
   /// Checks if a name is allowed by a filter.
   bool shouldInclude(String name) => _includer.shouldInclude(name);
@@ -112,23 +119,32 @@
 /// Matches `$<single_digit_int>`, value can be accessed in group 1 of match.
 final replaceGroupRegexp = RegExp(r'\$([0-9])');
 
-class RenamePattern {
+/// Match/rename using [regExp].
+class RegExpRenamer {
   final RegExp regExp;
   final String replacementPattern;
 
-  RenamePattern(this.regExp, this.replacementPattern);
+  RegExpRenamer(this.regExp, this.replacementPattern);
 
   /// Returns true if [str] has a full match with [regExp].
   bool matches(String str) => quiver.matchesFull(regExp, str);
 
   /// Renames [str] according to [replacementPattern].
+  ///
+  /// Returns [str] if [regExp] doesn't have a full match.
   String rename(String str) {
-    if (quiver.matchesFull(regExp, str)) {
+    if (matches(str)) {
+      // Get match.
       final regExpMatch = regExp.firstMatch(str);
+
+      /// Get group values.
+      /// E.g for `str`: `clang_dispose` and `regExp`: `clang_(.*)`
+      /// groups will be `0`: `clang_disponse`, `1`: `dispose`.
       final groups = regExpMatch.groups(
           List.generate(regExpMatch.groupCount, (index) => index) +
               [regExpMatch.groupCount]);
 
+      /// Replace all `$<int>` symbols with respective groups (if any).
       final result =
           replacementPattern.replaceAllMapped(replaceGroupRegexp, (match) {
         final groupInt = int.parse(match.group(1));
@@ -136,7 +152,6 @@
       });
       return result;
     } else {
-      /// We return [str] if pattern doesn't have a full match.
       return str;
     }
   }
@@ -147,8 +162,8 @@
   }
 }
 
+/// Handles `include/exclude` logic for a declaration.
 class Includer {
-  // matchers
   final List<RegExp> _includeMatchers;
   final Set<String> _includeFull;
   final List<RegExp> _excludeMatchers;
@@ -164,6 +179,9 @@
         _excludeMatchers = excludeMatchers ?? [],
         _excludeFull = excludeFull ?? {};
 
+  /// Returns true if [name] is allowed.
+  ///
+  /// Exclude overrides include.
   bool shouldInclude(String name) {
     if (_excludeFull.contains(name)) {
       return false;
@@ -195,17 +213,22 @@
   }
 }
 
+/// Handles `full/regexp` renaming logic.
 class Renamer {
   final Map<String, String> _renameFull;
-  final List<RenamePattern> _renameMatchers;
+  final List<RegExpRenamer> _renameMatchers;
 
   Renamer({
-    List<RenamePattern> renamePatterns,
+    List<RegExpRenamer> renamePatterns,
     Map<String, String> renameFull,
   })  : _renameMatchers = renamePatterns ?? [],
         _renameFull = renameFull ?? {};
 
-  String renameUsingConfig(String name) {
+  Renamer.noRename()
+      : _renameMatchers = [],
+        _renameFull = {};
+
+  String rename(String name) {
     // Apply full rename (if any).
     if (_renameFull.containsKey(name)) {
       return _renameFull[name];
@@ -222,3 +245,59 @@
     return name;
   }
 }
+
+/// Match declaration name using [declarationRegExp].
+class RegExpMemberRenamer {
+  final RegExp declarationRegExp;
+  final Renamer memberRenamer;
+
+  RegExpMemberRenamer(this.declarationRegExp, this.memberRenamer);
+
+  /// Returns true if [declaration] has a full match with [regExp].
+  bool matchesDeclarationName(String declaration) =>
+      quiver.matchesFull(declarationRegExp, declaration);
+
+  @override
+  String toString() {
+    return 'DeclarationRegExp: $declarationRegExp, MemberRenamer: $memberRenamer';
+  }
+}
+
+/// Handles `full/regexp` member renaming.
+class MemberRenamer {
+  final Map<String, Renamer> _memberRenameFull;
+  final List<RegExpMemberRenamer> _memberRenameMatchers;
+
+  final Map<String, Renamer> _cache = {};
+
+  MemberRenamer({
+    Map<String, Renamer> memberRenameFull,
+    List<RegExpMemberRenamer> memberRenamePattern,
+  })  : _memberRenameFull = memberRenameFull ?? {},
+        _memberRenameMatchers = memberRenamePattern ?? [];
+
+  String rename(String declaration, String member) {
+    if (_cache.containsKey(declaration)) {
+      return _cache[declaration].rename(member);
+    }
+
+    // Apply full rename (if any).
+    if (_memberRenameFull.containsKey(declaration)) {
+      // Add to cache.
+      _cache[declaration] = _memberRenameFull[declaration];
+      return _cache[declaration].rename(member);
+    }
+
+    // Apply rename regexp (if matches).
+    for (final renamer in _memberRenameMatchers) {
+      if (renamer.matchesDeclarationName(declaration)) {
+        // Add to cache.
+        _cache[declaration] = renamer.memberRenamer;
+        return _cache[declaration].rename(member);
+      }
+    }
+
+    // No renaming is provided for this declaration, return unchanged.
+    return member;
+  }
+}
diff --git a/lib/src/config_provider/spec_utils.dart b/lib/src/config_provider/spec_utils.dart
index 8b29d9d..ddf6b89 100644
--- a/lib/src/config_provider/spec_utils.dart
+++ b/lib/src/config_provider/spec_utils.dart
@@ -182,8 +182,10 @@
       includeFull = <String>{},
       excludeMatchers = <RegExp>[],
       excludeFull = <String>{};
-  final renamePatterns = <RenamePattern>[];
+  final renamePatterns = <RegExpRenamer>[];
   final renameFull = <String, String>{};
+  final memberRenamePatterns = <RegExpMemberRenamer>[];
+  final memberRenamerFull = <String, Renamer>{};
 
   final include = (yamlMap[strings.include] as YamlList)?.cast<String>();
   if (include != null) {
@@ -215,7 +217,43 @@
         renameFull[str] = rename[str];
       } else {
         renamePatterns
-            .add(RenamePattern(RegExp(str, dotAll: true), rename[str]));
+            .add(RegExpRenamer(RegExp(str, dotAll: true), rename[str]));
+      }
+    }
+  }
+
+  final memberRename =
+      (yamlMap[strings.memberRename] as YamlMap)?.cast<String, YamlMap>();
+
+  if (memberRename != null) {
+    for (final decl in memberRename.keys) {
+      final renamePatterns = <RegExpRenamer>[];
+      final renameFull = <String, String>{};
+
+      final memberRenameMap = memberRename[decl].cast<String, String>();
+      for (final member in memberRenameMap.keys) {
+        if (isFullDeclarationName(member)) {
+          renameFull[member] = memberRenameMap[member];
+        } else {
+          renamePatterns.add(RegExpRenamer(
+              RegExp(member, dotAll: true), memberRenameMap[member]));
+        }
+      }
+      if (isFullDeclarationName(decl)) {
+        memberRenamerFull[decl] = Renamer(
+          renameFull: renameFull,
+          renamePatterns: renamePatterns,
+        );
+      } else {
+        memberRenamePatterns.add(
+          RegExpMemberRenamer(
+            RegExp(decl, dotAll: true),
+            Renamer(
+              renameFull: renameFull,
+              renamePatterns: renamePatterns,
+            ),
+          ),
+        );
       }
     }
   }
@@ -231,6 +269,10 @@
       renameFull: renameFull,
       renamePatterns: renamePatterns,
     ),
+    memberRenamer: MemberRenamer(
+      memberRenameFull: memberRenamerFull,
+      memberRenamePattern: memberRenamePatterns,
+    ),
   );
 }
 
@@ -253,6 +295,29 @@
             }
           }
         }
+      } else if (key == strings.memberRename) {
+        if (!checkType<YamlMap>([name, key as String], value[key])) {
+          _result = false;
+        } else {
+          for (final declNameKey in value[key].keys) {
+            if (!checkType<YamlMap>(
+                [name, key as String, declNameKey as String],
+                value[key][declNameKey])) {
+              _result = false;
+            } else {
+              for (final memberNameKey in value[key][declNameKey].keys) {
+                if (!checkType<String>([
+                  name,
+                  key as String,
+                  declNameKey as String,
+                  memberNameKey as String,
+                ], value[key][declNameKey][memberNameKey])) {
+                  _result = false;
+                }
+              }
+            }
+          }
+        }
       } else {
         _logger.severe("Unknown key '$key' in '$name'.");
         _result = false;
diff --git a/lib/src/header_parser/sub_parsers/enumdecl_parser.dart b/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
index 99b6b57..0fbb9e8 100644
--- a/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
@@ -100,7 +100,11 @@
           cursor,
           nesting.length + commentPrefix.length,
         ),
-        name: cursor.spelling(),
+        originalName: cursor.spelling(),
+        name: config.enumClassDecl.renameMemberUsingConfig(
+          _stack.top.enumClass.originalName,
+          cursor.spelling(),
+        ),
         value: clang.clang_getEnumConstantDeclValue_wrap(cursor)),
   );
 }
diff --git a/lib/src/header_parser/sub_parsers/functiondecl_parser.dart b/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
index f3e1507..d958c57 100644
--- a/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
@@ -36,7 +36,7 @@
     _logger.fine('++++ Adding Function: ${cursor.completeStringRepr()}');
 
     final rt = _getFunctionReturnType(cursor);
-    final parameters = _getParameters(cursor);
+    final parameters = _getParameters(cursor, funcName);
 
     //TODO(3): Remove this when support for Structs by value arrives.
     if (rt.broadType == BroadType.Struct || _stack.top.structByValueParameter) {
@@ -80,7 +80,8 @@
   return cursor.returnType().toCodeGenTypeAndDispose();
 }
 
-List<Parameter> _getParameters(Pointer<clang_types.CXCursor> cursor) {
+List<Parameter> _getParameters(
+    Pointer<clang_types.CXCursor> cursor, String funcName) {
   final parameters = <Parameter>[];
 
   final totalArgs = clang.clang_Cursor_getNumArguments_wrap(cursor);
@@ -102,7 +103,8 @@
     /// If [pn] is null or empty, its set to `arg$i` by code_generator.
     parameters.add(
       Parameter(
-        name: pn,
+        originalName: pn,
+        name: config.functionDecl.renameMemberUsingConfig(funcName, pn),
         type: pt,
       ),
     );
diff --git a/lib/src/header_parser/sub_parsers/structdecl_parser.dart b/lib/src/header_parser/sub_parsers/structdecl_parser.dart
index 32e3a76..5081aed 100644
--- a/lib/src/header_parser/sub_parsers/structdecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/structdecl_parser.dart
@@ -130,7 +130,11 @@
             cursor,
             nesting.length + commentPrefix.length,
           ),
-          name: cursor.spelling(),
+          originalName: cursor.spelling(),
+          name: config.structDecl.renameMemberUsingConfig(
+            _stack.top.struc.originalName,
+            cursor.spelling(),
+          ),
           type: mt,
         ),
       );
diff --git a/lib/src/header_parser/sub_parsers/unnamed_enumdecl_parser.dart b/lib/src/header_parser/sub_parsers/unnamed_enumdecl_parser.dart
index 8f8487b..fedeb7e 100644
--- a/lib/src/header_parser/sub_parsers/unnamed_enumdecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/unnamed_enumdecl_parser.dart
@@ -60,7 +60,11 @@
 void _addUnNamedEnumConstant(Pointer<clang_types.CXCursor> cursor) {
   _constants.add(
     Constant(
-      name: cursor.spelling(),
+      originalName: cursor.spelling(),
+      name: config.enumClassDecl.renameMemberUsingConfig(
+        '', // Un-named enum constants have an empty declaration name.
+        cursor.spelling(),
+      ),
       rawType: 'int',
       rawValue: clang.clang_getEnumConstantDeclValue_wrap(cursor).toString(),
     ),
diff --git a/lib/src/strings.dart b/lib/src/strings.dart
index b432442..e055347 100644
--- a/lib/src/strings.dart
+++ b/lib/src/strings.dart
@@ -46,6 +46,7 @@
 const include = 'include';
 const exclude = 'exclude';
 const rename = 'rename';
+const memberRename = 'member-rename';
 const sizemap = 'size-map';
 
 // Sizemap values.
diff --git a/test/rename_tests/rename.h b/test/rename_tests/rename.h
index 8ef04e8..8ff6d88 100644
--- a/test/rename_tests/rename.h
+++ b/test/rename_tests/rename.h
@@ -15,10 +15,21 @@
 struct FullMatchStruct3
 {
 };
+struct MemberRenameStruct4
+{
+    int _underscore;
+    float fullMatch;
+};
+
+struct AnyMatchStruct5
+{
+    int _underscore;
+};
 
 void func1(struct Struct1 *s);
 void test_func2(struct Test_Struct2 *s);
 void fullMatch_func3(struct FullMatchStruct3 *s);
+void memberRename_func4(int _underscore, float fullMatch, int);
 
 enum Enum1
 {
@@ -38,3 +49,13 @@
     j = 1,
     k = 2
 };
+enum MemberRenameEnum4
+{
+    _underscore = 0,
+    fullMatch = 1
+};
+enum
+{
+    _unnamed_underscore = 0,
+    unnamedFullMatch = 1
+};
diff --git a/test/rename_tests/rename_test.dart b/test/rename_tests/rename_test.dart
index 3461cb6..c3425cc 100644
--- a/test/rename_tests/rename_test.dart
+++ b/test/rename_tests/rename_test.dart
@@ -36,19 +36,36 @@
     'test_(.*)': '\$1'
     '.*': '$functionPrefix\$0'
     'fullMatch_func3': 'func3'
-
+  ${strings.memberRename}:
+    'memberRename_.*':
+      '_(.*)': '\$1'
+      'fullMatch': 'fullMatchSuccess'
+      '': 'unnamed'
 
 structs:
   ${strings.rename}:
     'Test_(.*)': '\$1'
     '.*': '$structPrefix\$0'
     'FullMatchStruct3': 'Struct3'
+  ${strings.memberRename}:
+    'MemberRenameStruct4':
+      '_(.*)': '\$1'
+      'fullMatch': 'fullMatchSuccess'
+    '.*':
+      '_(.*)': '\$1'
 
 enums:
   ${strings.rename}:
     'Test_(.*)': '\$1'
     '.*': '$enumPrefix\$0'
     'FullMatchEnum3': 'Enum3'
+  ${strings.memberRename}:
+    'MemberRenameEnum4':
+      '_(.*)': '\$1'
+      'fullMatch': 'fullMatchSuccess'
+    '':
+      '_(.*)': '\$1'
+      'unnamedFullMatch': 'unnamedFullMatchSuccess'
 
 macros:
   ${strings.rename}:
@@ -107,6 +124,30 @@
       expect(actual.getBindingAsString('Macro3'),
           expected.getBindingAsString('Macro3'));
     });
+    test('Struct member rename', () {
+      expect(actual.getBindingAsString('${structPrefix}MemberRenameStruct4'),
+          expected.getBindingAsString('${structPrefix}MemberRenameStruct4'));
+    });
+    test('Any Struct member rename', () {
+      expect(actual.getBindingAsString('${structPrefix}AnyMatchStruct5'),
+          expected.getBindingAsString('${structPrefix}AnyMatchStruct5'));
+    });
+    test('Function member rename', () {
+      expect(actual.getBindingAsString('${functionPrefix}memberRename_func4'),
+          expected.getBindingAsString('${functionPrefix}memberRename_func4'));
+    });
+    test('Enum member rename', () {
+      expect(actual.getBindingAsString('${enumPrefix}MemberRenameEnum4'),
+          expected.getBindingAsString('${enumPrefix}MemberRenameEnum4'));
+    });
+    test('unnamed Enum regexp rename', () {
+      expect(actual.getBindingAsString('unnamed_underscore'),
+          expected.getBindingAsString('unnamed_underscore'));
+    });
+    test('unnamed Enum full match rename', () {
+      expect(actual.getBindingAsString('unnamedFullMatchSuccess'),
+          expected.getBindingAsString('unnamedFullMatchSuccess'));
+    });
   });
 }
 
@@ -156,9 +197,52 @@
           ),
         ],
       ),
+      Func(
+        name: '${functionPrefix}memberRename_func4',
+        originalName: 'memberRename_func4',
+        returnType: Type.nativeType(
+          SupportedNativeType.Void,
+        ),
+        parameters: [
+          Parameter(
+            name: 'underscore',
+            type: Type.nativeType(SupportedNativeType.Int32),
+          ),
+          Parameter(
+            name: 'fullMatchSuccess',
+            type: Type.nativeType(SupportedNativeType.Float),
+          ),
+          Parameter(
+            name: 'unnamed',
+            type: Type.nativeType(SupportedNativeType.Int32),
+          ),
+        ],
+      ),
       struc1,
       struc2,
       struc3,
+      Struc(
+        name: '${structPrefix}MemberRenameStruct4',
+        members: [
+          Member(
+            name: 'underscore',
+            type: Type.nativeType(SupportedNativeType.Int32),
+          ),
+          Member(
+            name: 'fullMatchSuccess',
+            type: Type.nativeType(SupportedNativeType.Float),
+          ),
+        ],
+      ),
+      Struc(
+        name: '${structPrefix}AnyMatchStruct5',
+        members: [
+          Member(
+            name: 'underscore',
+            type: Type.nativeType(SupportedNativeType.Int32),
+          ),
+        ],
+      ),
       EnumClass(
         name: '${enumPrefix}Enum1',
         enumConstants: [
@@ -183,6 +267,13 @@
           EnumConstant(name: 'k', value: 2),
         ],
       ),
+      EnumClass(
+        name: '${enumPrefix}MemberRenameEnum4',
+        enumConstants: [
+          EnumConstant(name: 'underscore', value: 0),
+          EnumConstant(name: 'fullMatchSuccess', value: 1),
+        ],
+      ),
       Constant(
         name: '${macroPrefix}Macro1',
         rawType: 'int',
@@ -198,6 +289,16 @@
         rawType: 'int',
         rawValue: '3',
       ),
+      Constant(
+        name: 'unnamed_underscore',
+        rawType: 'int',
+        rawValue: '0',
+      ),
+      Constant(
+        name: 'unnamedFullMatchSuccess',
+        rawType: 'int',
+        rawValue: '1',
+      ),
     ],
   );
 }