Skip to content

Commit 940b40c

Browse files
committed
Polyfill FNM_EXTMATCH features in Vala to improve portability
1 parent 28dbf44 commit 940b40c

3 files changed

Lines changed: 157 additions & 12 deletions

File tree

‎src/Application.vala‎

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016-2019 elementary LLC. (https://github.com/elementary/vala-lint)
2+
* Copyright (c) 2026 elementary LLC. (https://github.com/elementary/vala-lint)
33
*
44
* This program is free software; you can redistribute it and/or
55
* modify it under the terms of the GNU General Public
@@ -30,8 +30,8 @@ public class ValaLint.Application : GLib.Application {
3030
private static bool auto_fix = false;
3131
private static bool json_output = false;
3232
private static string? config_file = null;
33-
private static string ignore_pattern_list = "";
34-
private static int fnmatch_flags = Posix.FNM_EXTMATCH | Posix.FNM_PERIOD | Posix.FNM_PATHNAME;
33+
private static IgnoreMatcher ignore_matcher;
34+
private static int fnmatch_flags = Posix.FNM_PERIOD | Posix.FNM_PATHNAME;
3535
private static File root_dir;
3636

3737
private ApplicationCommandLine application_command_line;
@@ -105,6 +105,8 @@ public class ValaLint.Application : GLib.Application {
105105
}
106106

107107
this.application_command_line = command_line;
108+
ignore_matcher = new IgnoreMatcher (fnmatch_flags);
109+
108110
/* Get ignore patterns. Ignore patterns are glob patterns relative to the scanned directory */
109111
string[] ignore_patterns = {};
110112
string ignore_root = lint_directory != null ? lint_directory : args[1];
@@ -234,12 +236,7 @@ public class ValaLint.Application : GLib.Application {
234236
ApplicationCommandLine command_line, string[] patterns, string[] ignore_patterns ) throws Error, IOError {
235237

236238
foreach (string pattern in ignore_patterns) {
237-
ignore_pattern_list += ("|" + pattern);
238-
}
239-
240-
debug ("Ignore pattern list: %s", ignore_pattern_list);
241-
if (ignore_pattern_list.length > 0) {
242-
ignore_pattern_list = "+(" + ignore_pattern_list[1 : ignore_pattern_list.length] + ")";
239+
ignore_matcher.add_pattern (pattern);
243240
}
244241

245242
var result = new Vala.ArrayList<FileData?> ();
@@ -289,8 +286,7 @@ public class ValaLint.Application : GLib.Application {
289286
string child_name = info.get_name ();
290287
var child_file = dir.resolve_relative_path (child_name);
291288
var rel_path = root_dir.get_relative_path (child_file);
292-
if (!info.get_is_hidden () &&
293-
Posix.fnmatch (ignore_pattern_list, rel_path, fnmatch_flags) != 0) {
289+
if (!info.get_is_hidden () && !ignore_matcher.matches (rel_path)) {
294290

295291
if (info.get_file_type () == FileType.DIRECTORY) {
296292
var sub_files = get_files_from_directory (child_file);

‎src/IgnoreMatcher.vala‎

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright (c) 2026 Colin Kiama
3+
*
4+
* This program is free software; you can redistribute it and/or
5+
* modify it under the terms of the GNU General Public
6+
* License as published by the Free Software Foundation; either
7+
* version 2 of the License, or (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
* General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public
15+
* License along with this program; if not, write to the
16+
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17+
* Boston, MA 02110-1301 USA.
18+
*/
19+
20+
namespace ValaLint {
21+
public class IgnoreMatcher : Object {
22+
private Vala.ArrayList<GLib.Regex> regexes = new Vala.ArrayList<GLib.Regex> ();
23+
private int flags;
24+
25+
public IgnoreMatcher (int flags) {
26+
this.flags = flags;
27+
}
28+
29+
public void add_pattern (string pattern) {
30+
if (pattern == "" || pattern.has_prefix ("#")) {
31+
return;
32+
}
33+
34+
try {
35+
var regex_pattern = glob_to_regex (pattern);
36+
regexes.add (new GLib.Regex (regex_pattern, RegexCompileFlags.OPTIMIZE));
37+
} catch (Error e) {
38+
warning ("Invalid ignore pattern '%s': %s", pattern, e.message);
39+
}
40+
}
41+
42+
public bool matches (string path) {
43+
foreach (var regex in regexes) {
44+
if (regex.match (path)) {
45+
return true;
46+
}
47+
}
48+
return false;
49+
}
50+
51+
private string glob_to_regex (string glob) {
52+
bool pathname = (flags & Posix.FNM_PATHNAME) != 0;
53+
bool period = (flags & Posix.FNM_PERIOD) != 0;
54+
55+
var res = new StringBuilder ("^");
56+
var stack = new Vala.ArrayList<string> ();
57+
bool in_brackets = false;
58+
59+
for (int i = 0; i < glob.length; i++) {
60+
char c = glob[i];
61+
char next = (i + 1 < glob.length) ? glob[i+1] : '\0';
62+
63+
if (in_brackets) {
64+
if (c == ']') {
65+
in_brackets = false;
66+
res.append ("]");
67+
} else if (c == '\\') {
68+
res.append ("\\\\");
69+
} else {
70+
res.append_c (c);
71+
}
72+
continue;
73+
}
74+
75+
switch (c) {
76+
case '*':
77+
if (next == '(') {
78+
res.append ("(?:");
79+
stack.add (")*");
80+
i++;
81+
} else {
82+
if (pathname) {
83+
if (period) res.append ("(?![./])");
84+
res.append ("[^/]*");
85+
} else {
86+
if (period) res.append ("(?!\\.)");
87+
res.append (".*");
88+
}
89+
}
90+
break;
91+
case '?':
92+
if (next == '(') {
93+
res.append ("(?:");
94+
stack.add (")?");
95+
i++;
96+
} else {
97+
if (pathname) res.append ("[^/]");
98+
else res.append (".");
99+
}
100+
break;
101+
case '+':
102+
if (next == '(') {
103+
res.append ("(?:");
104+
stack.add (")+");
105+
i++;
106+
} else {
107+
res.append ("\\+");
108+
}
109+
break;
110+
case '@':
111+
if (next == '(') {
112+
res.append ("(?:");
113+
stack.add (")");
114+
i++;
115+
} else {
116+
res.append ("@");
117+
}
118+
break;
119+
case '!':
120+
if (next == '(') {
121+
res.append ("(?!");
122+
stack.add (").*");
123+
i++;
124+
} else {
125+
res.append ("!");
126+
}
127+
break;
128+
case '(': res.append ("\\("); break;
129+
case ')':
130+
if (!stack.is_empty) res.append (stack.remove_at (stack.size - 1));
131+
else res.append ("\\)");
132+
break;
133+
case '|': res.append ("|"); break;
134+
case '[': in_brackets = true; res.append ("["); break;
135+
case '.': case '\\': case '$': case '^': case '{': case '}':
136+
res.append ("\\"); res.append_c (c); break;
137+
case '/':
138+
res.append_c (c);
139+
break;
140+
default: res.append_c (c); break;
141+
}
142+
}
143+
144+
res.append ("$");
145+
return res.str;
146+
}
147+
}
148+
}

‎src/meson.build‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
app_files = files (
22
'Application.vala',
3-
'FileData.vala'
3+
'FileData.vala',
4+
'IgnoreMatcher.vala'
45
)
56

67
vala_lint = executable(

0 commit comments

Comments
 (0)