Skip to content

Commit 287e21d

Browse files
committed
feat(icons): add per-entry regex and nameStyle fields
Support nameValidateRegexp, nameReplaceRegexp, and nameStyle at the icons entry level for iOS, Android, Flutter, and Web. Entry-level values override common.icons fallback settings.
1 parent 67b04c3 commit 287e21d

13 files changed

Lines changed: 439 additions & 52 deletions

File tree

CLAUDE.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,31 @@ let loader = IconsLoader(client: client, params: params, platform: .ios, logger:
133133

134134
**Key types:**
135135

136-
| Type | Purpose |
137-
| -------------------- | -------------------------------------------------------- |
138-
| `IconsConfiguration` | Enum with `.single`/`.multiple` for backward compat |
139-
| `IconsEntry` | Per-frame config (figmaFrameName, format, assetsFolder) |
140-
| `IconsLoaderConfig` | Sendable struct passed to IconsLoader for frame settings |
136+
| Type | Purpose |
137+
| -------------------- | --------------------------------------------------------------------------------------------------------- |
138+
| `IconsConfiguration` | Enum with `.single`/`.multiple` for backward compat |
139+
| `IconsEntry` | Per-frame config (figmaFrameName, format, assetsFolder, nameValidateRegexp, nameReplaceRegexp, nameStyle) |
140+
| `IconsLoaderConfig` | Sendable struct passed to IconsLoader for frame settings |
141141

142-
**Frame name resolution:** `entry.figmaFrameName``params.common?.icons?.figmaFrameName``"Icons"`
142+
**Per-entry fields with fallback:**
143+
144+
| Field | Fallback Order |
145+
| -------------------- | ------------------------------------------------------------------------ |
146+
| `figmaFrameName` | entry → `common.icons.figmaFrameName``"Icons"` |
147+
| `nameValidateRegexp` | entry → `common.icons.nameValidateRegexp``nil` |
148+
| `nameReplaceRegexp` | entry → `common.icons.nameReplaceRegexp``nil` |
149+
| `nameStyle` | entry → platform default (iOS: `nil`, Android/Flutter/Web: `.snakeCase`) |
150+
151+
**Fallback logic in export files:**
152+
153+
```swift
154+
let processor = ImagesProcessor(
155+
platform: .android,
156+
nameValidateRegexp: entry.nameValidateRegexp ?? params.common?.icons?.nameValidateRegexp,
157+
nameReplaceRegexp: entry.nameReplaceRegexp ?? params.common?.icons?.nameReplaceRegexp,
158+
nameStyle: entry.nameStyle ?? .snakeCase
159+
)
160+
```
143161

144162
### Multiple Colors Configuration
145163

CONFIG.md

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,8 @@ android:
237237
stylesFile: "../../../values/styles.xml"
238238
# [optional] Path to styles-night.xml for dark mode (relative to mainRes)
239239
stylesNightFile: "../../../values-night/styles.xml"
240-
# Theme name used in markers (e.g., "Theme.BaseTheme.inDrive")
241-
themeName: "Theme.BaseTheme.inDrive"
240+
# Theme name used in markers (e.g., "Theme.MyApp.Main")
241+
themeName: "Theme.MyApp.Main"
242242
# [optional] Custom marker start text. Default: "FIGMA COLORS MARKER START"
243243
markerStart: "FIGMA COLORS MARKER START"
244244
# [optional] Custom marker end text. Default: "FIGMA COLORS MARKER END"
@@ -460,16 +460,57 @@ ios:
460460
461461
Each entry in the array supports all the same fields as the legacy format, plus:
462462
463-
| Field | Description |
464-
| ---------------- | ------------------------------------------------------------------------------ |
465-
| `figmaFrameName` | Figma frame name to export icons from. Overrides `common.icons.figmaFrameName` |
463+
| Field | Description |
464+
| -------------------- | -------------------------------------------------------------------------------------- |
465+
| `figmaFrameName` | Figma frame name to export icons from. Overrides `common.icons.figmaFrameName` |
466+
| `nameValidateRegexp` | RegExp pattern for icon name validation. Overrides `common.icons.nameValidateRegexp` |
467+
| `nameReplaceRegexp` | RegExp pattern for name replacement. Overrides `common.icons.nameReplaceRegexp` |
468+
| `nameStyle` | Name style (camelCase, snake_case, etc.). Platform-specific default applies if not set |
466469

467470
### Fallback Behavior
468471

469-
If `figmaFrameName` is not specified in an entry, it falls back to:
472+
For `figmaFrameName`, fallback order is:
473+
474+
1. Entry-level `figmaFrameName`
475+
2. `common.icons.figmaFrameName` (if defined)
476+
3. `"Icons"` (default)
477+
478+
For `nameValidateRegexp`, `nameReplaceRegexp`, and `nameStyle`, fallback order is:
479+
480+
1. Entry-level field (if defined)
481+
2. `common.icons.*` field (if defined)
482+
3. Platform default (e.g., `snake_case` for Android/Flutter/Web)
470483

471-
1. `common.icons.figmaFrameName` (if defined)
472-
2. `"Icons"` (default)
484+
### Per-Entry Regex Example
485+
486+
Use per-entry regex when different icon categories require different naming transformations:
487+
488+
```yaml
489+
common:
490+
icons:
491+
# Global fallback regex (optional)
492+
nameValidateRegexp: "^(.+)$"
493+
nameReplaceRegexp: "ic_$1"
494+
495+
android:
496+
icons:
497+
- figmaFrameName: Actions
498+
output: "action"
499+
nameStyle: snake_case
500+
# Uses global regex from common.icons
501+
502+
- figmaFrameName: Flags
503+
output: "flag"
504+
nameStyle: snake_case
505+
# Custom regex for flags: strips "flags_" prefix
506+
nameValidateRegexp: "^flags_(.+)$"
507+
nameReplaceRegexp: "ic_flag_$1"
508+
509+
- figmaFrameName: Status
510+
output: "status"
511+
# Custom style, uses global regex
512+
nameStyle: camelCase
513+
```
473514

474515
### Performance
475516

@@ -838,24 +879,24 @@ Theme attributes allow your app to reference colors like `?attr/colorBackgroundP
838879

839880
```xml
840881
<resources>
841-
<!-- FIGMA COLORS MARKER START: Theme.BaseTheme.inDrive -->
882+
<!-- FIGMA COLORS MARKER START: Theme.MyApp.Main -->
842883
<attr name="colorBackgroundPrimary" format="color" />
843884
<attr name="colorBackgroundSecondary" format="color" />
844885
<attr name="colorTextPrimary" format="color" />
845-
<!-- FIGMA COLORS MARKER END: Theme.BaseTheme.inDrive -->
886+
<!-- FIGMA COLORS MARKER END: Theme.MyApp.Main -->
846887
</resources>
847888
```
848889

849890
**styles.xml** (theme values):
850891

851892
```xml
852893
<resources>
853-
<style name="Theme.BaseTheme.inDrive" parent="Theme.MaterialComponents.DayNight">
854-
<!-- FIGMA COLORS MARKER START: Theme.BaseTheme.inDrive -->
894+
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.DayNight">
895+
<!-- FIGMA COLORS MARKER START: Theme.MyApp.Main -->
855896
<item name="colorBackgroundPrimary">@color/background_primary</item>
856897
<item name="colorBackgroundSecondary">@color/background_secondary</item>
857898
<item name="colorTextPrimary">@color/text_primary</item>
858-
<!-- FIGMA COLORS MARKER END: Theme.BaseTheme.inDrive -->
899+
<!-- FIGMA COLORS MARKER END: Theme.MyApp.Main -->
859900
</style>
860901
</resources>
861902
```
@@ -871,7 +912,7 @@ android:
871912
attrsFile: "../../../values/attrs.xml"
872913
stylesFile: "../../../values/styles.xml"
873914
stylesNightFile: "../../../values-night/styles.xml"
874-
themeName: "Theme.BaseTheme.inDrive"
915+
themeName: "Theme.MyApp.Main"
875916
markerStart: "FIGMA COLORS MARKER START"
876917
markerEnd: "FIGMA COLORS MARKER END"
877918
autoCreateMarkers: false

Sources/ExFig/Batch/SharedThemeAttributes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Foundation
55
/// In batch mode, multiple configs may export to the same `attrs.xml` and `styles.xml` files.
66
/// This struct captures one config's contribution for later merging.
77
public struct ThemeAttributesCollection: Sendable {
8-
/// Theme name used in markers (e.g., "Theme.BaseTheme.inDrive").
8+
/// Theme name used in markers (e.g., "Theme.MyApp.Main").
99
public let themeName: String
1010

1111
/// Base marker start text (e.g., "FIGMA COLORS MARKER START").

Sources/ExFig/Input/Params.swift

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ struct Params: Decodable {
227227
let assetsFolder: String
228228
let preservesVectorRepresentation: [String]?
229229
let nameStyle: NameStyle
230+
/// Regex pattern for validating/capturing icon names. Overrides common.icons.nameValidateRegexp.
231+
let nameValidateRegexp: String?
232+
/// Replacement pattern using captured groups. Overrides common.icons.nameReplaceRegexp.
233+
let nameReplaceRegexp: String?
230234

231235
let imageSwift: URL?
232236
let swiftUIImageSwift: URL?
@@ -264,6 +268,8 @@ struct Params: Decodable {
264268
assetsFolder: icons.assetsFolder,
265269
preservesVectorRepresentation: icons.preservesVectorRepresentation,
266270
nameStyle: icons.nameStyle,
271+
nameValidateRegexp: nil,
272+
nameReplaceRegexp: nil,
267273
imageSwift: icons.imageSwift,
268274
swiftUIImageSwift: icons.swiftUIImageSwift,
269275
renderMode: icons.renderMode,
@@ -413,6 +419,12 @@ struct Params: Decodable {
413419
let composePackageName: String?
414420
let composeFormat: ComposeIconFormat?
415421
let composeExtensionTarget: String?
422+
/// Name style for icon names. Overrides default snake_case.
423+
let nameStyle: NameStyle?
424+
/// Regex pattern for validating/capturing icon names. Overrides common.icons.nameValidateRegexp.
425+
let nameValidateRegexp: String?
426+
/// Replacement pattern using captured groups. Overrides common.icons.nameReplaceRegexp.
427+
let nameReplaceRegexp: String?
416428
}
417429

418430
/// Icons configuration supporting both single object and array formats.
@@ -437,7 +449,10 @@ struct Params: Decodable {
437449
output: icons.output,
438450
composePackageName: icons.composePackageName,
439451
composeFormat: icons.composeFormat,
440-
composeExtensionTarget: icons.composeExtensionTarget
452+
composeExtensionTarget: icons.composeExtensionTarget,
453+
nameStyle: nil,
454+
nameValidateRegexp: nil,
455+
nameReplaceRegexp: nil
441456
)]
442457
case let .multiple(entries):
443458
entries
@@ -464,7 +479,7 @@ struct Params: Decodable {
464479
/// Path to styles-night.xml relative to mainRes.
465480
let stylesNightFile: String?
466481

467-
/// Theme name used in markers (e.g., "Theme.BaseTheme.inDrive").
482+
/// Theme name used in markers (e.g., "Theme.MyApp.Main").
468483
let themeName: String
469484

470485
/// Custom marker start text (default: "FIGMA COLORS MARKER START").
@@ -751,6 +766,12 @@ struct Params: Decodable {
751766
let output: String
752767
let dartFile: String?
753768
let className: String?
769+
/// Name style for icon names. Overrides default snake_case.
770+
let nameStyle: NameStyle?
771+
/// Regex pattern for validating/capturing icon names. Overrides common.icons.nameValidateRegexp.
772+
let nameValidateRegexp: String?
773+
/// Replacement pattern using captured groups. Overrides common.icons.nameReplaceRegexp.
774+
let nameReplaceRegexp: String?
754775
}
755776

756777
/// Icons configuration supporting both single object and array formats.
@@ -774,7 +795,10 @@ struct Params: Decodable {
774795
figmaFrameName: nil,
775796
output: icons.output,
776797
dartFile: icons.dartFile,
777-
className: icons.className
798+
className: icons.className,
799+
nameStyle: nil,
800+
nameValidateRegexp: nil,
801+
nameReplaceRegexp: nil
778802
)]
779803
case let .multiple(entries):
780804
entries
@@ -951,6 +975,12 @@ struct Params: Decodable {
951975
let generateReactComponents: Bool?
952976
/// Icon size in pixels for viewBox. Defaults to 24.
953977
let iconSize: Int?
978+
/// Name style for icon names. Overrides default snake_case.
979+
let nameStyle: NameStyle?
980+
/// Regex pattern for validating/capturing icon names. Overrides common.icons.nameValidateRegexp.
981+
let nameValidateRegexp: String?
982+
/// Replacement pattern using captured groups. Overrides common.icons.nameReplaceRegexp.
983+
let nameReplaceRegexp: String?
954984
}
955985

956986
/// Icons configuration supporting both single object and array formats.
@@ -975,7 +1005,10 @@ struct Params: Decodable {
9751005
outputDirectory: icons.outputDirectory,
9761006
svgDirectory: icons.svgDirectory,
9771007
generateReactComponents: icons.generateReactComponents,
978-
iconSize: icons.iconSize
1008+
iconSize: icons.iconSize,
1009+
nameStyle: nil,
1010+
nameValidateRegexp: nil,
1011+
nameReplaceRegexp: nil
9791012
)]
9801013
case let .multiple(entries):
9811014
entries

Sources/ExFig/Subcommands/Export/AndroidIconsExport.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,9 @@ extension ExFigCommand.ExportIcons {
143143
// 2. Process images
144144
let processor = ImagesProcessor(
145145
platform: .android,
146-
nameValidateRegexp: params.common?.icons?.nameValidateRegexp,
147-
nameReplaceRegexp: params.common?.icons?.nameReplaceRegexp,
148-
nameStyle: .snakeCase
146+
nameValidateRegexp: entry.nameValidateRegexp ?? params.common?.icons?.nameValidateRegexp,
147+
nameReplaceRegexp: entry.nameReplaceRegexp ?? params.common?.icons?.nameReplaceRegexp,
148+
nameStyle: entry.nameStyle ?? .snakeCase
149149
)
150150

151151
let (icons, iconsWarning):
@@ -376,9 +376,9 @@ extension ExFigCommand.ExportIcons {
376376
// 2. Process images
377377
let processor = ImagesProcessor(
378378
platform: .android,
379-
nameValidateRegexp: params.common?.icons?.nameValidateRegexp,
380-
nameReplaceRegexp: params.common?.icons?.nameReplaceRegexp,
381-
nameStyle: .snakeCase
379+
nameValidateRegexp: entry.nameValidateRegexp ?? params.common?.icons?.nameValidateRegexp,
380+
nameReplaceRegexp: entry.nameReplaceRegexp ?? params.common?.icons?.nameReplaceRegexp,
381+
nameStyle: entry.nameStyle ?? .snakeCase
382382
)
383383

384384
let (icons, iconsWarning):

Sources/ExFig/Subcommands/Export/FlutterIconsExport.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ extension ExFigCommand.ExportIcons {
124124
// 2. Process images
125125
let processor = ImagesProcessor(
126126
platform: .flutter,
127-
nameValidateRegexp: params.common?.icons?.nameValidateRegexp,
128-
nameReplaceRegexp: params.common?.icons?.nameReplaceRegexp,
129-
nameStyle: .snakeCase
127+
nameValidateRegexp: entry.nameValidateRegexp ?? params.common?.icons?.nameValidateRegexp,
128+
nameReplaceRegexp: entry.nameReplaceRegexp ?? params.common?.icons?.nameReplaceRegexp,
129+
nameStyle: entry.nameStyle ?? .snakeCase
130130
)
131131

132132
let (icons, iconsWarning): ([AssetPair<ImagesProcessor.AssetType>], AssetsValidatorWarning?) =

Sources/ExFig/Subcommands/Export/WebIconsExport.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ extension ExFigCommand.ExportIcons {
124124
// 2. Process images
125125
let processor = ImagesProcessor(
126126
platform: .web,
127-
nameValidateRegexp: params.common?.icons?.nameValidateRegexp,
128-
nameReplaceRegexp: params.common?.icons?.nameReplaceRegexp,
129-
nameStyle: .snakeCase
127+
nameValidateRegexp: entry.nameValidateRegexp ?? params.common?.icons?.nameValidateRegexp,
128+
nameReplaceRegexp: entry.nameReplaceRegexp ?? params.common?.icons?.nameReplaceRegexp,
129+
nameStyle: entry.nameStyle ?? .snakeCase
130130
)
131131

132132
let (icons, iconsWarning): ([AssetPair<ImagesProcessor.AssetType>], AssetsValidatorWarning?) =

Sources/ExFig/Subcommands/Export/iOSIconsExport.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ extension ExFigCommand.ExportIcons {
130130

131131
let processor = ImagesProcessor(
132132
platform: .ios,
133-
nameValidateRegexp: params.common?.icons?.nameValidateRegexp,
134-
nameReplaceRegexp: params.common?.icons?.nameReplaceRegexp,
133+
nameValidateRegexp: entry.nameValidateRegexp ?? params.common?.icons?.nameValidateRegexp,
134+
nameReplaceRegexp: entry.nameReplaceRegexp ?? params.common?.icons?.nameReplaceRegexp,
135135
nameStyle: entry.nameStyle
136136
)
137137

0 commit comments

Comments
 (0)