19 stable releases
3.2.2 | Oct 31, 2023 |
---|---|
3.1.8 | Sep 13, 2023 |
3.1.7 | Aug 28, 2023 |
3.1.5 | Jul 10, 2023 |
1.9.0 | Jun 27, 2023 |
#93 in Configuration
135KB
1.5K
SLoC
Token Parser
Overview
Token Parser is a universal tool for generating runnable code for any language from your Figma Tokens. It is written in Rust so you have the freedom to use it anywhere you would like without having node.js or anything else installed other than the executable on your system. The full configuration is happening through a configuration yaml file from which you can customize to build for as many different languages as you want from a single place.
Tested with
You can use tokens from multiple sources as long as they are with the correct json structure!
Setup
You can get the whole project and build it yourself or if you don't have Rust or just don't want to deal with the builds yourself, go in the Release section and get the executables from there.
- Setup the
assts/design_tokens_config.yaml
file - Run with: for windows (
WIN_design_token_parser.exe
) for MAC (MAC_design_token_parser
) you can find them in Release section
.\WIN_design_token_parser.exe --generate --config "path/design_tokens_config.yaml"
That's all, your files will be generated and ready to use
The process for generating the usable tokens is split into two.
-
Converting the Figma tokens to usable json files (similar to style-dictionary)
-
Generating the end files for the langages from the previously generated json files
If you have already generated the usable json files you can just run the end code generation by running.
WIN_design_token_parser.exe --config "path/design_tokens_config.yaml"
Homebrew
- brew tap vrrashkov/tokenparser
- brew install tokenparser
- Configure the yaml config file
design_token_parser --generate --config "design_tokens_config.yaml"
Configuration
Input/Output paths for loading and generation
global:
# Figma source paths
# These are the pure files from Figma, they can contain aliases
# For example if we have aliases we will need the actual value and not the alias
# Separating different files is necessary in case there are duplicate trees but different values/aliases
# So if we have button-md and button-big with the same trees but different values with aliases that need to be accesed from core.json
# this should be the setup
# Look at the figma/variables and figma/generated_styles for better understanding how it works
figma_source_paths:
- combine:
file_name: "button-lg"
files:
- "assets/figma/variables/button-lg.json"
- "assets/figma/variables/core-.json"
- combine:
file_name: "button-md"
files:
- "assets/figma/variables/button-md.json"
- "assets/figma/variables/core-.json"
- combine:
file_name: "button-sm"
files:
- "assets/figma/variables/button-sm.json"
- "assets/figma/variables/core-.json"
- combine:
file_name: "color-light"
files:
- "assets/figma/variables/color-accent-primary.json"
- "assets/figma/variables/color-accent-secondary.json"
- "assets/figma/variables/color-status-success.json"
- "assets/figma/variables/color-status-danger.json"
- "assets/figma/variables/color-light.json"
- "assets/figma/variables/palette-.json"
- combine:
file_name: "color-dark"
files:
- "assets/figma/variables/color-accent-primary.json"
- "assets/figma/variables/color-accent-secondary.json"
- "assets/figma/variables/color-status-success.json"
- "assets/figma/variables/color-status-danger.json"
- "assets/figma/variables/color-dark.json"
- "assets/figma/variables/palette-.json"
# file_name: If set this will be the name of the merged file
# if not, than the first file name will be used
figma_output_paths:
- combine:
file_name: "button-lg"
merge: ["button-lg"]
files:
- path: "assets/figma/variables/button-lg.json"
- combine:
file_name: "button-md"
merge: ["button-md"]
files:
- path: "assets/figma/variables/button-md.json"
- combine:
file_name: "button-sm"
merge: ["button-sm"]
files:
- path: "assets/figma/variables/button-sm.json"
- combine:
file_name: "color-light"
merge: ["color-light"]
files:
- path: "assets/figma/variables/color-accent-primary.json"
# if mode is set this will wrap the whole json object with a parent of the mode's value
# this helps if you have similar trees in multiple files
mode: "primary"
- path: "assets/figma/variables/color-accent-secondary.json"
mode: "secondary"
- path: "assets/figma/variables/color-status-success.json"
mode: "success"
- path: "assets/figma/variables/color-status-danger.json"
mode: "danger"
- path: "assets/figma/variables/color-light.json"
- combine:
file_name: "color-dark"
merge: ["color-dark"]
files:
- path: "assets/figma/variables/color-accent-primary.json"
mode: "primary"
- path: "assets/figma/variables/color-accent-secondary.json"
mode: "secondary"
- path: "assets/figma/variables/color-status-success.json"
mode: "success"
- path: "assets/figma/variables/color-status-danger.json"
mode: "danger"
- path: "assets/figma/variables/color-dark.json"
#Output path
style_output_path: "assets/generated_styles/"
Template config
templates:
- settings_general:
generate_file_path: "generated_templates"
file_name:
format: "DS{{style}}"
extension: "swift"
#case: "kebab"
settings_custom:
# For header and footer {{style}} is a secial variable that can be used
header:
- "import SwiftUI"
- "public class DSCore{{style}} {"
footer:
- "}"
template_type:
# For themes
- type: color
value: "public static let {{variable_name | camel}} = {{value | color: 'Color(red: rgb_r_v1, green: rgb_g_v1, blue: rgb_b_v1, opacity: rgb_a_v1)'}} {{description | optional: '// desc = %value'}}"
# For Core
- type: string
value: "public static let {{variable_name | camel}} = {{value}} {{description | optional: '// desc = %value'}}"
- type: float
value: "public static let {{variable_name | camel}} = CGFloat({{value | as_text_or_number}}) {{description | optional: '// desc = %value'}}"
- type: boolean
value: "public static let {{variable_name | camel}} = {{value}} {{description | optional: '// des c = %value'}}"
- type: composition
value: "{% if verticalPadding != '' %} test1: {{verticalPadding | optional: 'vertical-padding-test-first: %value'}} {% endif %}"
- type: composition
value: "{% if verticalPadding != '' %} test2: {{verticalPadding | optional: 'vartical-padding-test-second: %value'}} {% endif %}"
- type: boxShadow
value:
- "{{variable_name}} {{color-0 | color: 'hex'}} blur: {{blur-0}} x: {{x-0}}"
- "{{variable_name}} {{color-0 | color: 'hex'}} {{color-1 | color: 'hex'}} blur: {{blur-0}} x: {{x-0}} blur: {{blur-1}} x: {{x-1}}"
You can use every type multiple times for more clean way of creating your values. There are many filters that can help you create the template you want (check them bellow). Also because this tool is using Liquid you can expect every filter/tags/blocks to be usable in your templates. As you can see from the above code there are if statements that check if a variable is present and if it is display something.
Optional values can be added with the optional filter. Instead of using if statements sometimes it's easier to just use the optional filter and display the value only if it exists.
Valid JSON
Both type of jsons are valid and represent the same structure becase of the forward slash, you can have infinite amount of nesting or no nesting at all.
{
"size/XL": {
"type": "float",
"value": "56",
"description": ""
},
"text/fr": {
"type": "string",
"value": "Some Text",
"description": ""
},
"color/bg": {
"type": "color",
"value": "#000000",
"description": ""
}
}
{
"size": {
"XL": {
"type": "float",
"value": "56",
"description": ""
}
},
"text": {
"fr": {
"type": "string",
"value": "Some Text",
"description": ""
}
},
"color": {
"bg": {
"type": "color",
"value": "#000000",
"description": ""
}
}
}
JSON value format
You can have as much nesting of the tree as you want as long as the end node contains
value
andtype
(description
is optional)
Valid Examples and how to use JSON -> Template
"XL": {
"type": "float",
"value": "56",
"description": ""
}
- type: float
value: "public static let {{variable_name | camel}} = CGFloat({{value | as_text_or_number}}) {{description | optional: '// desc = %value'}}"
public static let xl = CGFloat(56)
"bold": {
"type": "typography",
"value": {
"fontFamily": "Noir Pro",
"fontSize": "16",
"fontWeight": "Bold",
"letterSpacing": "-0.41",
"lineHeight": "23"
}
}
- type: typography
value: 'public static let {{variable_name | camel}} = TextStyle(name: "{{fontFamily}}", size: {{fontSize}}, weight: TextStyle.Weight({{fontWeight | as_text_or_number}}), lineHeight: {{lineHeight}})'
public static let bold = TextStyle(name: "Noir Pro", size: 16, weight: TextStyle.Weight("Bold"), lineHeight: 23)
"tabBar": {
"type": "boxShadow",
"value": [
{
"blur": "20",
"color": "rgba(0,0,0,0.1)",
"spread": "0",
"type": "dropShadow",
"x": "0",
"y": "0"
},
{
"blur": "8",
"color": "rgba(0,0,0,0.1)",
"spread": "0",
"type": "dropShadow",
"x": "0",
"y": "4"
}
]
}
- type: boxShadow
value:
- 'public static let {{variable_name | camel}} = Shadow(x: CGFloat({{x-0}}), y: CGFloat({{y-0}}), color: Color(hex: "{{color-0 | color: ''hex''}}"), radius: CGFloat({{blur-0}}))'
- 'public static let {{variable_name | camel}} = [Shadow(x: CGFloat({{x-0}}), y: CGFloat({{y-0}}), color: Color(hex: "{{color-0 | color: ''hex''}}"), radius: CGFloat({{blur-0}})), Shadow(x: CGFloat({{x-1}}), y: CGFloat({{y-1}}), color: Color(hex: "{{color-1 | color: ''hex''}}"), radius: CGFloat({{blur-1}}))]'
public static let shadowTabBar = [Shadow(x: CGFloat(0), y: CGFloat(0), color: Color(hex: "#00000019"), radius: CGFloat(20)), Shadow(x: CGFloat(0), y: CGFloat(4), color: Color(hex: "#00000019"), radius: CGFloat(8))]
"dissolve": {
"description": null,
"type": "custom-transition",
"value": {
"duration": 0.45,
"easingFunction": {
"x1": 0.6968395709991455,
"x2": 0.06683959811925888,
"y1": 0.05232666060328483,
"y2": 0.9323266744613647
},
"transitionType": "dissolve"
}
}
# Object
# {{easingFunction | empty}} use empty filter to initialize variable if you need it in the scope of the template without printing it
- type: "custom-transition"
value: "public static let {{variable_name | camel}} = CustomTransition(duration: {{duration}},
{{easingFunction | empty}}
x1: CGFloat({{easingFunction.x1}}),
x2: CGFloat({{easingFunction.x2}}),
y1: CGFloat({{easingFunction.y1}}),
y2: CGFloat({{easingFunction.y2}}))
"
public static let dissolve = CustomTransition(duration: 0.45, x1: CGFloat(0.6968395709991455), x2: CGFloat(0.06683959811925888), y1: CGFloat(0.05232666060328483), y2: CGFloat(0.9323266744613647))
"gradient": {
"single with multiple color stops": {
"description": "Four color stops from yellow to red",
"type": "custom-gradient",
"value": {
"gradientType": "radial",
"rotation": 180,
"stops": [
{
"color": "#ffb800ff",
"position": 0
},
{
"color": "#ff8a00ff",
"position": 0.34
},
{
"color": "#ff2e00ff",
"position": 0.65
},
{
"color": "#ff0000ff",
"position": 1
}
]
}
}
}
# Array
# {{stops | empty}} use empty filter to initialize variable if you need it in the scope of the template without printing it
- type: "custom-gradient"
value: "public static let {{variable_name | camel}} = CustomGradient(gradientType: {{gradientType}}, rotation: {{rotation}},
{{stops | empty}}
color1: Style1(
{% for stop in stops %}
{{stop.color | color: 'Color(red: rgb_r_v1, green: rgb_g_v1, blue: rgb_b_v1, opacity: rgb_a_v1)'}}
{% if forloop.last == false -%}, {% endif %}
{% endfor -%}
),
color2: Style2(
{% assign colors = stops | map: 'color' %}
{% for item in colors %}
{{item | color: 'Color(red: rgb_r_v2, green: rgb_g_v2, blue: rgb_b_v2, opacity: rgb_a_v2)'}}
{% if forloop.last == false -%}, {% endif %}
{% endfor %}
)
"
public static let defaultGradientSingleWithMultipleColorStops = CustomGradient(gradientType: radial, rotation: 180,
color1: Style1(
Color(red: 1.000, green: 0.722, blue: 0.000, opacity: 1.000) ,
Color(red: 1.000, green: 0.541, blue: 0.000, opacity: 1.000) ,
Color(red: 1.000, green: 0.180, blue: 0.000, opacity: 1.000) ,
Color(red: 1.000, green: 0.000, blue: 0.000, opacity: 1.000) ),
color2: Style2(
Color(red: 255, green: 184, blue: 0, opacity: 184) ,
Color(red: 255, green: 138, blue: 0, opacity: 138) ,
Color(red: 255, green: 46, blue: 0, opacity: 46) ,
Color(red: 255, green: 0, blue: 0, opacity: 0) )
}
Use
{{easingFunction | empty}}
empty filter to initialize a variable without displaying it. This filter is unique to this library and necessary for a lot of the blocks that the liquid templating provides.
Keywords
You can use the keywords in the following way: {{variable_name | kebab }}
name of the keyword and next to it you can add a filter, or multiple filters by separating them with |
like this {{variable_name | kebab | no_space }}
.
Special filters
name | extra options/info |
---|---|
empty |
Initialize variable without displaying it's value |
no_space |
If the value contains space, remove it. For example "Test No Space " will turnto "TestNoSpace " |
as_text_or_number |
If the value is text it will add double quotes to it. |
pascal , kebab , camel |
Different case filters |
color |
rgb_r_v1 , rgb_g_v1 , rgb_b_v1 , rgb_a_v1 rgb_r_v2 , rgb_g_v2 , rgb_b_v2 , rgb_a_v2 hex v1 - values from 0 to 255 v2 - values from 0 to 1 |
You can get example of full configuration from the assets folder
Arrays have a bit different take on how you shuld template them. For example in the file the boxShadow
type is working like this. Because we can expect array values with uknown length here is how you can handle them.
# All the color related values from above
# For every new line of the boxShadow value, a new index can be used. For example:
# On line 1 you have only values with index 0
# On line 2 you have values with index 0 and 1
# On line 3 you have values with index 0, 1 and 2 and etc..
# All possible variants should be made with a template
# If there is a missing one you will be notified with an error to add it
- type: boxShadow
value:
- "{{variable_name}} {{color-0 | color: 'hex'}} blur: {{blur-0}} x: {{x-0}}"
- "{{variable_name}} {{color-0 | color: 'hex'}} {{color-1 | color: 'hex'}} blur: {{blur-0}} x: {{x-0}} blur: {{blur-1}} x: {{x-1}}"
Liquid filters
Check the available filters/blocks and etc.. for the liquid templating from here:
More
For any ideas or issues please don't hasitate to ask/report.
Dependencies
~13MB
~233K SLoC