Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/vrrashkov/tokenparser

Tool that converts your Figma tokens to any language made with Rust
https://github.com/vrrashkov/tokenparser

build-system cross-platform design-tokens design-tools figma figma-studio figma-variables style-tokens styles tokens

Last synced: 5 days ago
JSON representation

Tool that converts your Figma tokens to any language made with Rust

Awesome Lists containing this project

README

        

# Token Parser

Crates.io

## 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

1. [Figma Variables](https://www.figma.com/plugin-docs/working-with-variables/)

2. [Figma Studio Tokens](https://github.com/tokens-studio/figma-plugin)

3. [lukasoppermann/design-tokens](https://github.com/lukasoppermann/design-tokens)

> 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.

1. Setup the **`assts/design_tokens_config.yaml`** file
2. Run with: for windows (`WIN_design_token_parser.exe`) for MAC (`MAC_design_token_parser`) you can find them in Release section

```shell
   .\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.

```shell
 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

```yaml
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

```yaml
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](https://github.com/cobalt-org/liquid-rust) 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.

```json
{
"size/XL": {
"type": "float",
"value": "56",
"description": ""
},
"text/fr": {
"type": "string",
"value": "Some Text",
"description": ""
},
"color/bg": {
"type": "color",
"value": "#000000",
"description": ""
}
}
```

```json
{
"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`and `type`(`description`is optional)

###### Valid Examples and how to use JSON -> Template

---

```json
"XL": {
"type": "float",
"value": "56",
"description": ""
}
```

```yaml
- type: float
value: "public static let {{variable_name | camel}} = CGFloat({{value | as_text_or_number}}) {{description | optional: '// desc = %value'}}"
```

```swift
public static let xl = CGFloat(56)
```

---

```json
"bold": {
"type": "typography",
"value": {
"fontFamily": "Noir Pro",
"fontSize": "16",
"fontWeight": "Bold",
"letterSpacing": "-0.41",
"lineHeight": "23"
}
}
```

```yaml
- type: typography
value: 'public static let {{variable_name | camel}} = TextStyle(name: "{{fontFamily}}", size: {{fontSize}}, weight: TextStyle.Weight({{fontWeight | as_text_or_number}}), lineHeight: {{lineHeight}})'
```

```swift
public static let bold = TextStyle(name: "Noir Pro", size: 16, weight: TextStyle.Weight("Bold"), lineHeight: 23)
```

---

```json
"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"
}
]
}
```

```yaml
- 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}}))]'
```

```swift
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))]
```

---

```json
"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"
}
}
```

```yaml
# 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}}))
"
```

```swift
public static let dissolve = CustomTransition(duration: 0.45, x1: CGFloat(0.6968395709991455), x2: CGFloat(0.06683959811925888), y1: CGFloat(0.05232666060328483), y2: CGFloat(0.9323266744613647))
```

---

```json
"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
}
]
}
}
}
```

```yaml
# 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 %}
)
"
```

```swift
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.

```yaml
# 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:

- [Liquid for Designers · Shopify/liquid Wiki · GitHub](https://github.com/Shopify/liquid/wiki/Liquid-for-Designers)

- https://jg-rp.github.io/liquid/language/filters

##### More

For any ideas or issues please don't hasitate to ask/report.