diff options
author | Brian Ashworth <bosrsf04@gmail.com> | 2018-07-28 09:34:25 -0400 |
---|---|---|
committer | Brian Ashworth <bosrsf04@gmail.com> | 2018-08-01 22:47:54 -0400 |
commit | 8463a2896a932cd99f3dc93608b03cb4aba93293 (patch) | |
tree | b8c4ba994cf4d9ca7510d0f80a1864cce144092a /swaynag/main.c | |
parent | Address first round review for swaynag (diff) | |
download | sway-8463a2896a932cd99f3dc93608b03cb4aba93293.tar.gz sway-8463a2896a932cd99f3dc93608b03cb4aba93293.tar.zst sway-8463a2896a932cd99f3dc93608b03cb4aba93293.zip |
swaynag: implement config file support
Diffstat (limited to 'swaynag/main.c')
-rw-r--r-- | swaynag/main.c | 357 |
1 files changed, 256 insertions, 101 deletions
diff --git a/swaynag/main.c b/swaynag/main.c index 60560c72..b199fac4 100644 --- a/swaynag/main.c +++ b/swaynag/main.c | |||
@@ -1,10 +1,14 @@ | |||
1 | #define _XOPEN_SOURCE 500 | 1 | #define _XOPEN_SOURCE 700 |
2 | #define _POSIX_C_SOURCE 200112L | ||
2 | #include <getopt.h> | 3 | #include <getopt.h> |
3 | #include <signal.h> | 4 | #include <signal.h> |
5 | #include <stdlib.h> | ||
6 | #include <wordexp.h> | ||
4 | #include "log.h" | 7 | #include "log.h" |
5 | #include "list.h" | 8 | #include "list.h" |
6 | #include "readline.h" | 9 | #include "readline.h" |
7 | #include "swaynag/nagbar.h" | 10 | #include "swaynag/nagbar.h" |
11 | #include "swaynag/types.h" | ||
8 | #include "wlr-layer-shell-unstable-v1-client-protocol.h" | 12 | #include "wlr-layer-shell-unstable-v1-client-protocol.h" |
9 | 13 | ||
10 | static struct sway_nagbar nagbar; | 14 | static struct sway_nagbar nagbar; |
@@ -19,22 +23,6 @@ void sway_terminate(int code) { | |||
19 | exit(code); | 23 | exit(code); |
20 | } | 24 | } |
21 | 25 | ||
22 | static void set_nagbar_colors() { | ||
23 | if (nagbar.type == NAGBAR_ERROR) { | ||
24 | nagbar.colors.button_background = 0x680A0AFF; | ||
25 | nagbar.colors.background = 0x900000FF; | ||
26 | nagbar.colors.text = 0xFFFFFFFF; | ||
27 | nagbar.colors.border = 0xD92424FF; | ||
28 | nagbar.colors.border_bottom = 0x470909FF; | ||
29 | } else if (nagbar.type == NAGBAR_WARNING) { | ||
30 | nagbar.colors.button_background = 0xFFC100FF; | ||
31 | nagbar.colors.background = 0xFFA800FF; | ||
32 | nagbar.colors.text = 0x000000FF; | ||
33 | nagbar.colors.border = 0xAB7100FF; | ||
34 | nagbar.colors.border_bottom = 0xAB7100FF; | ||
35 | } | ||
36 | } | ||
37 | |||
38 | static char *read_from_stdin() { | 26 | static char *read_from_stdin() { |
39 | char *buffer = NULL; | 27 | char *buffer = NULL; |
40 | while (!feof(stdin)) { | 28 | while (!feof(stdin)) { |
@@ -61,32 +49,11 @@ static char *read_from_stdin() { | |||
61 | return buffer; | 49 | return buffer; |
62 | } | 50 | } |
63 | 51 | ||
64 | int main(int argc, char **argv) { | 52 | static int parse_options(int argc, char **argv, struct sway_nagbar *nagbar, |
65 | int exit_code = EXIT_SUCCESS; | 53 | list_t *types, char **config, bool *debug) { |
66 | bool debug = false; | ||
67 | |||
68 | memset(&nagbar, 0, sizeof(nagbar)); | ||
69 | nagbar.anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ||
70 | | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ||
71 | | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; | ||
72 | nagbar.type = NAGBAR_ERROR; | ||
73 | set_nagbar_colors(); | ||
74 | nagbar.font = strdup("pango:monospace 10"); | ||
75 | nagbar.buttons = create_list(); | ||
76 | |||
77 | struct sway_nagbar_button *button_close = | ||
78 | calloc(sizeof(struct sway_nagbar_button), 1); | ||
79 | button_close->text = strdup("X"); | ||
80 | button_close->type = NAGBAR_ACTION_DISMISS; | ||
81 | list_add(nagbar.buttons, button_close); | ||
82 | |||
83 | struct sway_nagbar_button *button_details = | ||
84 | calloc(sizeof(struct sway_nagbar_button), 1); | ||
85 | button_details->text = strdup("Toggle Details"); | ||
86 | button_details->type = NAGBAR_ACTION_EXPAND; | ||
87 | |||
88 | static struct option opts[] = { | 54 | static struct option opts[] = { |
89 | {"button", required_argument, NULL, 'b'}, | 55 | {"button", required_argument, NULL, 'b'}, |
56 | {"config", required_argument, NULL, 'c'}, | ||
90 | {"debug", no_argument, NULL, 'd'}, | 57 | {"debug", no_argument, NULL, 'd'}, |
91 | {"edge", required_argument, NULL, 'e'}, | 58 | {"edge", required_argument, NULL, 'e'}, |
92 | {"font", required_argument, NULL, 'f'}, | 59 | {"font", required_argument, NULL, 'f'}, |
@@ -106,6 +73,7 @@ int main(int argc, char **argv) { | |||
106 | "\n" | 73 | "\n" |
107 | " -b, --button <text> <action> Create a button with text that " | 74 | " -b, --button <text> <action> Create a button with text that " |
108 | "executes action when pressed. Multiple buttons can be defined.\n" | 75 | "executes action when pressed. Multiple buttons can be defined.\n" |
76 | " -c, --config <path> Path to config file.\n" | ||
109 | " -d, --debug Enable debugging.\n" | 77 | " -d, --debug Enable debugging.\n" |
110 | " -e, --edge top|bottom Set the edge to use.\n" | 78 | " -e, --edge top|bottom Set the edge to use.\n" |
111 | " -f, --font <font> Set the font to use.\n" | 79 | " -f, --font <font> Set the font to use.\n" |
@@ -115,114 +83,301 @@ int main(int argc, char **argv) { | |||
115 | " -m, --message <msg> Set the message text.\n" | 83 | " -m, --message <msg> Set the message text.\n" |
116 | " -o, --output <output> Set the output to use.\n" | 84 | " -o, --output <output> Set the output to use.\n" |
117 | " -s, --dismiss-button <text> Set the dismiss button text.\n" | 85 | " -s, --dismiss-button <text> Set the dismiss button text.\n" |
118 | " -t, --type error|warning Set the message type.\n" | 86 | " -t, --type <type> Set the message type.\n" |
119 | " -v, --version Show the version number and quit.\n"; | 87 | " -v, --version Show the version number and quit.\n"; |
120 | 88 | ||
89 | optind = 1; | ||
121 | while (1) { | 90 | while (1) { |
122 | int c = getopt_long(argc, argv, "b:de:f:hlL:m:o:s:t:v", opts, NULL); | 91 | int c = getopt_long(argc, argv, "b:c:de:f:hlL:m:o:s:t:v", opts, NULL); |
123 | if (c == -1) { | 92 | if (c == -1) { |
124 | break; | 93 | break; |
125 | } | 94 | } |
126 | switch (c) { | 95 | switch (c) { |
127 | case 'b': // Button | 96 | case 'b': // Button |
128 | if (optind >= argc) { | 97 | if (nagbar) { |
129 | fprintf(stderr, "Missing action for button %s\n", optarg); | 98 | if (optind >= argc) { |
130 | exit_code = EXIT_FAILURE; | 99 | fprintf(stderr, "Missing action for button %s\n", optarg); |
131 | goto cleanup; | 100 | return EXIT_FAILURE; |
132 | } | 101 | } |
133 | struct sway_nagbar_button *button; | 102 | struct sway_nagbar_button *button; |
134 | button = calloc(sizeof(struct sway_nagbar_button), 1); | 103 | button = calloc(sizeof(struct sway_nagbar_button), 1); |
135 | button->text = strdup(optarg); | 104 | button->text = strdup(optarg); |
136 | button->type = NAGBAR_ACTION_COMMAND; | 105 | button->type = NAGBAR_ACTION_COMMAND; |
137 | button->action = strdup(argv[optind]); | 106 | button->action = strdup(argv[optind]); |
138 | optind++; | 107 | optind++; |
139 | list_add(nagbar.buttons, button); | 108 | list_add(nagbar->buttons, button); |
109 | } | ||
110 | break; | ||
111 | case 'c': // Config | ||
112 | if (config) { | ||
113 | *config = strdup(optarg); | ||
114 | } | ||
140 | break; | 115 | break; |
141 | case 'd': // Debug | 116 | case 'd': // Debug |
142 | debug = true; | 117 | if (debug) { |
118 | *debug = true; | ||
119 | } | ||
143 | break; | 120 | break; |
144 | case 'e': // Edge | 121 | case 'e': // Edge |
145 | if (strcmp(optarg, "top") == 0) { | 122 | if (nagbar) { |
146 | nagbar.anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | 123 | if (strcmp(optarg, "top") == 0) { |
147 | | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | 124 | nagbar->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
148 | | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; | 125 | | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
149 | } else if (strcmp(optarg, "bottom") == 0) { | 126 | | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; |
150 | nagbar.anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | 127 | } else if (strcmp(optarg, "bottom") == 0) { |
151 | | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | 128 | nagbar->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
152 | | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; | 129 | | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
153 | } else { | 130 | | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; |
154 | fprintf(stderr, "Invalid edge: %s\n", optarg); | 131 | } else { |
155 | exit_code = EXIT_FAILURE; | 132 | fprintf(stderr, "Invalid edge: %s\n", optarg); |
156 | goto cleanup; | 133 | return EXIT_FAILURE; |
134 | } | ||
157 | } | 135 | } |
158 | break; | 136 | break; |
159 | case 'f': // Font | 137 | case 'f': // Font |
160 | free(nagbar.font); | 138 | if (nagbar) { |
161 | nagbar.font = strdup(optarg); | 139 | free(nagbar->font); |
140 | nagbar->font = strdup(optarg); | ||
141 | } | ||
162 | break; | 142 | break; |
163 | case 'l': // Detailed Message | 143 | case 'l': // Detailed Message |
164 | free(nagbar.details.message); | 144 | if (nagbar) { |
165 | nagbar.details.message = read_from_stdin(); | 145 | free(nagbar->details.message); |
166 | nagbar.details.button_up.text = strdup("▲"); | 146 | nagbar->details.message = read_from_stdin(); |
167 | nagbar.details.button_down.text = strdup("▼"); | 147 | nagbar->details.button_up.text = strdup("▲"); |
148 | nagbar->details.button_down.text = strdup("▼"); | ||
149 | } | ||
168 | break; | 150 | break; |
169 | case 'L': // Detailed Button Text | 151 | case 'L': // Detailed Button Text |
170 | free(button_details->text); | 152 | if (nagbar) { |
171 | button_details->text = strdup(optarg); | 153 | free(nagbar->details.button_details.text); |
154 | nagbar->details.button_details.text = strdup(optarg); | ||
155 | } | ||
172 | break; | 156 | break; |
173 | case 'm': // Message | 157 | case 'm': // Message |
174 | free(nagbar.message); | 158 | if (nagbar) { |
175 | nagbar.message = strdup(optarg); | 159 | free(nagbar->message); |
160 | nagbar->message = strdup(optarg); | ||
161 | } | ||
176 | break; | 162 | break; |
177 | case 'o': // Output | 163 | case 'o': // Output |
178 | free(nagbar.output.name); | 164 | if (nagbar) { |
179 | nagbar.output.name = strdup(optarg); | 165 | free(nagbar->output.name); |
166 | nagbar->output.name = strdup(optarg); | ||
167 | } | ||
180 | break; | 168 | break; |
181 | case 's': // Dismiss Button Text | 169 | case 's': // Dismiss Button Text |
182 | free(button_close->text); | 170 | if (nagbar) { |
183 | button_close->text = strdup(optarg); | 171 | struct sway_nagbar_button *button_close; |
172 | button_close = nagbar->buttons->items[0]; | ||
173 | free(button_close->text); | ||
174 | button_close->text = strdup(optarg); | ||
175 | } | ||
184 | break; | 176 | break; |
185 | case 't': // Type | 177 | case 't': // Type |
186 | if (strcmp(optarg, "error") == 0) { | 178 | if (nagbar) { |
187 | nagbar.type = NAGBAR_ERROR; | 179 | nagbar->type = nagbar_type_get(types, optarg); |
188 | } else if (strcmp(optarg, "warning") == 0) { | 180 | if (!nagbar->type) { |
189 | nagbar.type = NAGBAR_WARNING; | 181 | fprintf(stderr, "Unknown type %s\n", optarg); |
190 | } else { | 182 | return EXIT_FAILURE; |
191 | fprintf(stderr, "Type must be either 'error' or 'warning'\n"); | 183 | } |
192 | exit_code = EXIT_FAILURE; | ||
193 | goto cleanup; | ||
194 | } | 184 | } |
195 | set_nagbar_colors(); | ||
196 | break; | 185 | break; |
197 | case 'v': // Version | 186 | case 'v': // Version |
198 | fprintf(stdout, "swaynag version " SWAY_VERSION "\n"); | 187 | fprintf(stdout, "swaynag version " SWAY_VERSION "\n"); |
199 | exit_code = EXIT_SUCCESS; | 188 | return -1; |
200 | goto cleanup; | ||
201 | default: // Help or unknown flag | 189 | default: // Help or unknown flag |
202 | fprintf(c == 'h' ? stdout : stderr, "%s", usage); | 190 | fprintf(c == 'h' ? stdout : stderr, "%s", usage); |
203 | exit_code = c == 'h' ? EXIT_SUCCESS : EXIT_FAILURE; | 191 | return -1; |
204 | goto cleanup; | 192 | } |
193 | } | ||
194 | |||
195 | return 0; | ||
196 | } | ||
197 | |||
198 | static bool file_exists(const char *path) { | ||
199 | return path && access(path, R_OK) != -1; | ||
200 | } | ||
201 | |||
202 | static char *get_config_path(void) { | ||
203 | static const char *config_paths[] = { | ||
204 | "$HOME/.swaynag/config", | ||
205 | "$XDG_CONFIG_HOME/swaynag/config", | ||
206 | SYSCONFDIR "/swaynag/config", | ||
207 | }; | ||
208 | |||
209 | if (!getenv("XDG_CONFIG_HOME")) { | ||
210 | char *home = getenv("HOME"); | ||
211 | char *config_home = malloc(strlen(home) + strlen("/.config") + 1); | ||
212 | if (!config_home) { | ||
213 | wlr_log(WLR_ERROR, "Unable to allocate $HOME/.config"); | ||
214 | } else { | ||
215 | strcpy(config_home, home); | ||
216 | strcat(config_home, "/.config"); | ||
217 | setenv("XDG_CONFIG_HOME", config_home, 1); | ||
218 | wlr_log(WLR_DEBUG, "Set XDG_CONFIG_HOME to %s", config_home); | ||
219 | free(config_home); | ||
220 | } | ||
221 | } | ||
222 | |||
223 | wordexp_t p; | ||
224 | char *path; | ||
225 | for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) { | ||
226 | if (wordexp(config_paths[i], &p, 0) == 0) { | ||
227 | path = strdup(p.we_wordv[0]); | ||
228 | wordfree(&p); | ||
229 | if (file_exists(path)) { | ||
230 | return path; | ||
231 | } | ||
232 | free(path); | ||
233 | } | ||
234 | } | ||
235 | |||
236 | return NULL; | ||
237 | } | ||
238 | |||
239 | static int load_config(char *path, struct sway_nagbar *nagbar, list_t *types) { | ||
240 | FILE *config = fopen(path, "r"); | ||
241 | if (!config) { | ||
242 | fprintf(stderr, "Failed to read config. Running without it.\n"); | ||
243 | return 0; | ||
244 | } | ||
245 | struct sway_nagbar_type *type = NULL; | ||
246 | char *line; | ||
247 | int line_number = 0; | ||
248 | while (!feof(config)) { | ||
249 | line = read_line(config); | ||
250 | if (!line) { | ||
251 | continue; | ||
252 | } | ||
253 | |||
254 | line_number++; | ||
255 | if (line[0] == '#') { | ||
256 | free(line); | ||
257 | continue; | ||
205 | } | 258 | } |
259 | if (strlen(line) == 0) { | ||
260 | free(line); | ||
261 | continue; | ||
262 | } | ||
263 | |||
264 | if (line[0] == '[') { | ||
265 | char *close = strchr(line, ']'); | ||
266 | if (!close) { | ||
267 | free(line); | ||
268 | fclose(config); | ||
269 | fprintf(stderr, "Closing bracket not found on line %d\n", | ||
270 | line_number); | ||
271 | return 1; | ||
272 | } | ||
273 | char *name = calloc(1, close - line); | ||
274 | strncat(name, line + 1, close - line - 1); | ||
275 | type = nagbar_type_get(types, name); | ||
276 | if (!type) { | ||
277 | type = calloc(1, sizeof(struct sway_nagbar_type)); | ||
278 | type->name = strdup(name); | ||
279 | list_add(types, type); | ||
280 | } | ||
281 | free(name); | ||
282 | } else { | ||
283 | char flag[strlen(line) + 3]; | ||
284 | sprintf(flag, "--%s", line); | ||
285 | char *argv[] = {"swaynag", flag}; | ||
286 | int result; | ||
287 | if (type) { | ||
288 | result = nagbar_parse_type(2, argv, type); | ||
289 | } else { | ||
290 | result = parse_options(2, argv, nagbar, types, NULL, NULL); | ||
291 | } | ||
292 | if (result != 0) { | ||
293 | free(line); | ||
294 | fclose(config); | ||
295 | return result; | ||
296 | } | ||
297 | } | ||
298 | |||
299 | free(line); | ||
206 | } | 300 | } |
301 | fclose(config); | ||
302 | return 0; | ||
303 | } | ||
304 | |||
305 | int main(int argc, char **argv) { | ||
306 | int exit_code = EXIT_SUCCESS; | ||
307 | |||
308 | list_t *types = create_list(); | ||
309 | nagbar_types_add_default(types); | ||
310 | |||
311 | memset(&nagbar, 0, sizeof(nagbar)); | ||
312 | nagbar.anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ||
313 | | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ||
314 | | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; | ||
315 | nagbar.font = strdup("pango:monospace 10"); | ||
316 | nagbar.buttons = create_list(); | ||
317 | |||
318 | struct sway_nagbar_button *button_close = | ||
319 | calloc(sizeof(struct sway_nagbar_button), 1); | ||
320 | button_close->text = strdup("X"); | ||
321 | button_close->type = NAGBAR_ACTION_DISMISS; | ||
322 | list_add(nagbar.buttons, button_close); | ||
323 | |||
324 | nagbar.details.button_details.text = strdup("Toggle Details"); | ||
325 | nagbar.details.button_details.type = NAGBAR_ACTION_EXPAND; | ||
207 | 326 | ||
327 | |||
328 | char *config_path = NULL; | ||
329 | bool debug = false; | ||
330 | int launch_status = parse_options(argc, argv, NULL, NULL, | ||
331 | &config_path, &debug); | ||
332 | if (launch_status != 0) { | ||
333 | exit_code = launch_status; | ||
334 | goto cleanup; | ||
335 | } | ||
208 | wlr_log_init(debug ? WLR_DEBUG : WLR_ERROR, NULL); | 336 | wlr_log_init(debug ? WLR_DEBUG : WLR_ERROR, NULL); |
209 | 337 | ||
338 | if (!config_path) { | ||
339 | config_path = get_config_path(); | ||
340 | } | ||
341 | if (config_path) { | ||
342 | wlr_log(WLR_DEBUG, "Loading config file: %s", config_path); | ||
343 | int config_status = load_config(config_path, &nagbar, types); | ||
344 | free(config_path); | ||
345 | if (config_status != 0) { | ||
346 | exit_code = config_status; | ||
347 | goto cleanup; | ||
348 | } | ||
349 | } | ||
350 | |||
351 | if (argc > 1) { | ||
352 | int result = parse_options(argc, argv, &nagbar, types, NULL, NULL); | ||
353 | if (result != 0) { | ||
354 | exit_code = result; | ||
355 | goto cleanup; | ||
356 | } | ||
357 | } | ||
358 | |||
210 | if (!nagbar.message) { | 359 | if (!nagbar.message) { |
211 | wlr_log(WLR_ERROR, "No message passed. Please provide --message/-m"); | 360 | wlr_log(WLR_ERROR, "No message passed. Please provide --message/-m"); |
212 | exit_code = EXIT_FAILURE; | 361 | exit_code = EXIT_FAILURE; |
213 | goto cleanup; | 362 | goto cleanup; |
214 | } | 363 | } |
215 | 364 | ||
365 | if (!nagbar.type) { | ||
366 | nagbar.type = nagbar_type_get(types, "error"); | ||
367 | } | ||
368 | |||
369 | nagbar.type = nagbar_type_clone(nagbar.type); | ||
370 | nagbar_types_free(types); | ||
371 | |||
216 | if (nagbar.details.message) { | 372 | if (nagbar.details.message) { |
217 | list_add(nagbar.buttons, button_details); | 373 | list_add(nagbar.buttons, &nagbar.details.button_details); |
218 | } else { | 374 | } else { |
219 | free(button_details->text); | 375 | free(nagbar.details.button_details.text); |
220 | free(button_details); | ||
221 | } | 376 | } |
222 | 377 | ||
223 | wlr_log(WLR_DEBUG, "Output: %s", nagbar.output.name); | 378 | wlr_log(WLR_DEBUG, "Output: %s", nagbar.output.name); |
224 | wlr_log(WLR_DEBUG, "Anchors: %d", nagbar.anchors); | 379 | wlr_log(WLR_DEBUG, "Anchors: %d", nagbar.anchors); |
225 | wlr_log(WLR_DEBUG, "Type: %d", nagbar.type); | 380 | wlr_log(WLR_DEBUG, "Type: %s", nagbar.type->name); |
226 | wlr_log(WLR_DEBUG, "Message: %s", nagbar.message); | 381 | wlr_log(WLR_DEBUG, "Message: %s", nagbar.message); |
227 | wlr_log(WLR_DEBUG, "Font: %s", nagbar.font); | 382 | wlr_log(WLR_DEBUG, "Font: %s", nagbar.font); |
228 | wlr_log(WLR_DEBUG, "Buttons"); | 383 | wlr_log(WLR_DEBUG, "Buttons"); |
@@ -238,8 +393,8 @@ int main(int argc, char **argv) { | |||
238 | return exit_code; | 393 | return exit_code; |
239 | 394 | ||
240 | cleanup: | 395 | cleanup: |
241 | free(button_details->text); | 396 | nagbar_types_free(types); |
242 | free(button_details); | 397 | free(nagbar.details.button_details.text); |
243 | nagbar_destroy(&nagbar); | 398 | nagbar_destroy(&nagbar); |
244 | return exit_code; | 399 | return exit_code; |
245 | } | 400 | } |