+23
.github/workflows/test.yml
+23
.github/workflows/test.yml
···
1
+
name: test
2
+
3
+
on:
4
+
push:
5
+
branches:
6
+
- master
7
+
- main
8
+
pull_request:
9
+
10
+
jobs:
11
+
test:
12
+
runs-on: ubuntu-latest
13
+
steps:
14
+
- uses: actions/checkout@v3
15
+
- uses: erlef/setup-beam@v1
16
+
with:
17
+
otp-version: "26.0.2"
18
+
gleam-version: "1.0.0-rc1"
19
+
rebar3-version: "3"
20
+
# elixir-version: "1.15.4"
21
+
- run: gleam deps download
22
+
- run: gleam test
23
+
- run: gleam format --check src test
+25
README.md
+25
README.md
···
1
+
# cymbal
2
+
3
+
[](https://hex.pm/packages/cymbal)
4
+
[](https://hexdocs.pm/cymbal/)
5
+
6
+
```sh
7
+
gleam add cymbal
8
+
```
9
+
```gleam
10
+
import cymbal
11
+
12
+
pub fn main() {
13
+
// TODO: An example of the project in use
14
+
}
15
+
```
16
+
17
+
Further documentation can be found at <https://hexdocs.pm/cymbal>.
18
+
19
+
## Development
20
+
21
+
```sh
22
+
gleam run # Run the project
23
+
gleam test # Run the tests
24
+
gleam shell # Run an Erlang shell
25
+
```
+19
gleam.toml
+19
gleam.toml
···
1
+
name = "cymbal"
2
+
version = "1.0.0"
3
+
4
+
# Fill out these fields if you intend to generate HTML documentation or publish
5
+
# your project to the Hex package manager.
6
+
#
7
+
# description = ""
8
+
# licences = ["Apache-2.0"]
9
+
# repository = { type = "github", user = "username", repo = "project" }
10
+
# links = [{ title = "Website", href = "https://gleam.run" }]
11
+
#
12
+
# For a full reference of all the available options, you can have a look at
13
+
# https://gleam.run/writing-gleam/gleam-toml/.
14
+
15
+
[dependencies]
16
+
gleam_stdlib = "~> 0.34 or ~> 1.0"
17
+
18
+
[dev-dependencies]
19
+
gleeunit = "~> 1.0"
+11
manifest.toml
+11
manifest.toml
···
1
+
# This file was generated by Gleam
2
+
# You typically do not need to edit this file
3
+
4
+
packages = [
5
+
{ name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" },
6
+
{ name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" },
7
+
]
8
+
9
+
[requirements]
10
+
gleam_stdlib = { version = "~> 0.34 or ~> 1.0" }
11
+
gleeunit = { version = "~> 1.0" }
+213
src/cymbal.gleam
+213
src/cymbal.gleam
···
1
+
import gleam/int
2
+
import gleam/float
3
+
import gleam/string
4
+
5
+
/// A YAML document which can be converted into a string using the `encode`
6
+
/// function.
7
+
///
8
+
pub opaque type Yaml {
9
+
Int(Int)
10
+
Float(Float)
11
+
String(String)
12
+
Array(List(Yaml))
13
+
Block(List(#(String, Yaml)))
14
+
}
15
+
16
+
/// Convert a YAML document into a string.
17
+
///
18
+
pub fn encode(document: Yaml) -> String {
19
+
let start = case own_line(document) {
20
+
True -> "---"
21
+
False -> "---\n"
22
+
}
23
+
24
+
en(start, 0, document) <> "\n"
25
+
}
26
+
27
+
/// Create a YAML document from an int.
28
+
///
29
+
pub fn int(i: Int) -> Yaml {
30
+
Int(i)
31
+
}
32
+
33
+
/// Create a YAML document from a float.
34
+
///
35
+
pub fn float(i: Float) -> Yaml {
36
+
Float(i)
37
+
}
38
+
39
+
/// Create a YAML document from a string.
40
+
///
41
+
pub fn string(i: String) -> Yaml {
42
+
String(i)
43
+
}
44
+
45
+
/// Create a YAML document from a list of YAML documents.
46
+
///
47
+
pub fn array(i: List(Yaml)) -> Yaml {
48
+
Array(i)
49
+
}
50
+
51
+
/// Create a YAML document from a list of named YAML values.
52
+
///
53
+
pub fn block(i: List(#(String, Yaml))) -> Yaml {
54
+
Block(i)
55
+
}
56
+
57
+
fn en(acc: String, in: Int, doc: Yaml) -> String {
58
+
case doc {
59
+
Int(i) -> acc <> int.to_string(i)
60
+
Float(i) -> acc <> float.to_string(i)
61
+
String(i) -> en_string(acc, i)
62
+
Array(i) -> en_array(acc, in, i)
63
+
Block(i) -> en_block(acc, in, i)
64
+
}
65
+
}
66
+
67
+
fn en_array(acc: String, in: Int, docs: List(Yaml)) -> String {
68
+
case docs {
69
+
[] -> acc
70
+
[doc, ..docs] ->
71
+
acc
72
+
|> string.append("\n")
73
+
|> indent(in)
74
+
|> string.append(case own_line(doc) {
75
+
True -> "-"
76
+
False -> "- "
77
+
})
78
+
|> en(in + 1, doc)
79
+
|> en_array(in, docs)
80
+
}
81
+
}
82
+
83
+
fn en_block(acc: String, in: Int, docs: List(#(String, Yaml))) -> String {
84
+
case docs {
85
+
[] -> acc
86
+
[#(name, doc), ..docs] ->
87
+
acc
88
+
|> string.append("\n")
89
+
|> indent(in)
90
+
|> en_string(name)
91
+
|> string.append(case own_line(doc) {
92
+
True -> ":"
93
+
False -> ": "
94
+
})
95
+
|> en(in + 1, doc)
96
+
|> en_block(in, docs)
97
+
}
98
+
}
99
+
100
+
fn indent(acc: String, i: Int) -> String {
101
+
acc <> string.repeat(" ", i)
102
+
}
103
+
104
+
fn is_simple_string(s: String) -> Bool {
105
+
case s {
106
+
"0" <> _
107
+
| "1" <> _
108
+
| "2" <> _
109
+
| "3" <> _
110
+
| "4" <> _
111
+
| "5" <> _
112
+
| "6" <> _
113
+
| "7" <> _
114
+
| "8" <> _
115
+
| "9" <> _ -> False
116
+
_ -> is_simple_string_rest(s)
117
+
}
118
+
}
119
+
120
+
fn is_simple_string_rest(s: String) -> Bool {
121
+
case s {
122
+
"" -> True
123
+
"0" <> s
124
+
| "1" <> s
125
+
| "2" <> s
126
+
| "3" <> s
127
+
| "4" <> s
128
+
| "5" <> s
129
+
| "6" <> s
130
+
| "7" <> s
131
+
| "8" <> s
132
+
| "9" <> s
133
+
| "a" <> s
134
+
| "b" <> s
135
+
| "c" <> s
136
+
| "d" <> s
137
+
| "e" <> s
138
+
| "f" <> s
139
+
| "g" <> s
140
+
| "h" <> s
141
+
| "i" <> s
142
+
| "j" <> s
143
+
| "k" <> s
144
+
| "l" <> s
145
+
| "m" <> s
146
+
| "n" <> s
147
+
| "o" <> s
148
+
| "p" <> s
149
+
| "q" <> s
150
+
| "r" <> s
151
+
| "s" <> s
152
+
| "t" <> s
153
+
| "u" <> s
154
+
| "v" <> s
155
+
| "w" <> s
156
+
| "x" <> s
157
+
| "y" <> s
158
+
| "z" <> s
159
+
| "A" <> s
160
+
| "B" <> s
161
+
| "C" <> s
162
+
| "D" <> s
163
+
| "E" <> s
164
+
| "F" <> s
165
+
| "G" <> s
166
+
| "H" <> s
167
+
| "I" <> s
168
+
| "J" <> s
169
+
| "K" <> s
170
+
| "L" <> s
171
+
| "M" <> s
172
+
| "N" <> s
173
+
| "O" <> s
174
+
| "P" <> s
175
+
| "Q" <> s
176
+
| "R" <> s
177
+
| "S" <> s
178
+
| "T" <> s
179
+
| "U" <> s
180
+
| "V" <> s
181
+
| "W" <> s
182
+
| "X" <> s
183
+
| "Y" <> s
184
+
| "Z" <> s
185
+
| "_" <> s -> is_simple_string_rest(s)
186
+
_ -> False
187
+
}
188
+
}
189
+
190
+
fn en_string(acc: String, i: String) -> String {
191
+
case is_simple_string(i) {
192
+
True -> acc <> i
193
+
False -> en_quoted_string(acc, i)
194
+
}
195
+
}
196
+
197
+
fn en_quoted_string(acc: String, i: String) -> String {
198
+
acc
199
+
<> "\""
200
+
<> {
201
+
i
202
+
|> string.replace("\\", "\\\\")
203
+
|> string.replace("\"", "\\\"")
204
+
}
205
+
<> "\""
206
+
}
207
+
208
+
fn own_line(doc: Yaml) -> Bool {
209
+
case doc {
210
+
Int(_) | Float(_) | String(_) -> False
211
+
Array(_) | Block(_) -> True
212
+
}
213
+
}
+188
test/cymbal_test.gleam
+188
test/cymbal_test.gleam
···
1
+
import cymbal.{array, block, float, int, string}
2
+
import gleeunit
3
+
import gleeunit/should
4
+
5
+
pub fn main() {
6
+
gleeunit.main()
7
+
}
8
+
9
+
pub fn encode_int_test() {
10
+
int(123)
11
+
|> cymbal.encode
12
+
|> should.equal(
13
+
"---
14
+
123
15
+
",
16
+
)
17
+
}
18
+
19
+
pub fn encode_float_test() {
20
+
float(123.45)
21
+
|> cymbal.encode
22
+
|> should.equal(
23
+
"---
24
+
123.45
25
+
",
26
+
)
27
+
}
28
+
29
+
pub fn encode_string_test() {
30
+
string("hello")
31
+
|> cymbal.encode
32
+
|> should.equal(
33
+
"---
34
+
hello
35
+
",
36
+
)
37
+
}
38
+
39
+
pub fn encode_dash_string_test() {
40
+
string("hello-world")
41
+
|> cymbal.encode
42
+
|> should.equal(
43
+
"---
44
+
\"hello-world\"
45
+
",
46
+
)
47
+
}
48
+
49
+
pub fn encode_string_with_quote_test() {
50
+
string("\"")
51
+
|> cymbal.encode
52
+
|> should.equal(
53
+
"---
54
+
\"\\\"\"
55
+
",
56
+
)
57
+
}
58
+
59
+
pub fn encode_string_with_escaped_quote_test() {
60
+
string("\\")
61
+
|> cymbal.encode
62
+
|> should.equal(
63
+
"---
64
+
\"\\\\\"
65
+
",
66
+
)
67
+
}
68
+
69
+
pub fn encode_array_test() {
70
+
array([
71
+
int(1),
72
+
int(2),
73
+
int(3),
74
+
int(4),
75
+
int(5),
76
+
int(6),
77
+
int(7),
78
+
int(8),
79
+
int(9),
80
+
int(10),
81
+
])
82
+
|> cymbal.encode
83
+
|> should.equal(
84
+
"---
85
+
- 1
86
+
- 2
87
+
- 3
88
+
- 4
89
+
- 5
90
+
- 6
91
+
- 7
92
+
- 8
93
+
- 9
94
+
- 10
95
+
",
96
+
)
97
+
}
98
+
99
+
pub fn encode_array_nested_test() {
100
+
array([
101
+
int(1),
102
+
int(2),
103
+
array([
104
+
int(3),
105
+
int(4),
106
+
int(5),
107
+
array([int(6), int(7)]),
108
+
int(8),
109
+
int(9),
110
+
int(10),
111
+
]),
112
+
])
113
+
|> cymbal.encode
114
+
|> should.equal(
115
+
"---
116
+
- 1
117
+
- 2
118
+
-
119
+
- 3
120
+
- 4
121
+
- 5
122
+
-
123
+
- 6
124
+
- 7
125
+
- 8
126
+
- 9
127
+
- 10
128
+
",
129
+
)
130
+
}
131
+
132
+
pub fn encode_block_test() {
133
+
block([
134
+
#("it1", int(1)),
135
+
#("it2", int(2)),
136
+
#("it3", int(3)),
137
+
#("it4", int(4)),
138
+
#("it5", int(5)),
139
+
])
140
+
|> cymbal.encode
141
+
|> should.equal(
142
+
"---
143
+
it1: 1
144
+
it2: 2
145
+
it3: 3
146
+
it4: 4
147
+
it5: 5
148
+
",
149
+
)
150
+
}
151
+
152
+
pub fn encode_nested_block_test() {
153
+
block([
154
+
#("it1", int(1)),
155
+
#("it2", int(2)),
156
+
#(
157
+
"nested1",
158
+
block([
159
+
#("it3", int(3)),
160
+
#("it4", int(4)),
161
+
#(
162
+
"nested2",
163
+
block([#("it3", int(3)), #("it4", int(4)), #("it5", int(5))]),
164
+
),
165
+
#("it5", int(5)),
166
+
]),
167
+
),
168
+
#("it6", int(6)),
169
+
#("it7", int(7)),
170
+
])
171
+
|> cymbal.encode
172
+
|> should.equal(
173
+
"---
174
+
it1: 1
175
+
it2: 2
176
+
nested1:
177
+
it3: 3
178
+
it4: 4
179
+
nested2:
180
+
it3: 3
181
+
it4: 4
182
+
it5: 5
183
+
it5: 5
184
+
it6: 6
185
+
it7: 7
186
+
",
187
+
)
188
+
}