File: ext_stack.ha

package info (click to toggle)
hare 0.25.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,948 kB
  • sloc: asm: 1,264; makefile: 123; sh: 114; lisp: 101
file content (144 lines) | stat: -rw-r--r-- 4,661 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use bytes;
use strings;

// Add extensions onto the end of the final path segment. The separating '.'
// will be inserted for you. If the final path segment consists entirely of dots
// or the path is root, this function will return [[cant_extend]].
export fn push_ext(buf: *buffer, exts: str...) (str | error) = {
	match (peek(buf)) {
	case void => return cant_extend;
	case let s: str => if (strings::ltrim(s, '.') == "") return cant_extend;
	};
	for (let ext .. exts) {
		const newend = buf.end + 1 + len(ext);
		if (MAX < newend) return too_long;
		buf.buf[buf.end] = '.';
		buf.buf[buf.end+1..newend] = strings::toutf8(ext);
		buf.end = newend;
	};
	return string(buf);
};

// Remove and return the final extension in a path. The result will not
// include the leading '.'. The result is borrowed from the buffer. Leading dots
// will be ignored when looking for extensions, such that ".ssh" isn't
// considered to have any extensions.
export fn pop_ext(buf: *buffer) (str | void) = {
	const ext = split_ext(buf);
	buf.end = ext.0;
	return ext.1;
};

// Examine the final extension in a path. The result will not
// include the leading '.'. The result is borrowed from the buffer. Leading dots
// will be ignored when looking for extensions, such that ".ssh" isn't
// considered to have any extensions.
export fn peek_ext(buf: *buffer) (str | void) = split_ext(buf).1;

// helper function, returns (end of non-extension, extension string)
fn split_ext(buf: *buffer) (size, (str | void)) = {
	match (peek(buf)) {
	case void =>
		return (buf.end, void);
	case let s: str =>
		const bs = strings::toutf8(s);
		bs = bytes::ltrim(bs, '.');
		match (bytes::rindex(bs, '.')) {
		case void =>
			return (buf.end, void);
		case let i: size =>
			return (buf.end - len(bs) + i, strings::fromutf8_unsafe(bs[i+1..]));
		};
	};
};

// Remove and return all the extensions in a path. The result will not
// include the leading '.', but will include separating dots. Leading dots
// will be ignored when looking for extensions, such that ".ssh" isn't
// considered to have any extensions. The result is borrowed from the buffer.
export fn pop_exts(buf: *buffer) (str | void) = {
	const ext = split_exts(buf);
	buf.end = ext.0;
	return ext.1;
};

// Examine all the extensions in a path. The result will not include the
// leading '.', but will include separating dots. Leading dots will
// be ignored when looking for extensions, such that ".ssh" isn't considered
// to have any extensions. The result is borrowed from the buffer.
export fn peek_exts(buf: *buffer) (str | void) = split_exts(buf).1;

// helper function, returns (end of non-extension, extension string)
fn split_exts(buf: *buffer) (size, (str | void)) = {
	match (peek(buf)) {
	case void =>
		return (buf.end, void);
	case let s: str =>
		const bs = strings::toutf8(s);
		bs = bytes::ltrim(bs, '.');
		match (bytes::index(bs, '.')) {
		case void =>
			return (buf.end, void);
		case let i: size =>
			return (buf.end - len(bs) + i, strings::fromutf8_unsafe(bs[i+1..]));
		};
	};
};

@test fn ext() void = {
	// push_ext
	let buf = init()!;
	assert(push_ext(&buf, "bash") is cant_extend);
	set(&buf, sepstr)!;
	assert(push_ext(&buf, "bash") is cant_extend);
	set(&buf, "....")!;
	assert(push_ext(&buf, "bash") is cant_extend);
	set(&buf, "bashrc")!;
	assert(push_ext(&buf, "bash") as str == "bashrc.bash");
	set(&buf, ".bashrc")!;
	assert(push_ext(&buf, "bash") as str == ".bashrc.bash");

	// pop_ext
	set(&buf)!;
	assert(pop_ext(&buf) is void);
	set(&buf, "..")!;
	assert(pop_ext(&buf) is void);
	set(&buf, sepstr)!;
	assert(pop_ext(&buf) is void);

	set(&buf, "index.html.tmpl")!;
	assert(pop_ext(&buf) as str == "tmpl");
	assert(string(&buf) == "index.html");
	assert(pop_ext(&buf) as str == "html");
	assert(string(&buf) == "index");
	assert(pop_ext(&buf) is void);
	assert(string(&buf) == "index");

	set(&buf, ".secret.tar.gz")!;
	assert(pop_ext(&buf) as str == "gz");
	assert(string(&buf) == ".secret.tar");
	assert(pop_ext(&buf) as str == "tar");
	assert(string(&buf) == ".secret");
	assert(pop_ext(&buf) is void);
	assert(string(&buf) == ".secret");

	set(&buf, "..ext")!;
	assert(pop_ext(&buf) is void);
	assert(string(&buf) == "..ext");

	// pop_exts
	set(&buf, "index.html.tmpl")!;
	assert(pop_exts(&buf) as str == "html.tmpl");
	assert(string(&buf) == "index");
	assert(pop_exts(&buf) is void);
	assert(string(&buf) == "index");

	set(&buf, ".secret.tar.gz")!;
	assert(pop_exts(&buf) as str == "tar.gz");
	assert(string(&buf) == ".secret");
	assert(pop_ext(&buf) is void);
	assert(string(&buf) == ".secret");
};