├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── css
└── custom.css
├── dub.sdl
├── dub.selections.json
├── js
└── custom.js
├── src
├── functional.d
├── main.d
└── main_with_parser.und
└── views
└── page.jade
/.gitignore:
--------------------------------------------------------------------------------
1 | .dub
2 | docs.json
3 | __dummy.html
4 | *.o
5 | *.obj
6 | _site
7 | __test__library__
8 | d-functional-garden
9 | d-functional-garden-test-library
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: d
2 | d:
3 | - dmd
4 | # - gdc
5 | - ldc
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Seb
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | d-functional-garden
2 | ===================
3 |
4 | [](https://travis-ci.org/wilzbach/d-functional-garden)
5 |
6 | Functional garden for the D Language
7 |
8 | The Functional DLang Garden provides a variety of snippets that can be used to learn D or as a quick reference.
9 |
10 | All samples are valid code and automatically tested on every run (dmd, ldc).
11 |
12 | Run it yourself
13 | ---------------
14 |
15 | ```
16 | dub test && dub
17 | ```
18 |
19 | Contribute
20 | ----------
21 |
22 | Contributions are more than welcome - just send a PR.
23 |
24 |
25 | How to add a new D snippet?
26 | ---------------------------
27 |
28 | Go to [functional.d](https://github.com/wilzbach/d-functional-garden/blob/master/src/functional.d) and add your snippet as new unittest.
29 | You can run the tests with `dub test` or `rdmd -unittest --main functional.d`.
30 |
--------------------------------------------------------------------------------
/css/custom.css:
--------------------------------------------------------------------------------
1 | .code-unittest{
2 | overflow: auto;
3 | white-space: pre-wrap;
4 | }
5 | .code-desc{
6 | padding-top: 10px;
7 | }
8 | body{
9 | background-color: #eee;
10 | }
11 | .mui-panel h2{
12 | margin-top: 0px;
13 | font-weight: 300;
14 | }
15 | #sidedrawer-list a{
16 | font-weight: 400;
17 | font-size: 16px;
18 | text-decoration: none;
19 | }
20 |
21 | #sidedrawer-list li.active{
22 | background-color: #E0E0E0;
23 | }
24 |
25 | #header li{
26 | padding-left: 15px;
27 | padding-right: 15px;
28 | }
29 |
30 | #header li a{
31 | color: #fff;
32 | font-size: 16px;
33 | }
34 |
35 | #header-title-span{
36 | float: right;
37 | display: inline-block;
38 | margin-top: 20px;
39 | margin-right: 120px;
40 | }
41 |
42 | @media (min-width: 544px){
43 | #github-badge{
44 | top: 194px;
45 | }
46 | }
47 |
48 | .padded-container{
49 | margin-right: auto;
50 | margin-left: auto;
51 | padding-left: 15px;
52 | padding-right: 15px;
53 | }
54 |
55 | #content-wrapper .text-panel{
56 | font-size: 16px;
57 | }
58 |
59 | body{
60 | font-family: 'Roboto', sans-serif;
61 | }
62 |
63 | /*.mui-appbar{*/
64 | /*padding: 1px;*/
65 | /*}*/
66 |
67 | /**
68 | * Body CSS
69 | */
70 | html,
71 | body {
72 | height: 100%;
73 | background-color: #eee;
74 | }
75 |
76 | html,
77 | body,
78 | input,
79 | textarea,
80 | buttons {
81 | -webkit-font-smoothing: antialiased;
82 | -moz-osx-font-smoothing: grayscale;
83 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
84 | }
85 |
86 |
87 | /**
88 | * Layout CSS
89 | */
90 | #header {
91 | position: fixed;
92 | top: 0;
93 | right: 0;
94 | left: 0;
95 | z-index: 2;
96 | transition: left 0.2s;
97 | }
98 |
99 | #sidedrawer {
100 | position: fixed;
101 | top: 0;
102 | bottom: 0;
103 | width: 200px;
104 | left: -200px;
105 | overflow: auto;
106 | z-index: 2;
107 | background-color: #fff;
108 | transition: transform 0.2s;
109 | }
110 |
111 | #content-wrapper {
112 | min-height: 100%;
113 | overflow-x: hidden;
114 | margin-left: 0px;
115 | transition: margin-left 0.2s;
116 |
117 | /* sticky bottom */
118 | /*margin-bottom: -160px;*/
119 | /*padding-bottom: 160px;*/
120 | }
121 |
122 | #footer {
123 | height: 160px;
124 | margin-left: 0px;
125 | transition: margin-left 0.2s;
126 | }
127 |
128 | @media (min-width: 768px) {
129 | #header {
130 | left: 200px;
131 | }
132 |
133 | #sidedrawer {
134 | transform: translate(200px);
135 | }
136 |
137 | #content-wrapper {
138 | margin-left: 200px;
139 | }
140 |
141 | #footer {
142 | margin-left: 200px;
143 | }
144 |
145 | body.hide-sidedrawer #header {
146 | left: 0;
147 | }
148 |
149 | body.hide-sidedrawer #sidedrawer {
150 | transform: translate(0px);
151 | }
152 |
153 | body.hide-sidedrawer #content-wrapper {
154 | margin-left: 0;
155 | }
156 |
157 | body.hide-sidedrawer #footer {
158 | margin-left: 0;
159 | }
160 | }
161 |
162 |
163 | /**
164 | * Toggle Side drawer
165 | */
166 | #sidedrawer.active {
167 | transform: translate(200px);
168 | }
169 |
170 |
171 | /**
172 | * Header CSS
173 | */
174 | .sidedrawer-toggle {
175 | color: #fff;
176 | cursor: pointer;
177 | font-size: 20px;
178 | line-height: 20px;
179 | margin-right: 10px;
180 | }
181 |
182 | .sidedrawer-toggle:hover {
183 | color: #fff;
184 | text-decoration: none;
185 | }
186 |
187 |
188 | /**
189 | * Footer CSS
190 | */
191 | #footer {
192 | background-color: #0288D1;
193 | color: #fff;
194 | }
195 |
196 | #footer a {
197 | color: #fff;
198 | text-decoration: underline;
199 | }
200 |
201 | /**
202 | * Side drawer CSS
203 | */
204 | #sidedrawer-brand {
205 | text-align: center;
206 | font-weight:300;
207 | }
208 |
209 | #sidedrawer ul {
210 | list-style: none;
211 | padding-left: 0px;
212 | }
213 |
214 | #sidedrawer > ul {
215 | padding-left: 0px;
216 | }
217 |
218 | #sidedrawer > ul > li:first-child {
219 | padding-top: 15px;
220 | }
221 |
222 | #sidedrawer a{
223 | display: block;
224 | padding: 5px 0px 5px 15px;
225 | cursor: pointer;
226 | }
227 |
228 | #sidedrawer a:hover {
229 | background-color: #E0E0E0;
230 | }
231 |
232 | #sidedrawer a+ ul > li {
233 | padding: 6px 0px;
234 | }
235 | /*
236 | ::-webkit-scrollbar {
237 | width: 10px;
238 | }
239 | */
240 |
241 | /*
242 | ::-webkit-scrollbar-thumb {
243 | -webkit-border-radius: 1ex;
244 | -webkit-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.75);
245 | }
246 | */
247 |
248 |
--------------------------------------------------------------------------------
/dub.sdl:
--------------------------------------------------------------------------------
1 | name "d-functional-garden"
2 | description "D Functional garden"
3 | copyright "Copyright © 2016, wilzbach"
4 | authors "wilzbach"
5 | dependency "libdparse" version="~>0.5"
6 | dependency "diet-ng" version="~>0.1"
7 | mainSourceFile "src/main.d"
8 |
--------------------------------------------------------------------------------
/dub.selections.json:
--------------------------------------------------------------------------------
1 | {
2 | "fileVersion": 1,
3 | "versions": {
4 | "libdparse": "~master",
5 | "diet-ng": "~master",
6 | "experimental_allocator": "2.70.0-b1"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/js/custom.js:
--------------------------------------------------------------------------------
1 | jQuery(function($) {
2 | var $bodyEl = $('body'),
3 | $sidedrawerEl = $('#sidedrawer');
4 |
5 |
6 | function showSidedrawer() {
7 | // show overlay
8 | var options = {
9 | onclose: function() {
10 | $sidedrawerEl
11 | .removeClass('active')
12 | .appendTo(document.body);
13 | }
14 | };
15 |
16 | var $overlayEl = $(mui.overlay('on', options));
17 |
18 | // show element
19 | $sidedrawerEl.appendTo($overlayEl);
20 | setTimeout(function() {
21 | $sidedrawerEl.addClass('active');
22 | }, 20);
23 | }
24 |
25 |
26 | function hideSidedrawer() {
27 | $bodyEl.toggleClass('hide-sidedrawer');
28 | }
29 |
30 |
31 | $('.js-show-sidedrawer').on('click', showSidedrawer);
32 | $('.js-hide-sidedrawer').on('click', hideSidedrawer);
33 |
34 | // get menu clicks
35 | $(function(){
36 | $(document).on('click','a',function(){
37 | var id = $(this).attr('id');
38 | menuItems.parent().removeClass("active")
39 | $(this).addClass("active");
40 | })
41 | })
42 | var topMenu = $("#sidedrawer-list"),
43 | topOffset = $("header").outerHeight(),
44 | // All list items
45 | menuItems = topMenu.find("a"),
46 | // Anchors corresponding to menu items
47 | scrollItems = menuItems.map(function(){
48 | var item = $($(this).attr("href"));
49 | if (item.length) { return item; }
50 | });
51 |
52 | // Bind to scroll
53 | $(window).scroll(function(){
54 | // Get container scroll position
55 | var fromTop = $(this).scrollTop();
56 |
57 | // Get id of current scroll item
58 | var curDist = 100000;
59 | var cur = this;
60 | scrollItems.each(function(i, el){
61 | var newDist = el.offset().top - fromTop;
62 | if (newDist < curDist && newDist >= 0){
63 | curDist = newDist;
64 | cur = el;
65 | }
66 | });
67 | // Get the id of the current element
68 | var id = cur && cur.length ? cur[0].id : "";
69 | // Set/remove active class
70 | menuItems
71 | .parent().removeClass("active")
72 | .end().filter("[href='#"+id+"']").parent().addClass("active");
73 | });
74 |
75 | });
76 |
--------------------------------------------------------------------------------
/src/functional.d:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Guidelines:
4 | - Please don't use global imports of the functions you want to show.
5 | (Every test should be independent.)
6 | - Add one line between imports and your tests if you have more than one
7 | import line
8 | */
9 |
10 | // we use this comparison method a lot
11 | import std.algorithm: equal;
12 |
13 | // Needed for custom name attribute
14 | struct name
15 | {
16 | string val;
17 | }
18 |
19 | /**
20 | With map we can call a custom function for every element
21 | */
22 | @name("Increment elements") @safe unittest{
23 | import std.algorithm: map;
24 | auto result = [1, 2, 3].map!(a => a + 1);
25 | assert(result.equal([2 ,3, 4]));
26 | }
27 |
28 | /**
29 | With reduce we can apply a function with a starting value and call it with
30 | the memo and the current value for all values.
31 | */
32 | @name("Reduce to minimum") @safe unittest{
33 | import std.algorithm: min, max, reduce;
34 | auto result = [3, 2, 1].reduce!min;
35 | assert(result == 1);
36 | result = [3, 2, 1].reduce!max;
37 | assert(result == 3);
38 | }
39 |
40 | /**
41 | We can also filter our input range with custom functions
42 | */
43 | @name("Filter elements") @safe unittest{
44 | import std.algorithm: count, filter;
45 | import std.string: indexOf;
46 |
47 | auto result = ["hello", "world"]
48 | .filter!(a => a.indexOf("wo") >= 0)
49 | .count;
50 | assert(result == 1);
51 | }
52 |
53 | /**
54 | Sort accepts a pred template, which means we can just pass our own
55 | */
56 | @name("Reverse sort") @safe unittest{
57 | import std.algorithm: sort;
58 |
59 | auto result = [1, 3, 2].sort!"a > b";
60 | assert(result.equal([3, 2, 1]));
61 | }
62 |
63 | /**
64 | A pretty common pattern is to read user input.
65 | Splitter is usually lazily evaluated.
66 | */
67 | @name("Split string to ints") @safe unittest{
68 | import std.algorithm: map, splitter;
69 | import std.array: array;
70 | import std.conv: to;
71 |
72 | auto result = "1 3 2".splitter().map!(to!int);
73 | assert(result.equal([1, 3, 2]));
74 | }
75 |
76 | /**
77 | A typical example from Unix is to sort and count the number of unique lines.
78 | In D we can do the same!
79 | */
80 | @name("Count number of unique elements") @safe unittest{
81 | import std.algorithm: count, sort, uniq;
82 | auto result = [1, 3, 2, 2, 3].sort().uniq.count;
83 | assert(result == 3);
84 | }
85 |
86 | /**
87 | We split our input range into chunks of the size two and calculate the sum for each.
88 | */
89 | @name("Pairwise sum") @safe unittest{
90 | import std.algorithm: map, sum;
91 | import std.range: chunks;
92 |
93 | auto result = [1, 2, 3, 4].chunks(2).map!(sum);
94 | assert(result.equal([3, 7]));
95 | }
96 |
97 | /**
98 | This approach requires sorting the array.
99 | Use a dict (below) - it doesn't require sorting.
100 | */
101 | @name("Count chars") unittest{
102 | import std.array: array;
103 | import std.algorithm: sort, group;
104 | import std.typecons: tuple; // create tuples manually
105 |
106 | auto result = "ABCA".array.sort().group.array;
107 | auto expected = [tuple('A', 2),
108 | tuple('B', 1),
109 | tuple('C', 1)];
110 | assert(expected == cast(typeof(expected)) result);
111 | }
112 |
113 | /**
114 | We can iterate pairwise over all k-mer and list them.
115 | The syntax to convert a tuple back to list is a bit hard to figure out.
116 | */
117 | @name("List k-mer") unittest{
118 | import std.algorithm: map;
119 | import std.array: array, join;
120 | import std.range: dropOne, only, save, zip;
121 | import std.conv: to;
122 |
123 | auto arr = "AGCGA".array;
124 | auto result = arr.zip(arr.save.dropOne)
125 | .map!"a.expand.only"
126 | .map!(to!string)
127 | .join(",");
128 | assert(result == "AG,GC,CG,GA");
129 | }
130 |
131 | /**
132 | We iterate over all pairs of the string and increment a key in our dictionary.
133 | In D a new key is automatically created once it is accessed for the first time.
134 | The syntax to convert a tuple back to list is a bit hard to figure out.
135 | */
136 | @name("Count k-mer with defaultdict") unittest{
137 | import std.array: array;
138 | import std.algorithm: each, map;
139 | import std.range: dropOne, only, save, zip;
140 | import std.conv: to;
141 |
142 | int[string] d;
143 | auto arr = "AGAGA".array;
144 | arr.zip(arr.save.dropOne)
145 | .map!"a.expand.only"
146 | .map!(to!string)
147 | .each!(a => d[a]++);
148 | assert(d == ["AG": 2, "GA": 2]);
149 | }
150 |
151 | @name("Filter by index") @safe unittest{
152 | import std.range: chain, zip;
153 | import std.typecons;
154 | import std.algorithm : map, max, reduce, sum;
155 | import std.array: array;
156 |
157 | auto a = tuple([1, 2, 3], [4, 5, 6], [7, 8, 9]);
158 | auto ab = a.array.map!"a.sum";
159 | auto ac = zip(ab).map!"sum(a[].only)";
160 | assert(ab.chain(ac).reduce!max == 24);
161 | }
162 |
163 | /**
164 | With enumerate we get an index which we can use to filter
165 | */
166 | @name("Filter by index") @safe unittest{
167 | import std.algorithm: filter, map;
168 | import std.range: enumerate;
169 |
170 | auto result = [3, 4, 5]
171 | .enumerate
172 | .filter!(a => a[0] != 1)
173 | .map!"a[1]";
174 | assert(result.equal([3, 5]));
175 | }
176 |
177 | /**
178 | With enumerate we get an index with which we can remove all odd numbers.
179 | */
180 | @name("Sum up even indexed number") @safe unittest{
181 | import std.algorithm: filter, map, sum;
182 | import std.range: enumerate;
183 |
184 | auto result = [3, 4, 5]
185 | .enumerate
186 | .filter!(a => a[0] % 2 == 0)
187 | .map!"a[1]"
188 | .sum;
189 | assert(result == 8);
190 | }
191 |
192 | /**
193 | Yet another good example of group.
194 | */
195 | @name("Most common word") @safe unittest{
196 | import std.algorithm: group, map, sort;
197 | import std.array: split;
198 | import std.string: toLower;
199 | import std.typecons: tuple;
200 |
201 | string text = "Two times two equals four";
202 | auto result = text
203 | .toLower
204 | .split(' ')
205 | .sort()
206 | .group;
207 | assert(result.equal([tuple("equals", 1u), tuple("four", 1u),
208 | tuple("times", 1u), tuple("two", 2u)]));
209 | }
210 |
211 | /**
212 | reverseArgs can be used to continue using the result in a UFCS pipe
213 | */
214 | @name("Format a range pipeline") @safe unittest{
215 | import std.algorithm : filter, map, sum;
216 | import std.range : iota;
217 | import std.format : format;
218 | import std.functional : reverseArgs;
219 |
220 | auto res = 6.iota
221 | .filter!(a => a % 2) // 0 2 4
222 | .map!(a => a * 2) // 0 4 8
223 | .sum
224 | .reverseArgs!format("Sum: %d");
225 | assert(res == "Sum: 18");
226 | }
227 |
228 | /**
229 | With cumulativeFold a lazy, stack-based parser can be written
230 | */
231 | @name("Lazy parser") @safe unittest{
232 | import std.algorithm : cumulativeFold, equal, map, until;
233 | import std.range : zip;
234 | auto input = "foo()bar)";
235 | auto result = input.cumulativeFold!((count, r){
236 | switch(r)
237 | {
238 | case '(':
239 | count++;
240 | break;
241 | case ')':
242 | count--;
243 | break;
244 | default:
245 | }
246 | return count;
247 | })(1).zip(input).until!(e => e[0] == 0).map!(e => e[1]);
248 | assert(result.equal("foo()bar"));
249 | }
250 |
251 | /**
252 | This is a good example how expressive functional programming can be.
253 | Also note that the second base case is not necessary.
254 | */
255 | @name("Quicksort") @safe unittest{
256 | import std.algorithm: filter;
257 | import std.array: array;
258 |
259 | int[] qs(int[] arr) {
260 | if(!arr.length) return [];
261 | if(arr.length == 1) return arr; // optional
262 | return qs(arr.filter!(a => a < arr[0]).array) ~ arr[0] ~ qs(arr[1..$].filter!(a => a >= arr[0]).array);
263 | }
264 | assert(qs([3, 2, 1, 4]) == [1, 2, 3, 4]);
265 | assert(qs([1]) == [1]);
266 | assert(qs([]) == []);
267 | }
268 |
--------------------------------------------------------------------------------
/src/main.d:
--------------------------------------------------------------------------------
1 | import std.stdio;
2 | import std.conv;
3 | import std.string;
4 | import std.file;
5 | import std.regex;
6 |
7 | auto slugR = ctRegex!(r"[^a-z]+", "g");
8 | string slug(string target) {
9 | import std.string : toLower;
10 | auto ret = target.toLower.replaceAll(slugR, "-");
11 |
12 | if(ret[0] == '-') {
13 | ret = ret[1..$];
14 | }
15 |
16 | if(ret[$-1] == '-') {
17 | ret = ret[0..$-1];
18 | }
19 |
20 | return ret;
21 | }
22 |
23 |
24 | struct Test{
25 | string name;
26 | string code;
27 | string desc;
28 | string slug;
29 | }
30 |
31 | import std.range;
32 |
33 | auto removeIndentR = ctRegex!(r"\n ", "g");
34 | Test[] parseTests(string filename){
35 | // even with a lot of workarounds libdparse doesn't work well with lambdas
36 | // for the time being we use regexs
37 | auto r = regex(`\/\*\*((.|\n)*?)\*\/\n\s*@name\("(.*?)"\)\s*(@safe)?\s*unittest\s*\{((.|\n)*?)\n\}`);
38 | auto text = filename.readText;
39 | Test[] tests;
40 | foreach (c; text.matchAll(r)){
41 | auto desc = c[1];
42 | auto name = c[3];
43 | auto code = c[5];
44 | auto slug = name.slug;
45 | // remove one indent - dirty fix
46 | code = code.replaceAll(removeIndentR, "\n");
47 | code = code.chompPrefix("\n");
48 | Test test = {name: name, code: code, desc: desc, slug: slug};
49 | tests ~= [test];
50 | }
51 | return tests;
52 | }
53 |
54 | void writeTests(Test[] tests, string filename){
55 | import diet.html;
56 | import std.stdio;
57 | import std.path: dirName;
58 | string dir = filename.dirName;
59 | if(!exists(dir)){
60 | mkdir(dir);
61 | }
62 | auto f = File(filename, "wt");
63 | scope(exit) f.close();
64 | auto dst = f.lockingTextWriter;
65 | string title = "D Functional garden";
66 | dst.compileHTMLDietFile!("./page.jade", tests, title);
67 | writefln("Wrote %d tests", tests.length);
68 | }
69 |
70 | void main(){
71 | string inFilename = "src/functional.d";
72 | assert(exists(inFilename));
73 | string outFilename = "_site/index.html";
74 | parseTests(inFilename).writeTests(outFilename);
75 | // other stuff
76 | copy("css/custom.css", "_site/custom.css");
77 | copy("js/custom.js", "_site/custom.js");
78 | }
79 |
--------------------------------------------------------------------------------
/src/main_with_parser.und:
--------------------------------------------------------------------------------
1 | import dparse.lexer;
2 | import dparse.ast;
3 | import dparse.parser;
4 | import dformat = dparse.formatter;
5 |
6 | // even with a lot of workarounds libdparse doesn't work well with lambdas
7 | // parking here for the time being
8 |
9 | import std.regex : ctRegex, replaceAll;
10 | import std.file;
11 | import std.stdio;
12 | import std.conv;
13 | import std.string;
14 |
15 | auto slugR = ctRegex!(r"[^a-z]+", "g");
16 | string slug(string target) {
17 | import std.string : toLower;
18 | auto ret = target.toLower.replaceAll(slugR, "-");
19 |
20 | if(ret[0] == '-') {
21 | ret = ret[1..$];
22 | }
23 |
24 | if(ret[$-1] == '-') {
25 | ret = ret[0..$-1];
26 | }
27 |
28 | return ret;
29 | }
30 |
31 | struct Test{
32 | string name;
33 | string text;
34 | string slug;
35 | string desc;
36 | }
37 |
38 | private string formatNode(T)(const T t)
39 | {
40 | import std.array;
41 | auto writer = appender!string();
42 | auto formatter = new dformat.Formatter!(typeof(writer))(writer);
43 | formatter.format(t);
44 | return writer.data;
45 | }
46 |
47 | Test[] parseTests(string filename){
48 | auto f = File(filename);
49 | immutable ulong fileSize = f.size();
50 | ubyte[] fileBytes = new ubyte[](fileSize);
51 | assert(f.rawRead(fileBytes).length == fileSize);
52 | StringCache cache = StringCache(StringCache.defaultBucketCount);
53 | LexerConfig config;
54 | config.stringBehavior = StringBehavior.source;
55 | config.fileName = filename;
56 | const(Token)[] tokens = getTokensForParser(fileBytes, config, &cache);
57 | auto dmod = parseModule(tokens, filename);
58 |
59 | Test[] tests;
60 | foreach(d; dmod.declarations){
61 | string name = "Untitled";
62 | foreach(a; d.attributes){
63 | auto al = a.atAttribute.argumentList;
64 | if(al !is null){
65 | name = formatNode(al.items[0])[1..$-1];
66 | }
67 | }
68 | if (auto id = d.unittest_) {
69 | string text = format(fileBytes, id.blockStatement);
70 |
71 | //writeln(text);
72 | // remove first and list newline
73 | if(text[0] == '\n'){
74 | text = text[1..$];
75 | }
76 | string desc = id.comment;
77 | // poor man's hack to get new lines
78 | desc = desc.replace("\n","
");
79 | tests ~= [Test(name, text, name.slug, desc)];
80 | }
81 | }
82 | return tests;
83 | }
84 |
85 | void log(T)(T obj) {
86 | static if (is(T == struct) || is(T == class)){
87 | writef("{");
88 | foreach(i,_;obj.tupleof) {
89 | writefln("%s : %s,", obj.tupleof[i].stringof[4..$], obj.tupleof[i]);
90 | }
91 | writefln("}");
92 | }
93 | else {
94 | writefln(obj);
95 | }
96 | }
97 |
98 | mixin template dump(Names ... )
99 | {
100 | auto _unused_dump = {
101 | import std.stdio : writeln, write;
102 | foreach(i,name; Names)
103 | {
104 | write(name, " = ", mixin(name), (i