-
Notifications
You must be signed in to change notification settings - Fork 0
/
cut.d
193 lines (153 loc) · 4.26 KB
/
cut.d
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
module cut.cut;
import std.stdio;
import std.algorithm;
import std.c.process;
import cut.optionsparser;
/**
* Cut Class
*
* Class representing the complete Cut cli tool
*
* @author Daniel Norman daniel.norman@sociomantic.com
*/
class Cut {
/**
* Constant new line char
*
* Used for easy reference
*/
immutable static char newLine = 0x0A;
/**
* Options object
*
* Used for parsing the cli options
*/
CutOptionsParser options;
/**
* @constructor
*
* @param string[] args arguments as passed to the main function.
*/
this(string[] args) {
this.options = new CutOptionsParser(args);
cutFile();
}
/**
* @constructor
*
* Function overloading without arguments for testing purposes
*/
this() {
}
void cutFile() {
auto f = File(options.file, "r");
char[] buf;
while (f.readln(buf)) {
writeln(cutLine(buf.strip(newLine)));
}
}
/**
* Cut line
*
* @TODO Merge the cutLineByFields an cutLineByBytes into one function
* and split for fields before the call.
*
* @param char[] line a single line
*
* @return char[] the line after it's been cut
*/
auto cutLine(char[] line) {
if (options.mode == CutMode.fields) {
return cutLineByFields(line);
} else {
return cutLineByBytes(line);
}
}
/**
* Cut lines based on fields
*
* Splits the line acording to the delimiter and returns the fields
*
* @param char[] line a single line
*
* @return char[]
*/
auto cutLineByFields(char[] line) {
auto fields = splitter(line, options.delimiter);
char[] result;
auto c = 1U;
foreach (field; fields) {
if (isInRanges(c)) {
// Append to the result the delimiter which has been removed by the splitter.
result ~= field ~ options.delimiter;
}
c++;
}
return result;
}
/**
* Cut lines based on bytes
*
* @param char[] line a single line
*
* @return char[]
*/
auto cutLineByBytes(char[] line) {
char[] result;
auto c = 1U;
foreach (field; line) {
if (isInRanges(c)) {
// Append to the result the field(byte).
result ~= field;
}
c++;
}
return result;
}
/**
* Checks if a given integer(unsiged) is in one of the selected ranges.
* If operating in complement mode, the boolean in inverted.
*
* O(n) complexity where n is the number of ranges
*
* @param uint c the column/field number
*
* @return boolean true if it's in the ranges
*/
bool isInRanges(uint c) {
auto isInRanges = false;
foreach (range; options.ranges) {
// stop iterating if the end field has been reached.
if (c > range.to) {
continue;
}
if (c >= range.from && c <= range.to) {
isInRanges = true;
break;
}
}
return (options.complement) ? !isInRanges : isInRanges;
}
// Unit test for cut lines by fields/bytes and complement
unittest {
auto cut = new Cut();
cut.options = new CutOptionsParser();
cut.options.ranges ~= Range(1, 5);
cut.options.delimiter = 0x20;
char[] test1 = "col1 col2 col3 col4 col5 col6 col7 col8".dup;
assert(cut.cutLineByFields(test1) == "col1 col2 col3 col4 col5 ");
cut.options.ranges[0].from = 1;
cut.options.ranges[0].to = uint.max;
assert(cut.cutLineByFields(test1) == "col1 col2 col3 col4 col5 col6 col7 col8 ");
cut.options.ranges[0].from = 1;
cut.options.ranges[0].to = 4;
assert(cut.cutLineByBytes(test1) == "col1");
cut.options.complement = true;
assert(cut.cutLineByBytes(test1) == " col2 col3 col4 col5 col6 col7 col8");
writeln("tests: Cut \t passed ✓");
}
}
// Main function
void main(string[] args) {
auto cut = new Cut(args);
}