Restore CLI
This commit is contained in:
parent
73ffe4ea88
commit
55fd264215
|
@ -0,0 +1,13 @@
|
|||
cmake_minimum_required(VERSION 3.14)
|
||||
project(hyperbuild-cli C)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
# TODO Include submodule config, don't hardcode submodule's dependencies
|
||||
include_directories(lib src ext/hyperbuild/lib)
|
||||
|
||||
add_executable(hyperbuild-cli
|
||||
src/hbcli/err.c
|
||||
src/hbcli/opt.c
|
||||
src/hbcli/arg/suppress.c
|
||||
src/hbcli/main.c src/hbcli/arg/tags.c)
|
|
@ -0,0 +1,318 @@
|
|||
|
||||
|
||||
## Parsing
|
||||
|
||||
Current limitations:
|
||||
|
||||
### Errors
|
||||
|
||||
Errors marked with a `⌫` can be suppressed using the [`--suppress`](#--suppress) option.
|
||||
|
||||
#### `MALFORMED_ENTITY` ⌫
|
||||
|
||||
It's an error if the sequence of characters following an ampersand (`&`) does not form a valid entity.
|
||||
|
||||
Entities must be of one of the following forms:
|
||||
|
||||
- `&name;`, where *name* is a reference to a valid HTML entity
|
||||
- `&#nnnn;`, where *nnnn* is a Unicode code point in base 10
|
||||
- `&#xhhhh;`, where *hhhh* is a Unicode code point in base 16
|
||||
|
||||
A malformed entity is an ampersand not followed by a sequence of characters that matches one of the above forms. This includes when the semicolon is missing.
|
||||
|
||||
Note that this is different from `INVALID_ENTITY`, which is when a well-formed entity references a non-existent entity name or Unicode code point.
|
||||
|
||||
While an ampersand by itself (i.e. followed by whitespace or as the last character) is a malformed entity, it is covered by `BARE_AMPERSAND`.
|
||||
|
||||
#### `BARE_AMPERSAND` ⌫
|
||||
|
||||
It's an error to have an ampersand followed by whitespace or as the last character.
|
||||
|
||||
This is intentionally a different error to `MALFORMED_ENTITY` due to the ubiquity of bare ampersands.
|
||||
|
||||
An ampersand by itself is not *necessarily* an invalid entity. However, HTML parsers and browsers may have different interpretations of bare ampersands, so it's a good idea to always use the encoded form (`&`).
|
||||
|
||||
When this error is suppressed, bare ampersands are outputted untouched.
|
||||
|
||||
#### `INVALID_ENTITY` ⌫
|
||||
|
||||
It's an error if an invalid HTML entity is detected.
|
||||
|
||||
If suppressed, invalid entities are outputted untouched.
|
||||
|
||||
See [entityrefs.c](src/main/c/rule/entity/entityrefs.c) for the list of entity references considered valid by hyperbuild.
|
||||
|
||||
Valid entities that reference a Unicode code point must be between 0x0 and 0x10FFFF (inclusive).
|
||||
|
||||
#### `NONSTANDARD_TAG` ⌫
|
||||
|
||||
It's an error if an unknown (non-standard) tag is reached.
|
||||
See [tags.c](src/main/c/rule/tag/tags.c) for the list of tags considered valid by hyperbuild.
|
||||
|
||||
#### `UCASE_TAG` ⌫
|
||||
|
||||
It's an error if an opening or closing tag's name has any uppercase characters.
|
||||
|
||||
#### `UCASE_ATTR` ⌫
|
||||
|
||||
It's an error if an attribute's name has any uppercase characters.
|
||||
|
||||
#### `UNQUOTED_ATTR` ⌫
|
||||
|
||||
It's an error if an attribute's value is not quoted with `"` (U+0022) or `'` (U+0027).
|
||||
This means that `` ` `` is not a valid quote mark regardless of whether this error is suppressed or not. Backticks are valid attribute value quotes in Internet Explorer.
|
||||
|
||||
#### `ILLEGAL_CHILD`
|
||||
|
||||
It's an error if a tag is declared where it can't be a child of.
|
||||
This is a very simple check, and does not cover the comprehensive HTML rules, which involve backtracking, tree traversal, and lots of conditionals.
|
||||
|
||||
This rule is enforced in four parts:
|
||||
[whitelistparents.c](src/main/c/rule/relation/whitelistparents.c),
|
||||
[blacklistparents.c](src/main/c/rule/relation/blacklistparents.c),
|
||||
[whitelistchildren.c](src/main/c/rule/relation/whitelistchildren.c), and
|
||||
[blacklistchildren.c](src/main/c/rule/relation/blacklistchildren.c).
|
||||
|
||||
#### `UNCLOSED_TAG`
|
||||
|
||||
It's an error if a non-void tag is not closed.
|
||||
See [voidtags.c](src/main/c/rule/tag/voidtags.c) for the list of tags considered void by hyperbuild.
|
||||
|
||||
This includes tags that close automatically because of siblings (e.g. `<li><li>`), as it greatly simplifies the complexity of the minifier due to guarantees about the structure.
|
||||
|
||||
#### `SELF_CLOSING_TAG` ⌫
|
||||
|
||||
It's an error if a tag is self-closed. Valid in XML, not in HTML.
|
||||
|
||||
#### `NO_SPACE_BEFORE_ATTR`
|
||||
|
||||
It's an error if there is no whitespace before an attribute.
|
||||
|
||||
Most likely, the cause of this error is either invalid syntax or something like:
|
||||
|
||||
```html
|
||||
<div class="a"name="1"></div>
|
||||
```
|
||||
|
||||
(Note the lack of space between the end of the `class` attribute and the beginning of the `name` attribute.)
|
||||
|
||||
#### `UNEXPECTED_END` and `EXPECTED_NOT_FOUND`
|
||||
|
||||
General syntax errors.
|
||||
|
||||
#### Additional errors
|
||||
|
||||
There are additional implicit errors that are considered as general syntax errors due to the way the parser works:
|
||||
|
||||
- Closing void tags; see [voidtags.c](src/main/c/rule/tag/voidtags.c) for the list of tags considered void by hyperbuild.
|
||||
- Placing whitespace between `=` and attribute names/values.
|
||||
- Placing whitespace before the tag name in an opening tag.
|
||||
- Placing whitespace around the tag name in a closing tag.
|
||||
- Not closing a tag before the end of the file/input.
|
||||
|
||||
#### Notes
|
||||
|
||||
- Closing `</script>` tags end single-line and multi-line JavaScript comments in `script` tags.
|
||||
For this to be detected by hyperbuild, the closing tag must not contain any whitespace (e.g. `</script >`).
|
||||
|
||||
### Options
|
||||
|
||||
#### `--in`
|
||||
|
||||
Path to a file to process. If omitted, hyperbuild will read from `stdin`.
|
||||
|
||||
#### `--out`
|
||||
|
||||
Path to a file to write to; it will be created if it doesn't exist already. If omitted, the output will be streamed to `stdout`.
|
||||
|
||||
#### `--keep`
|
||||
|
||||
Don't automatically delete the output file if an error occurred. If the output is `stdout`, or the output is a file but `--buffer` is provided, this option does nothing.
|
||||
|
||||
#### `--buffer`
|
||||
|
||||
Buffer all output until the process is complete and successful. This won't truncate or write anything to the output until the build process is done, but will use a non-constant amount of memory.
|
||||
This applies even when the output is `stdout`.
|
||||
|
||||
#### `--suppress`
|
||||
|
||||
Suppress errors specified by this option. hyperbuild will quitely ignore and continue processing when otherwise one of the provided errors would occur.
|
||||
|
||||
Suppressible errors are marked with a `⌫` in the [Errors](#errors) section. Omit the `` prefix. Separate the error names with commas.
|
||||
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
Note that only existing whitespace will be up for removal via minification. Entities that represent whitespace will not be decoded and then removed.
|
||||
|
||||
For options that have a list of tags as their value, the tags should be separated by a comma.
|
||||
|
||||
An `*` (asterisk, U+002A) can be used to represent the complete set of possible tags. Providing no value represents the empty set.
|
||||
Both values essentially fully enables or disables the option.
|
||||
|
||||
For brevity, hyperbuild has built-in sets of tags that can be used in place of declaring all their members; they begin with a `$` sign:
|
||||
|
||||
|Name|Tags|Source|
|
||||
|---|---|---|
|
||||
|`$content`|`address`, `audio`, `button`, `canvas`, `caption`, `figcaption`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `legend`, `meter`, `object`, `option`, `p`, `summary`, `textarea`, `video`|[contenttags.c](src/main/c/rule/tag/contenttags.c)|
|
||||
|`$contentfirst`|`dd`, `details`, `dt`, `iframe`, `label`, `li`, `noscript`, `output`, `progress`, `slot`, `td`, `template`, `th`|[contentfirsttags.c](src/main/c/rule/tag/contentfirsttags.c)|
|
||||
|`$formatting`|`a`, `abbr`, `b`, `bdi`, `bdo`, `cite`, `data`, `del`, `dfn`, `em`, `i`, `ins`, `kbd`, `mark`, `q`, `rp`, `rt`, `rtc`, `ruby`, `s`, `samp`, `small`, `span`, `strong`, `sub`, `sup`, `time`, `u`, `var`, `wbr`|[formattingtags.c](src/main/c/rule/tag/formattingtags.c)|
|
||||
|`$layout`|`blockquote`, `body`, `colgroup`, `datalist`, `dialog`, `div`, `dl`, `fieldset`, `figure`, `footer`, `form`, `head`, `header`, `hgroup`, `html`, `main`, `map`, `menu`, `nav`, `ol`, `optgroup`, `picture`, `section`, `select`, `table`, `tbody`, `tfoot`, `thead`, `tr`, `ul`|[layouttags.c](src/main/c/rule/tag/layouttags.c)|
|
||||
|`$specific`|All [SVG tags](src/main/c/rule/tag/svgtags.c), `area`, `base`, `br`, `code`, `col`, `embed`, `hr`, `img`, `input`, `param`, `pre`, `script`, `source`, `track`|[specifictags.c](src/main/c/rule/tag/specifictags.c)|
|
||||
|`$heading`|`hgroup`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`|[headingtags.c](src/main/c/rule/tag/headingtags.c)|
|
||||
|`$media`|`audio`, `video`|[mediatags.c](src/main/c/rule/tag/mediatags.c)|
|
||||
|`$sectioning`|`article`, `aside`, `nav`, `section`|[sectioningtags.c](src/main/c/rule/tag/sectioningtags.c)|
|
||||
|`$void`|`area`, `base`, `br`, `col`, `embed`, `hr`, `img`, `input`, `keygen`, `link`, `meta`, `param`, `source`, `track`, `wbr`|[voidtags.c](src/main/c/rule/tag/voidtags.c)|
|
||||
|`$wss`|`pre`, `code`|[wsstags.c](src/main/c/rule/tag/wsstags.c)|
|
||||
|
||||
As an example, for `--MXcollapseWhitespace`, here are some possible values:
|
||||
|
||||
|Arguments|Description|
|
||||
|---|---|
|
||||
|`--MXcollapseWhitespace $wss`|Collapse whitespace in all tags except `$wss` ones|
|
||||
|`--MXcollapseWhitespace $content,$wss`|Collapse whitespace in all tags except `$content` and `$wss` ones|
|
||||
|`--MXcollapseWhitespace $content,$wss,dd`|Collapse whitespace in all tags except `$content` and `$wss` ones, as well as the `dd` tag|
|
||||
|`--MXcollapseWhitespace sup,dd`|Collapse whitespace in all tags except `sup` and `dd`|
|
||||
|`--MXcollapseWhitespace`|Collapse whitespace in all tags|
|
||||
|`--MXcollapseWhitespace *`|Don't collapse whitespace in any tag|
|
||||
|
||||
#### `--MXcollapseWhitespace $wss`
|
||||
|
||||
Reduce a sequence of whitespace characters in text nodes to a single space (U+0020), unless they are a child of the tags specified by this option.
|
||||
|
||||
<table><thead><tr><th>Before<th>After<tbody><tr><td>
|
||||
|
||||
```html
|
||||
<p>↵
|
||||
··The·quick·brown·fox↵
|
||||
··jumps·over·the·lazy↵
|
||||
··dog.↵
|
||||
</p>
|
||||
```
|
||||
|
||||
<td>
|
||||
|
||||
```html
|
||||
<p>·The·quick·brown·fox·jumps·over·the·lazy·dog.·</p>
|
||||
```
|
||||
|
||||
</table>
|
||||
|
||||
#### `--MXdestroyWholeWhitespace $wss,$content,$formatting`
|
||||
|
||||
Remove any text nodes that only consist of whitespace characters, unless they are a child of the tags specified by this option.
|
||||
|
||||
Especially useful when using `display: inline-block` so that whitespace between elements (e.g. indentation) does not alter layout and styling.
|
||||
|
||||
<table><thead><tr><th>Before<th>After<tbody><tr><td>
|
||||
|
||||
```html
|
||||
<div>↵
|
||||
··<h1></h1>↵
|
||||
··<ul></ul>↵
|
||||
··A·quick·<strong>brown</strong>·<em>fox</em>.↵
|
||||
</div>
|
||||
```
|
||||
|
||||
<td>
|
||||
|
||||
```html
|
||||
<div><h1></h1><ul></ul>↵
|
||||
··A·quick·<strong>brown</strong><em>fox</em>.↵
|
||||
</div>
|
||||
```
|
||||
|
||||
</table>
|
||||
|
||||
#### `--MXtrimWhitespace $wss,$formatting`
|
||||
|
||||
Remove any whitespace from the start and end of a tag, if the first and/or last node is a text node, unless the tag is one of the tags specified by this option.
|
||||
|
||||
Useful when combined with whitespace collapsing.
|
||||
|
||||
Other whitespace between text nodes and tags are not removed, as it is not recommended to mix non-formatting tags with raw text.
|
||||
|
||||
Basically, a tag should only either contain text and [formatting tags](#formatting-tags), or only non-formatting tags.
|
||||
|
||||
<table><thead><tr><th>Before<th>After<tbody><tr><td>
|
||||
|
||||
```html
|
||||
<p>↵
|
||||
··Hey,·I·<em>just</em>·found↵
|
||||
··out·about·this·<strong>cool</strong>·website!↵
|
||||
··<div></div>↵
|
||||
</p>
|
||||
```
|
||||
|
||||
<td>
|
||||
|
||||
```html
|
||||
<p>Hey,·I·<em>just</em>·found↵
|
||||
··out·about·this·<strong>cool</strong>·website!↵
|
||||
··<div></div></p>
|
||||
```
|
||||
|
||||
</table>
|
||||
|
||||
#### `--MXtrimClassAttribute`
|
||||
|
||||
Don't trim and collapse whitespace in `class` attribute values.
|
||||
|
||||
<table><thead><tr><th>Before<th>After<tbody><tr><td>
|
||||
|
||||
```html
|
||||
<div class="
|
||||
hi
|
||||
lo
|
||||
a b c
|
||||
d e
|
||||
f g
|
||||
"></div>
|
||||
```
|
||||
|
||||
<td>
|
||||
|
||||
```html
|
||||
<div class="hi lo a b c d e f g"></div>
|
||||
```
|
||||
|
||||
</table>
|
||||
|
||||
#### `--MXdecEnt`
|
||||
|
||||
Don't decode any valid entities into their UTF-8 values.
|
||||
|
||||
#### `--MXcondComments`
|
||||
|
||||
Don't minify the contents of conditional comments, including downlevel-revealed conditional comments.
|
||||
|
||||
#### `--MXattrQuotes`
|
||||
|
||||
Don't remove quotes around attribute values when possible.
|
||||
|
||||
#### `--MXcomments`
|
||||
|
||||
Don't remove any comments. Conditional comments are never removed regardless of this setting.
|
||||
|
||||
#### `--MXoptTags`
|
||||
|
||||
Don't remove optional starting or ending tags.
|
||||
|
||||
#### `--MXtagWS`
|
||||
|
||||
Don't remove spaces between attributes when possible.
|
||||
|
||||
|
||||
|
||||
### Non-options
|
||||
|
||||
#### Explicitly important
|
||||
|
||||
The following removal of attributes and tags as minification strategies are not available in hyperbuild, as it is assumed there is a special reason for their declaration:
|
||||
|
||||
- empty attributes (including ones that would be empty after minification e.g. `class=" "`)
|
||||
- empty elements
|
||||
- redundant attributes
|
||||
- `type` attribute on `<script>` tags
|
||||
- `type` attribute on `<style>` and `<link>` tags
|
|
@ -0,0 +1 @@
|
|||
#pragma once
|
|
@ -0,0 +1,36 @@
|
|||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <hb/err.h>
|
||||
#include <hbcli/arg.h>
|
||||
#include <hbcli/err.h>
|
||||
|
||||
void hbcli_arg_suppress_parse(hb_err_set* suppressed_errors, char *argv) {
|
||||
if (argv == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(argv, "MALFORMED_ENTITY") == 0) {
|
||||
hb_err_set_add(suppressed_errors, HB_ERR_PARSE_MALFORMED_ENTITY);
|
||||
|
||||
} else if (strcmp(argv, "INVALID_ENTITY") == 0) {
|
||||
hb_err_set_add(suppressed_errors, HB_ERR_PARSE_INVALID_ENTITY);
|
||||
|
||||
} else if (strcmp(argv, "NONSTANDARD_TAG") == 0) {
|
||||
hb_err_set_add(suppressed_errors, HB_ERR_PARSE_NONSTANDARD_TAG);
|
||||
|
||||
} else if (strcmp(argv, "UCASE_ATTR") == 0) {
|
||||
hb_err_set_add(suppressed_errors, HB_ERR_PARSE_UCASE_ATTR);
|
||||
|
||||
} else if (strcmp(argv, "UCASE_TAG") == 0) {
|
||||
hb_err_set_add(suppressed_errors, HB_ERR_PARSE_UCASE_TAG);
|
||||
|
||||
} else if (strcmp(argv, "UNQUOTED_ATTR") == 0) {
|
||||
hb_err_set_add(suppressed_errors, HB_ERR_PARSE_UNQUOTED_ATTR);
|
||||
|
||||
} else if (strcmp(argv, "SELF_CLOSING_TAG") == 0) {
|
||||
hb_err_set_add(suppressed_errors, HB_ERR_PARSE_SELF_CLOSING_TAG);
|
||||
|
||||
} else {
|
||||
hbcli_err("Unrecognised suppressable error `%s`", argv);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#include <hbcli/arg.h>
|
||||
#include <hb/collection.h>
|
||||
|
||||
void hbcli_arg_tags_parse(char* raw, hb_set_tag_names* set) {
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void hbcli_err(char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
bool is_tty = isatty(fileno(stdout));
|
||||
|
||||
if (is_tty) {
|
||||
fprintf(stderr, "\x1B[31m\x1B[1m");
|
||||
}
|
||||
vfprintf(stderr, format, args);
|
||||
if (is_tty) {
|
||||
fprintf(stderr, "\x1B[0m");
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
exit(1);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
void hbcli_err(char* format, ...);
|
|
@ -0,0 +1,102 @@
|
|||
#include <getopt.h>
|
||||
#include <hbcli/cli.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <hb/hyperbuild.h>
|
||||
#include <hb/err.h>
|
||||
|
||||
|
||||
nh_set_str_t hbu_streamoptions_parse_list_of_tags(hbe_err_t *hbe_err, char *argv) {
|
||||
nh_set_str_t set = NULL;
|
||||
hb_list_charlist_t list = NULL;
|
||||
|
||||
if (argv != NULL && strcmp(argv, "*")) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
set = nh_set_str_create();
|
||||
|
||||
if (argv == NULL) {
|
||||
return set;
|
||||
}
|
||||
|
||||
list = hb_list_charlist_create_from_split((hb_proc_char_t *) argv, ',');
|
||||
|
||||
for (size_t i = 0; i < list->length; i++) {
|
||||
hb_list_char_t part = hb_list_charlist_get(list, i);
|
||||
hb_proc_char_t *part_c = hb_list_char_underlying(part);
|
||||
|
||||
if (hb_list_char_get(part, 0) == '$') {
|
||||
// Set of tags
|
||||
hb_list_char_shift(part);
|
||||
HBE_CATCH_F(hbu_streamoptions_parse_and_add_tag_set, (char *) part_c, set);
|
||||
|
||||
} else {
|
||||
// Single tag
|
||||
if (!hb_rule_tags_check(part_c)) {
|
||||
HBE_THROW_F(HBE_CLI_INVALID_TAG, "%s is not a standard tag and was provided as part of an argument's value", part_c);
|
||||
}
|
||||
nh_set_str_add(set, (char *) hb_list_char_underlying_copy(part));
|
||||
}
|
||||
}
|
||||
|
||||
finally:
|
||||
if (list != NULL) {
|
||||
hb_list_charlist_destroy_from_split(list);
|
||||
list = NULL;
|
||||
}
|
||||
if (*hbe_err != NULL) {
|
||||
if (set != NULL) {
|
||||
nh_set_str_destroy(set);
|
||||
set = NULL;
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void hbu_streamoptions_parse_and_add_tag_set(hbe_err_t *hbe_err, char *set_name, nh_set_str_t set) {
|
||||
if (strcmp(set_name, "content") == 0) {
|
||||
hb_rule_contenttags_add_elems(set);
|
||||
} else if (strcmp(set_name, "contentfirst") == 0) {
|
||||
hb_rule_contentfirsttags_add_elems(set);
|
||||
} else if (strcmp(set_name, "formatting") == 0) {
|
||||
hb_rule_formattingtags_add_elems(set);
|
||||
} else if (strcmp(set_name, "layout") == 0) {
|
||||
hb_rule_layouttags_add_elems(set);
|
||||
} else if (strcmp(set_name, "specific") == 0) {
|
||||
hb_rule_specifictags_add_elems(set);
|
||||
} else if (strcmp(set_name, "heading") == 0) {
|
||||
hb_rule_headingtags_add_elems(set);
|
||||
} else if (strcmp(set_name, "media") == 0) {
|
||||
hb_rule_mediatags_add_elems(set);
|
||||
} else if (strcmp(set_name, "sectioning") == 0) {
|
||||
hb_rule_sectioningtags_add_elems(set);
|
||||
} else if (strcmp(set_name, "void") == 0) {
|
||||
hb_rule_voidtags_add_elems(set);
|
||||
} else if (strcmp(set_name, "wss") == 0) {
|
||||
hb_rule_wsstags_add_elems(set);
|
||||
} else {
|
||||
HBE_THROW_V(HBE_CLI_INVALID_TAG_SET, "Unrecognised tag set `%s`", set_name);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
hyperbuild_init();
|
||||
|
||||
|
||||
|
||||
|
||||
hb_rune* output = hyperbuild_from_file(input_path, &cfg, &result);
|
||||
if (result->code != HB_ERR_OK) {
|
||||
// An error occurred.
|
||||
_cli_error(result->msg);
|
||||
}
|
||||
|
||||
open(output_path, )
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "hb-rule.h"
|
||||
#include "hb-config.h"
|
||||
|
||||
static struct hb_config_ex_s _ex_collapse_whitespace_default;
|
||||
static struct hb_config_ex_s _ex_destroy_whole_whitespace_default;
|
||||
static struct hb_config_ex_s _ex_trim_whitespace_default;
|
||||
|
||||
// WARNING: Rules must be initialised before calling this function
|
||||
void hb_config_init(void)
|
||||
{
|
||||
nh_set_str ex_collapse_whitespace_set = nh_set_str_create();
|
||||
hb_rule_wsstags_add_elems(ex_collapse_whitespace_set);
|
||||
_ex_collapse_whitespace_default = {HB_CONFIG_EX_MODE_DEFAULT,
|
||||
ex_collapse_whitespace_set};
|
||||
|
||||
nh_set_str ex_destroy_whole_whitespace_set = nh_set_str_create();
|
||||
hb_rule_wsstags_add_elems(ex_destroy_whole_whitespace_set);
|
||||
hb_rule_contenttags_add_elems(ex_destroy_whole_whitespace_set);
|
||||
hb_rule_formattingtags_add_elems(ex_destroy_whole_whitespace_set);
|
||||
_ex_destroy_whole_whitespace_default = {
|
||||
HB_CONFIG_EX_MODE_DEFAULT, ex_destroy_whole_whitespace_set};
|
||||
|
||||
nh_set_str ex_trim_whitespace_set = nh_set_str_create();
|
||||
hb_rule_wsstags_add_elems(ex_trim_whitespace_set);
|
||||
hb_rule_formattingtags_add_elems(ex_trim_whitespace_set);
|
||||
_ex_trim_whitespace_default = {HB_CONFIG_EX_MODE_DEFAULT,
|
||||
ex_trim_whitespace_set};
|
||||
}
|
||||
|
||||
hb_config_t* hb_config_create(void)
|
||||
{
|
||||
hb_config_t* config = malloc(sizeof(struct hb_config_s));
|
||||
config->ex_collapse_whitespace = _ex_collapse_whitespace_default;
|
||||
config->ex_destroy_whole_whitespace =
|
||||
_ex_destroy_whole_whitespace_default;
|
||||
config->ex_trim_whitespace = _ex_trim_whitespace_default;
|
||||
config->suppressed_errors = nh_set_int32_create();
|
||||
config->trim_class_attr = true;
|
||||
config->decode_entities = true;
|
||||
config->min_conditional_comments = true;
|
||||
config->remove_attr_quotes = true;
|
||||
config->remove_comments = true;
|
||||
config->remove_optional_tags = true;
|
||||
config->remove_tag_whitespace = true;
|
||||
return config;
|
||||
}
|
||||
|
||||
void hb_config_ex_use_none(hb_config_ex_t* config_ex)
|
||||
{
|
||||
*config_ex = {HB_CONFIG_EX_MODE_NONE, NULL};
|
||||
}
|
||||
|
||||
void hb_config_ex_use_custom(hb_config_ex_t* config_ex, nh_set_str custom_set)
|
||||
{
|
||||
*config_ex = {HB_CONFIG_EX_MODE_CUSTOM, custom_set};
|
||||
}
|
||||
|
||||
void hb_config_ex_use_all(hb_config_ex_t* config_ex)
|
||||
{
|
||||
*config_ex = {HB_CONFIG_EX_MODE_ALL};
|
||||
}
|
||||
|
||||
void hb_config_destroy(hb_config_t* opt)
|
||||
{
|
||||
nh_set_int32_destroy(opt->suppressed_errors);
|
||||
free(opt);
|
||||
}
|
||||
|
||||
bool hb_config_supressed_error_check(hb_config_t opt, hb_error_t errcode)
|
||||
{
|
||||
return nh_set_int32_has(&opt->suppressed_errors, errcode);
|
||||
}
|
||||
|
||||
bool hb_config_ex_check(hb_config_t* config, hb_proc_char_t* query)
|
||||
{
|
||||
switch (config->mode) {
|
||||
case HB_CONFIG_EX_MODE_ALL:
|
||||
return true;
|
||||
|
||||
case HB_CONFIG_EX_MODE_NONE:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return nh_set_str_has(config->set, query);
|
||||
}
|
||||
if (config->mode == HB_CONFIG_EX_MODE_ALL) {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
typedef enum {
|
||||
HB_CONFIG_EX_MODE_NONE, // i.e. minify all without exeption
|
||||
HB_CONFIG_EX_MODE_DEFAULT, // entire struct will not be destroyed
|
||||
HB_CONFIG_EX_MODE_CUSTOM, // set will be destroyed
|
||||
HB_CONFIG_EX_MODE_ALL, // i.e. don't minify
|
||||
} hb_config_ex_mode_t;
|
||||
|
||||
typedef struct {
|
||||
hb_config_ex_mode_t mode;
|
||||
nh_set_str set;
|
||||
} hb_config_ex_t;
|
||||
|
||||
typedef struct {
|
||||
hb_config_ex_t ex_collapse_whitespace;
|
||||
hb_config_ex_t ex_destroy_whole_whitespace;
|
||||
hb_config_ex_t ex_trim_whitespace;
|
||||
nh_set_int32 suppressed_errors;
|
||||
bool trim_class_attributes;
|
||||
bool decode_entities;
|
||||
bool remove_attr_quotes;
|
||||
bool remove_comments;
|
||||
bool remove_optional_tags;
|
||||
bool remove_tag_whitespace;
|
||||
} hb_config_t;
|
||||
|
||||
// WARNING: Rules must be initialised before calling this function
|
||||
void hb_config_init(void);
|
||||
hb_config_t* hb_config_create(void);
|
||||
void hb_config_ex_use_none(hb_config_ex_t* config_ex);
|
||||
void hb_config_ex_use_custom(hb_config_ex_t* config_ex, nh_set_str custom_set);
|
||||
void hb_config_ex_use_all(hb_config_ex_t* config_ex);
|
||||
void hb_config_destroy(hb_config_t* opt);
|
||||
bool hb_config_supressed_error_check(hb_config_t opt, hb_error_t errcode);
|
||||
bool hb_config_ex_check(hb_config_ex_t* config, hb_proc_char_t* query);
|
|
@ -0,0 +1,162 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include "../../rule/char/ucalpha.c"
|
||||
#include "../char/char.c"
|
||||
#include "../execution/error.c"
|
||||
#include "../list/char.c"
|
||||
#include "../fstream/fstreamin.c"
|
||||
#include "../fstream/fstreamout.c"
|
||||
|
||||
// Use macro to prevent having to allocate (and therefore free/manage) memory
|
||||
#define HB_PROC_FORMAT_WITH_POS(fn, a, format, ...) fn(a, format " at %s [line %d, column %d]", __VA_ARGS__, proc->name, proc->line, proc->column);
|
||||
|
||||
/**
|
||||
* Creates an error using a message with the current position appended.
|
||||
*
|
||||
* @param proc proc
|
||||
* @param errcode error code
|
||||
* @param reason message
|
||||
* @return error
|
||||
*/
|
||||
hbe_err_t hb_proc_error(hb_proc_t* proc, hb_error_t errcode, const char *reason, ...) {
|
||||
va_list args;
|
||||
va_start(args, reason);
|
||||
|
||||
char *msg = calloc(HB_PROC_MAX_ERR_MSG_LEN + 1, SIZEOF_CHAR);
|
||||
vsnprintf(msg, HB_PROC_MAX_ERR_MSG_LEN, reason, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
hbe_err_t err = HBU_FN_FORMAT_WITH_POS(hbe_err_create, errcode, "%s", msg);
|
||||
free(msg);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a character to the redirect, if enabled, otherwise output, of a proc,
|
||||
* unless the output is masked.
|
||||
*
|
||||
* @param hbe_err pointer to hbe_err_t
|
||||
* @param proc proc
|
||||
* @param c character to write
|
||||
* @return a freshly-created proc
|
||||
* @throws on write error
|
||||
*/
|
||||
static void _hb_proc_write_to_output(hbe_err_t *hbe_err, hb_proc_t* proc, hb_proc_char_t c) {
|
||||
if (!proc->mask) {
|
||||
hb_list_char_t redirect = proc->redirect;
|
||||
if (redirect != NULL) {
|
||||
hb_list_char_append(redirect, c);
|
||||
} else {
|
||||
HBE_CATCH_V((*proc->writer), proc->output, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* INSTANCE MANAGEMENT FUNCTIONS
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allocates memory for a proc, and creates one with provided arguments.
|
||||
*
|
||||
* @param input input
|
||||
* @param reader reader
|
||||
* @param name name
|
||||
* @param output output
|
||||
* @param writer writer
|
||||
* @return a freshly-created proc
|
||||
*/
|
||||
hb_proc_t* hb_proc_create_blank(char *name) {
|
||||
hb_proc_t* proc = calloc(1, sizeof(hb_proc_t));
|
||||
|
||||
proc->name = name;
|
||||
|
||||
proc->input = NULL;
|
||||
proc->reader = NULL;
|
||||
proc->EOI = false;
|
||||
|
||||
proc->line = 1;
|
||||
proc->column = 0;
|
||||
proc->CR = false;
|
||||
|
||||
proc->output = NULL;
|
||||
proc->writer = NULL;
|
||||
proc->buffer = nh_list_ucp_create();
|
||||
proc->mask = false;
|
||||
proc->redirect = NULL;
|
||||
|
||||
return proc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees all memory associated with a proc.
|
||||
*
|
||||
* @param proc proc
|
||||
*/
|
||||
void hb_proc_destroy(hb_proc_t* proc) {
|
||||
nh_list_ucp_destroy(proc->buffer);
|
||||
free(proc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the output mask.
|
||||
* When the output mask is enabled, all writes are simply discarded and not actually written to output.
|
||||
*
|
||||
* @param proc proc
|
||||
* @param mask 1 to enable, 0 to disable
|
||||
* @return previous state
|
||||
*/
|
||||
int hb_proc_toggle_output_mask(hb_proc_t* proc, int mask) {
|
||||
int current = proc->mask;
|
||||
proc->mask = mask;
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the output redirect.
|
||||
* When the output redirect is enabled, all writes are written to a buffer instead of the output.
|
||||
*
|
||||
* @param proc proc
|
||||
* @param redirect buffer to redirect writes to, or NULL to disable
|
||||
*/
|
||||
void hb_proc_set_redirect(hb_proc_t* proc, hb_list_char_t redirect) {
|
||||
proc->redirect = redirect;
|
||||
}
|
||||
|
||||
void hb_proc_blank_set_input_fstreamin(hb_proc_t* proc, hbu_fstreamin_t fstreamin) {
|
||||
proc->input = fstreamin;
|
||||
proc->reader = (hb_proc_reader_cb_t) &hbu_fstreamin_read;
|
||||
}
|
||||
|
||||
// Wrapper function for hb_list_char_shift to make it compatible with hb_proc_reader_cb_t
|
||||
static hb_eod_char_t hb_proc_read_from_list_char_input(hbe_err_t *hbe_err, hb_list_char_t input) {
|
||||
(void) hbe_err;
|
||||
return hb_list_char_shift(input);
|
||||
}
|
||||
|
||||
void hb_proc_blank_set_input_buffer(hb_proc_t* proc, hb_list_char_t buf) {
|
||||
proc->input = buf;
|
||||
proc->reader = (hb_proc_reader_cb_t) &hb_proc_read_from_list_char_input;
|
||||
}
|
||||
|
||||
static void hb_proc_blank_set_output_fstreamout(hb_proc_t* proc, hbu_fstreamout_t fstreamout) {
|
||||
proc->output = fstreamout;
|
||||
proc->writer = (hb_proc_writer_cb_t) &hbu_fstreamout_write;
|
||||
}
|
||||
|
||||
// Wrapper function for hb_list_char_append to make it compatible with hb_proc_writer_cb_t
|
||||
void hb_proc_write_to_list_char_output(hbe_err_t *hbe_err, hb_list_char_t output, hb_proc_char_t c) {
|
||||
(void) hbe_err;
|
||||
hb_list_char_append(output, c);
|
||||
}
|
||||
|
||||
void hb_proc_blank_set_output_buffer(hb_proc_t* proc, hb_list_char_t buf) {
|
||||
proc->output = buf;
|
||||
proc->writer = (hb_proc_writer_cb_t) &hb_proc_write_to_list_char_output;
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
|
||||
#include <setjmp.h>
|
||||
|
||||
#include "hb-data.h"
|
||||
#include "hb-config.h"
|
||||
|
||||
typedef int32_t hb_proc_char_t;
|
||||
|
||||
#define HB_PROC_CHAR_EOD -1 // End Of Data
|
||||
#define HB_PROC_CHAR_SIZE sizeof(hb_proc_char_t)
|
||||
|
||||
typedef bool hb_proc_predicate_t(hb_proc_char_t);
|
||||
|
||||
// Reader and writer callbacks. The last parameter is a pointer to an error
|
||||
// message. If the last parameter is not NULL, it is assumed an error occurred.
|
||||
// The error message WILL BE free'd by the callee automatically, so ensure the
|
||||
// message was created using malloc or strdup, and is not free'd by the function
|
||||
// or anything else afterwards.
|
||||
typedef hb_proc_char_t hb_proc_reader_t(void*, char**);
|
||||
typedef void hb_proc_writer_t(void*, hb_proc_char_t, char**);
|
||||
|
||||
#define HB_PROC_MEMORY_CREATE(name) \
|
||||
hb_proc_list_memory_instance_add_right_and_return( \
|
||||
config->memory_instances, name##_create()); \
|
||||
hb_proc_list_memory_destructor_add_right( \
|
||||
config->memory_destructors, \
|
||||
(hb_proc_memory_destructor_t*) &name##_destroy);
|
||||
|
||||
NH_LIST(hb_proc_list_memory_instance, void*, sizeof(void*), void*, NULL);
|
||||
void* hb_proc_list_memory_instance_add_right_and_return(
|
||||
hb_proc_list_memory_instance*, void*);
|
||||
|
||||
typedef void hb_proc_memory_destructor_t(void*);
|
||||
NH_LIST(hb_proc_list_memory_destructor, hb_proc_memory_destructor_t*,
|
||||
sizeof(hb_proc_memory_destructor_t*), hb_proc_memory_destructor_t*,
|
||||
NULL);
|
||||
|
||||
#define HB_PROC_ERROR_MESSAGE_SIZE 1024
|
||||
typedef struct {
|
||||
hb_error_t code;
|
||||
char* message;
|
||||
} hb_proc_result_t;
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
jmp_buf start;
|
||||
|
||||
hb_proc_list_memory_instance* memory_instances;
|
||||
hb_proc_list_memory_destructor* memory_destructors;
|
||||
|
||||
void* input;
|
||||
hb_proc_reader_t* reader;
|
||||
bool EOI;
|
||||
|
||||
int line;
|
||||
int column;
|
||||
bool CR;
|
||||
|
||||
void* output;
|
||||
hb_proc_writer_t* writer;
|
||||
nh_list_ucp* buffer;
|
||||
bool mask;
|
||||
nh_list_ucp* redirect;
|
||||
|
||||
hb_config_t config;
|
||||
} hb_proc_t;
|
||||
|
||||
hb_proc_t* hb_proc_create_blank(char* name);
|
||||
void hb_proc_result_destroy(hb_proc_result_t* result);
|
||||
|
||||
hb_proc_result_t* hb_proc_start(hb_proc_t* proc);
|
||||
void _hb_proc_error(hb_proc_t* proc, hb_error_t code, char const* format, ...);
|
|
@ -0,0 +1,5 @@
|
|||
#include <hb/hyperbuild.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
hyperbuild_init();
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
#include <hb/cfg.h>
|
||||
#include <hbcli/opt.h>
|
||||
#include <stddef.h>
|
||||
#include <hb/proc.h>
|
||||
#include <getopt.h>
|
||||
#include <hbcli/err.h>
|
||||
|
||||
hb_cfg hbcli_options_parse(int argc, char** argv) {
|
||||
// Prepare config
|
||||
char *input_path = NULL;
|
||||
char *output_path = NULL;
|
||||
|
||||
hb_cfg cfg;
|
||||
hb_proc_result result;
|
||||
|
||||
bool nondefault_ex_collapse_whitespace = false;
|
||||
bool nondefault_ex_destroy_whole_whitespace = false;
|
||||
bool nondefault_ex_trim_whitespace = false;
|
||||
|
||||
|
||||
struct option long_options[] = {
|
||||
{"input", required_argument, NULL, 'i'},
|
||||
{"output", required_argument, NULL, 'o'},
|
||||
{"suppress", required_argument, NULL, 's'},
|
||||
|
||||
{"!collapse-ws", optional_argument, NULL, 40},
|
||||
{"!destroy-whole-ws", optional_argument, NULL, 41},
|
||||
{"!trim-ws", optional_argument, NULL, 42},
|
||||
|
||||
{"!trim-class-attr", no_argument, &(cfg.trim_class_attr), false},
|
||||
{"!decode-ent", no_argument, &(cfg.decode_entities), false},
|
||||
{"!remove-attr-quotes", no_argument, &(cfg.decode_entities), false},
|
||||
{"!remove-comments", no_argument, &(cfg.remove_comments), false},
|
||||
{"!remove-tag-ws", no_argument, &(cfg.remove_tag_whitespace), false},
|
||||
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
// Parse arguments
|
||||
while (1) {
|
||||
int option_index = 0;
|
||||
int c = getopt_long(argc, argv, "+i:o:s:", long_options, &option_index);
|
||||
|
||||
if (c == -1) {
|
||||
if (optind != argc) {
|
||||
hbcli_err("Too many options");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case 'i':
|
||||
input_path = optarg;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
output_path = optarg;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
HBE_CATCH_F(hbu_streamoptions_parse_and_add_errors_to_suppress, config_stream->suppressed_errors, optarg);
|
||||
break;
|
||||
|
||||
case 40:
|
||||
nondefault_ex_collapse_whitespace = 1;
|
||||
config_stream->ex_collapse_whitespace = HBE_CATCH_F(hbu_streamoptions_parse_list_of_tags, optarg);
|
||||
break;
|
||||
|
||||
case 41:
|
||||
nondefault_ex_destroy_whole_whitespace = 1;
|
||||
config_stream->ex_destroy_whole_whitespace = HBE_CATCH_F(hbu_streamoptions_parse_list_of_tags, optarg);
|
||||
break;
|
||||
|
||||
case 42:
|
||||
nondefault_ex_trim_whitespace = 1;
|
||||
config_stream->ex_trim_whitespace = HBE_CATCH_F(hbu_streamoptions_parse_list_of_tags, optarg);
|
||||
break;
|
||||
|
||||
default:
|
||||
cli_error("Internal error: unknown option %c");
|
||||
}
|
||||
}
|
||||
|
||||
if (!nondefault_ex_collapse_whitespace) config_stream->ex_collapse_whitespace = hbu_streamoptions_default_ex_collapse_whitespace();
|
||||
if (!nondefault_ex_destroy_whole_whitespace) config_stream->ex_destroy_whole_whitespace = hbu_streamoptions_default_ex_destroy_whole_whitespace();
|
||||
if (!nondefault_ex_trim_whitespace) config_stream->ex_trim_whitespace = hbu_streamoptions_default_ex_trim_whitespace();
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <hb/cfg.h>
|
||||
|
||||
hb_cfg hbcli_opt_parse(int argc, char** argv);
|
Loading…
Reference in New Issue