├── .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 | [![Build Status](https://travis-ci.org/wilzbach/d-functional-garden.svg?branch=master)](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), (iContributions are welcome. 59 | a(href="https://travis-ci.org/wilzbach/d-functional-garden") 60 | img(src="https://travis-ci.org/wilzbach/d-functional-garden.svg?branch=master",alt="Build status") 61 | - foreach (test; tests) 62 | div(class="mui-panel") 63 | h2(id="#{test.slug}",class="mui--text-headline") #{test.name} 64 | div(class="mui-divider") 65 | p(class="code-desc") !{test.desc} 66 | pre 67 | code(class="d",class="code-unittest") #{test.code} 68 | footer 69 | div(class="mui-container mui--text-center") Made with ♥ by wilzbach 70 | script(type='text/javascript'). 71 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 72 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 73 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 74 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 75 | ga('create', 'UA-74308534-1', 'auto'); 76 | ga('send', 'pageview'); 77 | --------------------------------------------------------------------------------