tag:blogger.com,1999:blog-21087584055515172782024-03-05T15:01:43.917+02:00Andre Van Der MerweAnonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.comBlogger23125tag:blogger.com,1999:blog-2108758405551517278.post-21799002345549616962015-12-15T16:40:00.000+02:002015-12-15T16:40:03.688+02:00CentralConfig
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Untitled Document.md</title><style>@import 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.2.0/katex.min.css';code{color:#c7254e;background-color:#f9f2f4;border-radius:4px}code,kbd{padding:2px 4px}kbd{color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;box-shadow:none}pre{display:block;margin:0 0 10px;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}fieldset{border:0;min-width:0}legend{display:block;width:100%;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type="radio"],input[type="checkbox"]{margin:1px 0 0;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{padding-top:7px}output,.form-control{display:block;font-size:14px;line-height:1.4285714;color:#555}.form-control{width:100%;height:34px;padding:6px 12px;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#777;opacity:1}.form-control:-ms-input-placeholder{color:#777}.form-control::-webkit-input-placeholder{color:#777}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"]{line-height:34px;line-height:1.4285714 \0}input[type="date"].input-sm,.form-horizontal .form-group-sm input[type="date"].form-control,.input-group-sm>input[type="date"].form-control,.input-group-sm>input[type="date"].input-group-addon,.input-group-sm>.input-group-btn>input[type="date"].btn,input[type="time"].input-sm,.form-horizontal .form-group-sm input[type="time"].form-control,.input-group-sm>input[type="time"].form-control,.input-group-sm>input[type="time"].input-group-addon,.input-group-sm>.input-group-btn>input[type="time"].btn,input[type="datetime-local"].input-sm,.form-horizontal .form-group-sm input[type="datetime-local"].form-control,.input-group-sm>input[type="datetime-local"].form-control,.input-group-sm>input[type="datetime-local"].input-group-addon,.input-group-sm>.input-group-btn>input[type="datetime-local"].btn,input[type="month"].input-sm,.form-horizontal .form-group-sm input[type="month"].form-control,.input-group-sm>input[type="month"].form-control,.input-group-sm>input[type="month"].input-group-addon,.input-group-sm>.input-group-btn>input[type="month"].btn{line-height:30px}input[type="date"].input-lg,.form-horizontal .form-group-lg input[type="date"].form-control,.input-group-lg>input[type="date"].form-control,.input-group-lg>input[type="date"].input-group-addon,.input-group-lg>.input-group-btn>input[type="date"].btn,input[type="time"].input-lg,.form-horizontal .form-group-lg input[type="time"].form-control,.input-group-lg>input[type="time"].form-control,.input-group-lg>input[type="time"].input-group-addon,.input-group-lg>.input-group-btn>input[type="time"].btn,input[type="datetime-local"].input-lg,.form-horizontal .form-group-lg input[type="datetime-local"].form-control,.input-group-lg>input[type="datetime-local"].form-control,.input-group-lg>input[type="datetime-local"].input-group-addon,.input-group-lg>.input-group-btn>input[type="datetime-local"].btn,input[type="month"].input-lg,.form-horizontal .form-group-lg input[type="month"].form-control,.input-group-lg>input[type="month"].form-control,.input-group-lg>input[type="month"].input-group-addon,.input-group-lg>.input-group-btn>input[type="month"].btn{line-height:46px}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;min-height:20px;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="radio"].disabled,fieldset[disabled] input[type="radio"],input[type="checkbox"][disabled],input[type="checkbox"].disabled,fieldset[disabled] input[type="checkbox"],.radio-inline.disabled,fieldset[disabled] .radio-inline,.checkbox-inline.disabled,fieldset[disabled] .checkbox-inline,.radio.disabled label,fieldset[disabled] .radio label,.checkbox.disabled label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-horizontal .form-group-lg .form-control-static.form-control,.input-group-lg>.form-control-static.form-control,.input-group-lg>.form-control-static.input-group-addon,.input-group-lg>.input-group-btn>.form-control-static.btn,.form-control-static.input-sm,.form-horizontal .form-group-sm .form-control-static.form-control,.input-group-sm>.form-control-static.form-control,.input-group-sm>.form-control-static.input-group-addon,.input-group-sm>.input-group-btn>.form-control-static.btn{padding-left:0;padding-right:0}.input-sm,.form-horizontal .form-group-sm .form-control,.input-group-sm>.form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.input-group-sm>.input-group-addon{height:30px;line-height:1.5}.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm,.form-horizontal .form-group-sm select.form-control,.input-group-sm>select.form-control,.input-group-sm>select.input-group-addon,.input-group-sm>.input-group-btn>select.btn{height:30px;line-height:30px}textarea.input-sm,.form-horizontal .form-group-sm textarea.form-control,.input-group-sm>textarea.form-control,.input-group-sm>textarea.input-group-addon,.input-group-sm>.input-group-btn>textarea.btn,select[multiple].input-sm,.form-horizontal .form-group-sm select[multiple].form-control,.input-group-sm>select[multiple].form-control,.input-group-sm>select[multiple].input-group-addon,.input-group-sm>.input-group-btn>select[multiple].btn{height:auto}.input-lg,.form-horizontal .form-group-lg .form-control,.input-group-lg>.form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.input-group-lg>.input-group-addon{height:46px;line-height:1.33}.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg,.form-horizontal .form-group-lg select.form-control,.input-group-lg>select.form-control,.input-group-lg>select.input-group-addon,.input-group-lg>.input-group-btn>select.btn{height:46px;line-height:46px}textarea.input-lg,.form-horizontal .form-group-lg textarea.form-control,.input-group-lg>textarea.form-control,.input-group-lg>textarea.input-group-addon,.input-group-lg>.input-group-btn>textarea.btn,select[multiple].input-lg,.form-horizontal .form-group-lg select[multiple].form-control,.input-group-lg>select[multiple].form-control,.input-group-lg>select[multiple].input-group-addon,.input-group-lg>.input-group-btn>select[multiple].btn{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:25px;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center}.input-lg+.form-control-feedback,.form-horizontal .form-group-lg .form-control+.form-control-feedback,.input-group-lg>.form-control+.form-control-feedback,.input-group-lg>.input-group-addon+.form-control-feedback,.input-group-lg>.input-group-btn>.btn+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback,.form-horizontal .form-group-sm .form-control+.form-control-feedback,.input-group-sm>.form-control+.form-control-feedback,.input-group-sm>.input-group-addon+.form-control-feedback,.input-group-sm>.input-group-btn>.btn+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-group:before{content:" ";display:table}.form-horizontal .form-group:after{content:" ";display:table;clear:both}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}.btn{display:inline-block;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.btn-default.dropdown-toggle{background-image:none}.btn-default.disabled,.btn-default.disabled:hover,.btn-default.disabled:focus,.btn-default.disabled:active,.btn-default.disabled.active,.btn-default[disabled],.btn-default[disabled]:hover,.btn-default[disabled]:focus,.btn-default[disabled]:active,.btn-default[disabled].active,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default:hover,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.btn-primary.dropdown-toggle{background-image:none}.btn-primary.disabled,.btn-primary.disabled:hover,.btn-primary.disabled:focus,.btn-primary.disabled:active,.btn-primary.disabled.active,.btn-primary[disabled],.btn-primary[disabled]:hover,.btn-primary[disabled]:focus,.btn-primary[disabled]:active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary:hover,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.btn-success.dropdown-toggle{background-image:none}.btn-success.disabled,.btn-success.disabled:hover,.btn-success.disabled:focus,.btn-success.disabled:active,.btn-success.disabled.active,.btn-success[disabled],.btn-success[disabled]:hover,.btn-success[disabled]:focus,.btn-success[disabled]:active,.btn-success[disabled].active,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success:hover,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.btn-info.dropdown-toggle{background-image:none}.btn-info.disabled,.btn-info.disabled:hover,.btn-info.disabled:focus,.btn-info.disabled:active,.btn-info.disabled.active,.btn-info[disabled],.btn-info[disabled]:hover,.btn-info[disabled]:focus,.btn-info[disabled]:active,.btn-info[disabled].active,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info:hover,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.btn-warning.dropdown-toggle{background-image:none}.btn-warning.disabled,.btn-warning.disabled:hover,.btn-warning.disabled:focus,.btn-warning.disabled:active,.btn-warning.disabled.active,.btn-warning[disabled],.btn-warning[disabled]:hover,.btn-warning[disabled]:focus,.btn-warning[disabled]:active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning:hover,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.btn-danger.dropdown-toggle{background-image:none}.btn-danger.disabled,.btn-danger.disabled:hover,.btn-danger.disabled:focus,.btn-danger.disabled:active,.btn-danger.disabled.active,.btn-danger[disabled],.btn-danger[disabled]:hover,.btn-danger[disabled]:focus,.btn-danger[disabled]:active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger:hover,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#428bca;font-weight:400;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:hover,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm{padding:5px 10px}.btn-sm,.btn-xs{font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{padding:1px 5px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon{white-space:nowrap}.input-group-addon,.input-group-btn{width:1%;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm,.form-horizontal .form-group-sm .input-group-addon.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.input-group-addon.btn{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg,.form-horizontal .form-group-lg .input-group-addon.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.input-group-addon.btn{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{font-size:0;white-space:nowrap}.input-group-btn,.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.4285714;text-decoration:none;color:#428bca;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>a:focus,.pagination>li>span:hover,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:hover,.pagination>.active>a:focus,.pagination>.active>span,.pagination>.active>span:hover,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.modal-open,.modal{overflow:hidden}.modal{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate3d(0,-25%,0);transform:translate3d(0,-25%,0);-webkit-transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;box-shadow:0 3px 9px rgba(0,0,0,.5);background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.4285714px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.4285714}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer:before,.modal-footer:after{content:" ";display:table}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.hljs{display:block;overflow-x:auto;padding:.5em;background:#002b36;color:#839496;-webkit-text-size-adjust:none}.hljs-comment,.hljs-template_comment,.diff .hljs-header,.hljs-doctype,.hljs-pi,.lisp .hljs-string,.hljs-javadoc{color:#586e75}.hljs-keyword,.hljs-winutils,.method,.hljs-addition,.css .hljs-tag,.hljs-request,.hljs-status,.nginx .hljs-title{color:#859900}.hljs-number,.hljs-command,.hljs-string,.hljs-tag .hljs-value,.hljs-rules .hljs-value,.hljs-phpdoc,.hljs-dartdoc,.tex .hljs-formula,.hljs-regexp,.hljs-hexcolor,.hljs-link_url{color:#2aa198}.hljs-title,.hljs-localvars,.hljs-chunk,.hljs-decorator,.hljs-built_in,.hljs-identifier,.vhdl .hljs-literal,.hljs-id,.css .hljs-function{color:#268bd2}.hljs-attribute,.hljs-variable,.lisp .hljs-body,.smalltalk .hljs-number,.hljs-constant,.hljs-class .hljs-title,.hljs-parent,.hljs-type,.hljs-link_reference{color:#b58900}.hljs-preprocessor,.hljs-preprocessor .hljs-keyword,.hljs-pragma,.hljs-shebang,.hljs-symbol,.hljs-symbol .hljs-string,.diff .hljs-change,.hljs-special,.hljs-attr_selector,.hljs-subst,.hljs-cdata,.css .hljs-pseudo,.hljs-header{color:#cb4b16}.hljs-deletion,.hljs-important{color:#dc322f}.hljs-link_label{color:#6c71c4}.tex .hljs-formula{background:#073642}*,*:before,*:after{box-sizing:border-box}html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}images{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd{font-size:1em}code,kbd,pre,samp{font-family:monospace,monospace}samp{font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}.debug{background-color:#ffc0cb!important}.ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ir{background-color:transparent;border:0;overflow:hidden}.ir::before{content:'';display:block;height:150%;width:0}html{font-size:.875em;background:#fafafa;color:#373D49}html,body{font-family:Georgia,Cambria,serif;height:100%}body{font-size:1rem;font-weight:400;line-height:2rem}ul,ol{margin-bottom:.83999rem;padding-top:.16001rem}li{-webkit-font-feature-settings:'kern' 1,'onum' 1,'liga' 1;-moz-font-feature-settings:'kern' 1,'onum' 1,'liga' 1;font-feature-settings:'kern' 1,'onum' 1,'liga' 1;margin-left:1rem}li>ul,li>ol{margin-bottom:0}p{padding-top:.66001rem;-webkit-font-feature-settings:'kern' 1,'onum' 1,'liga' 1;-moz-font-feature-settings:'kern' 1,'onum' 1,'liga' 1;font-feature-settings:'kern' 1,'onum' 1,'liga' 1;margin-top:0}p,pre{margin-bottom:1.33999rem}pre{font-size:1rem;padding:.66001rem 9.5px 9.5px;line-height:2rem;background:-webkit-linear-gradient(top,#fff 0,#fff .75rem,#f5f7fa .75rem,#f5f7fa 2.75rem,#fff 2.75rem,#fff 4rem);background:linear-gradient(to bottom,#fff 0,#fff .75rem,#f5f7fa .75rem,#f5f7fa 2.75rem,#fff 2.75rem,#fff 4rem);background-size:100% 4rem;border-color:#D3DAEA}blockquote{margin:0}blockquote p{font-size:1rem;margin-bottom:.33999rem;font-style:italic;padding:.66001rem 1rem 1rem;border-left:3px solid #A0AABF}th,td{padding:12px}h1,h2,h3,h4,h5,h6{font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;-webkit-font-feature-settings:'dlig' 1,'liga' 1,'lnum' 1,'kern' 1;-moz-font-feature-settings:'dlig' 1,'liga' 1,'lnum' 1,'kern' 1;font-feature-settings:'dlig' 1,'liga' 1,'lnum' 1,'kern' 1;font-style:normal;font-weight:600;margin-top:0}h1{line-height:3rem;font-size:2.0571429rem;margin-bottom:.21999rem;padding-top:.78001rem}h2{font-size:1.953125rem;margin-bottom:.1835837rem;padding-top:.8164163rem}h2,h3{line-height:3rem}h3{font-size:1.6457143rem;margin-bottom:.07599rem;padding-top:.92401rem}h4{font-size:1.5625rem;margin-bottom:.546865rem;padding-top:.453135rem}h5{font-size:1.25rem;margin-bottom:-.56251rem;padding-top:.56251rem}h6{font-size:1rem;margin-bottom:-.65001rem;padding-top:.65001rem}a{cursor:pointer;color:#35D7BB;text-decoration:none}a:hover,a:focus{border-bottom-color:#35D7BB;color:#dff9f4}img{height:auto;max-width:100%}.g{display:block}.g:after{clear:both;content:'';display:table}.g-b{float:left;margin:0;width:100%}.g{margin-left:-16px;margin-right:-16px}.g-b{padding-left:16px;padding-right:16px}.g-b--center{display:block;float:none;margin:0 auto}.g-b--right{float:right}.g-b--1of1{width:100%}.g-b--1of2,.g-b--2of4,.g-b--3of6,.g-b--4of8,.g-b--5of10,.g-b--6of12{width:50%}.g-b--1of3,.g-b--2of6,.g-b--4of12{width:33.333%}.g-b--2of3,.g-b--4of6,.g-b--8of12{width:66.666%}.g-b--1of4,.g-b--2of8,.g-b--3of12{width:25%}.g-b--3of4,.g-b--6of8,.g-b--9of12{width:75%}.g-b--1of5,.g-b--2of10{width:20%}.g-b--2of5,.g-b--4of10{width:40%}.g-b--3of5,.g-b--6of10{width:60%}.g-b--4of5,.g-b--8of10{width:80%}.g-b--1of6,.g-b--2of12{width:16.666%}.g-b--5of6,.g-b--10of12{width:83.333%}.g-b--1of8{width:12.5%}.g-b--3of8{width:37.5%}.g-b--5of8{width:62.5%}.g-b--7of8{width:87.5%}.g-b--1of10{width:10%}.g-b--3of10{width:30%}.g-b--7of10{width:70%}.g-b--9of10{width:90%}.g-b--1of12{width:8.333%}.g-b--5of12{width:41.666%}.g-b--7of12{width:58.333%}.g-b--11of12{width:91.666%}.g-b--push--1of1{margin-left:100%}.g-b--push--1of2,.g-b--push--2of4,.g-b--push--3of6,.g-b--push--4of8,.g-b--push--5of10,.g-b--push--6of12{margin-left:50%}.g-b--push--1of3,.g-b--push--2of6,.g-b--push--4of12{margin-left:33.333%}.g-b--push--2of3,.g-b--push--4of6,.g-b--push--8of12{margin-left:66.666%}.g-b--push--1of4,.g-b--push--2of8,.g-b--push--3of12{margin-left:25%}.g-b--push--3of4,.g-b--push--6of8,.g-b--push--9of12{margin-left:75%}.g-b--push--1of5,.g-b--push--2of10{margin-left:20%}.g-b--push--2of5,.g-b--push--4of10{margin-left:40%}.g-b--push--3of5,.g-b--push--6of10{margin-left:60%}.g-b--push--4of5,.g-b--push--8of10{margin-left:80%}.g-b--push--1of6,.g-b--push--2of12{margin-left:16.666%}.g-b--push--5of6,.g-b--push--10of12{margin-left:83.333%}.g-b--push--1of8{margin-left:12.5%}.g-b--push--3of8{margin-left:37.5%}.g-b--push--5of8{margin-left:62.5%}.g-b--push--7of8{margin-left:87.5%}.g-b--push--1of10{margin-left:10%}.g-b--push--3of10{margin-left:30%}.g-b--push--7of10{margin-left:70%}.g-b--push--9of10{margin-left:90%}.g-b--push--1of12{margin-left:8.333%}.g-b--push--5of12{margin-left:41.666%}.g-b--push--7of12{margin-left:58.333%}.g-b--push--11of12{margin-left:91.666%}.g-b--pull--1of1{margin-right:100%}.g-b--pull--1of2,.g-b--pull--2of4,.g-b--pull--3of6,.g-b--pull--4of8,.g-b--pull--5of10,.g-b--pull--6of12{margin-right:50%}.g-b--pull--1of3,.g-b--pull--2of6,.g-b--pull--4of12{margin-right:33.333%}.g-b--pull--2of3,.g-b--pull--4of6,.g-b--pull--8of12{margin-right:66.666%}.g-b--pull--1of4,.g-b--pull--2of8,.g-b--pull--3of12{margin-right:25%}.g-b--pull--3of4,.g-b--pull--6of8,.g-b--pull--9of12{margin-right:75%}.g-b--pull--1of5,.g-b--pull--2of10{margin-right:20%}.g-b--pull--2of5,.g-b--pull--4of10{margin-right:40%}.g-b--pull--3of5,.g-b--pull--6of10{margin-right:60%}.g-b--pull--4of5,.g-b--pull--8of10{margin-right:80%}.g-b--pull--1of6,.g-b--pull--2of12{margin-right:16.666%}.g-b--pull--5of6,.g-b--pull--10of12{margin-right:83.333%}.g-b--pull--1of8{margin-right:12.5%}.g-b--pull--3of8{margin-right:37.5%}.g-b--pull--5of8{margin-right:62.5%}.g-b--pull--7of8{margin-right:87.5%}.g-b--pull--1of10{margin-right:10%}.g-b--pull--3of10{margin-right:30%}.g-b--pull--7of10{margin-right:70%}.g-b--pull--9of10{margin-right:90%}.g-b--pull--1of12{margin-right:8.333%}.g-b--pull--5of12{margin-right:41.666%}.g-b--pull--7of12{margin-right:58.333%}.g-b--pull--11of12{margin-right:91.666%}.splashscreen{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#373D49;z-index:22}.splashscreen-dillinger{width:260px;height:auto;display:block;margin:0 auto;padding-bottom:3rem}.splashscreen p{font-size:1.25rem;padding-top:.56251rem;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:400;text-align:center;max-width:500px;margin:0 auto;color:#FFF}.sp-center{position:relative;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);top:50%}.open-menu>.wrapper{overflow-x:hidden}.page{margin:0 auto;position:relative;top:0;left:0;width:100%;height:100%;z-index:2;-webkit-transition:all .25s ease-in-out;transition:all .25s ease-in-out;background-color:#fff;padding-top:51px;will-change:left}.open-menu .page{left:270px}.title{line-height:1rem;font-size:.8rem;margin-bottom:.77999rem;padding-top:.22001rem;font-weight:500;color:#A0AABF;letter-spacing:1px;text-transform:uppercase;padding-left:16px;padding-right:16px;margin-top:1rem}.split-preview .title{padding-left:0}.title-document{line-height:1rem;font-size:1.25rem;margin-bottom:.89999rem;padding-top:.10001rem;font-weight:400;font-family:"Ubuntu Mono",Monaco;color:#373D49;padding-left:16px;padding-right:16px;width:80%;min-width:300px;outline:0;border:none}.icon{display:block;margin:0 auto;width:36px;height:36px;border-radius:3px;text-align:center}.icon svg{display:inline-block;margin-left:auto;margin-right:auto}.icon-preview{background-color:#373D49;line-height:40px}.icon-preview svg{width:19px;height:12px}.icon-settings{background-color:#373D49;line-height:44px}.icon-settings svg{width:18px;height:18px}.icon-link{width:16px;height:16px;line-height:1;margin-right:24px;text-align:right}.navbar{background-color:#373D49;height:51px;width:100%;position:fixed;top:0;left:0;z-index:6;-webkit-transition:all .25s ease-in-out;transition:all .25s ease-in-out;will-change:left}.navbar:after{content:"";display:table;clear:both}.open-menu .navbar{left:270px}.navbar-brand{float:left;margin:0 0 0 24px;padding:0;line-height:42px}.navbar-brand svg{width:85px;height:11px}.nav-left{float:left}.nav-right{float:right}.nav-sidebar{width:100%}.menu{list-style:none;margin:0;padding:0}.menu a{border:0;color:#A0AABF;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;outline:none;text-transform:uppercase}.menu a:hover{color:#35D7BB}.menu .menu-item{border:0;display:none;float:left;margin:0;position:relative}.menu .menu-item>a{display:block;font-size:12px;height:51px;letter-spacing:1px;line-height:51px;padding:0 24px}.menu .menu-item--settings,.menu .menu-item--preview,.menu .menu-item--save-to.in-sidebar,.menu .menu-item--import-from.in-sidebar,.menu .menu-item--link-unlink.in-sidebar,.menu .menu-item--documents.in-sidebar{display:block}.menu .menu-item--documents{padding-bottom:1rem}.menu .menu-item.open>a{background-color:#1D212A}.menu .menu-item-icon>a{height:auto;padding:0}.menu .menu-item-icon:hover>a{background-color:transparent}.menu .menu-link.open i{background-color:#1D212A}.menu .menu-link.open g{fill:#35D7BB}.menu .menu-link-preview,.menu .menu-link-settings{margin-top:8px;width:51px}.menu-sidebar{width:100%}.menu-sidebar .menu-item{float:none;margin-bottom:1px;width:100%}.menu-sidebar .menu-item.open>a{background-color:#373D49}.menu-sidebar .open .caret{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.menu-sidebar>.menu-item:hover .dropdown a,.menu-sidebar>.menu-item:hover .settings a{background-color:transparent}.menu-sidebar .menu-link{background-color:#373D49;font-weight:600}.menu-sidebar .menu-link:after{content:"";display:table;clear:both}.menu-sidebar .menu-link>span{float:left}.menu-sidebar .menu-link>.caret{float:right;text-align:right;top:22px}.menu-sidebar .dropdown,.menu-sidebar .settings{background-color:transparent;position:static;width:100%}.dropdown{position:absolute;right:0;top:51px;width:188px}.dropdown,.settings{display:none;background-color:#1D212A}.dropdown{padding:0}.dropdown,.settings,.sidebar-list{list-style:none;margin:0}.sidebar-list{padding:0}.dropdown li{margin:32px 0;padding:0 0 0 32px}.dropdown li,.settings li{line-height:1}.sidebar-list li{line-height:1;margin:32px 0;padding:0 0 0 32px}.dropdown a{color:#D0D6E2}.dropdown a,.settings a,.sidebar-list a{display:block;text-transform:none}.sidebar-list a{color:#D0D6E2}.dropdown a:after,.settings a:after,.sidebar-list a:after{content:"";display:table;clear:both}.dropdown .icon,.settings .icon,.sidebar-list .icon{float:right}.open .dropdown,.open .settings,.open .sidebar-list{display:block}.open .dropdown.collapse,.open .collapse.settings,.open .sidebar-list.collapse{display:none}.open .dropdown.collapse.in,.open .collapse.in.settings,.open .sidebar-list.collapse.in{display:block}.dropdown .unlinked .icon,.settings .unlinked .icon,.sidebar-list .unlinked .icon{opacity:.3}.dropdown.documents li,.documents.settings li,.sidebar-list.documents li{background-image:url("../img/icons/file.svg");background-position:240px center;background-repeat:no-repeat;background-size:14px 16px;padding:3px 32px}.dropdown.documents li.octocat,.documents.settings li.octocat,.sidebar-list.documents li.octocat{background-image:url("../img/icons/octocat.svg");background-position:234px center;background-size:24px 24px}.dropdown.documents li:last-child,.documents.settings li:last-child,.sidebar-list.documents li:last-child{margin-bottom:1rem}.dropdown.documents li.active a,.documents.settings li.active a,.sidebar-list.documents li.active a{color:#35D7BB}.settings{position:fixed;top:67px;right:16px;border-radius:3px;width:288px;background-color:#373D49;padding:16px;z-index:7}.show-settings .settings{display:block}.settings .has-checkbox{float:left}.settings a{font-size:1.25rem;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:400;-webkit-font-smoothing:antialiased;line-height:28px;color:#D0D6E2}.settings a:after{content:"";display:table;clear:both}.settings a:hover{color:#35D7BB}.settings li{border-bottom:1px solid #4F535B;margin:0;padding:16px 0}.settings li:last-child{border-bottom:none}.brand{border:none;display:block}.brand:hover g{fill:#35D7BB}.toggle{display:block;float:left;height:16px;padding:25px 16px 26px;width:40px}.toggle span:after,.toggle span:before{content:'';left:0;position:absolute;top:-6px}.toggle span:after{top:6px}.toggle span{display:block;position:relative}.toggle span,.toggle span:after,.toggle span:before{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:#D3DAEA;height:2px;-webkit-transition:all .3s;transition:all .3s;width:20px}.open-menu .toggle span{background-color:transparent}.open-menu .toggle span:before{-webkit-transform:rotate(45deg)translate(3px,3px);-ms-transform:rotate(45deg)translate(3px,3px);transform:rotate(45deg)translate(3px,3px)}.open-menu .toggle span:after{-webkit-transform:rotate(-45deg)translate(5px,-6px);-ms-transform:rotate(-45deg)translate(5px,-6px);transform:rotate(-45deg)translate(5px,-6px)}.caret{display:inline-block;width:0;height:0;margin-left:6px;vertical-align:middle;position:relative;top:-1px;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.sidebar{overflow:auto;height:100%;padding-right:15px;padding-bottom:15px;width:285px}.sidebar-wrapper{-webkit-overflow-scrolling:touch;background-color:#2B2F36;left:0;height:100%;overflow-y:hidden;position:fixed;top:0;width:285px;z-index:1}.sidebar-branding{width:160px;padding:0;margin:16px auto}.header{border-bottom:1px solid #E8E8E8;position:relative}.words{line-height:1rem;font-size:.8rem;margin-bottom:.77999rem;padding-top:.22001rem;font-weight:500;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;color:#A0AABF;letter-spacing:1px;text-transform:uppercase;z-index:5;position:absolute;right:16px;top:0}.words span{color:#000}.btn{text-align:center;display:inline-block;width:100%;text-transform:uppercase;font-weight:600;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:0 1px 0 #1b8b77;padding:16px 24px;background-color:#35D7BB;border-radius:3px;margin:0 auto 16px;line-height:1;color:#fff;-webkit-transition:all .15s linear;transition:all .15s linear;-webkit-font-smoothing:antialiased}.btn--new,.btn--save{display:block;width:238px}.btn--new:hover,.btn--new:focus,.btn--save:hover,.btn--save:focus{color:#fff;border-bottom-color:transparent;box-shadow:0 1px 3px #24b59c;text-shadow:0 1px 0 #24b59c}.btn--save{background-color:#4A5261;text-shadow:0 1px 1px #1e2127}.btn--save:hover,.btn--save:focus{color:#fff;border-bottom-color:transparent;box-shadow:0 1px 5px #08090a;text-shadow:none}.btn--delete{display:block;width:238px;background-color:transparent;font-size:12px;text-shadow:none}.btn--delete:hover,.btn--delete:focus{color:#fff;border-bottom-color:transparent;text-shadow:0 1px 0 #08090a;opacity:.8}.btn--ok,.btn--close{border-top:0;background-color:#4A5261;text-shadow:0 1px 0 #08090a;margin:0}.btn--ok:hover,.btn--ok:focus,.btn--close:hover,.btn--close:focus{color:#fff;background-color:#292d36;text-shadow:none}.overlay{position:absolute;top:0;left:0;width:100%;height:100%;background-color:rgba(55,61,73,.8);-webkit-transition:all .25s ease-in-out;transition:all .25s ease-in-out;-webkit-transition-timing-function:ease-out;transition-timing-function:ease-out;will-change:left,opacity,visibility;z-index:5;opacity:0;visibility:hidden}.show-settings .overlay{visibility:visible;opacity:1}.switch{float:right;line-height:1}.switch input{display:none}.switch small{display:inline-block;cursor:pointer;padding:0 24px 0 0;-webkit-transition:all ease .2s;transition:all ease .2s;background-color:#2B2F36;border-color:#2B2F36}.switch small,.switch small:before{border-radius:30px;box-shadow:inset 0 0 2px 0 #14171F}.switch small:before{display:block;content:'';width:28px;height:28px;background:#fff}.switch.checked small{padding-right:0;padding-left:24px;background-color:#35D7BB;box-shadow:none}.modal--dillinger.about .modal-dialog{font-size:1.25rem;max-width:500px}.modal--dillinger .modal-dialog{max-width:600px;width:auto;margin:5rem auto}.modal--dillinger .modal-content{background:#373D49;border-radius:3px;box-shadow:0 2px 5px 0 #2C3B59;color:#fff;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:400;padding:2rem}.modal--dillinger ul{list-style-type:disc;margin:1rem 0;padding:0 0 0 1rem}.modal--dillinger li{padding:0;margin:0}.modal--dillinger .modal-header{border:0;padding:0}.modal--dillinger .modal-body{padding:0}.modal--dillinger .modal-footer{border:0;padding:0}.modal--dillinger .close{color:#fff;opacity:1}.modal-backdrop{background-color:#373D49}.pagination--dillinger{padding:0!important;margin:1.5rem 0!important;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch}.pagination--dillinger,.pagination--dillinger li{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.pagination--dillinger li{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;text-align:center}.pagination--dillinger li:first-child>a,.pagination--dillinger li.disabled>a,.pagination--dillinger li.disabled>a:hover,.pagination--dillinger li.disabled>a:focus,.pagination--dillinger li>a{background-color:transparent;border-color:#4F535B;border-right-color:transparent}.pagination--dillinger li.active>a,.pagination--dillinger li.active>a:hover,.pagination--dillinger li.active>a:focus{border-color:#4A5261;background-color:#4A5261;color:#fff}.pagination--dillinger li>a{float:none;color:#fff;width:100%;display:block;text-align:center;margin:0;border-right-color:transparent;padding:6px}.pagination--dillinger li>a:hover,.pagination--dillinger li>a:focus{border-color:#35D7BB;background-color:#35D7BB;color:#fff}.pagination--dillinger li:last-child a{border-color:#4F535B}.pagination--dillinger li:first-child a{border-right-color:transparent}.diNotify{position:absolute;z-index:9999;left:0;right:0;top:0;margin:0 auto;max-width:400px;text-align:center;-webkit-transition:top .5s ease-in-out,opacity .5s ease-in-out;transition:top .5s ease-in-out,opacity .5s ease-in-out;visibility:hidden}.diNotify-body{-webkit-font-smoothing:antialiased;background-color:#35D7BB;background:#666E7F;border-radius:3px;color:#fff;font-family:"Source Sans Pro","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:400;overflow:hidden;padding:1rem 2rem .5rem;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-webkit-align-items:baseline;-ms-flex-align:baseline;align-items:baseline;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.diNotify-icon{display:block;width:16px;height:16px;line-height:16px;position:relative;top:3px}.diNotify-message{padding-left:1rem}.zen-wrapper{position:fixed;top:0;left:0;right:0;bottom:0;width:100%;height:100%;z-index:10;background-color:#FFF;opacity:0;-webkit-transition:opacity .25s ease-in-out;transition:opacity .25s ease-in-out}.zen-wrapper.on{opacity:1}.enter-zen-mode{background-image:url("../img/icons/enter-zen.svg");right:.5rem;top:.5rem;display:none}.enter-zen-mode,.close-zen-mode{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;background-repeat:no-repeat;width:32px;height:32px;display:block;position:absolute}.close-zen-mode{background-image:url("../img/icons/exit-zen.svg");right:1rem;top:1rem}.zen-page{position:relative;top:0;bottom:0;z-index:11;height:100%;width:100%}#zen{font-size:1.25rem;width:300px;height:80%;margin:0 auto;position:relative;top:10%}#zen:before,#zen:after{content:"";position:absolute;height:10%;width:100%;z-index:12;pointer-events:none}.split{overflow:scroll;padding:0!important}.split-editor{padding-left:0;padding-right:0;position:relative}.show-preview .split-editor{display:none}.split-preview{background-color:#fff;display:none;top:0;position:relative;z-index:4}.show-preview .split-preview{display:block}#editor{font-size:1rem;font-family:"Ubuntu Mono",Monaco;font-weight:400;line-height:2rem;width:100%;height:100%}#editor .ace_gutter{-webkit-font-smoothing:antialiased}#preview{padding:10px}#preview a{color:#A0AABF;text-decoration:underline}.sr-only{visibility:hidden;text-overflow:110%;overflow:hidden;top:-100px;position:absolute}.mnone{margin:0!important}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}.form-horizontal .form-group-lg .control-label{padding-top:14.3px}.form-horizontal .form-group-sm .control-label{padding-top:6px}.modal-dialog{width:600px;margin:30px auto}.modal-content{box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}@media screen and (min-width:27.5em){html{font-size:.875em}body{font-size:1rem}ul,ol{margin-bottom:.83999rem;padding-top:.16001rem}p{padding-top:.66001rem}p,pre{margin-bottom:1.33999rem}pre,blockquote p{font-size:1rem;padding-top:.66001rem}blockquote p{margin-bottom:.33999rem}h1{font-size:2.0571429rem;margin-bottom:.21999rem;padding-top:.78001rem}h2{font-size:1.953125rem;margin-bottom:.1835837rem;padding-top:.8164163rem}h3{font-size:1.6457143rem;margin-bottom:.07599rem;padding-top:.92401rem}h4{font-size:1.5625rem;margin-bottom:.546865rem;padding-top:.453135rem}h5{font-size:1.25rem;margin-bottom:-.56251rem;padding-top:.56251rem}h6{font-size:1rem;margin-bottom:-.65001rem;padding-top:.65001rem}.g{margin-left:-16px;margin-right:-16px}.g-b{padding-left:16px;padding-right:16px}.g-b--m1of1{width:100%}.g-b--m1of2,.g-b--m2of4,.g-b--m3of6,.g-b--m4of8,.g-b--m5of10,.g-b--m6of12{width:50%}.g-b--m1of3,.g-b--m2of6,.g-b--m4of12{width:33.333%}.g-b--m2of3,.g-b--m4of6,.g-b--m8of12{width:66.666%}.g-b--m1of4,.g-b--m2of8,.g-b--m3of12{width:25%}.g-b--m3of4,.g-b--m6of8,.g-b--m9of12{width:75%}.g-b--m1of5,.g-b--m2of10{width:20%}.g-b--m2of5,.g-b--m4of10{width:40%}.g-b--m3of5,.g-b--m6of10{width:60%}.g-b--m4of5,.g-b--m8of10{width:80%}.g-b--m1of6,.g-b--m2of12{width:16.666%}.g-b--m5of6,.g-b--m10of12{width:83.333%}.g-b--m1of8{width:12.5%}.g-b--m3of8{width:37.5%}.g-b--m5of8{width:62.5%}.g-b--m7of8{width:87.5%}.g-b--m1of10{width:10%}.g-b--m3of10{width:30%}.g-b--m7of10{width:70%}.g-b--m9of10{width:90%}.g-b--m1of12{width:8.333%}.g-b--m5of12{width:41.666%}.g-b--m7of12{width:58.333%}.g-b--m11of12{width:91.666%}.g-b--push--m1of1{margin-left:100%}.g-b--push--m1of2,.g-b--push--m2of4,.g-b--push--m3of6,.g-b--push--m4of8,.g-b--push--m5of10,.g-b--push--m6of12{margin-left:50%}.g-b--push--m1of3,.g-b--push--m2of6,.g-b--push--m4of12{margin-left:33.333%}.g-b--push--m2of3,.g-b--push--m4of6,.g-b--push--m8of12{margin-left:66.666%}.g-b--push--m1of4,.g-b--push--m2of8,.g-b--push--m3of12{margin-left:25%}.g-b--push--m3of4,.g-b--push--m6of8,.g-b--push--m9of12{margin-left:75%}.g-b--push--m1of5,.g-b--push--m2of10{margin-left:20%}.g-b--push--m2of5,.g-b--push--m4of10{margin-left:40%}.g-b--push--m3of5,.g-b--push--m6of10{margin-left:60%}.g-b--push--m4of5,.g-b--push--m8of10{margin-left:80%}.g-b--push--m1of6,.g-b--push--m2of12{margin-left:16.666%}.g-b--push--m5of6,.g-b--push--m10of12{margin-left:83.333%}.g-b--push--m1of8{margin-left:12.5%}.g-b--push--m3of8{margin-left:37.5%}.g-b--push--m5of8{margin-left:62.5%}.g-b--push--m7of8{margin-left:87.5%}.g-b--push--m1of10{margin-left:10%}.g-b--push--m3of10{margin-left:30%}.g-b--push--m7of10{margin-left:70%}.g-b--push--m9of10{margin-left:90%}.g-b--push--m1of12{margin-left:8.333%}.g-b--push--m5of12{margin-left:41.666%}.g-b--push--m7of12{margin-left:58.333%}.g-b--push--m11of12{margin-left:91.666%}.g-b--pull--m1of1{margin-right:100%}.g-b--pull--m1of2,.g-b--pull--m2of4,.g-b--pull--m3of6,.g-b--pull--m4of8,.g-b--pull--m5of10,.g-b--pull--m6of12{margin-right:50%}.g-b--pull--m1of3,.g-b--pull--m2of6,.g-b--pull--m4of12{margin-right:33.333%}.g-b--pull--m2of3,.g-b--pull--m4of6,.g-b--pull--m8of12{margin-right:66.666%}.g-b--pull--m1of4,.g-b--pull--m2of8,.g-b--pull--m3of12{margin-right:25%}.g-b--pull--m3of4,.g-b--pull--m6of8,.g-b--pull--m9of12{margin-right:75%}.g-b--pull--m1of5,.g-b--pull--m2of10{margin-right:20%}.g-b--pull--m2of5,.g-b--pull--m4of10{margin-right:40%}.g-b--pull--m3of5,.g-b--pull--m6of10{margin-right:60%}.g-b--pull--m4of5,.g-b--pull--m8of10{margin-right:80%}.g-b--pull--m1of6,.g-b--pull--m2of12{margin-right:16.666%}.g-b--pull--m5of6,.g-b--pull--m10of12{margin-right:83.333%}.g-b--pull--m1of8{margin-right:12.5%}.g-b--pull--m3of8{margin-right:37.5%}.g-b--pull--m5of8{margin-right:62.5%}.g-b--pull--m7of8{margin-right:87.5%}.g-b--pull--m1of10{margin-right:10%}.g-b--pull--m3of10{margin-right:30%}.g-b--pull--m7of10{margin-right:70%}.g-b--pull--m9of10{margin-right:90%}.g-b--pull--m1of12{margin-right:8.333%}.g-b--pull--m5of12{margin-right:41.666%}.g-b--pull--m7of12{margin-right:58.333%}.g-b--pull--m11of12{margin-right:91.666%}.splashscreen p{font-size:1.25rem;margin-bottom:1.43749rem;padding-top:.56251rem}.title{font-size:.8rem;margin-bottom:.77999rem;padding-top:.22001rem}.title-document{margin-bottom:.89999rem;padding-top:.10001rem}.title-document,.settings a{font-size:1.25rem}.words{font-size:.8rem;margin-bottom:.77999rem;padding-top:.22001rem}.modal--dillinger.about .modal-dialog,#zen{font-size:1.25rem}#zen{width:400px}#editor{font-size:1rem}}@media screen and (min-width:46.25em){html{font-size:.875em}body{font-size:1rem}ul,ol{margin-bottom:.83999rem;padding-top:.16001rem}p{padding-top:.66001rem}p,pre{margin-bottom:1.33999rem}pre,blockquote p{font-size:1rem;padding-top:.66001rem}blockquote p{margin-bottom:.33999rem}h1{font-size:2.0571429rem;margin-bottom:.21999rem;padding-top:.78001rem}h2{font-size:1.953125rem;margin-bottom:.1835837rem;padding-top:.8164163rem}h3{font-size:1.6457143rem;margin-bottom:.07599rem;padding-top:.92401rem}h4{font-size:1.5625rem;margin-bottom:.546865rem;padding-top:.453135rem}h5{font-size:1.25rem;margin-bottom:-.56251rem;padding-top:.56251rem}h6{font-size:1rem;margin-bottom:-.65001rem;padding-top:.65001rem}.g{margin-left:-16px;margin-right:-16px}.g-b{padding-left:16px;padding-right:16px}.g-b--t1of1{width:100%}.g-b--t1of2,.g-b--t2of4,.g-b--t3of6,.g-b--t4of8,.g-b--t5of10,.g-b--t6of12{width:50%}.g-b--t1of3,.g-b--t2of6,.g-b--t4of12{width:33.333%}.g-b--t2of3,.g-b--t4of6,.g-b--t8of12{width:66.666%}.g-b--t1of4,.g-b--t2of8,.g-b--t3of12{width:25%}.g-b--t3of4,.g-b--t6of8,.g-b--t9of12{width:75%}.g-b--t1of5,.g-b--t2of10{width:20%}.g-b--t2of5,.g-b--t4of10{width:40%}.g-b--t3of5,.g-b--t6of10{width:60%}.g-b--t4of5,.g-b--t8of10{width:80%}.g-b--t1of6,.g-b--t2of12{width:16.666%}.g-b--t5of6,.g-b--t10of12{width:83.333%}.g-b--t1of8{width:12.5%}.g-b--t3of8{width:37.5%}.g-b--t5of8{width:62.5%}.g-b--t7of8{width:87.5%}.g-b--t1of10{width:10%}.g-b--t3of10{width:30%}.g-b--t7of10{width:70%}.g-b--t9of10{width:90%}.g-b--t1of12{width:8.333%}.g-b--t5of12{width:41.666%}.g-b--t7of12{width:58.333%}.g-b--t11of12{width:91.666%}.g-b--push--t1of1{margin-left:100%}.g-b--push--t1of2,.g-b--push--t2of4,.g-b--push--t3of6,.g-b--push--t4of8,.g-b--push--t5of10,.g-b--push--t6of12{margin-left:50%}.g-b--push--t1of3,.g-b--push--t2of6,.g-b--push--t4of12{margin-left:33.333%}.g-b--push--t2of3,.g-b--push--t4of6,.g-b--push--t8of12{margin-left:66.666%}.g-b--push--t1of4,.g-b--push--t2of8,.g-b--push--t3of12{margin-left:25%}.g-b--push--t3of4,.g-b--push--t6of8,.g-b--push--t9of12{margin-left:75%}.g-b--push--t1of5,.g-b--push--t2of10{margin-left:20%}.g-b--push--t2of5,.g-b--push--t4of10{margin-left:40%}.g-b--push--t3of5,.g-b--push--t6of10{margin-left:60%}.g-b--push--t4of5,.g-b--push--t8of10{margin-left:80%}.g-b--push--t1of6,.g-b--push--t2of12{margin-left:16.666%}.g-b--push--t5of6,.g-b--push--t10of12{margin-left:83.333%}.g-b--push--t1of8{margin-left:12.5%}.g-b--push--t3of8{margin-left:37.5%}.g-b--push--t5of8{margin-left:62.5%}.g-b--push--t7of8{margin-left:87.5%}.g-b--push--t1of10{margin-left:10%}.g-b--push--t3of10{margin-left:30%}.g-b--push--t7of10{margin-left:70%}.g-b--push--t9of10{margin-left:90%}.g-b--push--t1of12{margin-left:8.333%}.g-b--push--t5of12{margin-left:41.666%}.g-b--push--t7of12{margin-left:58.333%}.g-b--push--t11of12{margin-left:91.666%}.g-b--pull--t1of1{margin-right:100%}.g-b--pull--t1of2,.g-b--pull--t2of4,.g-b--pull--t3of6,.g-b--pull--t4of8,.g-b--pull--t5of10,.g-b--pull--t6of12{margin-right:50%}.g-b--pull--t1of3,.g-b--pull--t2of6,.g-b--pull--t4of12{margin-right:33.333%}.g-b--pull--t2of3,.g-b--pull--t4of6,.g-b--pull--t8of12{margin-right:66.666%}.g-b--pull--t1of4,.g-b--pull--t2of8,.g-b--pull--t3of12{margin-right:25%}.g-b--pull--t3of4,.g-b--pull--t6of8,.g-b--pull--t9of12{margin-right:75%}.g-b--pull--t1of5,.g-b--pull--t2of10{margin-right:20%}.g-b--pull--t2of5,.g-b--pull--t4of10{margin-right:40%}.g-b--pull--t3of5,.g-b--pull--t6of10{margin-right:60%}.g-b--pull--t4of5,.g-b--pull--t8of10{margin-right:80%}.g-b--pull--t1of6,.g-b--pull--t2of12{margin-right:16.666%}.g-b--pull--t5of6,.g-b--pull--t10of12{margin-right:83.333%}.g-b--pull--t1of8{margin-right:12.5%}.g-b--pull--t3of8{margin-right:37.5%}.g-b--pull--t5of8{margin-right:62.5%}.g-b--pull--t7of8{margin-right:87.5%}.g-b--pull--t1of10{margin-right:10%}.g-b--pull--t3of10{margin-right:30%}.g-b--pull--t7of10{margin-right:70%}.g-b--pull--t9of10{margin-right:90%}.g-b--pull--t1of12{margin-right:8.333%}.g-b--pull--t5of12{margin-right:41.666%}.g-b--pull--t7of12{margin-right:58.333%}.g-b--pull--t11of12{margin-right:91.666%}.splashscreen-dillinger{width:500px}.splashscreen p{font-size:1.25rem;margin-bottom:1.43749rem;padding-top:.56251rem}.title{font-size:.8rem;margin-bottom:.77999rem;padding-top:.22001rem}.title-document{font-size:1.25rem;margin-bottom:.89999rem;padding-top:.10001rem}.menu .menu-item--save-to,.menu .menu-item--import-from{display:block}.menu .menu-item--preview,.menu .menu-item--save-to.in-sidebar,.menu .menu-item--import-from.in-sidebar{display:none}.settings a{font-size:1.25rem}.words{font-size:.8rem;margin-bottom:.77999rem;padding-top:.22001rem}.modal--dillinger.about .modal-dialog{font-size:1.25rem}.enter-zen-mode{display:block}.close-zen-mode{right:3rem;top:3rem}#zen{font-size:1.25rem;width:500px}.split-editor{border-right:1px solid #E8E8E8;float:left;height:calc(100vh - 130px);-webkit-overflow-scrolling:touch;padding-right:16px;width:50%}.show-preview .split-editor{display:block}.split-preview{display:block;float:right;height:calc(100vh - 130px);-webkit-overflow-scrolling:touch;position:relative;top:0;width:50%}#editor{font-size:1rem}}@media screen and (min-width:62.5em){html{font-size:.875em}body{font-size:1rem}ul,ol{margin-bottom:.83999rem;padding-top:.16001rem}p{padding-top:.66001rem}p,pre{margin-bottom:1.33999rem}pre,blockquote p{font-size:1rem;padding-top:.66001rem}blockquote p{margin-bottom:.33999rem}h1{font-size:2.0571429rem;margin-bottom:.21999rem;padding-top:.78001rem}h2{font-size:1.953125rem;margin-bottom:.1835837rem;padding-top:.8164163rem}h3{font-size:1.6457143rem;margin-bottom:.07599rem;padding-top:.92401rem}h4{font-size:1.5625rem;margin-bottom:.546865rem;padding-top:.453135rem}h5{font-size:1.25rem;margin-bottom:-.56251rem;padding-top:.56251rem}h6{font-size:1rem;margin-bottom:-.65001rem;padding-top:.65001rem}.g{margin-left:-16px;margin-right:-16px}.g-b{padding-left:16px;padding-right:16px}.g-b--d1of1{width:100%}.g-b--d1of2,.g-b--d2of4,.g-b--d3of6,.g-b--d4of8,.g-b--d5of10,.g-b--d6of12{width:50%}.g-b--d1of3,.g-b--d2of6,.g-b--d4of12{width:33.333%}.g-b--d2of3,.g-b--d4of6,.g-b--d8of12{width:66.666%}.g-b--d1of4,.g-b--d2of8,.g-b--d3of12{width:25%}.g-b--d3of4,.g-b--d6of8,.g-b--d9of12{width:75%}.g-b--d1of5,.g-b--d2of10{width:20%}.g-b--d2of5,.g-b--d4of10{width:40%}.g-b--d3of5,.g-b--d6of10{width:60%}.g-b--d4of5,.g-b--d8of10{width:80%}.g-b--d1of6,.g-b--d2of12{width:16.666%}.g-b--d5of6,.g-b--d10of12{width:83.333%}.g-b--d1of8{width:12.5%}.g-b--d3of8{width:37.5%}.g-b--d5of8{width:62.5%}.g-b--d7of8{width:87.5%}.g-b--d1of10{width:10%}.g-b--d3of10{width:30%}.g-b--d7of10{width:70%}.g-b--d9of10{width:90%}.g-b--d1of12{width:8.333%}.g-b--d5of12{width:41.666%}.g-b--d7of12{width:58.333%}.g-b--d11of12{width:91.666%}.g-b--push--d1of1{margin-left:100%}.g-b--push--d1of2,.g-b--push--d2of4,.g-b--push--d3of6,.g-b--push--d4of8,.g-b--push--d5of10,.g-b--push--d6of12{margin-left:50%}.g-b--push--d1of3,.g-b--push--d2of6,.g-b--push--d4of12{margin-left:33.333%}.g-b--push--d2of3,.g-b--push--d4of6,.g-b--push--d8of12{margin-left:66.666%}.g-b--push--d1of4,.g-b--push--d2of8,.g-b--push--d3of12{margin-left:25%}.g-b--push--d3of4,.g-b--push--d6of8,.g-b--push--d9of12{margin-left:75%}.g-b--push--d1of5,.g-b--push--d2of10{margin-left:20%}.g-b--push--d2of5,.g-b--push--d4of10{margin-left:40%}.g-b--push--d3of5,.g-b--push--d6of10{margin-left:60%}.g-b--push--d4of5,.g-b--push--d8of10{margin-left:80%}.g-b--push--d1of6,.g-b--push--d2of12{margin-left:16.666%}.g-b--push--d5of6,.g-b--push--d10of12{margin-left:83.333%}.g-b--push--d1of8{margin-left:12.5%}.g-b--push--d3of8{margin-left:37.5%}.g-b--push--d5of8{margin-left:62.5%}.g-b--push--d7of8{margin-left:87.5%}.g-b--push--d1of10{margin-left:10%}.g-b--push--d3of10{margin-left:30%}.g-b--push--d7of10{margin-left:70%}.g-b--push--d9of10{margin-left:90%}.g-b--push--d1of12{margin-left:8.333%}.g-b--push--d5of12{margin-left:41.666%}.g-b--push--d7of12{margin-left:58.333%}.g-b--push--d11of12{margin-left:91.666%}.g-b--pull--d1of1{margin-right:100%}.g-b--pull--d1of2,.g-b--pull--d2of4,.g-b--pull--d3of6,.g-b--pull--d4of8,.g-b--pull--d5of10,.g-b--pull--d6of12{margin-right:50%}.g-b--pull--d1of3,.g-b--pull--d2of6,.g-b--pull--d4of12{margin-right:33.333%}.g-b--pull--d2of3,.g-b--pull--d4of6,.g-b--pull--d8of12{margin-right:66.666%}.g-b--pull--d1of4,.g-b--pull--d2of8,.g-b--pull--d3of12{margin-right:25%}.g-b--pull--d3of4,.g-b--pull--d6of8,.g-b--pull--d9of12{margin-right:75%}.g-b--pull--d1of5,.g-b--pull--d2of10{margin-right:20%}.g-b--pull--d2of5,.g-b--pull--d4of10{margin-right:40%}.g-b--pull--d3of5,.g-b--pull--d6of10{margin-right:60%}.g-b--pull--d4of5,.g-b--pull--d8of10{margin-right:80%}.g-b--pull--d1of6,.g-b--pull--d2of12{margin-right:16.666%}.g-b--pull--d5of6,.g-b--pull--d10of12{margin-right:83.333%}.g-b--pull--d1of8{margin-right:12.5%}.g-b--pull--d3of8{margin-right:37.5%}.g-b--pull--d5of8{margin-right:62.5%}.g-b--pull--d7of8{margin-right:87.5%}.g-b--pull--d1of10{margin-right:10%}.g-b--pull--d3of10{margin-right:30%}.g-b--pull--d7of10{margin-right:70%}.g-b--pull--d9of10{margin-right:90%}.g-b--pull--d1of12{margin-right:8.333%}.g-b--pull--d5of12{margin-right:41.666%}.g-b--pull--d7of12{margin-right:58.333%}.g-b--pull--d11of12{margin-right:91.666%}.splashscreen-dillinger{width:700px}.splashscreen p{font-size:1.25rem;margin-bottom:1.43749rem;padding-top:.56251rem}.title{font-size:.8rem;margin-bottom:.77999rem;padding-top:.22001rem}.title-document{font-size:1.25rem;margin-bottom:.89999rem;padding-top:.10001rem}.menu .menu-item--export-as{display:block}.menu .menu-item--preview{display:none}.settings a{font-size:1.25rem}.words{font-size:.8rem;margin-bottom:.77999rem;padding-top:.22001rem}.modal--dillinger.about .modal-dialog,#zen{font-size:1.25rem}#zen{width:700px}#editor{font-size:1rem}}@media screen and (min-width:87.5em){html{font-size:.875em}body{font-size:1rem}ul,ol{margin-bottom:.83999rem;padding-top:.16001rem}p{padding-top:.66001rem}p,pre{margin-bottom:1.33999rem}pre,blockquote p{font-size:1rem;padding-top:.66001rem}blockquote p{margin-bottom:.33999rem}h1{font-size:2.0571429rem;margin-bottom:.21999rem;padding-top:.78001rem}h2{font-size:1.953125rem;margin-bottom:.1835837rem;padding-top:.8164163rem}h3{font-size:1.6457143rem;margin-bottom:.07599rem;padding-top:.92401rem}h4{font-size:1.5625rem;margin-bottom:.546865rem;padding-top:.453135rem}h5{font-size:1.25rem;margin-bottom:-.56251rem;padding-top:.56251rem}h6{font-size:1rem;margin-bottom:-.65001rem;padding-top:.65001rem}.splashscreen-dillinger{width:800px}.splashscreen p{font-size:1.25rem;margin-bottom:1.43749rem;padding-top:.56251rem}.title{font-size:.8rem;margin-bottom:.77999rem;padding-top:.22001rem}.title-document{margin-bottom:.89999rem;padding-top:.10001rem}.title-document,.settings a{font-size:1.25rem}.words{font-size:.8rem;margin-bottom:.77999rem;padding-top:.22001rem}.modal--dillinger.about .modal-dialog,#zen{font-size:1.25rem}#editor{font-size:1rem}}</style></head><body id="preview">
<h1><a id="CentralConfig__Multimachine_configuration_manager_0"></a>CentralConfig - Multi-machine configuration manager</h1>
<p>CentralConfig is available on NuGet, source at <a href="https://github.com/andrevdm/CentralConfig">https://github.com/andrevdm/CentralConfig</a></p>
<h2><a id="Overview_4"></a>Overview</h2>
<p>CentralConfig is a replacement for the built in .net ConfigurationManager. It makes managing multi-machine configurations simple while, from a usability point of view, looking very similar to ConfigurationManager.</p>
<h2><a id="Features_9"></a>Features</h2>
<ul>
<li>All config must be able to be stored in version control</li>
<li>A way to have config centralised. I.e. you should not have to update the same setting on multiple machines.</li>
<li>Support for multiple environments (dev, build, test, prod etc)</li>
<li>A sane and simple way of having default values. You should not have to specify the same settings for each machine if a default will work.</li>
<li>Override a setting for a particular machine. e.g. one machine in an environment must have a slightly different config.</li>
<li>Simple and light weight</li>
<li>Support for simple values as well as more complex object types</li>
<li>A full replacement for ConfigManager</li>
</ul>
<h2><a id="What_about_the_net_config_transformer_21"></a>What about the .net config transformer</h2>
<p>The .net transformer does a reasonable job but did not quite do everything I required. The examples below should show some of the differences</p>
<h2><a id="Using_CentralConfig_26"></a>Using CentralConfig</h2>
<p>Install “CentralConfig” from nuget
Unless you have created a custom persistor use the mongo settings persistor
Set the central config mongo settings
Setup DI</p>
<h3><a id="appsettings_for_the_mong_persistor_33"></a>app.settings for the mong persistor</h3>
<pre><code class="language-xml"> <span class="hljs-tag"><<span class="hljs-title">appSettings</span>></span>
<span class="hljs-tag"><<span class="hljs-title">add</span> <span class="hljs-attribute">key</span>=<span class="hljs-value">"MongoDB.Server"</span> <span class="hljs-attribute">value</span>=<span class="hljs-value">"mongodb://localhost:27017"</span> /></span>
<span class="hljs-tag"><<span class="hljs-title">add</span> <span class="hljs-attribute">key</span>=<span class="hljs-value">"CentralConfig.MongoDatabase"</span> <span class="hljs-attribute">value</span>=<span class="hljs-value">"TestConfig"</span> /></span>
<span class="hljs-tag"></<span class="hljs-title">appSettings</span>></span>
</code></pre>
<h3><a id="DI_setup_42"></a>DI setup</h3>
<pre><code class="language-csharp"> ObjectFactory.Configure( x =>
{
x.For<IEnvironment>().Use<SystemEnvironment>();
x.For<IConfigPersistor>().Use<MongoDbConfigPersistor>();
} );
</code></pre>
<h3><a id="Configure_settings_in_Mongo_52"></a>Configure settings in Mongo</h3>
<pre><code class="language-javascript"> {
<span class="hljs-string">"key"</span> : <span class="hljs-string">"TestSetting"</span>,
<span class="hljs-string">"machine"</span> : <span class="hljs-literal">null</span>,
<span class="hljs-string">"value"</span> : <span class="hljs-string">"some value"</span>
}
</code></pre>
<h3><a id="Use_the_setting_from_code_62"></a>Use the setting from code</h3>
<pre><code class="language-csharp"> <span class="hljs-comment">//ConfigManager.AppSettings["TestSetting"]</span>
Console.WriteLine( <span class="hljs-string">"test: {0}"</span>, ConfigManager.AppSettings[<span class="hljs-string">"TestSetting"</span>] );
</code></pre>
<h2><a id="Different_Persistors_70"></a>Different Persistors</h2>
<p>I’ve always used the mongo persistor but you can easily create new persistors as required. To do so implement the IPersistor interface</p>
<pre><code class="language-csharp"> <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IConfigPersistor</span>
{
<span class="hljs-function"><span class="hljs-keyword">string</span> <span class="hljs-title">ReadAppSetting</span>(<span class="hljs-params"> <span class="hljs-keyword">string</span> key, <span class="hljs-keyword">string</span> machine, <span class="hljs-keyword">long</span> version </span>)</span>;
<span class="hljs-function"><span class="hljs-keyword">string</span> <span class="hljs-title">ReadAppSetting</span>(<span class="hljs-params"> <span class="hljs-keyword">string</span> key, <span class="hljs-keyword">string</span> machine </span>)</span>;
<span class="hljs-function"><span class="hljs-keyword">string</span> <span class="hljs-title">ReadAppSetting</span>(<span class="hljs-params"> <span class="hljs-keyword">string</span> key, <span class="hljs-keyword">long</span> version </span>)</span>;
<span class="hljs-function"><span class="hljs-keyword">string</span> <span class="hljs-title">ReadAppSetting</span>(<span class="hljs-params"> <span class="hljs-keyword">string</span> key </span>)</span>;
TResult GetSection<TResult>( <span class="hljs-keyword">string</span> sectionName, <span class="hljs-keyword">string</span> machine, <span class="hljs-keyword">long</span> version );
TResult GetSection<TResult>( <span class="hljs-keyword">string</span> sectionName, <span class="hljs-keyword">string</span> machine );
TResult GetSection<TResult>( <span class="hljs-keyword">string</span> sectionName, <span class="hljs-keyword">long</span> version );
TResult GetSection<TResult>( <span class="hljs-keyword">string</span> sectionName );
}
</code></pre>
<h2><a id="Order_of_matching_90"></a>Order of matching</h2>
<p>When you request a value, CentralConfig will try find the most specific value it can. So it will first look for the setting match both the key and the current machine name, if that does not match then it looks for a value matching on key only</p>
<pre><code class="language-javascript"> {
<span class="hljs-string">"key"</span> : <span class="hljs-string">"TestSetting"</span>,
<span class="hljs-string">"machine"</span> : <span class="hljs-literal">null</span>,
<span class="hljs-string">"value"</span> : <span class="hljs-string">"some value"</span>
},
{
<span class="hljs-string">"key"</span> : <span class="hljs-string">"TestSetting"</span>,
<span class="hljs-string">"machine"</span> : <span class="hljs-string">"MYPC"</span>,
<span class="hljs-string">"value"</span> : <span class="hljs-string">"another value"</span>
}
</code></pre>
<p>Given the mongo settings above when you request “TestSetting” you will get “another value” if your machine name is MYPC or “some value otherwise”</p>
</body></html>Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-28439624975322940622015-01-12T12:04:00.001+02:002015-01-12T12:04:18.936+02:00Capturing multiple channels of digital data on a two channel digital oscilloscopeIn my <a href="http://andrevdm.blogspot.com/2015/01/logic-level-parsing-rigol-oscilloscope.html">last post</a> I showed how I parsed the CSV data to do some basic logic analysis. The problem is that I have a two channel scope and need to capture three channels for SPI (i.e. clock + MOSI + MISO).<br/>
<br/>
As it turns out this can be done without too much fuss by using a digital to analogue (DAC) "resistor ladder". The two options I looked at were the R-2R ladder and the simpler binary weighted ladder. For two levels the binary weighted DAC is the simplest, it just needs two resistors (one double the other one's resistance).</br>
<br/>
Here is the circuit diagram<br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEime7Lw-Jkljvq0ezt2xffjxWPiGs-GY5Ek9PwZ8EsS5bMWU2Qckm0OnzVahVJMTRLEi2lE5jRYQrpIZPl5wrmPdSb7r1zncaxL_z0fFVLQhqs4I2P-66AlXDHphTV8nTztvx_Vfw3GyPg9/s1600/adc_2.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEime7Lw-Jkljvq0ezt2xffjxWPiGs-GY5Ek9PwZ8EsS5bMWU2Qckm0OnzVahVJMTRLEi2lE5jRYQrpIZPl5wrmPdSb7r1zncaxL_z0fFVLQhqs4I2P-66AlXDHphTV8nTztvx_Vfw3GyPg9/s1600/adc_2.png" /></a>
<br/>
So one input goes through the 1k resistor and the other through a 2k resistor and you measure the output. With an DAC with only two levels there is tolerance for some noise on the digital lines.
</br>
</br>
I'm working at ~3 volts, so the levels I'm going to see are
<table border="1">
<tr>
<th>Voltage</th>
<th>D0</th>
<th>D1</th>
</tr>
<tr>
<td>0v</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>1V</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>2V</td>
<td>1</td>
<td>0</td>
</tr>
<tr>
<td>3V</td>
<td>1</td>
<td>1</td>
</tr>
</table>
</br>
</br>
You can clearly see the three levels on the scope when measuring Vout<br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhU38q1ljhU8KMlwUd3duqfBhjH4rtcjb5ftjEKOHg2mozpgG_LGYcVmi7FvT_ZdJ0OXF5H600woSRQob2wo4U-sO5Y-577wcw1_lt0Wd_yKNUNKLWW1daCIWuth0xtFmUUAbCHIHi8Ordg/s1600/dac.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhU38q1ljhU8KMlwUd3duqfBhjH4rtcjb5ftjEKOHg2mozpgG_LGYcVmi7FvT_ZdJ0OXF5H600woSRQob2wo4U-sO5Y-577wcw1_lt0Wd_yKNUNKLWW1daCIWuth0xtFmUUAbCHIHi8Ordg/s1600/dac.png" /></a> <br/>
</br>
</br>
I kept the clock separate on channel 2 so that I could trigger on it. Here is the clock + data<br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyLsjo3lW8BGGa42bjxLbDVGcALLSI6xkgEGiDwwO7PVRvRx5rBV3y1Sw7VrXqX4QwB3d7HB0RvIU2523nnrKvLlgAC40r_gcLBcItkFQ7etR-6Hr8dS5A1hUzk99aU2nESN_PknAle8Ew/s1600/dac2.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyLsjo3lW8BGGa42bjxLbDVGcALLSI6xkgEGiDwwO7PVRvRx5rBV3y1Sw7VrXqX4QwB3d7HB0RvIU2523nnrKvLlgAC40r_gcLBcItkFQ7etR-6Hr8dS5A1hUzk99aU2nESN_PknAle8Ew/s1600/dac2.png" /></a><br/>
</br>
</br>
With only two channels going through the DAC you could reconstruct the two digital data channels by eye. That is not much fun though. So I modified my F# script from the previous post to split the data into two values and then do the logical analysis and print out two channels of data. This proved very useful when using a SPI device that returns data (e.g. SD card). Here is the full F# script <a href="https://gist.github.com/andrevdm/e05eff7b9e58024caba6">also available as a gist</a>.
<script class="brush: fsharp" type="syntaxhighlighter"><![CDATA[open System
open System.IO
open System.Text
open System.Globalization
open System.Text.RegularExpressions
let l1 = 1.0
let l2 = 2.0
let ttlTrue = 2.3
let getLines (fname:string) = seq{
use stream = new StreamReader( fname )
while not stream.EndOfStream do
yield stream.ReadLine()
}
let parseVoltage (s:string) =
try
System.Double.Parse( (s.Replace( ".", "," )), NumberStyles.Float )
with
| e ->
printfn "%s - %s" s (e.ToString())
0.0
let filterOnClockGoingHigh (s:seq<float * int>) = seq {
let e = s.GetEnumerator()
let last = ref 0
while e.MoveNext() do
if !last <> (snd e.Current) then
last := (snd e.Current)
if !last = 1 then
yield e.Current
}
//from http://fssnip.net/6A
let groupWhen f (input:seq<_>) = seq {
use en = input.GetEnumerator()
let running = ref true
// Generate a group starting with the current element. Stops generating
// when it founds element such that 'f en.Current' is 'true'
let rec group() =
[ yield en.Current
if en.MoveNext() then
if not (f en.Current) then yield! group()
else running := false ]
if en.MoveNext() then
// While there are still elements, start a new group
while running.Value do
yield group() |> Seq.ofList }
let toNum (g:int seq) =
let l = List.ofSeq g
(if l.Length > 0 && l.[0] = 1 then 128uy else 0uy) +
(if l.Length > 1 && l.[1] = 1 then 64uy else 0uy) +
(if l.Length > 2 && l.[2] = 1 then 32uy else 0uy) +
(if l.Length > 3 && l.[3] = 1 then 16uy else 0uy) +
(if l.Length > 4 && l.[4] = 1 then 8uy else 0uy) +
(if l.Length > 5 && l.[5] = 1 then 4uy else 0uy) +
(if l.Length > 6 && l.[6] = 1 then 2uy else 0uy) +
(if l.Length > 7 && l.[7] = 1 then 1uy else 0uy)
let adc (s:float seq) = seq {
let e = s.GetEnumerator()
while e.MoveNext() do
yield
if e.Current >= l1 + l2 then
(1,1)
elif e.Current >= l2 then
(1,0)
elif e.Current >= l1 then
(0,1)
else
(0,0)
}
getLines fsi.CommandLineArgs.[1]
//Skip headers
|> Seq.skip 2
//Split CSV
|> Seq.map (fun l -> l.Split( [|','|] ))
//Ignore empty lines
|> Seq.filter (fun f -> f.Length >= 3)
//Parse the voltages
|> Seq.map (fun f -> (parseVoltage f.[1], parseVoltage f.[2]) )
//Clock from voltage to logic level
|> Seq.map (fun (data,clock) -> (data, (if clock > ttlTrue then 1 else 0)))
//Get data - clock rising edge
|> filterOnClockGoingHigh
//remove the clock data
|> Seq.map (fun f -> fst f)
//ADC - single analogue singnal -> two digital values
|> adc
//Add a count to each item
|> Seq.mapi (fun i v -> (i,v))
//group into chunks of 8
|> groupWhen (fun (i,v) -> i % 8 = 0)
//get only the second item
|> Seq.map (Seq.map snd)
//unzip - get a pair of arrays for each virtual channel of data
|> Seq.map (fun f -> f
|> Array.ofSeq
|> Array.unzip)
//convert each group to a number
|> Seq.map (fun (x,y) -> (toNum x, toNum y))
|> Seq.iter (fun (x,y) -> printfn "%02x %02x %c" x y (if (y >= 0x20uy) && (y <= 0x7euy) then char y else ' ') )]]></script>
Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-3566264100593552562015-01-12T11:03:00.000+02:002015-01-12T11:04:44.101+02:00Logic level parsing Rigol oscilloscope CSV data with F#I was recently debugging a SPI communication but had no logic analyser. Fortunately my oscilloscope (rigol DS1052e) can export a capture as CSV (I assume that most digital oscilloscopes can export to CSV too).
<br/>
Parsing the CSV is trivial, getting a logic level from it is also fairly easy. I realise that this is terribly quick & dirty but it worked 100% for what I needed. Here is the high level process<br/>
<ol>
<li>Ignore timings - SPI is clocked so actual timing is irrelevant for this analysis</li>
<li>Parse X and Y channels</li>
<li>Convert clock channel voltage into logic level 1 or 0</li>
<li>Discard all rows apart from rows where the clock goes high</li>
<li>Convert data channel to logic level</li>
<li>Group into bytes</li>
<li>Display hex value</li>
</ol>
<br/>
As I said, very simple but it worked well and I was able to see exactly what was going on. Obviously this only works for synchronous (clocked) protocols like SPI/I2C but that is all I needed.
<br/>
<br/>
Here is the full F# script (also see the <a href="https://gist.github.com/andrevdm/04c9f5fae5cbee9701b1">gist</a>)
<br/>
<br/>
<script class="brush: fsharp" type="syntaxhighlighter"><![CDATA[open System
open System.IO
open System.Text
open System.Globalization
open System.Text.RegularExpressions
let ttlTrue = 2.3
let getLines (fname:string) = seq{
use stream = new StreamReader( fname )
while not stream.EndOfStream do
yield stream.ReadLine()
}
let parseVoltage (s:string) =
try
System.Double.Parse( (s.Replace( ".", "," )), NumberStyles.Float )
with
| e ->
printfn "%s - %s" s (e.ToString())
0.0
let filterOnClockGoingHigh (s:seq<int * int>) = seq {
let e = s.GetEnumerator()
let last = ref 0
while e.MoveNext() do
if !last <> (snd e.Current) then
last := (snd e.Current)
if !last = 1 then
yield e.Current
}
//from http://fssnip.net/6A
let groupWhen f (input:seq<_>) = seq {
use en = input.GetEnumerator()
let running = ref true
// Generate a group starting with the current element. Stops generating
// when it founds element such that 'f en.Current' is 'true'
let rec group() =
[ yield en.Current
if en.MoveNext() then
if not (f en.Current) then yield! group()
else running := false ]
if en.MoveNext() then
// While there are still elements, start a new group
while running.Value do
yield group() |> Seq.ofList }
let toNum (g:int seq) =
let l = List.ofSeq g
(if l.Length > 0 && l.[0] = 1 then 128uy else 0uy) +
(if l.Length > 1 && l.[1] = 1 then 64uy else 0uy) +
(if l.Length > 2 && l.[2] = 1 then 32uy else 0uy) +
(if l.Length > 3 && l.[3] = 1 then 16uy else 0uy) +
(if l.Length > 4 && l.[4] = 1 then 8uy else 0uy) +
(if l.Length > 5 && l.[5] = 1 then 4uy else 0uy) +
(if l.Length > 6 && l.[6] = 1 then 2uy else 0uy) +
(if l.Length > 7 && l.[7] = 1 then 1uy else 0uy)
getLines fsi.CommandLineArgs.[1]
//Skip headers
|> Seq.skip 2
//Split CSV
|> Seq.map (fun l -> l.Split( [|','|] ))
//Ignore empty lines
|> Seq.filter (fun f -> f.Length >= 3)
//Parse the voltages
|> Seq.map (fun f -> (parseVoltage f.[1], parseVoltage f.[2]) )
//Clock from voltage to logic level
|> Seq.map (fun (data,clock) -> ((if data > ttlTrue then 1 else 0), (if clock > ttlTrue then 1 else 0)))
//Get data - clock rising edge
|> filterOnClockGoingHigh
//remove the clock data
|> Seq.map (fun f -> fst f)
//Add a count to each item
|> Seq.mapi (fun i v -> (i,v))
//group into chunks of 8
|> groupWhen (fun (i,v) -> i % 8 = 0)
//get only the second item
|> Seq.map (Seq.map snd)
//convert each group to a number
|> Seq.map (fun x -> toNum x)
|> Seq.iter (fun x -> printfn "%02x %c" x (if (x >= 0x20uy) && (x <= 0x7euy) then char x else ' ') ) ]]></script>
Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-10940374379365737762015-01-05T21:38:00.000+02:002015-01-05T21:38:42.133+02:00Accurate clock with any frequency oscillator (e.g. embedded PIC projects)When I created an electric timer using a PIC one of the things I needed was an accurate clock. The PIC I was using did not have a built in real-time clock (RTC) and I did not want to use an external one. After some searching I found two popular suggestions to create an accurate clock
<ol>
<li>Use a 32.768kHz crystal which divides down perfectly to give you seconds etc</li>
<li>Use any frequency oscillator and manually set the value of the timer register on each interrupt. I.e. to skip some number of cycles</li>
</ol>
I did not like either of these solutions
<ol>
<li>32.768kHz is ridiculously slow compared to the 20-40MHz the PICs can run at</li>
<li>Manually setting the timer register takes some number of clock cycles and this will affect the timing. Working out how many clock cycles exactly sounds like an error prone and rather boring task</li>
</ol>
Fortunately I found this article <a href="http://www.romanblack.com/one_sec.htm" target="_blank">"A very versatile Zero Cumulative Error timing system with PIC source code"</a> @ http://www.romanblack.com/one_sec.htm. It shows a really simple way to get an accurate clock, i.e. no accumulating error,using any oscillator frequency. Take a look at the article. It has PIC source code and a nice explanation.
<br/>
Its a brilliantly simple solution. Here is my attempt to explain it. Perhaps having two different explanations helps.
<br/>
Imagine you have an oscillator that ticks every 3 units but a clock event that needs to happen every 5 units of time. The key to understanding this is that even though 3 does not go into 5 you can get close without accumulating error.
<br/>
Consider the first 5 oscillator ticks
<ul>
<li>3 - less that first time period (5), do nothing</li>
<li>6 - greater that first time period (5), do clock</li>
<li>9 - less that second time period (10), do nothing</li>
<li>12 - greater that second time period (10), do clock</li>
<li>15 - equal to third time period (15), do clock</li>
</ul>
So at each oscillator tick there is some discrepancy but it does not accumulate. In the example above the clock is 100% accurate every fifteenth oscillation.
<br/>
With a micro running at many megahertz you can adjust the timer prescale to make this periodic discrepancy as small or large as you like.
<br/>
In my timer I was happy to be out by a few seconds at any time. Of course you could of course use a smaller prescale value and keep it down to milliseconds if you really need to
<br/>
Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-56137170487265685492015-01-05T10:28:00.001+02:002015-01-05T10:37:43.767+02:00Using F# on a Raspberry PiThis is how I got F# working on a Raspberry Pi. The demo shows how to communicate with a Nokia 5110 LCD (PCD8544). While simple it does show how to use the wiringPi library, GPIO and SPI.
<br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhH5Jarw5L8SBz4OOO0bOETrksSs-YUBuJ0wSQ2n1pB4KEu6kQGX_e7gzOC2-zNl_u7MyhLSD9J7kYpaDbgxpAFXciFIiSrWvvQrWvjg_67pG0CNdFoT72jSgbPmzlG7ivaMy00mProAzFx/s1600/pcd8544.jpg" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhH5Jarw5L8SBz4OOO0bOETrksSs-YUBuJ0wSQ2n1pB4KEu6kQGX_e7gzOC2-zNl_u7MyhLSD9J7kYpaDbgxpAFXciFIiSrWvvQrWvjg_67pG0CNdFoT72jSgbPmzlG7ivaMy00mProAzFx/s1600/pcd8544.jpg" /></a>
<br />
<h1>
Installing mono</h1>
Installing mono on the latest Raspbian is easy. Just run the following Command
<pre> sudo apt-get install mono-complete</pre>
That should get everything you need.
<br />
<h1>
Using F#</h1>
While there are several tutorials on installing F# on the internet, I've chosen to rather compile the F# code on my main machine and copy the compiled assemblies across. I did this for two reasons
<ol>
<li> I prefer to work on my main computer, its just faster.</li>
<li> I'm having issues getting the F# compiler to work 100% on the pi, but given point one I've not tried very hard to sort it out.</li>
</ol>
<br />
<h1>
GPIO from F#</h1>
There are several options for accessing the Raspberry PI GPIO ports. I decided to use WiringPi as it seems to be one of the most popular libraries at the moment. There are a few .NET wrappers for WiringPi but I decided to write my own DllImports because its so easy and I can better control how I want the access to work.
To get WiringPi working I followed the instructions from http://wiringpi.com/download-and-install/
Once it is installed you will need to create the shared libraries. To do this run
<br />
<ol>
<li>cc -shared wiringPiI2C.o -o libwiringPiI2C.so</li>
<li>cc -shared wiringPiSPI.o -o libwiringPiSPI.so</li>
</ol>
I'll show you how to use these files shortly.
<br />
<h1>
Nokia 5110 / PCD8544</h1>
The Nokia 5110 LCDs are 84x48 pixel LCD screens that are easy to use. There are hundreds of tutorials around so I wont duplicate that effort here. If you want to understand how it works make sure you also look at the datasheet.
Wiring will be as follows
NB I prefer the GPIO pin numbering over the wiring pi numbering. Remember this when looking at GPIO pin numbers below
<br />
<table border="1">
<tbody>
<tr>
<th>LCD</th>
<th>Pi</th>
</tr>
<tr>
<td>Reset </td>
<td>gpio 24 (pin 18)</td>
</tr>
<tr>
<td>CE </td>
<td>CE0/gpio 8 (pin 10)</td>
</tr>
<tr>
<td>DC </td>
<td>gpio 23 (pin 16)</td>
</tr>
<tr>
<td>DIn </td>
<td>MOSI/gpio 10 (pin 19)</td>
</tr>
<tr>
<td>CLK </td>
<td>SCLK/gpio 11 (pin 23)</td>
</tr>
<tr>
<td>Vcc </td>
<td>+ 3.3v (pin 1)</td>
</tr>
<tr>
<td>Light </td>
<td>gpio 18 (pin 12)</td>
</tr>
<tr>
<td>Gnd </td>
<td>ground (pin 6)</td>
</tr>
</tbody></table>
<br />
<h1>
Using WiringPi from F#</h1>
Using WiringPi from F# is easy enough. Make sure that the two .so (shared libraries) created above are in the same directory as the compiled .exe. Then you can create DllImport statements for the functions that you want to export.
As an example here is how to import the "pinMode" function
<script class="brush: fsharp" type="syntaxhighlighter"><![CDATA[
[<DllImport( "libwiringPi.so", EntryPoint="pinMode", CallingConvention = CallingConvention.Cdecl, SetLastError=true )>]
extern void pinMode( int pin, int mode );
]]></script>
Rather than just using the raw imports, I chose to create a type safe F# wrapper for the imported functions. For example when setting the pin mode I'd like to have an type for the I/O mode so that it can be type checked rather than using an int. Also I want a simple way to specify the pin I'm using that works with the pin number, GPIO number or wiring pi standard numbering.
Here is the full code that does all of that
<script class="brush: fsharp" type="syntaxhighlighter"><![CDATA[module gpio
open System
open System.Runtime.InteropServices
module private imp =
[<DllImport( "libwiringPi.so", EntryPoint="pinMode", CallingConvention = CallingConvention.Cdecl, SetLastError=true )>]
extern void pinMode( int pin, int mode );
[<DllImport( "libwiringPi.so", EntryPoint="digitalWrite", CallingConvention = CallingConvention.Cdecl, SetLastError=true )>]
extern void digitalWrite( int pin, int state );
type pinMode =
| In
| Out
type pinState =
| High
| Low
type pinId =
| gpio14 = 15
| wipi15 = 15
| pin8 = 15
| gpio23 = 4
| wipi4 = 4
| pin16 = 4
| gpio24 = 5
| wipi5 = 5
| pin18 = 5
| gpio18 = 1
| wipi1 = 1
| pin12 = 1
| gpio8 = 10
| wipi10 = 10
| pin24 = 10
let pinMode (pin:pinId) mode =
match mode with
| In -> imp.pinMode( int pin, 0 )
| Out -> imp.pinMode( int pin, 1 )
let digitalWrite (pin:pinId) state =
match state with
| High -> imp.digitalWrite( int pin, 1 )
| Low -> imp.digitalWrite( int pin, 0 )
]]></script>
A few things to note
<br />
<ul>
<li>The DllImport is in a private module.</li>
<li>I've created F# wrappers for the imports that use strongly typed parameters</li>
<li>I've only defined a few of the pins, enough for this demo</li>
</ul>
<h1>
SPI</h1>
(See <a href="https://github.com/andrevdm/PiPcd8544Demo/blob/master/spi.fs" target="_blank">spi.fs</a>) <br/>
For the PSI imports I defined a few F# wrapper methods that make working with SPI easier. Remember that SPI is full-duplex so however many bytes you write you will get the same number of bytes returned. However quite often you don't care about the return values, e.g. with the LCD code. So I've created a function that does a write and a read (spiWR) and one that "only writes" (spiW) i.e. ignores the result.
<script class="brush: fsharp" type="syntaxhighlighter"><![CDATA[module spi
open System
open System.Text
open System.Runtime.InteropServices
module private imp =
[<DllImport( "libwiringPiSPI.so", EntryPoint="wiringPiSPISetup", CallingConvention = CallingConvention.Cdecl, SetLastError=true )>]
extern int wiringPiSPISetup( int channel, int speed );
[<DllImport( "libwiringPiSPI.so", EntryPoint="wiringPiSPIDataRW", CallingConvention = CallingConvention.Cdecl, SetLastError=true )>]
extern void wiringPiSPIDataRW( int channel, byte[] data, int len );
type spiChannel =
| channel0 = 0
| channel1 = 1
let spiSetup (channel:spiChannel) speed =
let res = imp.wiringPiSPISetup( (int channel), speed )
if res = -1 then
failwith (sprintf "spi setup failed %d" res)
let spiWR channel (data : byte[]) =
imp.wiringPiSPIDataRW( (int channel), data, data.Length )
data
let spiW (channel:spiChannel) (data : byte[]) =
spiWR channel data |> ignore
let spiWString (channel:spiChannel) (data : string) =
spiWR channel (System.Text.Encoding.ASCII.GetBytes( data )) |> ignore
let spiWRByte (channel:spiChannel) (data : byte) =
spiWR channel [| data |]
let spiWByte (channel:spiChannel) (data : byte) =
spiWRByte channel data |>ignore
]]></script>
<br />
<h1>
The LCD code</h1>
For the LCD I wanted to have the following
* Communication using hardware SPI using the wrappers defined above
* Reasonably efficient, e.g. send multi-byte commands/data in a singe DC/SPI "session"
* A simple interface that hides all of this detail
Since you may want to wire the LCD different for different projects the pins used must be configurable. To allow for this there is a lcdConfig record that must be passed to each LCD function containing the required settings. This also documents in code how the LCD should be wired.
<script class="brush: fsharp" type="syntaxhighlighter"><![CDATA[
type lcdConfig = { channel : spiChannel; pinRst : gpio.pinId; pinDc : gpio.pinId; pinLight : gpio.pinId }
]]></script>
When you start using the LCD you need to created an instance of this record and call lcdInit. lcdInit will initialise the SPI hardware and send all the required LCD initialisation commands.
Remember that to communicate with the LCD you first need to set DC high for data or low for a command. For example lcdGoto
<script class="brush: fsharp" type="syntaxhighlighter"><![CDATA[let lcdGoto config col row =
lcdSend
config
[| (0x40uy + row)
(0x80uy + col)
|]
Low
]]></script>
lcdGoto, sends two commands (DC is low for both). The first command 0x40+row sets the row. The second 0x80+col sets the column
Here is the full code
<script class="brush: fsharp" type="syntaxhighlighter"><![CDATA[module lcd
open System
open System.Threading
open System.Runtime.InteropServices
open gpio
open spi
open lcdFont
type lcdConfig = { channel : spiChannel; pinRst : gpio.pinId; pinDc : gpio.pinId; pinLight : gpio.pinId }
let lcdSend config (data: byte seq) dc =
digitalWrite config.pinDc dc
spiW config.channel (Seq.toArray data)
let lcdSendByte config data dc =
digitalWrite config.pinDc dc
spiWByte config.channel data
let bytesForLcdChar (c:char) =
let idx = ((int c) - 0x20) * 5
seq{ for i in 0..4 do yield lcdFont.font.[idx + i] }
let lcdWriteChar config (c : char) =
lcdSend config (bytesForLcdChar c) High
let lcdWriteString config (s:string) =
let bytes = s |> Seq.collect (fun c -> bytesForLcdChar c)
lcdSend config bytes High
let lcdGoto config col row =
lcdSend
config
[| (0x40uy + row)
(0x80uy + col)
|]
Low
let lcdCls config =
lcdGoto config 0uy 0uy
lcdSend config (Array.zeroCreate 504) High
lcdGoto config 0uy 0uy
let lcdInit config =
spiSetup config.channel 4000000
//output pins
[config.pinRst; config.pinDc; config.pinLight]
|> Seq.iter (fun pin -> pinMode pin Out)
//Light off
digitalWrite config.pinLight High
//Reset LCD
digitalWrite config.pinRst Low
Thread.Sleep( 10 )
digitalWrite config.pinRst High
Thread.Sleep( 10 )
//Light on
digitalWrite config.pinLight Low
lcdSend
config
[| (0x20uy + 0x01uy) //extended instructions on
(0x80uy + 0x40uy) //set contrast 0 - 127
(0x04uy + 0x02uy) //temp control
(0x10uy + 0x03uy) //set bias system
(0x20uy + 0x00uy) //return to basic instruction set, power on, set horizontal addressing
(0x08uy + 0x04uy) //display control set to normal mode
|]
Low
let lcdOff config =
//Light off
digitalWrite config.pinLight High
]]></script>
<br />
<h1>
Using the wrappers</h1>
Once all the wrappers are defined the code to interface with the LCD is simple. This demo displays the date and time and updates it every second for ~1 minute,
<script class="brush: fsharp" type="syntaxhighlighter"><![CDATA[open gpio
open spi
open lcdFont
open lcd
[<entrypoint>]
let main argv =
wipiInit.wiringPiSetup()
let lcdConfig = {channel = spiChannel.channel0; pinRst = pinId.gpio24; pinDc = pinId.gpio23; pinLight = pinId.gpio18}
lcdInit lcdConfig
lcdCls lcdConfig
lcdGoto lcdConfig 3uy 0uy
lcdWriteString lcdConfig "PCD8544 Demo"
for x in 1..60 do
lcdGoto lcdConfig 0uy 1uy
for i in 0..12 do
lcdSend lcdConfig [| 0x11uy; 0x21uy; 0x41uy; 0x21uy; 0x11uy; |] High
lcdGoto lcdConfig 6uy 2uy
lcdWriteString lcdConfig (DateTime.Now.ToString( "yyyy/MMM/dd" ))
lcdGoto lcdConfig 12uy 3uy
lcdWriteString lcdConfig (DateTime.Now.ToString( "HH:mm:ss" ))
lcdGoto lcdConfig 0uy 4uy
for i in 0..12 do
lcdSend lcdConfig [| 0x04uy; 0x02uy; 0x01uy; 0x02uy; 0x04uy; |] High
Thread.Sleep( 1000 )
lcdOff lcdConfig
0 // return an integer exit code
]]></script>
<br />
<h1>
Compiling the code and copying to the pi</h1>
To build and copy the code to the pi I use the following bash script, which assumes the pi is on IP 192.168.0.73
<pre>#!/bin/sh
fsharpc --out:lcdDemo.exe --target:exe --debug+ --debug:full --tailcalls+ --optimize font.fs wipiInit.fs gpio.fs spi.fs lcd.fs lcdDemo.fs && scp -r lcdDemo* pi@192.168.0.73:~/prog/fs/wiringPi_5110_Basic/</pre>
Make sure you pi a running SSHd. Configuring SSH to allow login using your private key is also a good idea as it means you don't need to type your password each time you build the code.
The sample also contains a Visual Studio solution should you be using Visual Studio or MonoDevelop etc.
Make sure you copy FSharp.Core.dll and the two .so shared wiringPi libraries discussed above across to the pi in the same directory as the compiled .exe before your first run.
<br />
<h1>
Thats it</h1>
Overall getting F# working was pretty simple. The performance is great, startup time is fantastic (the JVM was terrible :( ). I hope this helps get you started. Overall I'm really impressed with how well it all works
The full code for this demo can be found here: <a href="https://github.com/andrevdm/PiPcd8544Demo" target="_blank">https://github.com/andrevdm/PiPcd8544Demo</a>
Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-49054034333461736892014-11-18T11:05:00.001+02:002014-11-19T20:50:49.481+02:00F# FunScript with NancyFx and Ractive<br/> <h2>Getting started</h2> <br/> I just started with FunScript and got stuck with a few of the basics. Here is how I got it all working <br/> <br /><a href="http://funscript.info/" target="_blank">FunScript</a> is a library that compiles F# to JavaScript. This lets you write strongly typed client side code in F#.  It takes advantage of many of the F# features, async workflows (no callback) etc. Take a look at the <a href="http://funscript.info/" target="_blank">FunScript</a> page for more information. <br/> <br /><a href="http://www.ractivejs.org/" target="_blank">Ractive.JS</a> is a template drive reactive UI library created initially at the Guardian. The Ractive tutorials are very clear and definitely worth using <br /> <br /><a href="http://nancyfx.org/" target="_blank">NancyFx</a> is a lightweight HTTP framework for .NET. It is less popular in the F# world than C# but still works very well from F#. <br /> <br /> <br />Read the official docs at <a href="http://funscript.info/">http://funscript.info/</a>. The introduction and tutorials are very good and will give you a good background in what FunScript is and how it works. <br /> <br />The code below is based on these demos as well as one of Alfonso's projects <a href="https://github.com/alfonsogarciacaro/PinkBubbles.Informa">https://github.com/alfonsogarciacaro/PinkBubbles.Informa</a> <br /> <br /> <br />In this post I'll demonstrate the following <ol> <li>Serving data as JSON with NancyFx </li> <li>Declaring types in F# and using them in FunScript code </li> <li>Reading the JSON with FunScript and casting to the strongly typed type </li> <li>Rendering a basic Ractor template </li> </ol> Although I'm using NancyFx here any web framework should work much the same way. <br /> <br /> <br /> <h2>Creating the solution</h2> <br /> <ol> <li>Open Visual Studio </li> <li>Create a new F# console project </li> <li>Add the following nuget references <ul> <li>FunScript </li> <li>funscript.TypeScript.binding.lib </li> <li>nancy.hosting.self </li> </ul> </li> <li>Enable type providers when prompted </li> <li>Get FunScript.HTML.dll and FunScript.HTML.Ractive.dll. <br />I got them from Alfonso's github demo, they are included in my github repo <ul> <li><a href="https://github.com/alfonsogarciacaro/FunScript.Ractive.TodoMVC/blob/master/lib/FunScript.HTML.dll?raw=true">https://github.com/alfonsogarciacaro/FunScript.Ractive.TodoMVC/blob/master/lib/FunScript.HTML.dll?raw=true</a> </li> <li><a href="https://github.com/alfonsogarciacaro/FunScript.Ractive.TodoMVC/blob/master/lib/FunScript.HTML.Ractive.dll?raw=true">https://github.com/alfonsogarciacaro/FunScript.Ractive.TodoMVC/blob/master/lib/FunScript.HTML.Ractive.dll?raw=true</a> </li> </ul> </li> <li>Download and store the two DLLs in a local folder (e.g. lib) </li> <li>Add reference to the two DLLs </li> </ol> <br /> <h2>NancyFx - Serving some data</h2> The code to serve a simple page over nancy fx <script type="syntaxhighlighter" class="brush: fsharp"><![CDATA[<br />open System<br />open System.IO<br />open Nancy<br />open Nancy.Hosting.Self<br />open FunScript<br />open FunScript.TypeScript<br />open FunScript.HTML<br /><br />module Demo =<br /> type IndexModule() as x =<br /> inherit NancyModule()<br /> do<br /> x.Get.["/ping"] <- fun _ -> box (DateTime.Now.ToString( "yyyy/MM/DD HH:mm:ss" ))<br /><br />[<EntryPoint>]<br />let main argv =<br /> //Leave the type's cases they way they are in JSON<br /> Nancy.Json.JsonSettings.RetainCasing <- true<br /><br /> let uri = Uri("http://localhost:6543")<br /> use host = new NancyHost(uri)<br /> host.Start()<br /><br /> Console.WriteLine("Your application is running on " + uri.AbsoluteUri)<br /> Console.WriteLine("Press any [Enter] to close the host.")<br /> Console.ReadLine() |> ignore<br /><br /> 0 ]]></script> <br/> Run the projet. If you get permission errors, you will need to run studio as an administrator. Browse to <a href="http://localhost:6543/ping">h</a><a href="ttp://localhost:6543/ping">ttp://localhost:6543/ping</a> This snippet <ol> <li>Creates a nancyFx module "IndexModule" </li> <li>Defines a route for GET /ping <br /><script type="syntaxhighlighter" class="brush: fsharp"><![CDATA[do x.Get.["/ping"] <- fun _ -> box (DateTime.Now.ToString( "yyyy/MM/DD HH:mm:ss" ))]]></script> <br />This may look a bit strange but all it does is define that whenever a GET request is received for /ping the current DateTime string should be returned. 'box' is called to box the result as on object as that is what NancyFx is returning </li> <li>Starts the host on port 6543 <br /><script type="syntaxhighlighter" class="brush: fsharp"><![CDATA[let uri = Uri("http://localhost:6543")<br /> use host = new NancyHost(uri)<br /> host.Start()]]></script></li> </ol> <br/> <br/> <h2>F# Types</h2> <br/> As a simple example all types in the current assembly will be returned. Below is the function that does this as well as the <i>AssemblyTypes</i> record for this data. <script type="syntaxhighlighter" class="brush: fsharp"><![CDATA[type AssemblyType = {name:string; isClass:bool; }<br /><br />let getAssemblyTypes =<br /> System.Reflection.Assembly.GetExecutingAssembly().GetTypes()<br /> |> Seq.map (fun m -> {name = m.Name; isClass = m.IsClass; })<br />]]></script> <br/> <br/> <h2>Serving the data as JSON</h2> <br/> <script type="syntaxhighlighter" class="brush: fsharp"><![CDATA[<br />type IndexModule() as x =<br /> inherit NancyModule()<br /> do<br /> x.Get.["/ping"] <- fun _ -> box (DateTime.Now.ToString( "yyyy/MM/DD HH:mm:ss" ))<br /> x.Get.["/data"] <- fun _ -> box (FormatterExtensions.AsJson(x.Response, getAssemblyTypes))<br />]]></script> <br/> The /data route has been added. It calls the <i>getAssemblyTypes</i> function defined above and returns the data as JSON. <br/> To format the data as JSON you call NancyFx's <i>FormatterExtensions.AsJson</i> method <br/> If you run the project and open http://localhost:6543/data in your browser you should see the JSON data being returned <br/> <br/> <h2>FunScript - Index page and template</h2> <br/> At this point we have a simple HTTP server that can serve JSON data. Now the  scaffolding to get FunScript working needs to be added Create an index.html page that will be used to display the data <script type="syntaxhighlighter" class="brush: html"><![CDATA[<br /> <!-- The template will be rendered into this div --><br /> <div id='ractive-container'></div><br /><br /> <!-- The ractive template --><br /> <script id='ractive-template' type='text/ractive'><br /> <table border="1"><br /> <tr><br /> <th>name</th><br /> <th>isClass</th><br /> </tr><br /> {{#each types}}<br /> <tr><br /> <td>{{name}}</td><br /> <td>{{isClass}}</td><br /> </tr><br /> {{/each}}<br /> </table><br /> </script><br /> <script src="http://cdn.ractivejs.org/latest/ractive.js"></script><br /> <script src="app.js">]]></script> <br/> This page contains the following items <ul> <li>A "<i>ractive-container</i>" div into which the template will be rendered </li> <li>A "<i>ractive-template</i>" script block containing the template </li> <li>A script include for ractive </li> <li>A script include for the generate Javascript from the FunScript compiler. I'll show how this works below </li> </ul> <br />Add the NancyFx route for the index page. I'm loading the page using a <i>File.ReadAllText()</i>. (<span style="font-size: x-small">NancyFx can serve static pages but I'm avoiding the discussion about bootstrappers, resource directories etc here. You should read the NancyFx docs about this if you are using this in a real project</span>) <br /> <br /><script type="syntaxhighlighter" class="brush: fsharp"><![CDATA[<br />type IndexModule() as x =<br /> inherit NancyModule()<br /> do<br /> x.Get.["/ping"] <- fun _ -> box (DateTime.Now.ToString( "yyyy/MM/DD HH:mm:ss" ))<br /> x.Get.["/data"] <- fun _ -> box (FormatterExtensions.AsJson(x.Response, getAssemblyTypes))<br /> x.Get.["/"] <- fun _ -> box (File.ReadAllText( "../../index.html" ))<br />]]></script> <br/> <br/> <h2>FunScript - Compiling F# to JavaScript</h2> <br/> Here is the F# code that compiles the F# to JavaScript. I'll comment on each section of the code below <script type="syntaxhighlighter" class="brush: fsharp"><![CDATA[<br />[<ReflectedDefinition>]<br />module Web =<br /> type AppState = {reload:bool; types:AssemblyType array;}<br /><br /> let rec mainLoop (st: RactiveState<AppState>) = async {<br /> let! st = async {<br /> match st.ractive.get "reload" :?> bool with<br /> | true -><br /> let url = "http://localhost:6543/data"<br /> let req = System.Net.WebRequest.Create(url)<br /> let! asmTypes = req.AsyncGetJSON<AssemblyType array>()<br /> Globals.console.log asmTypes<br /> Globals.console.log asmTypes.[0].name<br /> return RactiveState( st, {st.data with reload = false; types = asmTypes} )<br /><br /> | false -><br /> return st<br /> }<br /><br /> return! mainLoop st<br /> }<br /><br /> let start () =<br /> let ractive = Globals.Ractive.CreateFast("#ractive-container", "#ractive-template")<br /> RactiveState.init(ractive, { reload = true; types = [||]; })<br /> |> mainLoop<br /> |> Async.StartImmediate<br /><br /> let compile =<br /> Compiler.Compiler.Compile(<br /> <@ start() @>,<br /> noReturn = true,<br /> shouldCompress = true )<br />]]></script> <br />Since this is the F# that needs to be compile by the FunScript compiler to JavaScript it needs the <em>ReflectedDefinition</em> attribute on the module <br />The compile function does the actual compilation. This should be familiar from the FunScript guide <script type="syntaxhighlighter" class="brush: fsharp"><![CDATA[<br />let compile =<br /> Compiler.Compiler.Compile(<br /> <@ start() @>,<br /> noReturn = true,<br /> shouldCompress = true )<br />]]></script> <br />The start function does the following <ul> <li>  Creates a Ractive instance </li> <li>  Creates the initial application state </li> <li>  Starts a Ractive loop (mainLoop function) </li> </ul> <br />The application state is stored in record defined like this <script type="syntaxhighlighter" class="brush: fsharp"><![CDATA[<br />type AppState = {reload:bool; types:AssemblyType array;}<br />]]></script> <br />Here is the start function. Note the creation of the initial RactiveState. Also note that JavaScript is expecting an <b>array</b> not a list or a seq. <br /> <script type="syntaxhighlighter" class="brush: fsharp"><![CDATA[<br />let start () =<br /> let ractive = Globals.Ractive.CreateFast("#ractive-container", "#ractive-template")<br /> RactiveState.init(ractive, { reload = true; types = [||]; })<br /> |> mainLoop<br /> |> Async.StartImmediate<br />]]></script> <br />That is the main scaffolding for getting FunScript and Ractive working. Next the mainLoop function which contains the F# code that is going to run in the browser. <br /> <br />This function does the following <br /> <ul> <li>Check if a reload of the data was requested </li> <li>Call our /data endpoint to get the JSON </li> <li>Cast the JSON data to the stronly typed F# type </li> <li>Mark as updated </li> </ul> <br/> <script type="syntaxhighlighter" class="brush: fsharp"><![CDATA[<br />let rec mainLoop (st: RactiveState<AppState>) = async {<br /> let! st = async {<br /> match st.ractive.get "reload" :?> bool with<br /> | true -><br /> let url = "http://localhost:6543/data"<br /> let req = System.Net.WebRequest.Create(url)<br /> let! asmTypes = req.AsyncGetJSON<AssemblyType array>()<br /> Globals.console.log asmTypes<br /> Globals.console.log asmTypes.[0].name<br /> return RactiveState( st, {st.data with reload = false; types = asmTypes} )<br /><br /> | false -><br /> return st<br /> }<br /><br /> return! mainLoop st<br />}<br />]]></script> <br/> This looks a little complex but the core of it is the following <script type="syntaxhighlighter" class="brush: fsharp"><![CDATA[<br />let url = "http://localhost:6543/data"<br />let req = System.Net.WebRequest.Create(url)<br />let! asmTypes = req.AsyncGetJSON<AssemblyType array>()<br />Globals.console.log asmTypes<br />Globals.console.log asmTypes.[0].name<br />return RactiveState( st, {st.data with reload = false; types = asmTypes} )<br />]]></script> <ol> <li>A <i>WebRequest</i> instance is created. </li> <li><i>AsyncGetJSON<></i> is called and casts the JSON to our F# type. <br />This is the real magic of FunScript. The <i>AsyncGetJSON</i> makes an AJAX call from the browser for you and then lets you work with your data in a strongly typed way. </li> <li>The two <i>Globals.console.log</i> calls show the typed data being used. </li> <li>Finally an updated state is returned </li> </ol> <br /> <br /> <h2>Serving app.js</h2> <br/> <br />All that is left is to get the FunScript compile F# served as app.js <script type="syntaxhighlighter" class="brush: fsharp"><![CDATA[<br />x.Get.["/app.js"] <- fun _ -><br /> let resp = FormatterExtensions.AsText(x.Response, Web.compile)<br /> resp.ContentType <- "application/javascript"<br /> box resp<br />]]></script> <br />Not terribly pretty but simple enough. A text response object is create a containing the javascript compiled from the F# (by the <i>Web.compile </i>function). Then the content type is changed to application/javascript. Finally the response object is returned <br /> <br />If you run the project and browse to http://localhost:6543 you will see your data being displayed. The code itself is mostly scaffolding and is pretty simple. With the basics working you can now continue with the tutorials from Ractive and FunScript to take things further. <br /> <br/> <h2>Code</h2> <br />The full code is on github <a href="https://github.com/andrevdm/FunScriptRactorAndNancyDemo">https://github.com/andrevdm/FunScriptRactorAndNancyDemo</a> <br /> <br />Feel free to suggest any improvements Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-22202046485030888282014-10-28T21:00:00.000+02:002014-10-28T21:00:16.984+02:00Connecting to Microsoft SQL server from ClojureFor some reason I battled to find a good reference on using MSSQL from Clojure. Here is how I got is working.<br />
<br />
There is a choice between the proprietary MS driver and an <a href="http://jtds.sourceforge.net/" target="_blank">open source jTDS one</a>. I opted for jTDS. see http://jtds.sourceforge.net/faq.html<br />
<br />
<br />
<h2>
jTDS JAR</h2>
Get the jTDS-n.n.n.JAR from the <a href="http://sourceforge.net/projects/jtds/files/" target="_blank">zip file on SourceForge</a> and place it on your class path. <br />
<br />
To get your class path you can run<br />
<b> lein classpath</b><br />
<b><br /></b>
<br />
<h2>
Project dependencies </h2>
Add the jTDS dependency to your project.clr <br />
<b>[org.clojure/java.jdbc "0.3.5"]</b><br />
<br />
<br />
<h2>
Authentication </h2>
Getting the JDBC connection string just right was where I had issues. This is what I ended up with <br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"> (let [sql-db {:subprotocol "jtds:sqlserver",<br /> :subname (str "//" sqlServer "//" sqlDb ";useNTLMv2=true;domain=" domain),<br /> :user userName,<br /> :password password}]</span><br />
<br />
Where<br />
<ul>
<li>sqlServer is the SQL server machine name / IP</li>
<li>sqlDb is the default SQL database</li>
<li>domain is the domain for your user account</li>
<li>userName is the userName</li>
<li>password is the password</li>
</ul>
<br />
<br />
<h2>
<b>SSO</b></h2>
If you want to use single sign on (SSO) / integrated security then you need the ntlmauth.dll from the jTDS download zip. Its in the /x64/SSO folder. The DLL must be placed in the same folder as the jtds JAR. This only works on a windows<b> </b>host<br />
<br />
If you use SSO remove the :user and :password from the map above<br />
<br />
<br />
<br />
<br />
Thats it. Good luckAnonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-52019903781227276552014-07-25T11:03:00.000+02:002014-07-25T11:04:08.936+02:00Unit testing embedded C projects with seatestI recently wrote my first embedded C project and was quite surprised to find that unit tests were not as widely used as I would have expected. There seems to be a general opinion that unit tests are less useful for embedded development that for application development. I find this very strange because debugging embedded systems is hard compared to application development. <br />
<br />
Fortunately not everyone agrees with this sentiment and in the end it was relatively easy to get unit tests working thanks to <a href="https://code.google.com/p/seatest/" target="_blank">SeaTest</a> (https://code.google.com/p/seatest/). SeaTest is simple and specifically designed for embedded-c projects.<br />
<br />
To get it working for a Microchip MPLabX project I did this<br />
<ol>
<li>Created a _test directory in my main projects directory</li>
<li>Created my unit tests in this directory in test.c </li>
<li>In this directory created a bash script named "test"</li>
<li>Copied seatest.c and seatest.h into the directory</li>
<li>Created dummy PIC include files in this directory</li>
<ol>
<li>xc.h</li>
<li>xc.c </li>
<li>pic18f4550.h</li>
</ol>
<li>Created a plib directory under _test containing</li>
<ol>
<li>timers.h</li>
</ol>
</ol>
The idea being that the my test directory (_test) contained files for mocking the Microchip libraries to make testing possible. This turned out to be much simpler than I had feared.<br /><br />
<br />
<u><b>Test runner bash script</b></u><br />
<br />
<pre>#/bin/sh
gcc -std=c99 -o test.o -D TESTING -I . -I .. xc.c test.c ../buttons.c ../lcd.c ../dateTime.c ../timerUi.c ../timer.c seatest.c && ./test.o</pre>
<br />
Nothing fancy... It does this<br />
<ul>
<li>Defines a TESTING constant</li>
<li>Includes the local directory for the "mock" libraries</li>
<li>Includes the parent directory for my actual code</li>
<li>Runs ./test.o if the compile succeeds</li>
</ul>
<br />
<u><b>Mock/Stub files</b></u><br />
<br />
Most of the stub files (timers.h etc) are empty files just to keep the compiler happy. I then just copied whatever definitions I needed from MPLabX's libraries to get the rest working.<br />
<br />
<br />
<u><b>Tests</b></u><br />
<br />
Tests are then simply a matter of calling a function and using the seatest assert functions.<br />
<br />
There definitely are things that are hard to test e.g. interrupt routines but as long as your code is modular you should usually be able to test the methods that e.g. the interrupt routine calls. <br />
<br />
Overall I found this to be a very simple approach and it certainly helped me get my project up and running a lot faster with a lot more confidenceAnonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-77891643137749881602014-02-16T10:27:00.005+02:002014-02-16T15:22:06.939+02:00Parsing s-expressions in Clojure<h2>
Introduction</h2>
<br />
This is a quick look at parsing in clojure. First using instaparse and then writing the lexer and parser by hand. The comparison should illustrate how great instaparse is but also show that writing a simple lexer & parser is not as complex as some would think.<br />
<br />
BTW this is my first clojure project so I may have got some of the idioms in the code incorrect. I'll update the code samples based on feedback here and on the project <a href="https://github.com/andrevdm/blog-clojure-sexpr-parse" target="_blank">repo in github</a> :)<br />
<br />
<h2>
The demo project</h2>
<br />
To demonstrate instaparse I'll be implementing a simple external DSL. The DSL should have the following characteristics<br />
<ol>
<li>Expressions written as sexprs</li>
<li>External DSL - I'm not interested in using the clojure reader to read the sexpr for this demo</li>
<li>Constrained - functions can only be defined in clojure not in the DSL itself. The functions available to the DSL must be strictly controlled.</li>
</ol>
All code is in the <a href="https://github.com/andrevdm/blog-clojure-sexpr-parse" target="_blank">github repository</a> (<a href="https://github.com/andrevdm/blog-clojure-sexpr-parse" target="_blank">https://github.com/andrevdm/blog-clojure-sexpr-parse</a>)<br />
<br />
<h3>
Instaparse </h3>
<h3>
Using instaparse</h3>
<a href="https://github.com/Engelberg/instaparse" target="_blank">Instaparse</a> (<a href="https://github.com/Engelberg/instaparse" target="_blank">https://github.com/Engelberg/instaparse</a>) is a clojure library for generating a parser (and lexer) from a EBNF/ABNF. It is one of the easiest parser generators I've used, I highly recommend giving it a try.<br />
<br />
<h3>
The grammar</h3>
The instaparse page has a nice introduction to the grammar syntax. Start there if you are not familiar with EBNF.<br />
<br />
Here is the grammar that I'll be parsing <br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"> S = (expression <ws>)*<br /> expression = list | vector | atom<br /> list = <'('> <ws> (expression <ws>)* <')'><br /> vector = <'['> (expression <ws>)* <']'><br /> atom = number | string | name<br /> number = #'\d+'<br /> string = <'"'> #'[^\"]+' <'"'><br /> name = #'[a-zA-Z\+-]([0-9a-zA-Z\+-]*)'<br /> ws = #'\s+'</ws></ws></ws></ws></span><br />
<br />
<br />
<br />
This is pretty standard EBNF. Some things to note<br />
<br />
<ol>
<li>Wrap an element in angle brackets to remove it from the output e.g. <ws><ws></ws></ws></li>
<li>Match literal characters with single quotes. e.g. '('</li>
<li>Regular expressions using #'regex'</li>
<li>Remember to escape regex characters correctly. See the code example for the correct escaping</li>
</ol>
<br />
Again the instaparse page has a nice introduction that covers all of this.<br />
<br />
<h3>
The output parse tree</h3>
<br />
The output parse tree from instaparse can be in hiccup or enliven format. I'll be using the default hiccup format.<br />
<br />
As an example here is the output for "<span style="font-family: "Courier New",Courier,monospace;">(+ 1 2 3) 4</span>"<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"> [:S<br /> [:expression<br /> [:list<br /> [:expression [:atom [:name "+"]]]<br /> [:expression [:atom [:number "1"]]]<br /> [:expression [:atom [:number "2"]]]<br /> [:expression [:atom [:number "3"]]]]]<br /> [:expression [:atom [:number "4"]]]]</span><br />
<br />
Instaparse can visualise a parse tree using graphviz and rhizome (see <a href="https://github.com/Engelberg/instaparse#visualizing-the-tree" target="_blank">https://github.com/Engelberg/instaparse#visualizing-the-tree</a>). E.g. for the parse tree above you get this<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL4oyQpZ7V5l7h8qvd8ETJ0coDY_mDaXoU-ySMF58MNUiVpeY3nn23xzGcI2YWZNU62aT6SYCHVrlrk8XfQTw2xvc3S0HntKUp0MrS7sUdKV0jFdqZH14Q6hbr_wc0oTHJNprHFkw_3g1v/s1600/sexprParsing_parseTree.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhL4oyQpZ7V5l7h8qvd8ETJ0coDY_mDaXoU-ySMF58MNUiVpeY3nn23xzGcI2YWZNU62aT6SYCHVrlrk8XfQTw2xvc3S0HntKUp0MrS7sUdKV0jFdqZH14Q6hbr_wc0oTHJNprHFkw_3g1v/s1600/sexprParsing_parseTree.png" height="516" width="640" /></a></div>
<br />
<br />
<h3>
Interpreting the parse tree</h3>
There are several ways to interpret the output from instaparse, e.g. using zippers or using the built in instaparse transformation function. However I chose to use simple recursive functions since it is so simple.<br />
<br />
<pre class="brush: clojure;"> (defmulti run (fn [s] (nth s 0)))
(defmethod run :S [[s & es]] (last (doall (map run es))))
(defmethod run :expression [[e t]] (run t))
(defmethod run :atom [[a t]] (run t))
(defmethod run :number [[n val]] (read-string val))
(defmethod run :string [[s val]] val)
(defmethod run :vector [[v & vs]] (vec (map run vs)))
(defmethod run :name [[n & nn]] (first nn))
(defmethod run :list [[l n & ls]] (let [args (map run ls)]
(apply (methods (run n)) args)))</pre>
<br />
<br />
<br />
The multimethod's dispatch function gets the first item from each vector. Look at the parse tree above, you'll see that this will always be the type of the current element (:S or :expression or :number etc)<br />
<br />
Each method then is responsible for destructuring its element type. E.g. the :number method must parse the number and return a string. The :vector method must return a vector. Each method calls the run multimethod recursively to get the lowest level atom<br />
<br />
Notice that the :S method calls last on doall, which is called to force evaluation of the whole lazy seq. last is called to get the last value. I.e. the parser will return the last value evaluated just as clojure would.<br />
<br />
The :list method is where the interpreter actually "runs" functions called by the DSL.<br />
<br />
<br />
<br />
<pre class="brush: clojure;"> (defmethod run :list [[l n & ls]] (let [args (map run ls)]
(apply (methods (run n)) args)))
</pre>
<br />
<br />
<br />
The parameters<span style="font-family: "Courier New",Courier,monospace;"> [ [l n & ls] ] </span> destructure the incoming element into<br />
<ol>
<li> l = the :list</li>
<li> n = the name of the function as a :name element</li>
<li> s = the method arguments</li>
</ol>
<br />
Remember that a list is executed by treating the first expression as the function and the rest as the arguments to that function.<br />
<br />
<br />
Once we have the arguments they must be evaluated by calling run for each argument<br />
<span style="font-family: "Courier New",Courier,monospace;">(map run ls)</span><br />
<br />
We get the name of the function to run<br />
<span style="font-family: "Courier New",Courier,monospace;"> (run n)</span><br />
<br />
We look up the actual function to call in the methods map. It is this map that lets us control exactly which functions can be called. All together it looks like this<br />
<span style="font-family: "Courier New",Courier,monospace;">(let [args (map run ls) (apply (methods (run n)) args)))</span><br />
<br />
<br />
<h3>
Full sample code</h3>
Here is the full code for the DSL parser and interpreter using instaparse<br />
<pre class="brush: clojure;">(ns cljsexp-instaparse.core
(:require [instaparse.core :as insta]))
(def parse
(insta/parser
"S = (expression <ws>)*
expression = list | vector | atom
list = <'('> <ws> (expression <ws>)* <')'>
vector = <'['> (expression <ws>)* <']'>
atom = number | string | name
number = #'\\d+'
string = <'\"'> #'[^\\\"]+' <'\"'>
name = #'[a-zA-Z\\+-]([0-9a-zA-Z\\+-]*)'
ws = #'\\s+'"))
(def methods
{"+" +
"-" -
"*" *
"/" /
"++" inc
"--" dec
"prn" println})
(defmulti run (fn [s] (nth s 0)))
(defmethod run :S [[s & es]] (last (doall (map run es))))
(defmethod run :expression [[e t]] (run t))
(defmethod run :atom [[a t]] (run t))
(defmethod run :number [[n val]] (read-string val))
(defmethod run :string [[s val]] val)
(defmethod run :vector [[v & vs]] (vec (map run vs)))
(defmethod run :name [[n & nn]] (first nn))
(defmethod run :list [[l n & ls]] (let [args (map run ls)]
(apply (methods (run n)) args)))</ws></ws></ws></ws></pre>
<br />
<h3>
Conclusion - instaparse</h3>
Instaparse is amazing. It makes writing a parser very easy indeed. A simple sexp parser and interpreter in less that 40 lines of clojure is a great result.<br />
<br />
<br />
<h2>
A simple recursive descent parser</h2>
<br />
Writing the lexer and parser by hand is an interesting exercise as it shows that its not too hard to do. However in my opinion it also shows how much simpler instaparse makes things even for simple projects.<br />
<br />
For what it is worth note that there is no mutable state in this code. All the functions are pure. This made testing very easy.<br />
<br />
<h3>
Lexing</h3>
<br />
<br />
Lexing or tokenising a string is the process of converting the characters from the source code into higher level tokens (equivalent to taking individual letters and making words).<br />
<br />
E.g. taking this character stream<br />
<br />
<span style="font-family: "Courier New",Courier,monospace;"> | | | | | | | | | | | | |<br /> | ( | i | f | | ( | a | n | d | ( | a | b | c |</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> | | | | | | | | | | | | |</span><br />
And creating these tokens<br />
<br />
left-paren, if, left-paren, and, left-paren, abc<br />
<br />
Each token has meta-data associated with it. Such as the line and column in the source file and the type of token (string vs name vs paren etc).<br />
<br />
Tokenising the input means that the parser does not need to deal with individual characters but rather can work with higher level tokens. This greatly simplifies the design as the concerns of lexing the input and parsing the resulting tokens can be separated. In a recursive descent parser you could lex the next token on demand rather than lex everything first as I have here.<br />
<br />
NB remember that the output of the tokeniser is a flat list of tokens. No meaning has yet been inferred from the source code<br />
<br />
<br />
In the code above each token has the following clojure structure<br />
<br />
<pre class="brush: clojure;">{:type :xxx,
:val xxx,
:line xxx,
:col xxx,
:expressions []}
</pre>
<br />
Each token has a<br />
<ol>
<li>Type (e.g. name/string/list)</li>
<li>Value (e.g. the numeric or string value of the text)</li>
<li>The line and column number that the token started in the source file</li>
<li>A place holder for nested expressions</li>
</ol>
<br />
<br />
<h4>
Matching the next token</h4>
<br />
<pre class="brush: clojure;">(def tokenMap {:byChar { \( :lparen
\) :rparen,
\[ :lbracket,
\] :rbracket}
:byRegex { #"'" parseString
#"\d+" parseNumber
#"[a-zA-Z\+\-\*\\\/\?_\$\<\>=]" parseName
#";" parseComment }})
</pre>
<br />
<br />
Here there are two maps. The first identifies single character tokens such as brackets or parentheses. The second uses a regular expression to match the first letter of a token and defines the function that gets called to tokenise it.<br />
<br />
For example if the tokeniser gets a semi-colon it calls the parseComment function which calls the parseRegex helper function. Below you can see these two methods. When a semi-colon is found the regex will match to the end of the line and the current position will be moved (moveRight) by the number of matched characters.<br />
<br />
<pre class="brush: clojure;">(defn parseRegex [state, typeName, token, re]
(let [s (subs (currentLine state) (:col state))
val (re-find re s)]
;Does the remainder of the line match the regex - it should!
(if val
(assoc
(moveRight state (count val))
:token token
:val val)
(throw (Exception. (str "Failed to parse " typeName))))))
(defn parseComment [state]
(parseRegex state "comment" :comment #";.*"))
</pre>
<br />
<br />
<h4>
Moving in the input stream</h4>
Below is the moveRight function which moves right in the input stream. Notice that this takes the current position in a state argument and returns a new state as a result. I.e. nothing is mutated.<br />
<br />
<pre class="brush: clojure;">(defn moveRight [state by]
"Move current position 1 char to the right, roll over to next line if required"
(let [updated (assoc state :col (+ (:col state) by) )]
(let [line (currentLine state)]
(if (< (:col updated) (count line))
;Still space on current line, return it
updated
;Move to next line
(assoc
state
:col 0
:line (inc (:line state)))))))
</pre>
<br />
<h4>
Running the tokeniser</h4>
<br />
Finally here are the two functions that control the tokenising<br />
<br />
<pre class="brush: clojure;">(defn- nextToken [state]
"Gets the next token"
(let [c (currentChar state)]
(cond
(nil? c) (clearToken state)
;Ignore white space
(Character/isSpaceChar c) (recur (moveRight state 1))
;Check if a token can be found in the token map by character
:else (if-let [token ((:byChar tokenMap) c)]
(assoc (moveRight state 1) :token token :val c)
;Nothing found so now search by regex
; Get the function associated with the first regex that matches and call that
(if-let [r (first (filter #(re-matches (% 0) (str c)) (:byRegex tokenMap)))]
((r 1) state)
(throw (Exception. (str "dont understand next token - " c state))))))))
(defn- tokenise [state]
(loop [nextState (nextToken state), tokens []]
(if (= :none (:token nextState))
tokens
(recur
(nextToken nextState)
(conj tokens {:line (:line nextState),
:col (:col nextState),
:val (:val nextState),:type (:token nextState)})))))
</pre>
<br />
<br />
<br />
nextToken gets 1 next token<br />
tokenise repeatedly calls nextToken until the whole input stream has been tokenised<br />
<br />
<h3>
Parsing</h3>
<br />
At this point the lexer has lexed the entire file and the parser can now parse the token stream.<br />
<br />
The function that runs the parser is parseAll<br />
<br />
<pre class="brush: clojure;">(defn- parseAll [allTokens]
(loop [expressions [], tokens allTokens]
(let [r (parseExpression (first tokens) (rest tokens))]
(if (= 0 (count (:expr r)))
expressions
(recur (conj expressions (:expr r)) (:tokens r))))))
</pre>
<br />
<br />
<br />
But all the work is actually done in parseExpression. This is quite a long function that is just a large case statement. Not pretty but reasonably clear, hopefully.<br />
<br />
<pre class="brush: clojure;">(defn- parseExpression [token tokens]
(case (:type token)
(nil '()) [nil tokens]
:name {:expr {:type :name,
:val (:val token),
:line (:line token),
:col (:col token),
:expressions []}
:tokens tokens}
:string {:expr {:type :string,
:val (:val token),
:line (:line token),
:col (:col token),
:expressions []}
:tokens tokens}
:number {:expr {:type :number,
:val (read-string (:val token)),
:line (:line token),
:col (:col token),
:expressions []}
:tokens tokens}
(:lparen :lbracket) (let [grp (if (= :lparen (:type token))
{:start :lparen, :end :rparen, :type :list}
{:start :lbracket, :end :rbracket, :type :vector})]
(loop [expressions []
[loopToken & loopTokens] tokens]
(let [type (:type loopToken)]
(cond
(or (nil? token) (= '() token)) (throw (Exception. (str "EOF waiting for :rparen")))
(= (:end grp) type) {:expr {:type (:type grp)
:val (:type grp)
:line (:line token)
:col (:col token)
:expressions expressions}
:tokens loopTokens}
:else (let [r (parseExpression loopToken loopTokens)]
(recur (conj expressions (:expr r)) (:tokens r)))))))))
</pre>
<br />
<br />
<br />
This function is switching on the first token and returning the matched token and the remaining tokens. For example when it gets a :name it returns an :expr of type :name and returns the rest of the tokens.<br />
<br />
When parseExpression gets lparen or lbracket it will recursively loop through the tokens until the end of the list or vector, returning the tokens that have not been consumed.<br />
<br />
<h3>
Interpreting the syntax tree</h3>
Evaluating the syntax tree is similar to the code in the instaparse Version<br />
<br />
<pre class="brush: clojure;">(declare eval)
(defmulti run (fn [x] (:type x)))
(defmethod run :string [e] (:val e))
(defmethod run :number [e] (:val e))
(defmethod run :name [e] (:val e))
(defmethod run :vector [e] (vec (map run (:expressions e))))
(defmethod run :list [e] (do
(let [f (run (first (:expressions e)))
args (map run (rest (:expressions e)))]
(apply (get funcs f) args))))
(defmethod run :default [e] (println "unknown: " e))
(defn eval [[car & cdr]]
(let [r (run car)]
(if (empty? cdr)
r
(recur cdr))))
</pre>
<br />
<br />
Again a multimethod is used to recursively evaluate the syntax tree and as with the instaparse code only functions defined in the 'funcs' map may be executed.<br />
<br />
<h3>
Conclusion - hand written</h3>
<br />
The hand written lexer and parser are a lot longer than just using instaparse. However it is not that complicated to do manually. Personally I'll be using instaparse for 99% of my Clojure DSL needs but it is always good to know how to do it manually.<br />
<br />
<h3>
The full source code</h3>
<pre class="brush: clojure;">(ns cljsexp-simple.core
(def funcs {"prn" println
"+" +})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn currentLine [state]
"Gets the current line"
(get (:code state) (:line state)))
(defn currentChar [state]
"Gets the current charater"
(get (currentLine state) (:col state)))
(defn moveRight [state by]
"Move current position 1 char to the right, roll over to next line if required"
(let [updated (assoc state :col (+ (:col state) by) )]
(let [line (currentLine state)]
(if (< (:col updated) (count line))
;Still space on current line, return it
updated
;Move to next line
(assoc
state
:col 0
:line (inc (:line state)))))))
(defn parseRegex [state, typeName, token, re]
(let [s (subs (currentLine state) (:col state))
val (re-find re s)]
;Does the remainder of the line match the regex - it should!
(if val
(assoc
(moveRight state (count val))
:token token
:val val)
(throw (Exception. (str "Failed to parse " typeName))))))
(defn parseName [state]
(parseRegex state "name" :name #"[a-zA-Z\+\-\*\\\/\?_\$\<\>=]+"))
(defn parseComment [state]
(parseRegex state "comment" :comment #";.*"))
(defn parseString [state]
(parseRegex state "string" :string #"'[^']+'"))
(defn parseNumber [state]
(parseRegex state "number" :number #"\d+"))
(def tokenMap {:byChar { \( :lparen
\) :rparen,
\[ :lbracket,
\] :rbracket}
:byRegex { #"'" parseString
#"\d+" parseNumber
#"[a-zA-Z\+\-\*\\\/\?_\$\<\>=]" parseName
#";" parseComment }})
(defn clearToken [state]
(assoc state
:token :none
:val :none))
(defn- nextToken [state]
"Gets the next token"
(let [c (currentChar state)]
(cond
(nil? c) (clearToken state)
;Ignore white space
(Character/isSpaceChar c) (recur (moveRight state 1))
;Check if a token can be found in the token map by character
:else (if-let [token ((:byChar tokenMap) c)]
(assoc (moveRight state 1) :token token :val c)
;Nothing found so now search by regex
; Get the function associated with the first regex that matches and call that
(if-let [r (first (filter #(re-matches (% 0) (str c)) (:byRegex tokenMap)))]
((r 1) state)
(throw (Exception. (str "dont understand next token - " c state))))))))
(defn- tokenise [state]
(loop [nextState (nextToken state), tokens []]
(if (= :none (:token nextState))
tokens
(recur
(nextToken nextState)
(conj tokens {:line (:line nextState),
:col (:col nextState),
:val (:val nextState),:type (:token nextState)})))))
(defn- parseExpression [token tokens]
(case (:type token)
(nil '()) [nil tokens]
:name {:expr {:type :name,
:val (:val token),
:line (:line token),
:col (:col token),
:expressions []}
:tokens tokens}
:string {:expr {:type :string,
:val (:val token),
:line (:line token),
:col (:col token),
:expressions []}
:tokens tokens}
:number {:expr {:type :number,
:val (read-string (:val token)),
:line (:line token),
:col (:col token),
:expressions []}
:tokens tokens}
(:lparen :lbracket) (let [grp (if (= :lparen (:type token))
{:start :lparen, :end :rparen, :type :list}
{:start :lbracket, :end :rbracket, :type :vector})]
(loop [expressions []
[loopToken & loopTokens] tokens]
(let [type (:type loopToken)]
(cond
(or (nil? token) (= '() token)) (throw (Exception. (str "EOF waiting for :rparen")))
(= (:end grp) type) {:expr {:type (:type grp)
:val (:type grp)
:line (:line token)
:col (:col token)
:expressions expressions}
:tokens loopTokens}
:else (let [r (parseExpression loopToken loopTokens)]
(recur (conj expressions (:expr r)) (:tokens r)))))))))
(defn- parseAll [allTokens]
(loop [expressions [], tokens allTokens]
(let [r (parseExpression (first tokens) (rest tokens))]
(if (= 0 (count (:expr r)))
expressions
(recur (conj expressions (:expr r)) (:tokens r))))))
(defn parse [code]
(let [tokens (tokenise {:code code, :line 0, :col 0, :val :none, :token :none})
result (parseAll tokens)]
result))
;;;;;;;;;;;;;;;;;;;;;;;
(declare eval)
(defmulti run (fn [x] (:type x)))
(defmethod run :string [e] (:val e))
(defmethod run :number [e] (:val e))
(defmethod run :name [e] (:val e))
(defmethod run :vector [e] (vec (map run (:expressions e))))
(defmethod run :list [e] (do
(let [f (run (first (:expressions e)))
args (map run (rest (:expressions e)))]
(apply (get funcs f) args))))
(defmethod run :default [e] (println "unknown: " e))
(defn eval [[car & cdr]]
(let [r (run car)]
(if (empty? cdr)
r
(recur cdr))))
</pre>
<br />
<br />Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-53083160705923892102014-02-13T12:55:00.002+02:002014-02-13T13:01:20.835+02:00Fixing assembly version conflicts in .net with AsmSpyOccasionally you will get assembly version conflicts when building / running .net projects. Here is a quick overview of how to fix<br />
<br />
<br />
<br />
<u><b>Using AsmSpy</b></u><br />
<br />
Using AsmSpy is the easiest way to find assembly version conflicts.<br />
<br />
Get it from: https://github.com/mikehadlow/AsmSpy <br />
<br />
Run it on the build output directory. E.g. <br />
AsmSpy c:\Projects\SomeProject\bin\Debug<br />
<br />
It will display a list of references and the assemblies that use them.<br />
<br />
<br />
For example<br />
<br />
Reference: log4net<br />
1.2.13.0 by ABCD<br />
1.2.13.0 by XYZ<br />
1.2.12.0 by EEE<br />
<br />
Here you can see that EEE is expecting a lower version of log4net that the rest of the assemblies. <br />
<br />
This makes it very easy to spot the errors. <u><b></b></u><br />
<br />
<br />
<u><b>Checking for errors manually</b></u><br />
<br />
You can also manually check for errors by looking at the output window after a build. Using AsmSpy is a lot easier though <br />
<br />
<br />
<br />Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-24266541867640485182013-08-11T11:57:00.001+02:002013-08-11T14:59:59.409+02:00Upgrading to ANTLR 4 with C#Upgrading from ANTLR 3.x to ANTLR 4 was pretty painless. Here are the changes I needed to make to get it all working<br />
<br />
<ol>
<li>Get ANTLR 4 from nuget (you will need to allow pre-release versions for now)</li>
<li>Change your ANTLR build script to reference the new ANTLR JAR. The JAR is included in the nuget. This what my build script looks like <br /> set classpath=C:\xxx\packages\Antlr4\tools\antlr4-csharp-4.1-SNAPSHOT-complete.jar <br /> java org.antlr.v4.Tool xxx.g4 -Dlanguage=CSharp_v4_0</li>
<li>Rename grammars from .g to .g4</li>
<li>Remove the options block or change it to match your C# version selection <br /> options <br /> { <br /> language=CSharp_v4_0; <br /> }</li>
<li>Tokens should be comma delimited not semicolon delimited</li>
<li>Always use a $ when referring to parameters, return variables, tokens etc. ANTLR 3.4 did not always enforce this so if you forgot it in a few places you will need to fix them</li>
<li>You can still check if a optional token exists just be sure to use the .ctx. E.g. use this <br /> if( $i.ctx != null )</li>
<li>You may get errors when using properties on matched tokens. Parentheses will fix this. E.g. <br /> ($i).Text</li>
<li>Use “-> skip” rather than “{$channel=HIDDEN}”</li>
<li>Use “.*?” rather than “options {greedy=false;}”</li>
</ol>
<br />
Hope this helps someoneAnonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-19071779754790009262013-05-28T08:27:00.001+02:002013-06-17T18:48:53.124+02:00Building for mono and Microsoft .NET<span style="font-family: inherit;">Recently I wanted to build my project on mono and Microsoft .NET. What I wanted to do was</span><br />
<ol>
<li><span style="font-family: inherit;">Build on windows using Microsoft .NET as per usual</span></li>
<li><span style="font-family: inherit;">Build using mono on windows</span></li>
<li><span style="font-family: inherit;">Build using mono on linux</span></li>
<li><span style="font-family: inherit;">Use nuget package restore on all three</span></li>
</ol>
<span style="font-family: inherit;">Getting this all working was not terribly difficult but I did have to do a fair amount of googling. Here is what I needed to do to hopefully this will save someone some time.</span><br />
<br />
<span style="font-family: inherit;"><u><b>Installing the latest version of mono</b></u></span><br />
<span style="font-family: inherit;"><u>Windows</u>: </span><br />
<ol>
<li><span style="font-family: inherit;">Download the 3.x from <a href="http://www.go-mono.com/mono-downloads/download.html" title="http://www.go-mono.com/mono-downloads/download.html">http://www.go-mono.com/mono-downloads/download.html</a></span></li>
<li><span style="font-family: inherit;">Create dmcs.bat in C:\Program Files (x86)\Mono-3.0.10\bin as this is missing in the latest download</span></li>
<ol>
<li><span style="font-family: inherit;">See <a href="https://bugzilla.xamarin.com/show_bug.cgi?id=8813" title="https://bugzilla.xamarin.com/show_bug.cgi?id=8813">https://bugzilla.xamarin.com/show_bug.cgi?id=8813</a></span></li>
<pre><span style="font-family: "Courier New",Courier,monospace;">REM dmcs.bat compatibility shim for mcs
@echo off
call mcs -sdk:4 %*</span></pre>
</ol>
</ol>
<span style="font-family: inherit;"><u>Linux</u>: </span><br />
<ol>
<li><span style="font-family: inherit;">See <a href="http://www.meebey.net/posts/mono_3.0_preview_debian_ubuntu_packages/" title="http://www.meebey.net/posts/mono_3.0_preview_debian_ubuntu_packages/">http://www.meebey.net/posts/mono_3.0_preview_debian_ubuntu_packages/</a></span></li>
<span style="font-family: inherit;"><br /></span>
<li><span style="font-family: inherit;">Add this line to your /etc/apt/sources.list file:<br /> <span style="font-family: "Courier New",Courier,monospace;"><code>deb http://debian.meebey.net/experimental/mono /</code></span></span></li>
<span style="font-family: inherit;"><br /></span>
<li><span style="font-family: inherit;"><code></code><span style="font-family: "Courier New",Courier,monospace;"><code>apt-get update</code></span></span></li>
<span style="font-family: "Courier New",Courier,monospace;"> </span>
<li><span style="font-family: "Courier New",Courier,monospace;"><code>apt-get install mono-complete</code></span></li>
<span style="font-family: inherit;"><br /></span></ol>
<span style="font-family: inherit;"><code></code></span><br />
<span style="font-family: inherit;"><code></code></span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><code><b><u>Creating a mono solution and projects</u></b></code></span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><code>Mono’s xbuild is not yet 100% compatible with msbuild. MonoDevelop is also not 100% compatible with the VS 2012 solution/project format. So to make my life easier I’ve written a simple C# script that generates mono solutions and projects from the VS2012 ones.</code></span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><code>This is pretty simple and works really well. It also means I can easily have separate output folders for the windows and mono binaries.</code></span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><code>Get the source from my gist at <a href="https://gist.github.com/andrevdm/5655285#file-updateprojectfileversion-cs" title="https://gist.github.com/andrevdm/5655285#file-updateprojectfileversion-cs">https://gist.github.com/andrevdm/5655285#file-updateprojectfileversion-cs</a></code></span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><code></code></span><br />
<span style="font-family: inherit;"><code></code></span><br />
<span style="font-family: inherit;"><code><b><u>Getting NuGet & package restore working on linux</u></b></code></span><br />
<ol>
<li><span style="font-family: inherit;"><code>Import the required certificates</code></span></li>
<ol>
<li><span style="font-family: inherit;"><code>See <a href="http://stackoverflow.com/questions/15181888/nuget-on-linux-error-getting-response-stream/16589218#comment24184791_16589218" title="http://stackoverflow.com/questions/15181888/nuget-on-linux-error-getting-response-stream/16589218#comment24184791_16589218">http://stackoverflow.com/questions/15181888/nuget-on-linux-error-getting-response-stream/16589218#comment24184791_16589218</a></code></span></li>
<li><div class="default prettyprint prettyprinted">
<code><span class="pln">sudo mozroots </span><span class="pun">--</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">--</span><span class="pln">machine </span><span class="pun">--</span><span class="pln">sync</span></code><span style="font-family: inherit;"> </span></div>
</li>
<li><span style="font-family: inherit;">sudo certmgr -ssl -m <a href="https://go.microsoft.com/">https://go.microsoft.com</a></span></li>
<li><span style="font-family: inherit;">sudo certmgr -ssl -m <a href="https://nugetgallery.blob.core.windows.net/">https://nugetgallery.blob.core.windows.net</a></span></li>
<li><span style="font-family: inherit;">sudo certmgr -ssl -m <a href="https://nuget.org/">https://nuget.org</a></span></li>
<span style="font-family: inherit;"><br /></span> </ol>
<li><span style="font-family: inherit;">Create a mono specific NuGet.target</span></li>
<ol><ol><span style="font-family: inherit;"><br /></span>
<li><span style="font-family: inherit;">See <a href="http://nuget.codeplex.com/SourceControl/changeset/view/0b1e224884a3#src/Build/NuGet.targets" title="http://nuget.codeplex.com/SourceControl/changeset/view/0b1e224884a3#src/Build/NuGet.targets">http://nuget.codeplex.com/SourceControl/changeset/view/0b1e224884a3#src/Build/NuGet.targets</a></span></li>
<span style="font-family: inherit;"><br /></span>
<li><span style="font-family: inherit;">Or use my version (minor modifications) that works with the project generator above <br /><a href="https://gist.github.com/andrevdm/5660800#file-nuget-mono-targets" title="https://gist.github.com/andrevdm/5660800#file-nuget-mono-targets">https://gist.github.com/andrevdm/5660800#file-nuget-mono-targets</a></span></li>
<span style="font-family: inherit;"><br /></span> </ol>
</ol>
</ol>
<span style="font-family: inherit;">There is also some great info on NuGet here</span><br />
<ol><span style="font-family: inherit;"><br /></span>
<li><span style="font-family: inherit;"><a href="http://www.lextm.com/2013/01/how-to-use-nuget-on-mono-part-i.html" title="http://www.lextm.com/2013/01/how-to-use-nuget-on-mono-part-i.html">http://www.lextm.com/2013/01/how-to-use-nuget-on-mono-part-i.html</a></span></li>
<li><span style="font-family: inherit;"><a href="http://www.lextm.com/2013/01/how-to-use-nuget-on-mono-part-ii.html" title="http://www.lextm.com/2013/01/how-to-use-nuget-on-mono-part-ii.html">http://www.lextm.com/2013/01/how-to-use-nuget-on-mono-part-ii.html</a></span></li>
<li><span style="font-family: inherit;"><a href="http://www.lextm.com/2013/02/debugging-on-mono-xbuild-issue.html" title="http://www.lextm.com/2013/02/debugging-on-mono-xbuild-issue.html">http://www.lextm.com/2013/02/debugging-on-mono-xbuild-issue.html</a></span></li>
<span style="font-family: inherit;"><br /></span></ol>
<span style="font-family: inherit;"><code></code></span><br />
<span style="font-family: inherit;"><code></code></span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><code><b><u>Build scripts</u></b></code></span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><code>Finally to wrap it all up here are build scripts for linux and windows</code></span><br />
<span style="font-family: inherit;"><code></code></span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><code><u>Linux</u></code></span><br />
<blockquote>
<span style="font-family: "Courier New",Courier,monospace;">#!/bin/bash</span><br />
<span style="font-family: "Courier New",Courier,monospace;">export EnableNuGetPackageRestore=true<br />mono ./makeMonoProjectsAndSln.exe MySolution.sln<br />xbuild /p:TargetFrameworkProfile="" MySolution.mono.sln</span><br />
<span style="font-family: inherit;"><br /></span></blockquote>
<span style="font-family: inherit;"><code></code></span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><code><u>Windows</u></code></span><br />
<blockquote>
<span style="font-family: "Courier New",Courier,monospace;">@echo off</span><br />
<span style="font-family: "Courier New",Courier,monospace;"><br /></span>
<span style="font-family: "Courier New",Courier,monospace;">makeMonoProjectsAndSln.exe MySolution.sln<br /> </span><span style="font-family: inherit;"><span style="font-family: "Courier New",Courier,monospace;"><br />"C:\Program Files (x86)\Mono-3.0.10\bin\xbuild.bat" /p:TargetFrameworkProfile="" MySolution.mono.sln</span></span> </blockquote>
Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-64682501804859069642013-03-29T21:18:00.001+02:002013-03-29T21:19:21.358+02:00Efficiently Tracking Response Time Percentiles (in C#)<p> </p> <p>When looking for a better way to track response times than a simple min/max/average statistic recently I found a great <a href="http://techblog.molindo.at/2009/11/efficiently-tracking-response-time-percentiles.html" target="_blank">article</a> that had a clever solution. This article shows how to efficiently track the n-th percentile performance while storing only a small amount of data. See the full original article here <a title="http://techblog.molindo.at/2009/11/efficiently-tracking-response-time-percentiles.html" href="http://techblog.molindo.at/2009/11/efficiently-tracking-response-time-percentiles.html">http://techblog.molindo.at/2009/11/efficiently-tracking-response-time-percentiles.html</a></p> <p>The original code is in Java but I needed it in .NET so I’ve created a <a href="https://github.com/andrevdm/PercentilePerformance" target="_blank">.net version on github</a> (<a title="https://github.com/andrevdm/PercentilePerformance" href="https://github.com/andrevdm/PercentilePerformance">https://github.com/andrevdm/PercentilePerformance</a>). I chose to do a complete rewrite rather than porting the Java code so the class names etc will be different. The idea however is the same.</p> <p>My .net version can generate output in three formats</p> <p>PNG</p> <blockquote> <p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEim4lh6iPFDcW9MA0wfT6h_vYDYC0RlW9-KRhsknVEukZ3Ceaj3EWujaOgKEKEzLV-ZcgGg2oz2uUoaGfn5U3VeKTB4uD-Gz7AbnrlwumepHjR6aKZht86AN4q-uhiSsfgM3x62-tEM6fKq/s1600-h/clip_image002%25255B6%25255D.gif"><img title="clip_image002" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="clip_image002" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgj_6OLScObBt-wSBrqnwdNGzwGIgM7JzLNX4jbS1jZD52kaCPsA2gRbZ-NNcW2Mz31-3q_lhSKOEnnyS3oyZFUsqPovAOAJr0_XFPUBb_y2traXTVvdNe8t1mKBAmLfYqbL3wvmsRRd0XY/?imgmax=800" width="216" height="305" /></a></p> </blockquote> <p>HTML</p> <blockquote> <p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8g00C0x61bxINAaMhiTBB0m6PUm__HcxRgabmauonZW9ICRBFkJgVo4pNt2-WVRKh5HTAk7AoorINgRcN5j89pTlsNJkmMGXbFZeXKSyw9oVcrSPRIB89sErlN3v_KNSF_5MPUmVhsbg4/s1600-h/image%25255B3%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaSZGbXJWGU65uR1YX2PiB5n2-4-bzzY58jwlL6-4rgDSQP6hJBx9XDli8QY9h69hCdz6Sp0fQ_GP6IHbv647J1tbc9qgS0jEL5cqkTpb-BgKJtddRsocsfDW1yxUYMSvOCeg4FVs0DzSP/?imgmax=800" width="465" height="332" /></a></p> </blockquote> <p> </p> <p>Text</p> <blockquote> <p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZIe_1Sp0Y88FkYP1FIlNjfVZMXrJms-q_c3quCbHtaIjKQJAibQXpk9x-1inCDS8wdoeAgrxxnmnVUWnHHuCSCG7GnSqcC561anau8o_x4XGV1YKJ7C3asouis61yk_hdoi3P6RJfcRHO/s1600-h/image%25255B7%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmaU6VKt6wuF4El00ncorPszVyoQCDcjlxXwT4y-edzO20mcZH1oQGOdIKZVG8JVRTOn4JJX-q4KgK2IYGrifYMStL7F9QAMg-8Y7byQ0Yggw4CItNyiAtw3vOv7-mmHfRAD546GbSjMdb/?imgmax=800" width="536" height="408" /></a></p> </blockquote> <p> </p> <p>I hope this proves useful to someone.</p> Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-42052671940373515942013-02-25T21:36:00.001+02:002013-02-25T21:38:24.102+02:00Learning AngularJs<p> </p> <p>I recently had to build a simple HTML application and decided to use AngularJS. I’ve used KnockOut in the past and found it easy to use. AngularJS is a little more opinionated than KO so there is a bit more that needs to be done to get a basic app working. However it is still simple and easy to follow and the end result definitely justifies the tiny bit of extra work.</p> <p>Where I did have a problem was in understanding how the change tracking works. In KnockOut it is very clear, observables do all of the work and observables are easy to understand. In AngularJS there are no observables and everything works like “magic”. This so far has been my biggest issue with AngularJS. I find it hard to simply follow a set of rules without any understanding of the reasoning behind them. When I started building a slightly more complicated application it refused to work correctly and I could not work out why with the “magic” explanation.</p> <p>After a little bit of searching I was able to come up with an explanation of how it worked and based on that some guidelines for structuring my app. The end result was very impressive. The code was simple, I had separated concerns and everything just worked. Overall I’ll definitely be using AngularJS more.</p> <p> </p> <p><strong><u>Pushing back the magic</u></strong></p> <p>Firstly AngularJS uses dirty tracking rather than observables. It will periodically scan you scope variables to see if their values have changed from the previous scan and if they have it will then updated the UI and fire the change events. That is it, pretty simple actually.</p> <p>The other thing that greatly simplified my application was to use broadcast messages rather than trying to share things across the $rootScope. This allowed me to have controllers that are completely separate. Interaction between the controllers is always event-based. This also avoids complexities that arise when you are going across scopes and having to check what phase angular is in</p> <p> </p> <p><strong><u>Example</u></strong></p> <p>As a simple example I’ll show how I structured a simple tabbed interface. As this is not a post about angular binding but rather about the JS structure I’ve just bound directly to JSON text</p> <p>This is what the UI looks like. There are two buttons that represent the tab pages and an area for the tab content.</p> <blockquote> <p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj728SmviUBJkmFWEqOHxNAGWWXc8VNU2JPUDeoOyB4M5Pc9EbiZqn077mOxPNFPjpnkD3x3Zq9ynpUxM9t-YT0hGohxlpbfgI4ytH47CQP3Mq8boQq-G2uU6sbEXHLNeKwsiOp2KTF46pe/s1600-h/image%25255B2%25255D.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRXp2ZkqS48_TWuARn1e75I3Izs-xFDVYieRWuj_Ht3oiU1M5bC6dYohgK5aNV3YOG8ntejPj9uM9b7kRAsgTn10G_wRPZj62oCXy7GoMbRgpiNdqxovYyTdPAsIdPaau-3yFD9VEsRwhg/?imgmax=800" width="201" height="73" /></a></p> </blockquote> <p> </p> <p>Here is overall structure. Red arrows show the controllers responsible for each section of the view. Green arrows show the message broadcasting.</p> <p> </p> <p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijPDfETK3OTg5_ppKjliTjg6og9ue3FtuwLCc8SYPVViAF2Ghegtp-FigPQksUIlFdthi1NtmkNpybXQ40GSMdjn9dNlbYh_pROpYwUMghochXgzcrAKAKSBFZpKjj7ymnemBZlgYKa0gZ/s1600-h/image%25255B6%25255D.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGrH4mQuaNQUzTIEoIEdHf6SSAWSnTAu9Mz2mLgeEASaQbV6AjwxEbt07i_mYP2eDZqa1M63_SPiOghM-0gJZhMza5ypjOQLjQ7f6WN3SZO8ovi6AxOckoTxbQiWhazzS48pNxk3yTKC2r/?imgmax=800" width="1318" height="764" /></a></p> <p> </p> <p>The code for the HTML page is straightforward</p> <pre class="brush: js; smart-tabs: false;"><!doctype html><br /><html ng-app="PerformanceApp"><br /><head><br /> <script src="angular.min.js"></script><br /> <script src="PerformanceApp.js"></script><br /> <script src="NodePerformanceCtrl.js"></script><br /> <script src="NodesCtrl.js"></script><br /> <br /> <link href="cluster.css" rel="stylesheet" type="text/css" /> <br /><br /> <title>Cluster overview</title><br /></head><br /><body><br /> <div class="tabs"><br /> <button ng-click="CurrentTab='Nodes'" ng-class="{'selTab': CurrentTab=='Nodes', 'unSelTab': CurrentTab!='Nodes'}">Nodes</button><br /> <button ng-click="CurrentTab='NodePerformance'" ng-class="{'selTab': CurrentTab=='NodePerformance', 'unSelTab': CurrentTab!='NodePerformance'}">Performance</button><br /> </div><br /><br /> <div class="tab" ng-controller="NodesCtrl" ng-show="CurrentTab=='Nodes'"><br /> <div class="tabHeader">Nodes</div><br /> <span class="tabBody"><br /> <pre>{{Nodes|json}}</pre><br /> </span><br /> </div><br /><br /> <div class="tab" ng-controller="NodePerformanceCtrl" ng-show="CurrentTab=='NodePerformance'"><br /> <div class="tabHeader">Performance</div><br /> <span class="tabBody"><br /> <pre>{{Performance|json}}</pre><br /> </span><br /> </div><br /><br /></body><br /></html></pre><br /><br /><p> </p><br /><br /><p>The “tab” buttons show or hide sections by setting the value of the CurrentTab scope variable. The “tabs” are just divs each with its own controller.</p><br /><br /><p>This is the application initialisation code</p><br /><br /><pre class="brush: js; smart-tabs: false;">var app = angular.module('PerformanceApp', []);<br /><br />app.run( function( $timeout, $http, $rootScope ){<br /> $rootScope.CurrentTab = "Nodes";<br /><br /> var machines = [<br /> {"Name": "m1", "IP": "127.0.0.1"},<br /> {"Name": "m2", "IP": "127.0.0.2"}<br /> ];<br /><br /> $timeout( <br /> function(){ <br /> $rootScope.$broadcast( 'machinesUpdated', machines ); <br /> }, <br /> 500 );<br /><br />} );</pre><br /><br /><p>Again nice and simple. On app.run </p><br /><br /><ol><br /> <li>the default tab is set</li><br /><br /> <li>A dummy list of machines is created. In the real app this is fetched using ajax</li><br /><br /> <li>A broadcast message is schedule in 500ms </li><br /></ol><br /><br /><p> </p><br /><br /><p>The controllers then respond to the broadcast and update their local scope variables</p><br /><br /><pre class="brush: js; smart-tabs: false;">function NodePerformanceCtrl($scope,$http,$timeout) {<br /> $scope.Performance = {};<br /><br /> $scope.$on( "machinesUpdated", function( event, args ){<br /> for( var m in args ){<br /> var machine = args[m];<br /><br /> $scope.Performance[machine.Name] = machine.IP;<br /> }<br /><br /> } )<br />}</pre><br /><br /><p> </p><br /><br /><p>Though this is a trivial example it does show how AngularJS helps you layout your application. It should also illustrate how using $broadcast messages help keep your controllers separated.</p> Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com1tag:blogger.com,1999:blog-2108758405551517278.post-8724656092748021752012-01-08T13:00:00.001+02:002012-01-08T20:52:47.292+02:00WPF Layered Drawing<p> <br />This post demonstrates a simple way to draw in WPF using multiple layers. The layered drawing classes are less than 100 lines in total and fairly simple. Below I’ll describe the demo app, layered drawing and then how the code for the layered drawing works.</p> <p> </p> <p><u><strong><font size="4">Retained mode drawing</font></strong></u></p> <p>Drawing in WPF is very different from drawing in windows forms. Drawing in windows forms is done in <em>immediate mode,</em> drawing in WPF is done in <em>retained mode</em>. What this means is that in WPF once you have drawn a visual that visual knows how to redraw itself. You don’t need to redraw it every time some part of the display changes, WPF does it for you. You should not be doing your drawing in OnRender, the WPF equivalent of OnDraw, rather you should be drawing visuals that are then only redrawn when required. </p> <p>As a practical example of how this makes things easier consider an application that draws graphics in multiple layers. In the application shown below there are multiple layers; one for the text, one for the background etc. </p> <blockquote> <p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEis87t3y0jh6Hb5INyBlRFPb5kzw_8IUXSELmyC0y879jXL1x-1sR37_DQePW7vkfEpWrqu6a-KXsZio0AV7LkzSm1Fjq6NxvDZdo5ogwxvAaXlvFmdOO4wGyL0NkUK93GjGQpqxAsWiWNx/s1600-h/image5.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0129bso04SzQ_Y9uUsSYuSA7bgu2PPvOtmTAX9tGPWIxgWgwnhZlVn5jKBX310zGlEqRGnwz4bEOWmKwbFEnKdGduauij9GWYW91c_MTwZNl5CFXEH_q9AlHc0JKd3Tv9O5KY_d6Et4w2/?imgmax=800" width="442" height="211" /></a></p> </blockquote> <p>Scrolling moves the text up/down</p> <blockquote> <p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXgo5jBtnnUOxMop9kpeXGGB0uIcPbPLpQMFU6465s0zkjl2yrOk1KYO1txoG25tySIh708MV1e_9S8N_fuhO-biVO5gKAphQ7VQwkRbMj6XQgI5kCclRzUzGeshb-x8dGtYM2IAEpoC0L/s1600-h/image9.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEih_4h7NCuSyC8w_VWbduu9rYwc6rohAnsrUiKjLoP4fnG8-mT8KZYYJXd5Uq1HFEihVJNTqHKOHxDZhyeHoAjpCNr-A1A6PX9Yw6NJSrGwDjQsce5hOvpHoRW3Kt35m0v-sx3IrK1QN_4K/?imgmax=800" width="383" height="223" /></a></p> </blockquote> <p>Since each element is drawn in a separate layer, to move the the text down all you do is redraw the text layer. All the other layers remain unchanged and thus don’t need to be redrawn manually.</p> <p> </p> <p> </p> <p><strong><u><font size="3">Drawing on the layers</font></u></strong></p> <p>The layered drawing classes presented here make it easy to create a UI like this. Here is an example of creating a layer</p> <blockquote> <p><span class="lnum">1: </span>m_layers.AddLayer( 10, DrawBackground, ChangeType.Resize );</p> </blockquote> <p>Here a layer is created. Its priority (discussed later) is set to 10. DrawBackground is the method responsible for drawing the layer being registered. Resize is the ChangeType that will cause the layer to be redrawn.</p> <p>Here is the DrawBackground method</p> <pre class="brush: csharp;">private void DrawBackground( DrawingContext ctx ) <br />{ <br /> var pen = new Pen( Brushes.Black, 1 ); <br /> var rect = new Rect( 0, 0, m_layers.ActualWidth, m_layers.ActualHeight ); <br /> ctx.DrawRoundedRectangle( Brushes.Black, pen, rect, 50, 50 ); </pre><br /><br /><p>Now that that is in place having the layer redrawn when the window resized is easy</p><br /><br /><pre class="brush: csharp;">protected override void OnRenderSizeChanged( SizeChangedInfo sizeInfo )<br />{<br /> base.OnRenderSizeChanged( sizeInfo );<br /><br /> Draw( ChangeType.Resize );<br />}</pre><br /><br /><p>What you need to do is call the Draw method with the appropriate ChangeType, here ChangeType.Resize. <style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> margin: 0em<br />}<br />.csharpcode .rem {<br /> color: #008000<br />}<br />.csharpcode .kwrd {<br /> color: #0000ff<br />}<br />.csharpcode .str {<br /> color: #006080<br />}<br />.csharpcode .op {<br /> color: #0000c0<br />}<br />.csharpcode .preproc {<br /> color: #cc6633<br />}<br />.csharpcode .asp {<br /> background-color: #ffff00<br />}<br />.csharpcode .html {<br /> color: #800000<br />}<br />.csharpcode .attr {<br /> color: #ff0000<br />}<br />.csharpcode .alt {<br /> background-color: #f4f4f4; margin: 0em; width: 100%<br />}<br />.csharpcode .lnum {<br /> color: #606060<br />}</style></p><br /><br /><p>The layer registration and change notification scheme allows you to decouple the drawing logic from the events that cause changes to the UI. So should you later want to add another layer that is also redrawn when the window is resized, then you just add a new layer registration. The code in the OnRenderSizeChanged method does not change at all. In a real application you would use this separation to keep the events and drawing logic separate (model vs view or separate layers managed by different classes)</p><br /><br /><p>To complete the discussion of the demo application here is the full layer registration</p><br /><br /><pre class="brush: csharp;">private void OnLoaded( object sender, RoutedEventArgs e )<br />{<br /> m_layers.AddLayer( 10, DrawBackground, ChangeType.Resize );<br /> m_layers.AddLayer( 11, DrawBackgroundBlock );<br /><br /> m_layers.AddLayer( 20, DrawStaticForeground );<br /> m_layers.AddLayer( 21, DrawText, ChangeType.Scroll );<br /><br /> m_layers.AddLayer( 30, DrawForeground );</pre><br /><br /><p>And here is the OnScroll handler.</p><br /><br /><pre class="brush: csharp;">private void OnScroll( object sender, ScrollEventArgs e )<br />{<br /> Draw( ChangeType.Scroll );<br />}</pre><br /><style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> margin: 0em<br />}<br />.csharpcode .rem {<br /> color: #008000<br />}<br />.csharpcode .kwrd {<br /> color: #0000ff<br />}<br />.csharpcode .str {<br /> color: #006080<br />}<br />.csharpcode .op {<br /> color: #0000c0<br />}<br />.csharpcode .preproc {<br /> color: #cc6633<br />}<br />.csharpcode .asp {<br /> background-color: #ffff00<br />}<br />.csharpcode .html {<br /> color: #800000<br />}<br />.csharpcode .attr {<br /> color: #ff0000<br />}<br />.csharpcode .alt {<br /> background-color: #f4f4f4; margin: 0em; width: 100%<br />}<br />.csharpcode .lnum {<br /> color: #606060<br />}</style><style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> margin: 0em<br />}<br />.csharpcode .rem {<br /> color: #008000<br />}<br />.csharpcode .kwrd {<br /> color: #0000ff<br />}<br />.csharpcode .str {<br /> color: #006080<br />}<br />.csharpcode .op {<br /> color: #0000c0<br />}<br />.csharpcode .preproc {<br /> color: #cc6633<br />}<br />.csharpcode .asp {<br /> background-color: #ffff00<br />}<br />.csharpcode .html {<br /> color: #800000<br />}<br />.csharpcode .attr {<br /> color: #ff0000<br />}<br />.csharpcode .alt {<br /> background-color: #f4f4f4; margin: 0em; width: 100%<br />}<br />.csharpcode .lnum {<br /> color: #606060<br />}</style><br /><br /><p>And that is it. Using layers like this is pretty simple and it is very efficient as is uses DrawingVisuals. Drawing in this manner can be used for many purposes, for example to build a text editor control. You could have a layer for the text, one for the gutters & margins and one for the selection highlight.</p><br /><br /><p>The source code for the demo app is attached. Hopefully you will find this method useful.</p><br /><br /><p>Below is a description of how the layered drawing is implemented.</p><br /><br /><p><strong><u><font size="3"></font></u></strong></p><br /><br /><p><strong><u><font size="3">Implementation</font></u></strong></p><br /><br /><p>The ChangeType enum is used to describe different types of changes. The exact values used would depend on your application.</p><br /><br /><pre class="brush: csharp;">[Flags]<br />public enum ChangeType<br />{<br /> Redraw = 1,<br /> Resize = 2,<br /> Scroll = 4,<br />}</pre><br /><style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> margin: 0em<br />}<br />.csharpcode .rem {<br /> color: #008000<br />}<br />.csharpcode .kwrd {<br /> color: #0000ff<br />}<br />.csharpcode .str {<br /> color: #006080<br />}<br />.csharpcode .op {<br /> color: #0000c0<br />}<br />.csharpcode .preproc {<br /> color: #cc6633<br />}<br />.csharpcode .asp {<br /> background-color: #ffff00<br />}<br />.csharpcode .html {<br /> color: #800000<br />}<br />.csharpcode .attr {<br /> color: #ff0000<br />}<br />.csharpcode .alt {<br /> background-color: #f4f4f4; margin: 0em; width: 100%<br />}<br />.csharpcode .lnum {<br /> color: #606060<br />}</style><style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> margin: 0em<br />}<br />.csharpcode .rem {<br /> color: #008000<br />}<br />.csharpcode .kwrd {<br /> color: #0000ff<br />}<br />.csharpcode .str {<br /> color: #006080<br />}<br />.csharpcode .op {<br /> color: #0000c0<br />}<br />.csharpcode .preproc {<br /> color: #cc6633<br />}<br />.csharpcode .asp {<br /> background-color: #ffff00<br />}<br />.csharpcode .html {<br /> color: #800000<br />}<br />.csharpcode .attr {<br /> color: #ff0000<br />}<br />.csharpcode .alt {<br /> background-color: #f4f4f4; margin: 0em; width: 100%<br />}<br />.csharpcode .lnum {<br /> color: #606060<br />}</style><br /><br /><p>The WpfLayerInfo class stores the details for each layer.</p><br /><br /><pre class="brush: csharp;">public class WpfLayerInfo<br />{<br /> public WpfLayerInfo( int priority, Action<DrawingContext> draw, DrawingVisual visual, ChangeType notifyOnChange )<br /> {<br /> NotifyOnChange = notifyOnChange;<br /> Priority = priority;<br /> Visual = visual;<br /> Draw = draw;<br /> }<br /><br /> public ChangeType NotifyOnChange { get; private set; }<br /> public int Priority { get; private set; }<br /> public DrawingVisual Visual { get; private set; }<br /> public Action<DrawingContext> Draw { get; private set; }<br />}</pre><br /><style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> margin: 0em<br />}<br />.csharpcode .rem {<br /> color: #008000<br />}<br />.csharpcode .kwrd {<br /> color: #0000ff<br />}<br />.csharpcode .str {<br /> color: #006080<br />}<br />.csharpcode .op {<br /> color: #0000c0<br />}<br />.csharpcode .preproc {<br /> color: #cc6633<br />}<br />.csharpcode .asp {<br /> background-color: #ffff00<br />}<br />.csharpcode .html {<br /> color: #800000<br />}<br />.csharpcode .attr {<br /> color: #ff0000<br />}<br />.csharpcode .alt {<br /> background-color: #f4f4f4; margin: 0em; width: 100%<br />}<br />.csharpcode .lnum {<br /> color: #606060<br />}</style><br /><br /><p><strong><u><font size="3">Layers in WPF</font></u></strong></p><br /><br /><p>Building the layers in WPF is relatively strait forward. Each layer is a <a title="MSDN - DrawingVisual" href="http://msdn.microsoft.com/en-us/library/ms742254.aspx" target="_blank">DrawingVisual</a> and the layers are contained in a FrameworkElement user control. You then override the GetVisualChild() method to return a layer’s visual.</p><br /><br /><pre class="brush: csharp;">public class WpfLayers : FrameworkElement<br />{<br /> private readonly VisualCollection m_children;<br /> private readonly List<WpfLayerInfo> m_layers = new List<WpfLayerInfo>();<br /><br /> public WpfLayers()<br /> {<br /> m_children = new VisualCollection( this );<br /> }<br /><br /> public void AddLayer( ... )<br /> {<br /> var drawingVisual = new DrawingVisual();<br /><br /> ...<br /> <br /> m_children.Add( l.Visual );<br /> }<br /><br /> protected override int VisualChildrenCount<br /> {<br /> get { return m_children.Count; }<br /> }<br /><br /> protected override Visual GetVisualChild( int index )<br /> {<br /> if( index < 0 || index >= m_children.Count )<br /> {<br /> throw new ArgumentOutOfRangeException( "index" );<br /> }<br /><br /> return m_children[index];<br /> }<br />}</pre><br /><style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode, .csharpcode pre<br />{<br /> font-size: small;<br /> color: black;<br /> font-family: consolas, "Courier New", courier, monospace;<br /> background-color: #ffffff;<br /> /*white-space: pre;*/<br />}<br />.csharpcode pre { margin: 0em; }<br />.csharpcode .rem { color: #008000; }<br />.csharpcode .kwrd { color: #0000ff; }<br />.csharpcode .str { color: #006080; }<br />.csharpcode .op { color: #0000c0; }<br />.csharpcode .preproc { color: #cc6633; }<br />.csharpcode .asp { background-color: #ffff00; }<br />.csharpcode .html { color: #800000; }<br />.csharpcode .attr { color: #ff0000; }<br />.csharpcode .alt <br />{<br /> background-color: #f4f4f4;<br /> width: 100%;<br /> margin: 0em;<br />}<br />.csharpcode .lnum { color: #606060; }</style><style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> margin: 0em<br />}<br />.csharpcode .rem {<br /> color: #008000<br />}<br />.csharpcode .kwrd {<br /> color: #0000ff<br />}<br />.csharpcode .str {<br /> color: #006080<br />}<br />.csharpcode .op {<br /> color: #0000c0<br />}<br />.csharpcode .preproc {<br /> color: #cc6633<br />}<br />.csharpcode .asp {<br /> background-color: #ffff00<br />}<br />.csharpcode .html {<br /> color: #800000<br />}<br />.csharpcode .attr {<br /> color: #ff0000<br />}<br />.csharpcode .alt {<br /> background-color: #f4f4f4; margin: 0em; width: 100%<br />}<br />.csharpcode .lnum {<br /> color: #606060<br />}</style><br /><br /><p>The code above illustrates the main points, there is a FrameworkElement with a VisualCollection (m_children) containing DrawingVisuals. Each DrawingVisual represents a layer. m_layers is a list of WpfLayerInfo objects each describing a layer.</p><br /><br /><p>Adding a layer is handled by the AddLayer method</p><br /><br /><pre class="brush: csharp;">public void AddLayer( int priority, Action<DrawingContext> draw, ChangeType notifyOnChange = ChangeType.Redraw )<br />{<br /> var drawingVisual = new DrawingVisual();<br /><br /> var layerInfo = new WpfLayerInfo( priority, draw, drawingVisual, notifyOnChange );<br /> m_layers.Add( layerInfo );<br /><br /> //Sort the layers by priority<br /> m_layers.Sort( ( x, y ) => x.Priority.CompareTo( y.Priority ) );<br /><br /> //Remove all the visual layers and add them in order<br /> m_children.Clear();<br /> m_layers.ForEach( l => m_children.Add( l.Visual ) );<br />}</pre><br /><style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> margin: 0em<br />}<br />.csharpcode .rem {<br /> color: #008000<br />}<br />.csharpcode .kwrd {<br /> color: #0000ff<br />}<br />.csharpcode .str {<br /> color: #006080<br />}<br />.csharpcode .op {<br /> color: #0000c0<br />}<br />.csharpcode .preproc {<br /> color: #cc6633<br />}<br />.csharpcode .asp {<br /> background-color: #ffff00<br />}<br />.csharpcode .html {<br /> color: #800000<br />}<br />.csharpcode .attr {<br /> color: #ff0000<br />}<br />.csharpcode .alt {<br /> background-color: #f4f4f4; margin: 0em; width: 100%<br />}<br />.csharpcode .lnum {<br /> color: #606060<br />}</style><br /><br /><p>This method creates a new DrawingVisual and a WpfLayerInfo for the new layer. The visuals are then added to the VisualCollection in order of priority. I.e lowest priority at the bottom of the Z-order.</p><br /><br /><p>The Draw method which controls what layers will be redrawn</p><br /><br /><pre class="brush: csharp;">public void Draw( ChangeType change )<br />{<br /> var affected = from l in m_layers<br /> where ((change & ChangeType.Redraw) != 0) || ((l.NotifyOnChange & change) != 0)<br /> orderby l.Priority<br /> select l;<br /><br /> foreach( WpfLayerInfo layer in affected )<br /> {<br /> DrawingContext ctx = layer.Visual.RenderOpen();<br /> layer.Draw( ctx );<br /> ctx.Close();<br /> }<br />}</pre><br /><style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> margin: 0em<br />}<br />.csharpcode .rem {<br /> color: #008000<br />}<br />.csharpcode .kwrd {<br /> color: #0000ff<br />}<br />.csharpcode .str {<br /> color: #006080<br />}<br />.csharpcode .op {<br /> color: #0000c0<br />}<br />.csharpcode .preproc {<br /> color: #cc6633<br />}<br />.csharpcode .asp {<br /> background-color: #ffff00<br />}<br />.csharpcode .html {<br /> color: #800000<br />}<br />.csharpcode .attr {<br /> color: #ff0000<br />}<br />.csharpcode .alt {<br /> background-color: #f4f4f4; margin: 0em; width: 100%<br />}<br />.csharpcode .lnum {<br /> color: #606060<br />}</style><br /><br /><p>This method first gets the layers that need to be redrawn (lines 3 to 6) ordered by priority. ChangeType.Redraw is treated as a special case, if it is selected then all layers are selected (i.e. to be redrawn)</p><br /><br /><p>Next for each selected layer a DrawingContext is created and passed to the layer’s draw method.</p><br /><br /><p><strong><u><font size="3">The Demo</font></u></strong></p><br /><br /><p>The XAML for the demo looks like this</p><br /><br /><pre class="brush: xml;"><Window x:Class="LayeredDrawingDemo.MainWindow"<br /> xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"<br /> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"<br /> xmlns:LayeredDrawingDemo="clr-namespace:LayeredDrawingDemo" <br /> Title="Layered Drawing" <br /> Loaded="OnLoaded"<br /> Height="350" <br /> Width="500"><br /> <DockPanel LastChildFill="true"><br /> <TextBlock Name="m_log" DockPanel.Dock="Right" Width="150" Margin="5"/><br /> <ScrollBar Name="m_scroll" DockPanel.Dock="Right" Scroll="OnScroll" /><br /> <LayeredDrawingDemo:WpfLayers x:Name="m_layers" DockPanel.Dock="Left"></LayeredDrawingDemo:WpfLayers><br /> </DockPanel><br /></Window></pre><br /><br /><p><style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> margin: 0em<br />}<br />.csharpcode .rem {<br /> color: #008000<br />}<br />.csharpcode .kwrd {<br /> color: #0000ff<br />}<br />.csharpcode .str {<br /> color: #006080<br />}<br />.csharpcode .op {<br /> color: #0000c0<br />}<br />.csharpcode .preproc {<br /> color: #cc6633<br />}<br />.csharpcode .asp {<br /> background-color: #ffff00<br />}<br />.csharpcode .html {<br /> color: #800000<br />}<br />.csharpcode .attr {<br /> color: #ff0000<br />}<br />.csharpcode .alt {<br /> background-color: #f4f4f4; margin: 0em; width: 100%<br />}<br />.csharpcode .lnum {<br /> color: #606060<br />}</style>The layers are added on line 12.</p><br /><br /><p>As shown above the OnLoad handler creates the layers and redraws all layers</p><br /><br /><pre class="brush: csharp;">private void OnLoaded( object sender, RoutedEventArgs e )<br />{<br /> m_layers.AddLayer( 10, DrawBackground, ChangeType.Resize );<br /> m_layers.AddLayer( 11, DrawBackgroundBlock );<br /><br /> m_layers.AddLayer( 20, DrawStaticForeground );<br /> m_layers.AddLayer( 21, DrawText, ChangeType.Scroll );<br /><br /> m_layers.AddLayer( 30, DrawForeground );<br /><br /> Draw( ChangeType.Redraw );<br />}</pre><br /><br /><pre class="brush: xml;">The window resize handler which causes the DrawBackground method to be called (ChangeType.Resize).</pre><br /><br /><pre class="brush: csharp;">protected override void OnRenderSizeChanged( SizeChangedInfo sizeInfo )<br />{<br /> base.OnRenderSizeChanged( sizeInfo );<br /><br /> m_scroll.Minimum = 0;<br /> m_scroll.Maximum = m_layers.ActualHeight - 70;<br /> Draw( ChangeType.Resize );<br />}</pre><br /><br /><p><style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> margin: 0em<br />}<br />.csharpcode .rem {<br /> color: #008000<br />}<br />.csharpcode .kwrd {<br /> color: #0000ff<br />}<br />.csharpcode .str {<br /> color: #006080<br />}<br />.csharpcode .op {<br /> color: #0000c0<br />}<br />.csharpcode .preproc {<br /> color: #cc6633<br />}<br />.csharpcode .asp {<br /> background-color: #ffff00<br />}<br />.csharpcode .html {<br /> color: #800000<br />}<br />.csharpcode .attr {<br /> color: #ff0000<br />}<br />.csharpcode .alt {<br /> background-color: #f4f4f4; margin: 0em; width: 100%<br />}<br />.csharpcode .lnum {<br /> color: #606060<br />}</style></p><br /><br /><p>The scroll handler</p><br /><br /><pre class="brush: csharp;">private void OnScroll( object sender, ScrollEventArgs e )<br />{<br /> Draw( ChangeType.Scroll );<br />}</pre><br /><br /><p>The Draw() method</p><br /><br /><pre class="brush: csharp;">private void Draw( ChangeType change )<br />{<br /> m_layers.Draw( change );<br />}</pre><br /><style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> margin: 0em<br />}<br />.csharpcode .rem {<br /> color: #008000<br />}<br />.csharpcode .kwrd {<br /> color: #0000ff<br />}<br />.csharpcode .str {<br /> color: #006080<br />}<br />.csharpcode .op {<br /> color: #0000c0<br />}<br />.csharpcode .preproc {<br /> color: #cc6633<br />}<br />.csharpcode .asp {<br /> background-color: #ffff00<br />}<br />.csharpcode .html {<br /> color: #800000<br />}<br />.csharpcode .attr {<br /> color: #ff0000<br />}<br />.csharpcode .alt {<br /> background-color: #f4f4f4; margin: 0em; width: 100%<br />}<br />.csharpcode .lnum {<br /> color: #606060<br />}</style><br /><br /><p>One of he draw methods</p><br /><br /><pre class="brush: csharp;">private void DrawForeground( DrawingContext ctx )<br />{<br /> var pen = new Pen( Brushes.Black, 1 );<br /> var rect = new Rect( 20, 20, 50, 55 );<br /> ctx.DrawRectangle( Brushes.Red, pen, rect );<br /><br /> Log( "foreground" );<br />}</pre><br /><br /><p><style type="text/css"><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />.csharpcode {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> background-color: #ffffff; font-family: consolas, "Courier New", courier, monospace; color: black; font-size: small<br />}<br />.csharpcode pre {<br /> margin: 0em<br />}<br />.csharpcode .rem {<br /> color: #008000<br />}<br />.csharpcode .kwrd {<br /> color: #0000ff<br />}<br />.csharpcode .str {<br /> color: #006080<br />}<br />.csharpcode .op {<br /> color: #0000c0<br />}<br />.csharpcode .preproc {<br /> color: #cc6633<br />}<br />.csharpcode .asp {<br /> background-color: #ffff00<br />}<br />.csharpcode .html {<br /> color: #800000<br />}<br />.csharpcode .attr {<br /> color: #ff0000<br />}<br />.csharpcode .alt {<br /> background-color: #f4f4f4; margin: 0em; width: 100%<br />}<br />.csharpcode .lnum {<br /> color: #606060<br />}</style>Notice that each of the draw methods in this example log that they have been called. This makes it easy to see from the demo UI which layers are being changes. E.g. when you resize the window you will see that the text is not redrawn etc.</p><br /><br /><p>The source code for the example is available <a title="github" href="https://github.com/andrevdm/WpfLayeredDrawingDemo" target="_blank">on github</a> or <a title="Demo source code" href="https://docs.google.com/open?id=0B7OoQauShnh-ZGQ2OWY0YzEtYjhlNy00ODViLTkxYmEtMmRjMDIzNDY4NzVk" target="_blank">as a zip</a> . Hopefully you will find this method of drawing in WPF as useful as I have.</p> Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com1tag:blogger.com,1999:blog-2108758405551517278.post-15304999862662094712011-11-06T09:54:00.001+02:002012-01-08T20:55:39.025+02:00Building a simple FTP server in F#<p> </p> <p><u>Why build a FTP server</u></p> <p>I’ve just started learning F# and the application I decided to build needs a way of exposing a read-only directory structure/file system. I initially looked at WebDAV but it seemed unnecessarily complex for what I was trying to do, there also is apparently a compatibility issue between the windows implementation and the standard. FTP on the other hand is well documented, well supported and cross platform.</p> <p>As it turns out building a FTP server that supports the minimum number of features is actually pretty simple. By implementing only ten of the many possible FTP commands I was able to build a FTP server that windows explorer and a number of FTP clients could happily use.</p> <p>I was very surprised how easy building the server was. I’m sure I’ll use this approach for a number of different applications in the future. Hopefully I can convince you to do the same :)</p> <p>Two things to note however; firstly this is obviously not meant to be a production grade server but it could certainly be taken to that extent if required. Secondly this is only my third day of F# so I may well have missed some F# tricks or done some things in a non-idiomatic way. </p> <p><u></u></p> <p><u></u></p> <p><u>The FTP Protocol</u></p> <p>FTP is a line based protocol like telnet. This is part of what makes implementing it so easy.</p> <p>For example this is what an authentication conversation would look like <table border="1" cellspacing="0" cellpadding="0"><tbody> <tr> <td valign="top" width="189"> <p><b>Client</b>: [Connect to server]</p> </td> <td valign="top" width="236"> </td> </tr> <tr> <td valign="top" width="189"> </td> <td valign="top" width="236"> <p><b>Server</b>: [220 Hello, welcome to …]</p> </td> </tr> <tr> <td valign="top" width="189"> <p><b>Client</b>: USER usr123</p> </td> <td valign="top" width="236"> </td> </tr> <tr> <td valign="top" width="189"> </td> <td valign="top" width="236"> <p><b>Server</b>: 331 password required</p> </td> </tr> <tr> <td valign="top" width="189"> <p><b>Client</b>: PASS abcde</p> </td> <td valign="top" width="236"> <p><b></b></p> </td> </tr> <tr> <td valign="top" width="189"> <p><b></b></p> </td> <td valign="top" width="236"> <p><b>Server: </b>230 logged in<b></b></p> </td> </tr> </tbody></table> </p> <p>That is it, pretty simple, right?</p> <p>For more details on the protocol you can take a look at RFC959 (<a href="http://www.ietf.org/rfc/rfc959.txt">http://www.ietf.org/rfc/rfc959.txt</a>). </p> <p>There are also many sites that explain the protocol in plain English. Here are three sites that I found particularly useful in getting started</p> <ol> <li>An Overview of the File Transfer Protocol - <a href="http://www.ncftp.com/libncftp/doc/ftp_overview.html">http://www.ncftp.com/libncftp/doc/ftp_overview.html</a> </li> <li>List of raw FTP commands - <a href="http://www.nsftools.com/tips/RawFTP.htm">http://www.nsftools.com/tips/RawFTP.htm</a> </li> <li>FTP, File Transfer Protocol - <a href="http://www.networksorcery.com/enp/protocol/ftp.htm">http://www.networksorcery.com/enp/protocol/ftp.htm</a> </li> </ol> <p><u></u></p> <p><u></u></p> <p><u>The minimal set of FTP commands</u></p> <p>To get the FTP server to support a few FTP clients (FileZilla, explorer and ftp.exe command line client) I had to implement the following commands</p> <ul> <li>USER - User name </li> <li>PASS - Password </li> <li>CWD - Change working directory </li> <li>PWD - Print working directory </li> <li>TYPE - Binary/text mode. I just ignored this command </li> <li>LIST/NLST - Print a directory listing </li> <li>PORT - Set the data port for an active data transfer </li> <li>RETR - Retrieve a file </li> <li>QUIT - Quit </li> </ul> <p>Clearly there is a lot that I have not implemented and I may extend what I support (e.g. CDUP, PASSV etc) but for now this is all I need.</p> <p><u></u></p> <p><u></u></p> <p><u>Code structure</u></p> <p>The full source code is available on githib at <a href="https://github.com/andrevdm/FSharpFtpServer">https://github.com/andrevdm/FSharpFtpServer</a>.</p> <p>The code is structured as follows</p> <ul> <li>SocketExtensions – contains extension methods for the Socket class. This was copied from <a href="http://fssnip.net/1E">http://fssnip.net/1E</a> <br /></li> <li>SocketServer - The main server. This accepts new connections and creates a new ISocketHandler derived class to handle the requests. <br /></li> <li>LineProtocolHandler - Implements ISocketHandler. Reads data off the socket and calls the abstract Handle method after a line is read. NB The way I’m reading one byte at a time is far from optimal but it is simple and works well enough for now. <br /></li> <li>FtpHandler - inherits from LineProtocolHandler and implements the Handle method to handle each command line as it is received. <br /></li> <li>IDirectoryProvider. Interface for classes exposing a directory. I.e. responsible for generating the directory listing, keeping track of the current directory and downloading files etc. <br /></li> <li>FileSystemDirectoryProvider - a directory provider for exposing a real file system. (This still needs work). </li> </ul> <p>The idea here is that it is easy to plug in different functionality, for example different directory providers. I’m using this server to expose a virtual file system (something like the Git file system, more about that in a future blog) so I’ve not put much effort into the FilySystemDirectoryProvider but it should be pretty simple to get it working correctly.</p> <p> </p> <p><u>Code examples</u></p> <p>The login conversation was show above this is how it looks in the code.</p> <p>When a client connects the server sends a 220 “hello” command.</p> <blockquote> <p>do! t.Send 220 "Hello" socket</p> </blockquote> <p>Here Send is a method that takes the code (220), the sting (“Hello”) and the socket to respond to.</p> <p>The logged in state is then managed by the loginState mutable state variable. This can have one of the following values</p> <blockquote> <pre class="brush: fsharp;">type ftpLoginState = <br /> | ExpectUserName <br /> | ExpectPassword <br /> | LoggedIn </pre><br /></blockquote><br /><br /><p> </p><br /><br /><p>The incoming lines are sent to the Handle() method</p><br /><br /><blockquote><br /> <pre class="brush: fsharp;">override t.Handle( line, socket ) = <br />async{ <br /> if line = "QUIT" then <br /> t.Stop() <br /> else <br /> match loginState with <br /> | ExpectUserName -> do! t.HandleLoginUserName( line, socket ) <br /> | ExpectPassword -> do! t.HandleLoginPassword( line, socket ) <br /> | LoggedIn -> do! t.HandleCommand( line, socket ) <br /> | _ -> failwith ("unknown ftpLoginState " + loginState.ToString()) <br />} </pre><br /></blockquote><br /><br /><p> </p><br /><br /><p>This method works as follows</p><br /><br /><ol><br /> <li>If the QUIT command is received then call the Stop() method to terminate the connection </li><br /><br /> <li>All other commands are then interpreted based on the current login state <br /> <ol><br /> <li>If expecting a user name call HandleLoginUserName </li><br /><br /> <li>If expecting a password call HandleLoginPassword </li><br /><br /> <li>If logged in the call the HandleCommand method which handles the main FTP commands </li><br /><br /> <li>Any other state is invalid </li><br /> </ol><br /> </li><br /></ol><br /><br /><p> </p><br /><br /><p>The HandleCommand method then responds to the rest of the FTP commands.</p><br /><br /><pre class="brush: fsharp;">member t.HandleCommand( line, socket ) = <br /> async{ <br /> let c,r = t.SplitCmd line<br /><br /> match c with <br /> | "PORT" -> do! t.SetPort( r, socket ) <br /> | "NLST" <br /> | "LIST" -> do! t.SendList( r, socket ) <br /> | "PWD" -> do! t.Send 257 ("\"" + dirProvider.CurrentPath + "\" is the current directory") socket <br /> | "TYPE" -> do! t.Send 200 "ignored" socket <br /> | "RETR" -> do! t.RetrieveFile( r, socket ) <br /> | "CWD" -> <br /> if dirProvider.ChangeDir( r ) then do! t.Send 200 "directory changed" socket <br /> else do! t.Send 552 "Invalid directory" socket <br /> | _ -> do! t.Send 502 "Command not implemented" socket<br /><br /> }</pre><br /><br /><p> </p><br /><br /><p>The SplitCmd call is a simple method to split the received line.</p><br /><br /><blockquote><br /> <p>member t.SplitCmd( line ) = <br /> <br />  let m = Regex.Match( line, @"^(?<cmd>[^ ]+)( (?<rest>.*))?" ) <br /><br /> <br />  if not m.Success then failwith ("invalid command: " + line) <br /><br /> <br />  (m.Groups.["cmd"].Value, m.Groups.["rest"].Value)</p><br /></blockquote><br /><br /><p> </p><br /><br /><p><u>LIST / NLST</u></p><br /><br /><p>The LIST and NLST commands request the server to send a directory listing. Rather bizarrely there is no actual standard for this format. However most FTP server return the listing matching the standard unix ls format and most FTP clients expect this too.</p><br /><br /><p>Here is how the FileSystemDirectoryProvider returns the files and sub-directories of the current path</p><br /><br /><pre class="brush: fsharp;">member t.List() = <br /> let dir = new StringBuilder() <br /> <br /> let info = new DirectoryInfo( physicalDirectory )<br /><br /> info.GetFiles() <br /> |> Array.iter (fun f -> <br /> dir.AppendFormat( "-r--r--r-- 1 owner group {1} 1970 01 01 {0}", f.Name, f.Length ).AppendLine() |> ignore )<br /><br /> info.GetDirectories() <br /> |> Array.iter (fun d -> <br /> dir.AppendFormat( "dr--r--r-- 1 owner group {1} 1970 01 01 {0}", d.Name, 0 ).AppendLine() |> ignore )<br /><br /> dir.ToString()</pre><br /><br /><p> </p><br /><br /><p>This listing is then sent back to the client by the FtpHandler, see the section on the PORT command below</p><br /><br /><blockquote><br /> <pre class="brush: fsharp;">member t.SendList( p, socket ) = <br /> async{ <br /> do! t.Send 150 "Opening ASCII mode data connection for /bin/ls" socket <br /> <br /> use sender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) <br /> let endpoint = IPEndPoint(ipAddr, port) <br /> sender.Connect( endpoint ) <br /> <br /> let! sent = sender.AsyncSend( Encoding.ASCII.GetBytes( dirProvider.List() ) ) <br /> do! t.Send 226 "Listing completed." socket <br /> }</pre><br /></blockquote><br /><br /><p> </p><br /><br /><p><u>PORT and active mode data transfer</u></p><br /><br /><p>FTP uses different ports for its command and data communications. The command socket is always on the port that you connect on (typically port 21), data ports are created dynamically as required.</p><br /><br /><p>There are two ways in which ports are assigned</p><br /><br /><ol><br /> <li>In <strong>active</strong> mode the client creates a socket to listen for data on and tells the server the IP and port number </li><br /><br /> <li>In <strong>passive</strong> mode the client requests that server create a listener and return the IP and port. Passive mode is often preferred as it is simpler to support behind a firewall. </li><br /></ol><br /><br /><p>It was marginally simpler for me to implement active mode so for now that is all the server supports.</p><br /><br /><p>In active mode a data transfer like the LIST command described above would look like this</p><br /><br /><table border="1" cellspacing="0" cellpadding="2" width="696"><tbody><br /> <tr><br /> <td valign="top" width="252"><strong>Client</strong>: PORT 127,0,0,1,4,1</td><br /><br /> <td valign="top" width="442"> </td><br /> </tr><br /><br /> <tr><br /> <td valign="top" width="252"> </td><br /><br /> <td valign="top" width="442"><strong>Server</strong>: 200 PORT command successful</td><br /> </tr><br /><br /> <tr><br /> <td valign="top" width="252"><strong>Client</strong>: LIST</td><br /><br /> <td valign="top" width="442"> </td><br /> </tr><br /><br /> <tr><br /> <td valign="top" width="252"> </td><br /><br /> <td valign="top" width="442"><strong>Server</strong>: 150 Opening ASCII mode data connection for /bin/ls</td><br /> </tr><br /><br /> <tr><br /> <td valign="top" width="252"> </td><br /><br /> <td valign="top" width="442"><font color="#ff0000"><strong>Server</strong>: [Data sent to 127.0.0.1:1026]</font></td><br /> </tr><br /><br /> <tr><br /> <td valign="top" width="252"> </td><br /><br /> <td valign="top" width="442"><strong>Server</strong>: 226 Listing completed</td><br /> </tr><br /> </tbody></table><br /><br /><p> </p><br /><br /><p>So the client creates a listening socket and sends the details to the server. The port is split into high,low. The number = high * 256 + low. In this case 4*256+1 = 1026</p><br /><br /><p>The client then requests the list.</p><br /><br /><p>Finally the server then</p><br /><br /><ol><br /> <li>Tells the client that it is about to send the data </li><br /><br /> <li>Sends the data over the new data connection </li><br /><br /> <li>Tells the client that the data was sent successfully </li><br /></ol><br /><br /><p> </p><br /><br /><p><u>In summary</u></p><br /><br /><p>I was able to build a simple FTP server in a few hundred lines of F#. I’m sure I’ll be using this code in a number of projects in the future. I hope that it will prove useful to you as well.</p><br /><br /><p> </p><br /><br /><p><u></u></p><br /><br /><p><u></u></p><br /><br /><p><u></u></p><br /><br /><p><u>Learning F#</u></p><br /><br /><p>These are the resource that I’ve found particularly useful in learning F#.</p><br /><br /><p>F# Snipits - <a href="http://fssnip.net">http://fssnip.net</a> - There are some fantastic sample here. My base server code is based on <a href="http://fssnip.net/1E">http://fssnip.net/1E</a></p><br /><br /><p>Expert F# - <a href="http://www.amazon.com/Expert-F-Experts-Voice-NET/dp/1590598504">http://www.amazon.com/Expert-F-Experts-Voice-NET/dp/1590598504</a> - A brilliant F# book.</p> Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-82934040899967617422011-09-11T07:45:00.001+02:002011-09-11T07:46:59.765+02:00TmMq - Trivial MongoDB Message QueueI've just pushed a very simple message queue system that used MongoDB as the data store. I've found this useful for testing message queuing and projects where I dont want to deploy a full blown message queuing system.<br />
<br />
Hopefully it will help someone else too.<br />
<br />
You can get the source and binaries from <a href="https://github.com/andrevdm/TrivialMongoMessageQueue">github</a><br />
<a href="https://github.com/andrevdm/TrivialMongoMessageQueue">https://github.com/andrevdm/TrivialMongoMessageQueue</a><br />
<br />
<br />
Below is the readme from the project<br />
----------------------------------------<br />
<div class="wikistyle"><h1>TmMq</h1>TmMq - Trivial MongoDB Message Queue is a very simple .net message queuing system built on MongoDB<br />
It is not in any way meant to compete with any of the fully fledged messaging solutions (Hortet, ActiveMQ etc) but it is a nice, lightweight alternative that has proved useful to me.<br />
<br />
<h2>Features</h2><ol><li>No TmMq server</li>
<li>Send & receive</li>
<li>Publish / subscribe</li>
<li>Redeliver on error with limit on retry</li>
<li>Limit on delivery (at-least-once delivery)</li>
<li>Message expiry</li>
<li>Message holding (only deliver in future)</li>
<li>Errors logged in message</li>
<li>Dynamic properties collection</li>
<li>Synchronous and asynchronous receive</li>
<li>Written in C#</li>
</ol><h2> </h2><h2>TODO</h2><ol><li>Triggers based on tailable MongoDB cursor. I'm not sure this is necessary, I will implement it if I find I need it.</li>
<li>More unit tests</li>
</ol><h2> </h2><h2>Licence</h2>FreeBSD License. See licence.txt<br />
<h2> </h2><h2>Usage</h2>See the unit tests for examples of all the features including pub/sub, retry, errors etc.<br />
<h4> </h4><h4>Send & receive</h4><pre><code>using( var send = new TmMqSender( "TestSendBeforeReceiveStarted" ) )
{
var msg = new TmMqMessage();
msg.Text = "msg1";
send.Send( msg );
}
using( var recv = new TmMqReceiver( "TestSendBeforeReceiveStarted" ) )
{
ITmMqMessage recieved = recv.Receive().FirstOrDefault();
}
</code></pre><h4> </h4><h4>Pub/sub</h4><pre><code>using( var rcvr1 = new TmMqPubSubReceiver( "TestPubSub" ) )
using( var rcvr2 = new TmMqPubSubReceiver( "TestPubSub" ) )
using( var rcvr3 = new TmMqPubSubReceiver( "TestPubSub" ) )
using( var rcvr4 = new TmMqPubSubReceiver( "TestPubSub" ) )
{
var r1 = new List<itmmqmessage>();
var r2 = new List<itmmqmessage>();
var r3 = new List<itmmqmessage>();
var r4 = new List<itmmqmessage>();
rcvr1.StartReceiving( 1, r1.Add );
rcvr2.StartReceiving( 1, r2.Add );
rcvr3.StartReceiving( 1, r3.Add );
rcvr4.StartReceiving( 1, r4.Add );
using( var sender = new TmMqPubSubSender( "TestPubSub" ) )
{
var msg = new TmMqMessage();
msg.Text = "ps-" + i;
sender.Send( msg );
}
</itmmqmessage></itmmqmessage></itmmqmessage></itmmqmessage></code></pre></div>Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-20582993902730143082010-12-16T11:59:00.002+02:002010-12-16T12:04:21.100+02:00Parameterised queries–don’t use AddWithValueI’ve just had another run in with the SQL query optimiser. Here is my tale of woe.<br />
I had a very simple parameterised query. Something like this<br />
<blockquote><div id="codeSnippetWrapper"><pre id="codeSnippet" style="background-color: #f4f4f4; border-bottom-style: none; border-left-style: none; border-right-style: none; border-top-style: none; color: black; direction: ltr; font-family: 'Courier New', courier, monospace; font-size: 8pt; line-height: 12pt; margin: 0em; overflow: visible; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; text-align: left; width: 100%;"><span style="color: blue;">select</span> * <span style="color: blue;">from</span> People <span style="color: blue;">where</span> ID10 = @idnumber</pre><br />
</div></blockquote>However when I looked at the SQL execution plan it looked like this<br />
<blockquote><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCPD_ncC7n2c-_WQdBKkVJEBuCz_LtknEc05Y3bwG7bcjyGJhFlxvAu7abp7GL75rE00lsewgjG6sVcGfCuWLOlkPsNANrP_yxqYwO2v-frgon0FK_99XLOVIAqhackJ72d76uf2BpZkiw/s1600-h/ex1%5B7%5D.png"><img alt="ex1" border="0" height="89" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlclojfRitDTJfJLMpcV-3MiLyih5mVSj5M8zWoTqHT-04_Apv37Ofi_rFisKzPU-1v8FBtwnZbWwwnHr18Tyu9bdPCOUBMhHjEmkWgH0UnwLwBqPq1td9vLpBPGU8zqjwfN3oB9hTkW1G/?imgmax=800" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="ex1" width="384" /></a></blockquote>The thing to notice here is that it is doing an index scan. This made no sense to me since the ID10 column is indexed and so I should be seeing an index seek.<br />
<br />
Using SQL profiler confirmed that this query was taking nearly half a second on our production server, which was way too slow. <br />
<br />
This is the query as recorded by SQL profiler after being executed by the C# code<br />
<span style="color: blue; font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;"> exec</span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;"> sp_executesql N</span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;"><span style="color: #006080;">'select * from People where ID10=@id'</span></span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;">, N</span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;"><span style="color: #006080;">'@id nvarchar(10)'</span></span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;">, N</span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;"><span style="color: #006080;">'1001010001'</span></span><br />
<div><br />
</div>And here is the table<br />
<blockquote><div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXj4SWmZ6bmfSepCz33ycdyFhfOpRmMpVH-DnuFC5NIoMIqiMdh_NJP4WGueBShEzpV3b-CEaoaewLRbd3AuAItED0Cwpnrzs_DUjW1J5_T7ojQCcAT3rfZSEv2xrJBfSEirC0pYr-qcmx/s1600-h/image11.png"><img alt="image" border="0" height="95" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5kYeJukDrXR3ZV3vy_YNET33_CtQ4gn96QNE3OBt8itjKJhRQZ5OdS8Np5iZ0cDZNp1Dsk4984JH1_EsxMQZQ13_ZbBeVtApjk-bAH0msgFijXYqGQoYYGsSXua-8Vd9GlMvsX8-9kyoY/?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="315" /></a></div><br />
</blockquote>What was strange is that when I executed the SQL without a parameter<br />
<div><pre id="codeSnippet" style="background-color: #f4f4f4; border-bottom-style: none; border-left-style: none; border-right-style: none; border-top-style: none; color: black; direction: ltr; font-family: 'Courier New', courier, monospace; font-size: 8pt; line-height: 12pt; margin: 0em; overflow: visible; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; text-align: left; width: 100%;"><span style="color: blue;"> exec</span> sp_executesql N<span style="color: #006080;">'select id10 from People where ID10='</span><span style="color: #006080;">'1001010001'</span><span style="color: #006080;">''</span></pre><br />
</div><div>I got this execution plan</div><blockquote><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7Vy5aIn504jlmUsn8LLB9GBG7aqgwMceb1CJCS7XLmyx6TVgItDLOZtTqc7dOTWsvAac4gGn-jiChtGKmPTlck7dsmgOvXyD2FkSvC71_CHloQBzo8vOj-oWXnho55plgOAoxQssM-HZz/s1600-h/ex2%5B3%5D.png"><img alt="ex2" border="0" height="91" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidbcfKmBNdgAGB5Swa07unmzf5AdkYek41HpAun4bSZg7OQrI40vXE3Hp1Ew6LlLkG9g3_QQ9Kiurr-Vl6gfTbZdXl7is7ByH2Y7LSBJKOtk4Lf0LAqupvhwg-YksR7-FGO8qG6-LC9oRs/?imgmax=800" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="ex2" width="289" /></a><br />
<br />
</blockquote>An index seek, exactly what I wanted. This query took between 1 and 10ms, so more than 400 times faster!<br />
<div><br />
</div>After much searching I finally found the answer;<br />
<br />
<div>This parameterised query works perfectly, it uses an index seek</div><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;"><span style="color: blue;">exec</span></span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;"> sp_executesql N</span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;"><span style="color: #006080;">'select id10 from People where ID10=@id'</span></span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;">, N</span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;"><span style="color: #006080;">'@id varchar(10)'</span></span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;">, </span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;"><span style="color: #006080;">'1001010001'</span></span><br />
<div id="codeSnippetWrapper"><br />
The difference? One letter… This query passes the ID number as a varchar not an nvarchar. Since the index is on an varchar, passing in a nvarchar means that there will be an index scan not a seek. I have no idea why SQL does not first convert to a varchar and then do a scan, but it does not…</div><br />
The culprit in the C# code was this line<br />
<span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;"> cmd.Parameters.AddWithValue( </span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;"><span style="color: #006080;">"id"</span></span><span class="Apple-style-span" style="font-family: 'Courier New', courier, monospace; font-size: 11px; line-height: 16px; white-space: pre;">, idNumber )</span><br />
<div id="codeSnippetWrapper"><br />
The AddWithValue forces .net to infer the type you are passing in and since all strings in .net are unicode the parameter is sent as an nvarchar.</div><br />
Know this, the fix was trivial<br />
<div id="codeSnippetWrapper"><pre id="codeSnippet" style="background-color: #f4f4f4; border-bottom-style: none; border-left-style: none; border-right-style: none; border-top-style: none; color: black; direction: ltr; font-family: 'Courier New', courier, monospace; font-size: 8pt; line-height: 12pt; margin: 0em; overflow: visible; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; text-align: left; width: 100%;">cmd.Parameters.Add( <span style="color: #006080;">"id"</span>, SqlDbType.VarChar, 10 ).Value = idNumber,</pre><br />
Here the type and length are specified explicitly so the query is correct and SQL uses an index scan.</div><br />
So in summary don’t use AddWithValueAnonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-19951080652181054612009-11-21T22:07:00.007+02:002009-11-22T07:22:26.577+02:00BlockingCollection & parallel yields.Net 4 has many new features. My favourite at the moment are the classes in the System.Collections.Concurrent namespace. System.Collections.Concurrent has four thread safe collections; ConcurrentQueue<>, ConcurrentStack<>, ConcurrentDictionary<> and BlockingCollection<>. <br />
Here are two articles that discuss these collections<br />
<ul><li><a href="http://geekswithblogs.net/vitus/archive/2009/10/24/thread-safe-data-structures-.net-4.0-part-1.aspx">Thread-safe data structures .NET 4.0 (part 1)</a></li>
<li><a href="http://www.lovethedot.net/2009/02/parallel-programming-in-net-40-and.html">Parallel Programming in .Net 4.0 and Visual Studio 2010: BlockingCollection<T></a></li>
</ul><br />
I’m finding the BlockingCollection’s GetConsumingEnumerable() method incredibly useful. What it does is return an IEnumerable<> that removes items from the collection and blocks while the collection is empty. This combined with the fact that the BlockingCollection is thread safe makes for very simple code.<br />
<br />
As an example image that you have this method that downloads data from several locations (DownloadSourceStreams), parses the data (ParseStream) and yields the processed results (ImportData)<br />
<br />
<pre class="brush: csharp">public override IEnumerable<ParsedData> ImportData()
{
foreach( Stream inputStream in DownloadSourceStreams() )
{
ParsedData data = ParseStream( inputStream );
yield return data;
}
}
public IEnumerable<Stream> DownloadSourceStreams()
{
foreach( string url in m_sourceUrls )
{
yield return DownloadStreamFrom( url );
}
}</pre><br />
<br />
It would make sense to download all the data in parallel. However you can’t simply make the foreach in ImportData() a Parallel.ForEach since you can't yield from within a Parallel.ForEach. So what you need to do is download the data in parallel and then have a single point for yielding all the results. The BlockingCollection makes this easy<br />
<br />
<br />
<pre class="brush: csharp">private BlockingCollection<ParsedData> m_imported = new BlockingCollection<ParsedData>();
public override IEnumerable<ParsedData> ImportData()
{
Task.Factory.StartNew( ParallelImportData );
return m_imported.GetConsumingEnumerable();
}
private void ParallelImportData()
{
Parallel.ForEach( DownloadSourceStreams(), inputStream =>
{
ParsedData data = ParseStream( inputStream );
m_imported.Add( data );
} );
m_imported.CompleteAdding();
}
public IEnumerable<Stream> DownloadSourceStreams()
{
foreach( string url in m_sourceUrls )
{
yield return DownloadStreamFrom( url );
}
}</pre><br />
<br />
<br />
The code now works as follows<br />
<br />
<br />
<ul><li>ImportData() starts a new task to import the data (line 5) and then returns the consuming enumerable (line 6)</li>
<li>ParallelImportData() is then called on a separate thread/task and does the downloads in parallel by using Parallel.Foreach (line 11). Each imported item is then added to the blocking collection (line 14).</li>
<li>When the import has been completed the BlockingCollection’s CompletedAdding() method is called. </li>
</ul><br />
<br />
<br />
<br />
When the consumer calls ImportData() it gets an IEnumerable (consuming enumerable) that blocks while there is no data in the collection. As soon as there is data it iterates over it and removes it from the base collection. This continues until CompletedAdding is called.<br />
<br />
<br />
What is striking about this code is that all of this happens without any explicit locking, the blocking collection handles it all for you.Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-42175846984528899012009-08-04T23:26:00.001+02:002009-08-05T09:37:15.687+02:00.net REPL<p>I often want to test things quickly in .net without creating a new project. There are quite a few options. <br /></p> <p><u><strong>REPL</strong></u></p> <p>Firstly the <a href="http://en.wikipedia.org/wiki/Read-eval-print_loop" target="_blank">read-eval-print-loop</a> options. These are great for quick tests.</p> <p>Most of the dynamic languages have a REPL. Personally I use booish because I like the <a href="http://boo.codehaus.org/" target="_blank">boo</a> programming language. <a href="http://www.codeplex.com/IronPython" target="_blank">IronPython</a> and <a href="http://www.ironruby.net/" target="_blank">IronRuby</a> also have REPL. You can even run <a href="http://ironpython.codeplex.com/Wiki/View.aspx?title=SilverlightInteractiveSession" target="_blank">IronPython in your browser</a> if you want. </p> <p>The best C# REPL I’ve seen is <a href="http://www.mono-project.com/CsharpRepl" target="_blank">gsharp</a> from <a href="http://www.mono-project.com" target="_blank">Mono</a>. You can happily run Mono side by side with Microsoft’s .net frameworks. So there is no reason not to try this. </p> <p> <br /><strong><u>Compiling</u></strong></p> <p>For more complex tests a light weigh editor/compiler are useful. By far my favourite in this category is <a href="http://www.scintilla.org/SciTE.html" target="_blank">SciTE</a>. SciTE is an incredible editor that will compile .cs files (and many others) out of the box. Just open a .cs, press F7 to compile and F5 to run. It can be configured to edit pretty much anything. Its light weigh, cross platform and it is free. IMO its an editor that every developer should take a look at.</p> <p>Other options include <a href="http://www.sliver.com/dotnet/SnippetCompiler/" target="_blank">Snippet Compiler</a> which has a nice IDE and intellisense. Also take a look at <a href="http://csharpindepth.com/Downloads.aspx" target="_blank">Snippy</a>, and the <a href="http://msmvps.com/blogs/jon_skeet/archive/2008/11/23/the-snippy-reflector-add-in.aspx" target="_blank">reflector snippy addin</a>. </p> Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-46055510085579664382009-04-15T23:28:00.001+02:002009-04-15T23:29:08.153+02:00C# T-Tree<p>I have just created a T-Tree project on github: <a href="http://github.com/andrevdm/ttree/tree/master">http://github.com/andrevdm/ttree/tree/master</a></p> <p><em>“A T-tree is a balanced index tree data structure optimized for cases where both the index and the actual data are fully kept in memory, just as a B-tree is an index structure optimized for storage on block oriented external storage devices like hard disks. T-trees seek to gain the performance benefits of in-memory tree structures such as AVL trees while avoiding the large storage space overhead which is common to them.” (from </em><a href="http://en.wikipedia.org/wiki/T-tree"><em>wikipedia</em></a><em>)</em></p> <p><em>See also: Tobin J. Lehman and Michael J. Carey, </em><a href="http://www.vldb.org/conf/1986/P294.PDF"><em>A Study of Index Structures for Main Memory Database Management Systems. VLDB 1986</em></a><em> for a comprehensive discussion of T-Trees</em></p> <p>The project is a C# implementation of a T-Tree. There is also a very simple command line profiler and a GUI for visualising inserts and deletes.</p> <p>There are still a few things outstanding (e.g. Making the class enumerable and supporting the visitor pattern) but all the basics (insert, search and delete) are now working.</p> <p>I'm sure that more can be done to speed up this implementation but it already performs very well. That coupled with the efficient use of memory makes it quite an attractive data structure. </p> <p>A quick disclaimer: I have only just gotten the basics working I'm sure there are still some bugs lurking. I'll be doing more testing and adding more unit tests as I have time.</p> <p>The code is released under the BSD licence. </p> <p>Please let me know if you find it useful, spot any bugs or can think of way to improve it.</p> Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0tag:blogger.com,1999:blog-2108758405551517278.post-42787850095631429742009-03-27T22:28:00.001+02:002009-08-04T22:47:11.943+02:00Visual Studio Dot Debugger Visualiser<p><a href="http://www.graphviz.org/">Graphviz</a> is amazing, I've never found a better or easier to use tool for generating graphs and data visualisation. The dot language is very easy to learn and the documentation is very good indeed. If you are doing any form of visualisation its worth taking a look at.</p> <p>In the past I've used it for visualising compiler ASTs and data structures. (Which reminds me Jim Idle <a href="http://www.antlr.org/pipermail/antlr-interest/2009-March/033417.html">posted a nice ANTLR to dot conversion sample</a>).</p> <p>At the moment I'm working on a couple of data structures. Visualising them while developing the structures helps a great deal with spotting obvious errors and checking that things look as expected (yes I have unit tests too :). However it was a little inconvenient to have to keep writing the dot out to disk and generating the images manually.</p> <p>Luckily Visual Studio supports visualisers and they are easy to write too. So I've just created a dot visualiser. My classes now have a ToDot() method which return a string. Whenever I want to visualise them I simply add a watch and select the dot visualiser. It could not be easier.</p> <p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-2wAeHIlNoa3t4ah1wAU18VcQOrv-sh_jN-v9Xp5IZyLFRDGz0acPQ2nLnFFAsKhkLccl5zqlZs9N-fCeaiVVdk_41MT0c5RCB_NE8T_JdnW0dynpiFvpQZhavkgJT4iuiqJw3tM3KZAg/s1600-h/image%5B7%5D.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIod0UkvuibPTO3_CkkilHQBYtq_5uuIiUocl2BV3Ix3r_o8OEtFa6AFLX6ZrJzFHXFmH3KWF52piRXWqho44DWVpF01kymPAe80Pq-Ng0TwMqcT_Gn0TLNGu12pwsBB3T3fo_yKOstHJ8/?imgmax=800" width="404" height="241" /></a> </p> <p> </p> <p><u>Download</u></p> <p><a href="https://sites.google.com/site/andredart/blogfiles/GraphvizDebuggerVisualizerSource.zip?attredirects=0">VS 2008 Source code</a> <br /><a href="https://sites.google.com/site/andredart/blogfiles/GraphvizDebuggerVisualizerBinaries.zip?attredirects=0">VS 2008 Binaries</a> </p> <p><u>Installing the binaries</u></p> <ol> <li>Copy the binaries to your visualisers directory (%USERPROFILE%\Documents\Visual Studio 2008\Visualizers) </li> <li>Edit the config file and set the path to dot.exe </li> </ol> <p><u>Disclaimer</u></p> <p>I’ve not made much effort to make this visualiser robust. It helps me during debugging, that’s all.  <a href="http://www.codinghorror.com/blog/archives/000818.html"><img border="0" alt="works on my machine, starburst" src="http://www.codinghorror.com/blog/images/works-on-my-machine-starburst.png" width="200" height="193" /></a></p> <p> <br />Update: Thanks <a href="http://ienumerable.wordpress.com/" target="_blank">Riaan</a> for spotting an horribly embarrassing  bug in the code :).</p> Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com1tag:blogger.com,1999:blog-2108758405551517278.post-57622260678171384572008-06-13T13:46:00.002+02:002009-03-27T12:57:50.415+02:00Species Browser<p><span style="color:#ff0000;">update 2009/01/01</span></p><span style="color:#ff0000;"> <p><span style="color:#ff0000;">[Warning! Abandonware: I am no longer developing/maintaining this application]</span></p></span><h3><span class="Apple-style-span" style="font-size:130%;"><span class="Apple-style-span" style=" font-weight: normal;font-size:16px;"><br /></span></span>Introduction</h3> <p>Species Browser is a simple application for browsing information saved from the web. Both a desktop PC version and a Windows Mobile version are available. I wrote it for my own use but I'm releasing it here as you might also find it useful.<br /><br />Species Browser browses information you have saved. <b>It does not come with any of its own data</b>.<br /></p> <p></p> <h3>Folders</h3> <p>For it to work you need to save your information one folder per species. In the picture below you can see the folder structure on my mobile phone.<br />Here you can see my plant folders. I have similar folders for fish and 'Other' which contains info on invertebrates etc. Each of these top level folders is referred to as a collection. You can setup whatever folder structure you want. I then have the exact same folder structure on my desktop machine.<br /><br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_Files9.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_Files_thumb3.gif" width="338" height="377" /></a><br /><br /><br /></p> <h3>Files</h3> <p>In the pic below you can see the files in one of these folders. The images are used for thumbnails. The saved web pages are displayed on the documents tab and the notes file is displayed on the main page.<br /><br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Files4.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Files_thumb.gif" width="414" height="179" /></a><br /><br /></p> <p></p> <hr /> <p></p> <h2>The Windows Mobile Version</h2> <p><br />The pic above shows the main screen displaying information on Hygrophilia polysperma.<br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile6.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_thumb.gif" width="240" height="318" /></a><br /></p> <p>On the "Docs" tab you can view your saved web pages<br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_Docs_014.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_Docs_01_thumb.gif" width="240" height="318" /></a><br /><br />The windows mobile version can only view htm and html pages. It can not display mht files. So if you are saving web pages with IE select "Webpage, complete" and not "Web archive".<br /><br /><br /></p> <h3>Setup</h3> <p>Before you can start using the mobile app you need to setup your collections. I.E. you need to tell the app where to look for your files. To do this click on the "Menu", and select "Setup Collections"<br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_SelectCollection4.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_SelectCollection_thumb.gif" width="240" height="318" /></a><br /></p> <p><br />You will then see the collection setup page. On this page you can create links to new collections and edit your existing collections.<br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_SetupCollections_02_MainScree.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_SetupCollections_02_MainScree1.gif" width="240" height="318" /></a><br /><br />To add a new collection </p> <ol> <li>Click on the "New Collection" </li> <li>Enter a name </li> <li>Click the browse button"..." and browse for the folder containing your collection <br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_SetupCollections_03_Browse4.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_SetupCollections_03_Browse_th.gif" width="240" height="318" /></a> </li> </ol><br /><h3>Selecting a collection</h3> <p>You can now select a collection from the main menu<br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_SelectCollection7.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_SelectCollection_thumb1.gif" width="240" height="318" /></a><br /></p> <p><br /></p> <h3>Selecting a species</h3> <p>There are two ways to select a species to view. Either select the item from the drop down or click on the "Species" menu item.<br />If you click on the "Species" menu item you will see this screen<br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_SelectSpecies4.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_SelectSpecies_thumb.gif" width="240" height="318" /></a><br /><br /><br />On this screen you can select an item from the listbox or search for items containing a phrase. To search for a phrase type in the edit box at the top of the screen.<br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_SelectSpecies_Search4.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_SelectSpecies_Search_thumb.gif" width="240" height="318" /></a><br /></p> <p><br /></p> <h3>Selecting a document</h3> <p>Once you have selected an item from the listbox click the "Select" menu item to load it.<br /><br />Once you have loaded a species you can view your saved documents by clicking on the "Docs" tab. As with the species page you can either select a document from the drop down or search for it.<br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_Docs_02_Select4.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Mobile_Docs_02_Select_thumb.gif" width="240" height="318" /></a><br /></p> <p><br /></p> <h3>Keyboard shortcuts</h3> This pics shows how you can navigate the mobile app without using a stylus<br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Keys4.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/Keys_thumb.gif" width="788" height="724" /></a><br /><br /><br /><br /><hr /> <h2>The Windows Desktop Version</h2> I've done a lot less work on the desktop version as I mainly use the mobile version. However it does work...<br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/PC4.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/PC_thumb.gif" width="549" height="441" /></a><br /><br />The PC version is very similar to the mobile version the only difference being that the PC version allows you to edit the notes.<br /><br /><h3>Setup</h3> Click on the "Tools" menu, select "Collection Setup"<br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/PC_SetupCollections_014.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/PC_SetupCollections_01_thumb.gif" width="498" height="189" /></a><br /><br />Click on "Add" to add a collection. The browse button lets you browse for your collection folder<br /><a href="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/PC_SetupCollections_02_AddEdit4.gif"><img src="http://www.computronicsisp.com/Andre/blogImages/f92a76f9b146_11D8B/PC_SetupCollections_02_AddEdit_thumb.gif" width="487" height="123" /></a><br /><br /><br /><hr /> <h2>Download</h2> <p>Both versions are free. I may update them from time to time but I'm not making any promises :)<br /><br /><br /><u><b>Prerequisites</b></u><br />To run the windows mobile version you will need to have the Microsoft .Net Compact Framework version 2 installed. If you don't have it you can download it <a href="http://www.microsoft.com/downloads/details.aspx?familyid=9655156b-356b-4a2c-857c-e62f50ae9a55&displaylang=en">here</a>.<br /><br />To run the windows desktop version you will need to have the Microsoft .Net Framework version 2 (this is not the same framework as the one above) installed. If you don't have it you can download it <a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=0856EACB-4362-4B0D-8EDD-AAB15C5E04F5&displaylang=en">here</a>.<br /><br /><br /><u><b>Species Browser for Windows Mobile version 1.0.2.3</b></u><br />Download the mobile version's installer <a href="http://computronicsisp.com/Andre/SpeciesBrowser/Mobile%20Species%20Browser%20Installer%20-%201.0.2.3.exe">here</a><br /><br /><u><b>Species Browser for Windows Desktop machines version 1.0.2.1</b></u><br />Download the desktop version's installer <a href="http://computronicsisp.com/Andre/SpeciesBrowser/PC%20Species%20Browser%20Installer%20-%201.0.2.1.exe">here</a><br /></p> <p><br /></p> <hr /> <p></p> <h2>Feedback</h2> <p></p> <p>Let me know if you are using Species Browser by emailing me <<a href="mailto:dart-software@pobox.com">dart-software@pobox.com</a>>.</p>Anonymoushttp://www.blogger.com/profile/02919360938963587197noreply@blogger.com0