Skip to content

Commit c964a48

Browse files
committed
Add support for space-based indentation
Also began working on improving code coverage
1 parent 4b34d71 commit c964a48

11 files changed

Lines changed: 462 additions & 60 deletions

File tree

java/pom.xml

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
34
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
45
<modelVersion>4.0.0</modelVersion>
56

@@ -72,14 +73,40 @@
7273
</dependencies>
7374

7475
<build>
75-
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
76+
<plugins>
77+
<plugin>
78+
<groupId>org.jacoco</groupId>
79+
<artifactId>jacoco-maven-plugin</artifactId>
80+
<version>0.8.14</version>
81+
<executions>
82+
<execution>
83+
<goals>
84+
<goal>prepare-agent</goal>
85+
</goals>
86+
</execution>
87+
<!-- attached to Maven test phase -->
88+
<execution>
89+
<id>report</id>
90+
<phase>test</phase>
91+
<goals>
92+
<goal>report</goal>
93+
</goals>
94+
</execution>
95+
</executions>
96+
</plugin>
97+
</plugins>
98+
99+
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to
100+
parent pom) -->
76101
<plugins>
77-
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
102+
<!-- clean lifecycle, see
103+
https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
78104
<plugin>
79105
<artifactId>maven-clean-plugin</artifactId>
80106
<version>3.4.0</version>
81107
</plugin>
82-
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
108+
<!-- default lifecycle, jar packaging: see
109+
https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
83110
<plugin>
84111
<artifactId>maven-resources-plugin</artifactId>
85112
<version>3.3.1</version>
@@ -104,7 +131,8 @@
104131
<artifactId>maven-deploy-plugin</artifactId>
105132
<version>3.1.2</version>
106133
</plugin>
107-
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
134+
<!-- site lifecycle, see
135+
https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
108136
<plugin>
109137
<artifactId>maven-site-plugin</artifactId>
110138
<version>3.12.1</version>
@@ -125,5 +153,5 @@
125153
<version>3.11.2</version>
126154
</plugin>
127155
</plugins>
128-
</reporting>
129-
</project>
156+
</reporting>
157+
</project>

java/src/main/java/io/github/moctave/weftspace/DataReader.java

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,18 @@ public void parse(Option... options) {
6767
int lineNumber = 0;
6868

6969
Deque<DataNode> nodeStack = new ArrayDeque<>();
70-
71-
int tabs = 0;
72-
int lastTabs = 0;
7370
DataNode currentNode = null;
7471

72+
int indent = 0;
73+
Deque<Integer> indentDepths = new ArrayDeque<>();
74+
String expectedIndentString = null;
75+
76+
indentDepths.push(0);
77+
7578
while (s.hasNextLine()) {
7679
lineNumber ++;
7780
String line = s.nextLine();
81+
7882
if (line.split("#").length == 0) {
7983
if (line.isBlank())
8084
continue;
@@ -83,12 +87,28 @@ public void parse(Option... options) {
8387
continue;
8488
}
8589

86-
tabs = countLeadingTabs(line);
87-
if (tabs > lastTabs && currentNode != null) {
90+
indent = countLeadingWhitespace(line);
91+
String indentSubstring = getIndentSubstring(line, indentDepths.peek());
92+
if (expectedIndentString == null && indentSubstring.length() > 0) {
93+
expectedIndentString = indentSubstring;
94+
if (
95+
expectedIndentString.contains(" ")
96+
&& expectedIndentString.contains("\t")
97+
) {
98+
Logger.WARN_MIXED_WHITESPACE.log(file);
99+
}
100+
}
101+
102+
if (indent > indentDepths.peek() && currentNode != null) {
103+
if (!expectedIndentString.equals(indentSubstring)) {
104+
Logger.WARN_MIXED_WHITESPACE.log(file);
105+
}
88106
nodeStack.push(currentNode);
107+
indentDepths.push(indent);
89108
} else {
90-
while (nodeStack.size() > tabs) {
109+
while (indentDepths.peek() > indent) {
91110
nodeStack.pop();
111+
indentDepths.pop();
92112
}
93113
}
94114
currentNode = makeNode(line, lineNumber, !ignoreNodeFlags);
@@ -101,8 +121,6 @@ public void parse(Option... options) {
101121
parent.addChild(currentNode);
102122
currentNode.setParent(parent);
103123
}
104-
105-
lastTabs = tabs;
106124
}
107125

108126
s.close();
@@ -193,6 +211,24 @@ public DataNode makeNode(String line, int number, boolean checkForFlags) {
193211

194212

195213

214+
/**
215+
* Gets the substring which appears to be being used for indentation.
216+
* @param line The line to find the indent of.
217+
* @param depth The base depth from which the line is being indented.
218+
* @return The substring used for indentation.
219+
*/
220+
public static String getIndentSubstring(String line, int depth) {
221+
int i = depth;
222+
String s = "";
223+
while (i < line.length() && (line.charAt(i) == '\t' || line.charAt(i) == ' ')) {
224+
s += line.charAt(i);
225+
i++;
226+
}
227+
return s;
228+
}
229+
230+
231+
196232
/**
197233
* Trims comments from a line of text, removing everything after the first {@code #} character.
198234
* @param line The line to trim.
@@ -205,20 +241,33 @@ public static String trimComments(String line) {
205241

206242

207243
/**
208-
* Counts the number of leading tabs on a line.
244+
* Counts the number of leading tab or space characters on a line.
209245
* @param line The line to count.
210-
* @return The number of tabs that come before the first non-tab character on the line.
246+
* @return The number of tabs or spaces that come before the first
247+
* non-whitespace character on the line.
211248
*/
212-
public static int countLeadingTabs(String line) {
249+
public static int countLeadingWhitespace(String line) {
213250
int i = 0;
214-
while (i < line.length() && line.charAt(i) == '\t') {
251+
while (i < line.length() && (line.charAt(i) == '\t' || line.charAt(i) == ' ')) {
215252
i++;
216253
}
217254
return i;
218255
}
219256

220257

221258

259+
/**
260+
* Deprecated method introduced to support programs written expecting legacy
261+
* whitespace handling. Calls {@link #countLeadingWhitespace(String)}, see that
262+
* method for documentation.
263+
*/
264+
@Deprecated
265+
public static int countLeadingTabs(String line) {
266+
return countLeadingWhitespace(line);
267+
}
268+
269+
270+
222271
/**
223272
* Checks if a line contains only whitespace, as defined by {@link Character#isWhitespace(char)}.
224273
* @param line The line to check.

java/src/main/java/io/github/moctave/weftspace/Logger.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ public static enum Severity {
7676
/** Error: Node argument not a valid double */
7777
public static final DynamicMessage ERROR_BUILDER_MALFORMED_REAL = new DynamicMessage(Severity.ERROR, "$NODE argument in $CONTEXT is not a real number.");
7878

79+
/** Warning: File is using mixed whitespace for indentation */
80+
public static final DynamicMessage WARN_MIXED_WHITESPACE = new DynamicMessage(Severity.WARN, "Mixed or unevenly-sized whitespace found in indents when parsing $FILENAME.");
81+
7982
/** Warning: Node is a root node and should not be written */
8083
public static final DynamicMessage WARN_NODE_WRITE_ROOT = new DynamicMessage(Severity.WARN, "$NODE is a root node and should not be written.");
8184

@@ -94,6 +97,14 @@ public static enum Severity {
9497

9598

9699
// MARK: Error Count
100+
/**
101+
* Helper method to reset both error and warning counts.
102+
*/
103+
public static void resetAlertCounts() {
104+
resetErrorCount();
105+
resetWarningCount();
106+
}
107+
97108
private static int errorCount = 0;
98109

99110
/**
@@ -118,6 +129,33 @@ public static void countError() {
118129
public static int getErrorCount() {
119130
return errorCount;
120131
}
132+
133+
134+
135+
private static int warningCount = 0;
136+
137+
/**
138+
* Mutator method to reset the warning count to 0.
139+
*/
140+
public static void resetWarningCount() {
141+
warningCount = 0;
142+
}
143+
144+
/**
145+
* Mutator method to increment the warning count by 1.
146+
*/
147+
public static void countWarning() {
148+
warningCount++;
149+
}
150+
151+
152+
/**
153+
* Getter: Returns the number of warnings currently tracked.
154+
* @return {@link #warningCount}
155+
*/
156+
public static int getWarningCount() {
157+
return warningCount;
158+
}
121159

122160

123161

@@ -163,6 +201,7 @@ public void formatOn(PrintStream stream) {
163201
break;
164202
case WARN:
165203
stream.print(Logger.YELLOW);
204+
countWarning();
166205
break;
167206
case SUCCESS:
168207
stream.print(Logger.GREEN);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) 2025 by mOctave
2+
//
3+
// This program is free software: you can redistribute it and/or modify it under the
4+
// terms of the GNU Affero General Public License as published by the Free Software
5+
// Foundation, either version 3 of the License, or (at your option) any later version.
6+
//
7+
// This program is distributed in the hope that it will be useful, but WITHOUT ANY
8+
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
9+
// PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
10+
//
11+
// You should have received a copy of the GNU Affero General Public License along with
12+
// this program. If not, see <https://www.gnu.org/licenses/>.
13+
14+
package io.github.moctave.weftspace;
15+
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
import org.junit.jupiter.api.Test;
18+
19+
/** Unit tests for DataReader */
20+
public class TestDataReader {
21+
22+
/** Tests for {@link DataReader#getIndentSubstring()}. */
23+
@Test
24+
public void getIndentSubstring() {
25+
assertEquals("", DataReader.getIndentSubstring("", 0));
26+
assertEquals("\t", DataReader.getIndentSubstring("\tHello World", 0));
27+
assertEquals("\t\t", DataReader.getIndentSubstring("\t\tHello World", 0));
28+
assertEquals("\t", DataReader.getIndentSubstring("\t\tHello World", 1));
29+
assertEquals(" ", DataReader.getIndentSubstring(" Hello World", 2));
30+
assertEquals(" ", DataReader.getIndentSubstring(" Hello World", 3));
31+
assertEquals("\t", DataReader.getIndentSubstring(" \tHello World", 1));
32+
}
33+
34+
35+
36+
/** Tests for {@link DataReader#trimComments()}. */
37+
@Test
38+
public void trimComments() {
39+
assertEquals("", DataReader.trimComments(""));
40+
assertEquals(" ", DataReader.trimComments(" "));
41+
assertEquals("", DataReader.trimComments("#only comment"));
42+
assertEquals("only text", DataReader.trimComments("only text"));
43+
assertEquals("text and ", DataReader.trimComments("text and #comment"));
44+
assertEquals("Here is a", DataReader.trimComments("Here is a# double #comment"));
45+
}
46+
47+
48+
/** Tests for {@link DataReader#countLeadingWhitespace(String)}. */
49+
@Test
50+
public void countLeadingWhitespace() {
51+
assertEquals(0, DataReader.countLeadingWhitespace(""));
52+
assertEquals(1, DataReader.countLeadingWhitespace(" "));
53+
assertEquals(2, DataReader.countLeadingWhitespace("\t\t"));
54+
assertEquals(0, DataReader.countLeadingWhitespace("hello world"));
55+
assertEquals(2, DataReader.countLeadingWhitespace(" hello world "));
56+
assertEquals(3, DataReader.countLeadingWhitespace("\t \tGoodbye World!\t"));
57+
}
58+
59+
60+
/** Tests for {@link DataReader#containsOnlyWhitespace(String)} */
61+
@Test
62+
public void containsOnlyWhitespace() {
63+
assertEquals(true, DataReader.containsOnlyWhitespace(""));
64+
assertEquals(false, DataReader.containsOnlyWhitespace("foo"));
65+
assertEquals(true, DataReader.containsOnlyWhitespace(" "));
66+
assertEquals(true, DataReader.containsOnlyWhitespace("\t"));
67+
assertEquals(true, DataReader.containsOnlyWhitespace(" \t "));
68+
assertEquals(false, DataReader.containsOnlyWhitespace("a \t"));
69+
assertEquals(false, DataReader.containsOnlyWhitespace(" b"));
70+
}
71+
}

0 commit comments

Comments
 (0)