-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfeed.rss
1222 lines (1179 loc) · 93.3 KB
/
feed.rss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
<channel>
<title>Warren Bates</title>
<link>https://wbates.net/</link>
<description>Warren Bates</description>
<copyright>2018</copyright>
<pubDate>Sat, 27 Jan 2018 20:40:08 GMT</pubDate>
<lastBuildDate>Sat, 27 Jan 2018 20:40:08 GMT</lastBuildDate>
<item>
<title>Setting up the addin build process</title>
<link>https://wbates.net/posts/baking-a-cake-addin-part-3-recipe</link>
<description><p>Now that we have our Cake addin written, it's time to put the finishing touches on and utilise the Cake teams preferred way of building addins - Cake.Recipe. I've written about <a href="./getting-started-with-cake-recipe.md">getting started with Cake.Recipe</a>, and there might be some crossover with that post, but most the content should be new.</p></description>
<guid>https://wbates.net/posts/baking-a-cake-addin-part-3-recipe</guid>
<pubDate>Sat, 26 Aug 2017 00:00:00 GMT</pubDate>
<content:encoded><p>Now that we have our Cake addin written, it's time to put the finishing touches on and utilise the Cake teams preferred way of building addins - Cake.Recipe. I've written about <a href="./getting-started-with-cake-recipe.md">getting started with Cake.Recipe</a>, and there might be some crossover with that post, but most the content should be new.</p>
<p>We're going to make use of Cake.Recipe's tasks for documentation, versioning, as well as the normal aspects of building and running unit tests.</p>
<h1 id="ready-the-repository">Ready the repository</h1>
<h2 id="move-the-files">Move the files</h2>
<p>Cake.Recipe assumes a certain folder structure, and whilst it can be changed using the setup.cake file to alter various parameters, it's far easier if we stick with the convention.
In the root of the working copy, create a new folder called src. I suggest using git to move the files into this directory so you can maintain the history of each. We need to move both the project folders (Cake.Markdown-pdf and Cake.Markdown-pdf.Tests) as well as the solution file.</p>
<pre><code class="language-bash"># Hint - Case sensitive for directories/files even in Windows
mkdir src
git mv Cake.Markdown-Pdf src
git mv Cake.Markdown-Pdf.Tests src
git mv Cake.Markdown-Pdf.sln src
</code></pre>
<p>I suggest you also move the packages folder from the root into the src folder, otherwise the first time you build it will recreate the packages folder and you'll have a duplicate that could cause confusion. It doesn't need tracking by git though.</p>
<h2 id="create-a-nuspec-file">Create a nuspec file</h2>
<p>Add another new folder called nuspec with a subdirectory of nuget</p>
<pre><code class="language-bash">mkdir -p nuspec\nuget
</code></pre>
<p>Create a new text file in here called <code>Cake.Markdown_Pdf.nuspec</code>. The filename must be the same as the root namespace for the project or cake will not be able to locate the files for packaging.
<img src="../assets/images/default-namespace.png" class="img-fluid" alt="Default namespace" />
Open it up and paste the following in, replacing as appropriate.</p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;package &gt;
&lt;metadata&gt;
&lt;id&gt;Cake.Markdown-Pdf&lt;/id&gt;
&lt;title&gt;Cake.Markdown-Pdf&lt;/title&gt;
&lt;authors&gt;Warren Bates&lt;/authors&gt;
&lt;owners&gt;Warren Bates&lt;/owners&gt;
&lt;licenseUrl&gt;https://github.com/wozzo/Cake.Markdown-Pdf/blob/master/LICENSE&lt;/licenseUrl&gt;
&lt;projectUrl&gt;https://github.com/wozzo/Cake.Markdown-Pdf&lt;/projectUrl&gt;
&lt;iconUrl&gt;https://cdn.rawgit.com/cake-contrib/graphics/a5cf0f881c390650144b2243ae551d5b9f836196/png/cake-contrib-medium.png&lt;/iconUrl&gt;
&lt;requireLicenseAcceptance&gt;false&lt;/requireLicenseAcceptance&gt;
&lt;summary&gt;Adds Markdown-Pdf alias for Cake&lt;/summary&gt;
&lt;description&gt;An alias for Cake to help with running Markdown-Pdf commands as part of a build&lt;/description&gt;
&lt;tags&gt;Cake Script Build Markdown Pdf Markdown-Pdf&lt;/tags&gt;
&lt;/metadata&gt;
&lt;files&gt;
&lt;file src=&quot;Cake.Markdown-Pdf.xml&quot; target=&quot;lib\net45&quot; /&gt;
&lt;file src=&quot;Cake.Markdown-Pdf.dll&quot; target=&quot;lib\net45&quot; /&gt;
&lt;/files&gt;
&lt;/package&gt;
</code></pre>
<h2 id="gitversion">GitVersion</h2>
<p>Add a new <code>SolutionInfo.cs</code> file in the src folder and give it the following content</p>
<pre><code class="language-xml">using System.Reflection;
[assembly: AssemblyVersion(&quot;0.1.0&quot;)]
[assembly: AssemblyFileVersion(&quot;0.1.0&quot;)]
[assembly: AssemblyInformationalVersion(&quot;0.1.0&quot;)]
</code></pre>
<p>This is going to be a shared file that will get updated with version information as part of the cake build. The following steps need to be repeated for each project in the solution.</p>
<ol>
<li>Open up the <code>properties\AssesmblyInfo.cs</code> file and remove the three properties we've just added to the <code>SolutionInfo.cs</code> file.</li>
<li>Open up the csproj file, find the reference to the <code>AssemblyInfo.cs</code> file, and add the following after</li>
</ol>
<pre><code class="language-xml">&lt;Compile Include=&quot;..\SolutionInfo.cs&quot;&gt;
&lt;Link&gt;Properties\SolutionInfo.cs&lt;/Link&gt;
&lt;/Compile&gt;
</code></pre>
<h2 id="xml-documentation">XML Documentation</h2>
<p>Open up the solution in Visual Studio and open the Cake.Markdown-Pdf project properties. On the Build page select the 'XML Documentation file' checkbox. Do this for each build configuration.</p>
<p><img src="../assets/images/xml-documentation.png" class="img-fluid" alt="XML Documentation" /></p>
<p>This will mean you'll need to add in xml comments or the build will fail. Building in Visual Studio should show warnings were they are missing</p>
<h1 id="get-cake.recipe">Get Cake.Recipe</h1>
<p>Head to the <a href="https://github.com/cake-contrib/Cake.Recipe">Cake.Recipe repository</a> on GitHub and download the following list of files into the root of our repository.</p>
<ul>
<li>.appveyor.yml</li>
<li>.gitignore</li>
<li>GitReleaseManager.yaml</li>
<li>build.ps1</li>
<li>config.wyam</li>
<li>setup.cake</li>
</ul>
<p>Open up <code>setup.cake</code> in a text editor and change the title, repositoryOwner, repositoryName, and appVeyorAccountName settings as appropriate. I've also added the nuspecFilePath from earlier.</p>
<pre><code class="language-csharp">BuildParameters.SetParameters(context: Context,
buildSystem: BuildSystem,
sourceDirectoryPath: &quot;./src&quot;,
title: &quot;Cake.Markdown-Pdf&quot;,
repositoryOwner: &quot;wozzo&quot;,
repositoryName: &quot;Cake.Markdown-Pdf&quot;,
appVeyorAccountName: &quot;wozzo&quot;,
nuspecFilePath: &quot;nuspec/nuget/Cake.Markdown-Pdf.nuspec&quot;);
</code></pre>
<p>Ensure the last line calls the <code>Run()</code> method.</p>
<p>You should now be able to run the build from the command line</p>
<pre><code class="language-powershell">.\build.ps1
</code></pre>
<h1 id="documentation">Documentation</h1>
<p>Cake.Recipe uses <a href="https://wyam.io">Wyam</a> to auto generate a static site and commit it to the gh-pages branch of the project on GitHub. For those that don't know this is a special branch that GitHub uses to serve up site content at the url <a href="https://%3Cusername%3E.github.io/%3Creponame%3E.">https://&lt;username&gt;.github.io/&lt;reponame&gt;.</a> For example this project's documentation will be at <a href="https://wozzo.github.io/Cake.Markdown-Pdf.">https://wozzo.github.io/Cake.Markdown-Pdf.</a></p>
<p>Add a new folder called <code>docs</code> in the root of the repo. Get wyam installed and run the following from that directory.</p>
<pre><code>wyam new -r docs
</code></pre>
<p>You can then delete the wyam.config file it creates in that directory.</p>
<p>You can now start editing the markdown pages to change the content of the site. I won't go into detail of using wyam as there are plenty of other blogs on that that go outside the scope of this tutorial.</p>
<p>You can preview the site by running this command and then visisting <a href="http://localhost:5080/Cake.Markdown-Pdf">http://localhost:5080/Cake.Markdown-Pdf</a> (case sensitive)</p>
<pre><code class="language-powershell">.\build.ps1 -target preview
</code></pre>
<h1 id="appveyor">AppVeyor</h1>
<p>To use AppVeyor and cake together you'll need to setup several environment variables for your project. This lies outside of the scope of this tutorial but you can figure most of it out by reading through the appveyor.cake/environment.cake files in Cake.Recipe.</p>
<p>The only additional change I needed to do to get the build working was to add an <code>Image:</code> property specifying which <a href="https://www.appveyor.com/docs/build-environment/#build-worker-images">build worker image</a> AppVeyor should use. Because I used Visual Studio 2017 I needed to add the following to my <code>.appveyor.yml</code> file.</p>
<pre><code class="language-yaml">image: Visual Studio 2017
</code></pre>
<h1 id="finishing-up">Finishing up</h1>
<p>Commit the files and push to GitHub. The build should take care of the rest. You should definitely consider getting your project added to the Cake-Contrib team on GitHub. See <a href="https://github.com/cake-contrib/Home">the cake-contrib</a> page for more details.</p>
</content:encoded>
</item>
<item>
<title>Getting Started with Cake.Recipe</title>
<link>https://wbates.net/posts/getting-started-with-cake-recipe</link>
<description><p>I've been playing around with the Cake build tool for a little while now, but having recently had need of a plugin that didn't yet exist I embarked on a journey that lead me to Cake.Recipe.</p></description>
<guid>https://wbates.net/posts/getting-started-with-cake-recipe</guid>
<pubDate>Fri, 25 Aug 2017 00:00:00 GMT</pubDate>
<content:encoded><p>I've been playing around with the Cake build tool for a little while now, but having recently had need of a plugin that didn't yet exist I embarked on a journey that lead me to Cake.Recipe.</p>
<blockquote class="blockquote">
<p>Cake.Recipe is a set of convention based Cake scripts.</p>
</blockquote>
<p>If you check out the Content folder in that repository you'll find a host of pre-written cake scripts that cover a wide range of use cases. They've been designed so that they all work together and can be called through a single cake file that contains a few bits of setup. They're used by most of the addin's under the cake-contrib team. The only thing that was lacking at the start of this journey was documentation about how to use Cake.Recipe, which is why I'm writing this post.</p>
<h1 id="getting-started">Getting started</h1>
<p>Before I knew what Cake.Recipe was I was looking around various repositories looking for good ideas for how to improve the addin I was working on at the time (Cake.Bower), and I started to notice that several of the repositories were missing a build.cake file. The .cake file can of course be called whatever you like, but by default it is called build.cake, so for this to be missing, or rather called setup.cake, I found a bit odd.
Looking into the setup.cake file I found something very different from what I'm used to seeing in build.cake files.</p>
<pre><code class="language-csharp">#load nuget:https://www.myget.org/F/cake-contrib/api/v2?package=Cake.Recipe&amp;prerelease
Environment.SetVariableNames();
BuildParameters.SetParameters(context: Context,
buildSystem: BuildSystem,
sourceDirectoryPath: &quot;./&quot;,
title: &quot;Cake.Bower&quot;,
repositoryOwner: &quot;cake-contrib&quot;,
repositoryName: &quot;Cake.Bower&quot;,
appVeyorAccountName: &quot;cakecontrib&quot;);
BuildParameters.PrintParameters(Context);
ToolSettings.SetToolSettings(context: Context,
dupFinderExcludePattern: new string[] {
BuildParameters.RootDirectoryPath + &quot;/src/Cake.Bower.Tests/*.cs&quot; },
testCoverageFilter: &quot;+[*]* -[xunit.*]* -[Cake.Core]* -[Cake.Testing]* -[*.Tests]* &quot;,
testCoverageExcludeByAttribute: &quot;*.ExcludeFromCodeCoverage*&quot;,
testCoverageExcludeByFile: &quot;*/*Designer.cs;*/*.g.cs;*/*.g.i.cs&quot;);
Build.Run();
</code></pre>
<p>No tasks. None at all.</p>
<p>The top line is the critical one. It fetches the Cake.Recipe scripts and loads each one providing the methods you can see being used here. The bootstrapped build.ps1 that you'd normally download if you followed the instructions on the &quot;Setting up a new project&quot; tutorial page has been modified to use a default file of setup.cake purely as a convention to distinguish between normal cake builds and recipe builds. You can then modify the parameters in the setup.cake file to achieve the desired result. There are close to 80 different parameters that can be set here that can be used to configure the ~40 odd tasks that cake.recipe provides.</p>
<h1 id="parameters">Parameters</h1>
<p>Take a look at the parameters.cake file to see what options are available to you out of the box. These can all be set either in the SetParameters or using environment variables.
Tasks
The tasks.cake file paradoxically doesn't contain any tasks, but does give you a handy list of tasks that are configured. It also provides you with the first way of overriding cake.recipe functionality by changing the value assigned to these properties, should one of the tasks need tweaking to suit your needs. More on that in a bit.
The tasks are set in a cake file specific to the purpose of the task, i.e. the tasks related to testing are all in testing.cake, the tasks for code analysis are in analyzing.cake, etc. The best starting point for figuring out what is going is in the build.cake file, currently on line 354</p>
<pre><code class="language-csharp">BuildParameters.Tasks.DefaultTask = Task(&quot;Default&quot;)
.IsDependentOn(&quot;Package&quot;);
</code></pre>
<p>We can start to see the intended targets for a full build. Don't get thrown by the lack of dependencies here. For example the Package task target doesn't only depend on the Export-Release-Notes task. Look above the task definition and find the SetupTasks method. In there you can see more dependencies being set.</p>
<p>This should start to give you an idea what will happen when you run one of the targets listed here. The default is probably the best place to start. If you need a safe project to test it out on, fork Cake.Bower and run .\build.ps1. It will take a while on the first run as it has to download all of the tools and addins required to run.</p>
<h1 id="cake.bower-build-running">Cake.Bower build running</h1>
<p>The tasks that are run by the default target are</p>
<pre><code>Task Duration
---------------------------------------------------------
Export-Release-Notes Skipped
Show-Info 00:00:00.0093039
Print-AppVeyor-Environment-Variables Skipped
Clean 00:00:00.0292549
Restore 00:00:01.0742619
Build 00:00:05.7580859
DupFinder 00:00:09.2430267
InspectCode 00:00:22.4277417
Analyze 00:00:00.0042806
Install-ReportGenerator 00:00:02.2837979
Install-ReportUnit 00:00:02.2187105
Install-OpenCover 00:00:02.2890312
Test-NUnit Skipped
Test-xUnit 00:00:06.9958299
Test-MSTest Skipped
Test-VSTest Skipped
Test-Fixie Skipped
Test 00:00:00.0065763
Create-NuGet-Packages 00:00:00.7237132
Create-Chocolatey-Packages Skipped
Package 00:00:00.0050486
Default 00:00:00.0055089
---------------------------------------------------------
Total: 00:00:53.0741721
</code></pre>
<p>You can see some of the tasks were skipped. This means the task appeared in the list of dependencies but the criteria to run it (defined using the <code>WithCriteria</code> method) were not met. For example the Test-NUnit task will only run if NUnit tests are found. There are none in Cake.Bower so it gets skipped. If I wanted to always skip a step I can find the parameter that governs whether it runs and set it in the setup.cake file. For example if I wanted to disable the InspectCode step I would change my setup.cake's call to BuildParameters.SetParameters to the following.</p>
<pre><code class="language-csharp">BuildParameters.SetParameters(context: Context,
buildSystem: BuildSystem,
sourceDirectoryPath: &quot;./&quot;,
title: &quot;Cake.Bower&quot;,
repositoryOwner: &quot;cake-contrib&quot;,
repositoryName: &quot;Cake.Bower&quot;,
appVeyorAccountName: &quot;cakecontrib&quot;,
shouldRunInspectCode: false);
</code></pre>
<p>Run it again and this time the task will be skipped but the rest should be the same.</p>
<h1 id="replacing-a-task">Replacing a task</h1>
<p>With Cake, once a Task has been created you can't create another with the same name. Calling the .Does(...) method adds an additional action but doesn't replace the existing one. If you want to replace the tasks actions with something completely different you first need to clear the existing actions. To clear the InspectCode actions use the following</p>
<pre><code class="language-csharp">BuildParameters.Tasks.InspectCodeTask.Task.Actions.Clear();
</code></pre>
<p>I'm now free to call the .Does(...) method with the action I want it to undertake and be sure it is the only thing it will do, for example:</p>
<pre><code class="language-csharp">BuildParameters.Tasks.InspectCodeTask.Does( () =&gt; Information(&quot;And now for something completely different...&quot;));
</code></pre>
<p>Running .\build.ps1 now yields the following output when you get to the InspectCode task.</p>
<pre><code>========================================
InspectCode
========================================
Executing task: InspectCode
And now for something completely different...
Finished executing task: InspectCode
</code></pre>
<p>You are also free to create your own tasks and add them as dependencies to the existing ones. Something like this...</p>
<pre><code class="language-csharp">Task(&quot;MakeTea&quot;).Does( () =&gt; Information(&quot;Make Tea not Love&quot;));
BuildParameters.Tasks.InspectCodeTask.Task.Actions.Clear();
BuildParameters.Tasks.InspectCodeTask
.IsDependentOn(&quot;MakeTea&quot;)
.Does( () =&gt; Information(&quot;And now for something completely different...&quot;));
</code></pre>
<p>which yields...</p>
<pre><code>========================================
MakeTea
========================================
Executing task: MakeTea
Make Tea not Love
Finished executing task: MakeTea
========================================
InspectCode
========================================
Executing task: InspectCode
And now for something completely different...
Finished executing task: InspectCode
</code></pre>
<p>And of course if you find that you need loads of additional scripts for a particular use case consider a pull request into Cake.Recipe</p>
</content:encoded>
</item>
<item>
<title>Setting up a Wyam blog with Cake.Recipe and GitHub pages</title>
<link>https://wbates.net/posts/setting-up-a-wyam-blog-with-cake-recipe-and-github-pages</link>
<description><p>I first saw <a href="https://wyam.io">Wyam</a> in action when I was working on the Cake.Recipe blog series, and was so impressed I decided to move the entire blog to it using GitHub pages to serve it up.
This post is going to document that process for others to follow.
One important distinction to get out of the way is the difference between a personal github page and a project page.</p></description>
<guid>https://wbates.net/posts/setting-up-a-wyam-blog-with-cake-recipe-and-github-pages</guid>
<pubDate>Thu, 24 Aug 2017 00:00:00 GMT</pubDate>
<content:encoded><p>I first saw <a href="https://wyam.io">Wyam</a> in action when I was working on the Cake.Recipe blog series, and was so impressed I decided to move the entire blog to it using GitHub pages to serve it up.
This post is going to document that process for others to follow.
One important distinction to get out of the way is the difference between a personal github page and a project page.</p>
<p>Personal page url's take the form <code>https://&lt;username&gt;.github.io/</code> while project pages take the form <code>https://&lt;username&gt;.github.io/&lt;project_name&gt;</code>. Project pages can be served from any branch in the repository but personal pages can only be served from the master branch. Because of this there will be some slightly different instructions/hacks depending on whether you intend to use this process to create a personal or project page.</p>
<h1 id="setting-up-the-repositories">Setting up the repositories</h1>
<h2 id="personal-pages">Personal pages</h2>
<p>Create a repository which matches the pattern <code>&lt;username&gt;.github.io</code>, e.g. mine is <a href="https://github.com/wozzo/wozzo.github.io">wozzo.github.io</a>. For personal pages the <code>master</code> branch must be the one that contains the output from the Wyam build. Make a single commit to the <code>master</code> branch of this empty repository.</p>
<p>Use the following commands to create a new orphaned branch called <code>develop</code>. It must be called <code>develop</code> for cake.recipe to publish the documentation using AppVeyor (remember I mentioned some hacks?).</p>
<pre><code>git checkout --orphan develop
</code></pre>
<p>Do an initial commit on this branch too. The majority of our work will be in this <code>develop</code> branch, while we leave the <code>master</code> branch to AppVeyor. Use the branch name <code>develop</code> elsewhere in this tutorial where I use the term &quot;working branch&quot;</p>
<h2 id="project-pages">Project pages</h2>
<p>If a branch exists called <code>gh-pages</code> then GitHub will use this branch to serve your sites content.
Create a new orphaned branch with the following command</p>
<pre><code>git checkout --orphan gh-pages
</code></pre>
<p>Do an initial commit on this branch and then switch back to the master branch. Use the branch name <code>master</code> elsewhere in this tutorial where I use the term &quot;working branch&quot;.</p>
<h1 id="starting-with-wyam">Starting with Wyam</h1>
<p>Not strictly a required step, but it might make life easier. Install wyam on your machine - to see how to get Wyam go to their <a href="https://wyam.io/docs/usage/obtaining">obtaining</a> page.
Note: I had to add %appdata%/local/wyam to my path to allow powershell to be able to run wyam commands.</p>
<h1 id="creating-your-blog">Creating your blog</h1>
<p>Wyam uses recipes to setup the outline of your site quickly. Create a folder called &quot;docs&quot; in the root of your working branch. In your command line interface of choice browse to the docs folder and run the following command</p>
<pre><code>wyam new --recipe blog
</code></pre>
<p>This will create a new folder called input and setup a couple of pages under there. Follow the next few steps to see what this blog will look like, then we'll start adding some content. The recipe will also add a config.wyam file, but we won't be needing this. It is safe to delete.</p>
<h1 id="getting-cake.recipe">Getting Cake.Recipe</h1>
<p>Go to <a href="https://github.com/cake-contrib/Cake.Recipe">Cake.Recipe</a> on GitHub and download the following files to the root of your working branch.</p>
<ul>
<li>.appveyor.yml</li>
<li>.gitignore</li>
<li>build.ps1</li>
<li>config.wyam</li>
<li>GitReleaseManager.yaml</li>
<li>setup.cake</li>
</ul>
<h2 id="setup-cake.recipe">Setup Cake.Recipe</h2>
<p>Open up the <code>setup.cake</code> file in your favourite text editor and edit the parameters so that it reflects the name of your blog.</p>
<pre><code class="language-csharp">BuildParameters.SetParameters(context: Context,
buildSystem: BuildSystem,
title: &quot;Wozzo.Blog&quot;,
repositoryOwner: &quot;wozzo&quot;,
repositoryName: &quot;wozzo.github.io&quot;,
appVeyorAccountName: &quot;wozzo&quot;,
wyamRecipe: &quot;Blog&quot;,
wyamTheme: &quot;CleanBlog&quot;);
</code></pre>
<p>If you're working on a personal page then you may also want to provide an additional parameter to change the virtual directory the website is compiled for. By default Cake will use the <code>repositoryName</code> value.
For a personal site you add the following parameter.</p>
<pre><code>webLinkRoot: &quot;/&quot;
</code></pre>
<p>I've also added two additional parameters telling Wyam that it should use the blog recipe and Phantom theme.</p>
<h2 id="setup-appveyors-config">Setup AppVeyor's config</h2>
<p>We won't setup AppVeyor just yet, but we can start to get things in place with the <code>.appveyor.yml</code> config file. Open it up and change the target of the build to <code>Force-Publish-Documentation</code>.</p>
<pre><code>#---------------------------------#
# Build Script #
#---------------------------------#
build_script:
- ps: .\build.ps1 -Target Force-Publish-Documentation
</code></pre>
<p>If you're working on a personal page you should also remove <code>master</code> from the branch whitelist</p>
<pre><code>#---------------------------------#
# Branches to build #
#---------------------------------#
branches:
# Whitelist
only:
- develop
- /release/.*/
- /hotfix/.*/
</code></pre>
<p>At this point you should make another commit. If you don't have any commits GitVersion will throw an exception when attempting to run the cake build.</p>
<h1 id="previewing-the-site-locally">Previewing the site locally</h1>
<p>Time to see what it looks like. The cake.recipe Wyam script comes with a Preview task which will fire up a local web server, and run wyam with a watch on your files. This means you can view the site in your browser, edit your posts and the browser will refressh so you can see your changes immediately.</p>
<p>Run the following command in the root of your repository.</p>
<pre><code>.\build.ps1 -target preview
</code></pre>
<p>The first run may take a while as it is going to get all the tools it requires, future runs should be much faster.
Look at the end of the build log and you should find a message like the following</p>
<pre><code>Preview server listening on port 5080 and serving from path file:///.../wozzo
.blog/BuildArtifacts/Documentation with virtual directory Wozzo.Blog and LiveReload support
Watching paths(s) file:///.../content, theme, input
Watching configuration file file:///.../wozzo.blog/config.wyam
Hit any key to exit
</code></pre>
<p>The key parts are the port and virtual directory. For personal sites there will be no virtual directory. Replace them in the following url in your browser.</p>
<pre><code>http://localhost:&lt;port&gt;/&lt;virtual_directory&gt;/
# Example
http://localhost:5080/Wozzo.Blog
</code></pre>
<p>The virtual directory is case sensitive by the way ;-)</p>
<p><img src="../assets/images/wyam-in-browser.png" class="img-fluid" alt="Wyam blog in the browser inception" /></p>
<p>Isn't it pretty. For some reason when I tried to use the SolidState theme I got exceptions due to missing files. If you get it working, please get in touch.</p>
<h1 id="appveyor">AppVeyor</h1>
<p>Wyam's site has some really great <a href="https://wyam.io/docs/deployment/appveyor">instructions for setting up Wyam with AppVeyor</a>, and it's worth looking through but we're going to do things differently to get the benefits of working with Cake.Recipe.</p>
<p>Cake.Recipe has everything setup with either parameters, or Environment variables. The credentials and settings for most of what we'll need is done through environment variables.
Log in to AppVeyor and add a new project, select your repository. If doing a personal site then change the default branch to <code>develop</code> on the project settings page.
Go to the project settings page and find the Environment section. We'll need to add three variables here for Cake.Recipe to be able to deploy our site.</p>
<pre><code># Url of the git repository
WYAM_DEPLOY_REMOTE = https://github.com/wozzo/wozzo.github.io
# Branch to deploy the site too
WYAM_DEPLOY_BRANCH = master / gh-pages
# GitHub personal access token
WYAM_ACCESS_TOKEN = **************************************
</code></pre>
<p>The access token must be kept secret. You can create one by following these <a href="https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/">instructions</a>. This token will need the repo -&gt; public_repo scope.</p>
<p><img src="../assets/images/appveyor-environment-variables.png" class="img-fluid" alt="Setting AppVeyor environment variables" /></p>
<h1 id="get-blogging">Get Blogging</h1>
<p>That's all the setup complete. Now commit any uncommitted changes and push your working branch to the remote. AppVeyor should pick it up and run it deploying your site.</p>
<p>If there are any problems check the AppVeyor build log. In particular the <code>Printing Build Parameters...</code> section can provide useful insight.</p>
</content:encoded>
</item>
<item>
<title>Testing a Cake Addin</title>
<link>https://wbates.net/posts/baking-a-cake-adding-part-2-testing</link>
<description><p>Tasting the cake would be the obvious pun here, but hopefully by now you've gotten stuck in and had more than a slice. Today I'm going to walkthrough setting up some simple unit tests on the Markdown-Pdf addin I wrote in <a href="http://wbates.net/baking-a-cake-addin/">part 1</a>.
In your solution add a new .Net framework class library project. Add the Cake.Testing nuget package to this solution along with your testing framework of choice. I'll be using <a href="https://xunit.github.io/">xUnit</a> along with the <a href="https://github.com/shouldly/shouldly">Shouldly</a> assertion package.</p></description>
<guid>https://wbates.net/posts/baking-a-cake-adding-part-2-testing</guid>
<pubDate>Mon, 21 Aug 2017 00:00:00 GMT</pubDate>
<content:encoded><p>Tasting the cake would be the obvious pun here, but hopefully by now you've gotten stuck in and had more than a slice. Today I'm going to walkthrough setting up some simple unit tests on the Markdown-Pdf addin I wrote in <a href="http://wbates.net/baking-a-cake-addin/">part 1</a>.
In your solution add a new .Net framework class library project. Add the Cake.Testing nuget package to this solution along with your testing framework of choice. I'll be using <a href="https://xunit.github.io/">xUnit</a> along with the <a href="https://github.com/shouldly/shouldly">Shouldly</a> assertion package.</p>
<h2 id="fixture">Fixture</h2>
<p>Create a new class for your test fixture called MarkdownPdfRunnerFixture. This class should inherit from Cake.Testing's ToolFixture&lt;TSetting&gt; where TSetting is the settings class from the addin that we want to test.
Add a constructor that calls the base constructor passing in a string with the tool's name.</p>
<pre><code class="language-csharp">public MarkdownPdfFixture() : base(&quot;markdown-pdf&quot;) { }
</code></pre>
<p>then add a property which will hold an action for configuring the settings</p>
<pre><code class="language-csharp">public Action&lt;MarkdownPdfRunnerSettings&gt; RunnerSettings;
</code></pre>
<p>Finally add override the RunTool method and use it to instantiate a runner and call its Run method</p>
<pre><code class="language-csharp">protected override void RunTool()
{
var tool = new MarkdownPdfRunner(FileSystem, Environment, ProcessRunner, Tools);
tool.Run(RunnerSettings);
}
</code></pre>
<p>That's the fixture complete. We're going to try various settings out and then call the Run() method on the fixture, which will in turn call the RunTool method we just created and will return a <a href="http://cakebuild.net/api/Cake.Testing.Fixtures/ToolFixtureResult/">ToolFixtureResult</a> to us. We'll be able to query this result and check that the correct arguments were sent to the tool.</p>
<h2 id="tests">Tests</h2>
<p>I'm not going to go through each test I'll write for this class. You can check the code <a href="https://github.com/wozzo/Cake_Addin_Blog_Posts/tree/master/Part%202">here</a> if you want to see them all. I'll just showcase a couple of examples.
In the first test the configurator is null, so default settings should be used. In this case that means that no arguments are to get passed to the markdown-pdf tool so the Args should be empty.</p>
<pre><code class="language-csharp">[Fact]
public void No_Settings_Should_Use_Correct_Argument_Provided_In_MarkdownPdfRunnerSettings()
{
fixture.RunnerSettings = null;
var result = fixture.Run();
result.Args.ShouldBe(&quot;&quot;);
}
</code></pre>
<p>Here we use the WithFilePath method and pass in a test string. Because the desired action here is to pass that string as the only argument that's what we check for</p>
<pre><code class="language-csharp">[Fact]
public void WithFilePath_Settings_Should_Use_Correct_Argument_Provided_In_MarkdownPdfRunnerSettings()
{
fixture.RunnerSettings = s =&gt; s.WithFilePath(TestFilePath);
var result = fixture.Run();
result.Args.ShouldBe(TestFilePath);
}
</code></pre>
<p>The WithHelp() method should use the --help switch</p>
<pre><code class="language-csharp">[Fact]
public void WithHelp_Settings_Should_Use_Correct_Argument_Provided_In_MarkdownPdfRunnerSettings()
{
fixture.RunnerSettings = s =&gt; s.WithHelp();
var result = fixture.Run();
result.Args.ShouldBe(&quot;--help&quot;);
}
</code></pre>
<p>Finally a combination test that checks that all the correct settings are applied and in the correct order.</p>
<pre><code class="language-csharp">[Fact]
public void WithFilePath_And_CssFilePath_Settings_Should_Use_Correct_Argument_Provided_In_MarkdownPdfRunnerSettings()
{
fixture.RunnerSettings = s =&gt; s.WithFilePath(TestFilePath).WithCssPath(TestCssFilePath);
var result = fixture.Run();
result.Args.ShouldBe($&quot;--css-path {TestCssFilePath} {TestFilePath}&quot;);
}
</code></pre>
<h2 id="run-the-tests">Run the tests</h2>
<p>Hopefully you've added a cake build script to your project by now, in which case you can add the following to it to get cake to run your unit tests as part of a task</p>
<pre><code class="language-csharp">#tool &quot;xunit.runner.console&quot;
...
var testResultsPath = MakeAbsolute(Directory(artifacts + &quot;./test-results&quot;));
var testAssemblies = new List&lt;FilePath&gt; { MakeAbsolute(File(&quot;./src/Cake.Markdown-Pdf.Tests/bin/&quot; + configuration + &quot;/Cake.Markdown-Pdf.Tests.dll&quot;)) };
Task(&quot;Run-Unit-Tests&quot;)
.IsDependentOn(&quot;Build&quot;)
.Does(() =&gt;
{
CreateDirectory(testResultsPath);
var settings = new XUnit2Settings {
XmlReportV1 = true,
NoAppDomain = true,
OutputDirectory = testResultsPath,
};
settings.ExcludeTrait(&quot;Category&quot;, &quot;Integration&quot;);
XUnit2(testAssemblies, settings);
});
</code></pre>
<p>Happy testing :)</p>
</content:encoded>
</item>
<item>
<title>Baking a Cake Addin</title>
<link>https://wbates.net/posts/baking-a-cake-addin-part-1</link>
<description><p>No blog post on Cake is complete without a few puns, and bonus, now while you're googling technical stuff you always get a few recipes for delicious cakes thrown in. So what is cake and why do we need add ins?</p></description>
<guid>https://wbates.net/posts/baking-a-cake-addin-part-1</guid>
<pubDate>Sun, 20 Aug 2017 00:00:00 GMT</pubDate>
<content:encoded><p>No blog post on Cake is complete without a few puns, and bonus, now while you're googling technical stuff you always get a few recipes for delicious cakes thrown in. So what is cake and why do we need add ins?</p>
<blockquote class="blockquote">
<p><a href="https://www.cakebuild.net/">Cake (C# Make)</a> is a cross platform build automation system with a C# DSL to do things like compiling code, copy files/folders, running unit tests, compress files and build NuGet packages.</p>
</blockquote>
<p>I've recently been switching my organisation from a psake build process to Cake. The psake process had been in place for a long time, but since psake is no longer actively maintained and doesn't support the latest version of Visual Studio we knew we had to move to something different to allow us to use the latest .Net framework and C# language features. Enter Cake. Cake scripts are written in C#, and support using methods from referenced add ins that have been compiled into a dll. As C# developers being able to write our build process in C# is a no brainer right? You still use powershell to compile and run the cake script, but that's all taken care of by the bootstrapped build.ps1 that cake provides. Anyway we're here to talk about cake addins; if you need more information on getting started with cake please visit <a href="https://www.cakebuild.net/docs/tutorials/getting-started">cakebuild.net</a>.</p>
<p>At the time of writing there are 166 cake addins available performing a variety of jobs. We're going to write an addin which runs a command line tool available from npm. Based on <a href="https://github.com/cake-contrib/Cake.Npm/issues/16">this issue</a> I decided to make a markdown-pdf addin for this walkthrough.</p>
<p>Start with a new .NET framework class library project in Visual Studio and add the Cake.Core Nuget project.
We're going to start with two classes and an interface which will form the basics of our addin.</p>
<pre><code class="language-csharp">namespace Cake.Markdown_Pdf
{
public interface IMarkdownPdfRunner
{
}
public class MarkdownPdfRunnerSettings : ToolSettings
{
}
public class MarkdownPdfRunner : Tool&lt;MarkdownPdfRunnerSettings&gt;, IMarkdownPdfRunner
{
}
}
</code></pre>
<p>The interface will define the commands that can be run from the cake script.
The settings describes the command to be run and will generate the arguments that get passed into it.
The runner will implement the interface as well as helping cake to locate the command we want to run.</p>
<h2 id="markdownpdfrunner-basics">MarkdownPdfRunner basics</h2>
<p>You'll need a constructor that calls the Tool&lt;TSettings&gt; base constructor.</p>
<pre><code class="language-csharp">public MarkdownPdfRunner(IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, IToolLocator tools)
: base(fileSystem, environment, processRunner, tools) { }
</code></pre>
<p>Then override the GetToolName() method and get it to return a string giving the name of your tool</p>
<pre><code class="language-csharp">protected override string GetToolName() =&gt; &quot;Markdown-pdf Runner&quot;;
</code></pre>
<p>Override GetToolExecutableNames(), here is where we provide the names of the files the ToolLocator needs to look for. To find out go to %appdata%\roaming\npm on your machine.</p>
<p><img src="../assets/images/finding-npm-commands.png" class="img-fluid" alt="Finding Npm commands" /></p>
<p>This will usually be the normal command you'd run suffixed with .cmd followed without the suffix</p>
<pre><code class="language-csharp">protected override IEnumerable&lt;string&gt; GetToolExecutableNames() =&gt; new[] { &quot;markdown-pdf.cmd&quot;, &quot;markdown-pdf&quot; };
</code></pre>
<p>I'm a big fan of expression bodied methods for these trivial one liners in case you hadn't realised ;)
One final thing to add at this stage is a method that will process the settings and convert them to arguments, or more accurately a <a href="http://cakebuild.net/api/Cake.Core.IO/ProcessArgumentBuilder/">ProcessArgumentBuilder</a>
All it will really do is call a method on the settings object that knows how to do that for the specific settings type that we've passed in.</p>
<pre><code class="language-csharp">protected static ProcessArgumentBuilder GetSettingsArguments(MarkdownPdfRunnerSettings settings)
{
var args = new ProcessArgumentBuilder();
settings?.Evaluate(args);
return args;
}
</code></pre>
<p>Put it all together and so far our runner class should look like this. We'll start adding some commands to it shortly.</p>
<pre><code class="language-csharp"> public class MarkdownPdfRunner : Tool&lt;MarkdownPdfRunnerSettings&gt;, IMarkdownPdfRunner
{
public MarkdownPdfRunner(IFileSystem fileSystem, ICakeEnvironment environment, IProcessRunner processRunner, IToolLocator tools)
: base(fileSystem, environment, processRunner, tools) { }
protected override string GetToolName() =&gt; &quot;Markdown-pdf Runner&quot;;
protected override IEnumerable&lt;string&gt; GetToolExecutableNames() =&gt; new[] { &quot;markdown-pdf.cmd&quot;, &quot;markdown-pdf&quot; };
protected static ProcessArgumentBuilder GetSettingsArguments(MarkdownPdfRunnerSettings settings)
{
var args = new ProcessArgumentBuilder();
settings?.Evaluate(args);
return args;
}
}
</code></pre>
<h2 id="markdownpdfrunnersettings">MarkdownPdfRunnerSettings</h2>
<p>This class is going to contain the properties that apply to all the commands we're going to use and the instructions for converting those into command line arguments. Markdown-pdf doesn't have separate &quot;commands&quot; just options so we only need one settings type. See <a href="https://github.com/cake-contrib/Cake.Bower">Cake.Bower</a> for an example of a plugin that uses multiple settings types.
We now need to know what options are available to us through markdown-pdf's CLI. Handily this is available <a href="https://www.npmjs.com/package/markdown-pdf#cli-interface">here</a>.</p>
<pre><code>Usage: markdown-pdf [options] &lt;markdown-file-path&gt;
Options:
-h, --help output usage information
-V, --version output the version number
&lt;markdown-file-path&gt; Path of the markdown file to convert
-c, --cwd [path] Current working directory
-p, --phantom-path [path] Path to phantom binary
-h, --runnings-path [path] Path to runnings (header, footer)
-s, --css-path [path] Path to custom CSS file
-z, --highlight-css-path [path] Path to custom highlight-CSS file
-m, --remarkable-options [json] Options to pass to Remarkable
-f, --paper-format [format] 'A3', 'A4', 'A5', 'Legal', 'Letter' or 'Tabloid'
-r, --paper-orientation [orientation] 'portrait' or 'landscape'
-b, --paper-border [measurement] Supported dimension units are: 'mm', 'cm', 'in', 'px'
-d, --render-delay [millis] Delay before rendering the PDF
-t, --load-timeout [millis] Timeout before the page is rendered in case `page.onLoadFinished` isn't fired
-o, --out [path] Path of where to save the PDF
</code></pre>
<p>I'm going to add a boolean property for each of the help and version switches, and string/int/enum properties for each of the other values as appropriate, except for the working directory. The working directory is already a property on the ToolSettings we inherited from. Our MarkdownPdfRunnerSettings class should now look like this</p>
<pre><code class="language-csharp">public class MarkdownPdfRunnerSettings : ToolSettings
{
public bool Help { get; set; }
public bool Version { get; set; }
public string FilePath { get; set; }
public string PhantomPath { get; set; }
public string RunningsPath { get; set; }
public string CssPath { get; set; }
public string HighlightCssPath { get; set; }
public string RemarkableOptions { get; set; }
public MarkdownPdfPaperFormat PaperFormat { get; set; }
public MarkdownPdfOrientation Orientation { get; set; }
public string PaperBorder { get; set; }
public int RenderDelay { get; set; }
public int LoadTimeout { get; set; }
public string OutFilePath { get; set; }
}
public enum MarkdownPdfPaperFormat
{
None,
A3,
A4,
A5,
Legal,
Letter,
Tabloid
}
public enum MarkdownPdfOrientation
{
None,
Portrait,
Landscape
}
</code></pre>
<p>I've obviously made some decisions about what type to use to store details about the property, such as an enum for PaperFormat and Orientation and you'll need to make similar decisions when creating your adding depending on what options are available for the tool you're creating an addin for.
We need to add a method that will evaulate these properties and produce our ProcessArgumentBuilder for use in the runner. This is where the evaluate command comes in. Effectively it will query each property and the relevant string to the ProcessArgumentBuilder passed in.</p>
<pre><code class="language-csharp">internal void Evaluate(ProcessArgumentBuilder args)
{
if (Help)
args.Append(&quot;--help&quot;);
if (Version)
args.Append(&quot;--version&quot;);
if (!string.IsNullOrWhiteSpace(PhantomPath))
args.Append($&quot;--phantom-path {PhantomPath}&quot;);
if (!string.IsNullOrWhiteSpace(RunningsPath))
args.Append($&quot;--runnings-path {RunningsPath}&quot;);
if (!string.IsNullOrWhiteSpace(CssPath))
args.Append($&quot;--css-path {CssPath}&quot;);
if (!string.IsNullOrWhiteSpace(HighlightCssPath))
args.Append($&quot;--highlight-css-path {HighlightCssPath}&quot;);
if (!string.IsNullOrWhiteSpace(RemarkableOptions))
args.Append($&quot;--remarkable-options {RemarkableOptions}&quot;);
if (PaperFormat != MarkdownPdfPaperFormat.None)
args.Append($&quot;--paper-format {PaperFormat}&quot;);
if (Orientation != MarkdownPdfOrientation.None)
args.Append($&quot;--paper-orientation {Orientation}&quot;);
if (!string.IsNullOrWhiteSpace(PaperBorder))
args.Append($&quot;--paper-border {PaperBorder}&quot;);
if (RenderDelay &gt; 0)
args.Append($&quot;--render-delay {RenderDelay}&quot;);
if (LoadTimeout &gt; 0)
args.Append($&quot;--load-timeout {LoadTimeout}&quot;);
if (!string.IsNullOrWhiteSpace(OutFilePath))
args.Append(OutFilePath);
if (!string.IsNullOrWhiteSpace(FilePath))
args.Append(FilePath);
}
</code></pre>
<h2 id="setting-properties">Setting properties</h2>
<p>I'm going to add a set of fluent extension methods for setting these properties so we can set several of them at once in a single lambda expression.</p>
<pre><code class="language-csharp">public static class MarkdownPdfRunnerSettingsExtensions
{
public static MarkdownPdfRunnerSettings UseWorkingDirectory(this MarkdownPdfRunnerSettings settings, DirectoryPath workingDirectory)
{
settings.WorkingDirectory = workingDirectory;
return settings;
}
public static MarkdownPdfRunnerSettings WithHelp(this MarkdownPdfRunnerSettings settings)
{
settings.Help = true;
return settings;
}
public static MarkdownPdfRunnerSettings WithVersion(this MarkdownPdfRunnerSettings settings)
{
settings.Version = true;
return settings;
}
public static MarkdownPdfRunnerSettings WithPhantomPath(this MarkdownPdfRunnerSettings settings, string phantomPath)
{
settings.PhantomPath = phantomPath;
return settings;
}
public static MarkdownPdfRunnerSettings WithRunningsPath(this MarkdownPdfRunnerSettings settings, string runningsPath)
{
settings.RunningsPath = runningsPath;
return settings;
}
public static MarkdownPdfRunnerSettings WithCssPath(this MarkdownPdfRunnerSettings settings, string cssPath)
{
settings.CssPath = cssPath;
return settings;
}
public static MarkdownPdfRunnerSettings WithHighlightCssPath(this MarkdownPdfRunnerSettings settings, string highlightCssPath)
{
settings.HighlightCssPath = highlightCssPath;
return settings;
}
public static MarkdownPdfRunnerSettings WithRemarkableOptions(this MarkdownPdfRunnerSettings settings, string remarkableOptions)
{
settings.RemarkableOptions = remarkableOptions;
return settings;
}
public static MarkdownPdfRunnerSettings WithPaperFormat(this MarkdownPdfRunnerSettings settings, MarkdownPdfPaperFormat paperFormat)
{
settings.PaperFormat = paperFormat;
return settings;
}
public static MarkdownPdfRunnerSettings WithOrientation(this MarkdownPdfRunnerSettings settings, MarkdownPdfOrientation orientation)
{
settings.Orientation = orientation;
return settings;
}
public static MarkdownPdfRunnerSettings WithPaperBorder(this MarkdownPdfRunnerSettings settings, string border)
{
settings.PaperBorder = border;
return settings;
}
public static MarkdownPdfRunnerSettings WithRenderDelay(this MarkdownPdfRunnerSettings settings, int renderDelay)
{
settings.RenderDelay = renderDelay;
return settings;
}
public static MarkdownPdfRunnerSettings WithLoadTimeout(this MarkdownPdfRunnerSettings settings, int loadTimeout)
{
settings.LoadTimeout = loadTimeout;
return settings;
}
public static MarkdownPdfRunnerSettings WithFilePath(this MarkdownPdfRunnerSettings settings, string filePath)
{
settings.FilePath = filePath;
return settings;
}
public static MarkdownPdfRunnerSettings WithOutFilePath(this MarkdownPdfRunnerSettings settings, string outFilePath)
{
settings.OutFilePath = outFilePath;
return settings;
}
}
</code></pre>
<h2 id="commands">Commands</h2>
<p>Finally we're going to add the commands to process the settings and run the command line tool.
Add the following two definitions to the interface (again for addins that will require more than one command use Cake.Bower as an example)</p>
<pre><code class="language-csharp">IMarkdownPdfRunner Run(Action&lt;MarkdownPdfRunnerSettings&gt; configure = null);
IMarkdownPdfRunner Run(MarkdownPdfRunnerSettings settings);
</code></pre>
<p>Then to the MarkdownPdfRunner class to fill in the implementation
The first method will create a new settings object and run the lambda passed in before passing on the responsibility for execution to the other method.</p>
<pre><code class="language-csharp">public IMarkdownPdfRunner Run(Action&lt;MarkdownPdfRunnerSettings&gt; configure = null)
{
var settings = new MarkdownPdfRunnerSettings();
configure?.Invoke(settings);
return Run(settings);
}
</code></pre>
<p>The method accepting a settings object will use our GetSettingsArguments method from earlier to build the ProcessArgumentBuilder and pass that on to the base classes Run method which will run the tool passing in the arguments given</p>
<pre><code class="language-csharp">public IMarkdownPdfRunner Run(MarkdownPdfRunnerSettings settings)
{
var args = GetSettingsArguments(settings);
Run(settings, args);
return this;
}
</code></pre>
<p>The only thing that's missing now (apart from a build script, unit tests, etc.) is the ability to run this from a cake file.
In order to call any of this from cake you need a cake alias.
I'm going to show two different options for this part, one using a CakeMethodAlias and the other a CakePropertyAlias. For this particular addin the CakeMethodAlias is probably the most suitable since there aren't different commands which the markdown-pdf tool accepts, but for something like npm, bower, gulp, etc which have subcommands the property alias approach is the way to go. The difference between the two is that a CakePropertyAlias can't accept arguments.</p>
<h3 id="cakemethodalias">CakeMethodAlias</h3>
<p>Here we'll create a new static class with an ICakeContext extension method. My adding the CakeMethodAlias attribute this method will be available to us from our cake script. This method will instantiate a runner and call its Run method.</p>
<pre><code class="language-csharp">public static class MarkdownPdfRunnerAliases
{
[CakeMethodAlias]
public static IMarkdownPdfRunner MarkdownPdf(this ICakeContext context,
Action&lt;MarkdownPdfRunnerSettings&gt; configure)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
var runner = new MarkdownPdfRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools);
return runner.Run(configure);
}
}
</code></pre>
<p>This addin is now ready to be used in a cake script as below.</p>
<pre><code class="language-csharp">#reference &quot;./bin/Cake.Markdown-Pdf.dll&quot; // Use the relative path to your Cake.Markdown-Pdf.dll file built by Visual Studio.
var target = Argument(&quot;target&quot;, &quot;Default&quot;);
Task(&quot;Default&quot;)
.Does(() =&gt;
{
MarkdownPdf(s =&gt; s.WithFilePath(&quot;README.md&quot;));
});
RunTarget(target);
</code></pre>
<h3 id="cakepropertyalias">CakePropertyAlias</h3>
<p>This approach will return a new MarkdownPdfRunner. This exposes all of the public methods on the runner to the cake script through the object, which is why this approach is more useful to addins that have multiple commands (e.g. bower's install, update, and uninstall commands etc.)</p>
<pre><code class="language-csharp">[CakePropertyAlias]
public static IMarkdownPdfRunner MarkdownPdf(this ICakeContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
return new MarkdownPdfRunner(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools);
}
</code></pre>
<p>This changes the call from <code>MarkdownPdf(s =&gt; s.WithFilePath(&quot;README.md&quot;));</code> to <code>MarkdownPdf.Run(s =&gt; s.WithFilePath(&quot;README.md&quot;));</code>
A subtle difference but as stated, important if there's more than one command that needs to be run.</p>
<h2 id="final-steps">Final steps</h2>
<ul>
<li>Check out Cake.Testing for some utilities to help with testing these scripts. I'll write a follow up blog post soon with more details on how to unit test this addin.
Add to nuget, and then you can change the #reference line to #addin and your script will automatically download it when run.</li>
<li>Setup a cake script for this project.</li>
<li>Check out <a href="https://github.com/cake-contrib/Home">Cake-Contrib on Github</a> and consider adding your addin on there. I started with Cake.Bower, and decided to write this blog post off the back of that.</li>
</ul>
<p>The resulting addin from this blog post can be found at <a href="https://github.com/wozzo/Cake.Markdown-Pdf">Wozzo/Cake.Markdown-Pdf on github</a> and the blog series can be found at <a href="https://github.com/wozzo/Cake_Addin_Blog_Posts">Wozzo/Cake_Addin_Blog_Posts</a></p>
</content:encoded>
</item>
<item>
<title>Seeding data using Entity Framework in ASP.NET Core</title>
<link>https://wbates.net/posts/seeding-data-using-entity-framework-in-asp.net-core</link>
<description><p>With thanks to <a href="http://coderhints.com/ef7-seed-user/">coderhints.com</a> for the initial steps in doing this. I've done a bit of refactoring and added some additional steps to enable setting up a lot of data quickly and easily.</p></description>
<guid>https://wbates.net/posts/seeding-data-using-entity-framework-in-asp.net-core</guid>
<pubDate>Sat, 17 Sep 2016 00:00:00 GMT</pubDate>
<content:encoded><p>With thanks to <a href="http://coderhints.com/ef7-seed-user/">coderhints.com</a> for the initial steps in doing this. I've done a bit of refactoring and added some additional steps to enable setting up a lot of data quickly and easily.</p>
<p>I'm going to demonstrate how you can set up some default information in your database using Entity Framework, and a default user for your application. Firstly find the ConfigureServices method in the Startup class. This is the first way my approach differs from above; ConfigureServices is where the DbContext is added to the application and it's where it should be setup, while Configure is where you tell the application how to respond to HTTP requests [1]. At the end of this method add the following code.</p>
<pre><code class="language-csharp">var serviceProvider = services.BuildServiceProvider();
var dbContext = serviceProvider.GetService&lt;ApplicationDbContext&gt;();
var userManager = serviceProvider.GetService&lt;UserManager&lt;ApplicationUser&gt;&gt;();
var roleManager = serviceProvider.GetService&lt;RoleManager&lt;IdentityRole&gt;&gt;();
dbContext.Database.Migrate();
dbContext.EnsureSeedData(userManager, roleManager).Wait();
</code></pre>
<p>The serviceProvider is what allows us to retrieve instances of the services we need, in this case the ApplicationDbContext, UserManager and RoleManager. Calling the migrate method ensures we have applied the latest migrations and are working with an up to date schema. Finally that horrible red line there is the method where we're going to our seeding. So flick over to your ApplicationDbContext class and add a new method with the following signature</p>
<pre><code class="language-csharp">public async Task EnsureSeedData(UserManager&lt;ApplicationUser&gt; userManager, RoleManager&lt;IdentityRole&gt; roleManager)
</code></pre>
<p>This method needs to be rerunnable without throwing exceptions for adding repeat items, and nor do you want that, so every call you make that's going to update the database should have a check to make sure that data doesn't already exist.</p>
<h1 id="adding-a-default-user">Adding a default user</h1>
<p>We're going to start by setting up our default 'Administrator' user. To do that we need to have an Administrator role.</p>
<pre><code class="language-csharp">// Add roles
if (!await Roles.AnyAsync(r =&gt; string.Equals(r.Name, &quot;Administrator&quot;, System.StringComparison.OrdinalIgnoreCase)))
await roleManager.CreateAsync(new IdentityRole(&quot;Administrator&quot;));
</code></pre>
<p>Here we check for the roles existence and if it isn't there, we add it. This should be repeated for any roles you require.
Now to create our new user</p>
<pre><code class="language-csharp">// Add Admin user
var adminUser = userManager.Users.FirstOrDefault(u =&gt; string.Equals(u.UserName, &quot;admin&#64;wbates.net&quot;, System.StringComparison.OrdinalIgnoreCase));
if (adminUser == null)
{
adminUser = new ApplicationUser
{
UserName = &quot;admin&#64;wbates.net&quot;,
Email = &quot;admin&#64;wbates.net&quot;
};
var result = await userManager.CreateAsync(adminUser, &quot;AReallyDifficultImpossibleToGuessPassword123#&quot;);
if (result != IdentityResult.Success)
throw new System.Exception($&quot;Unable to create '{adminUser.UserName}' account: {result}&quot;);
}
</code></pre>
<p>As always, check for the users existence and because we'll be wanting to ensure this user is enabled and has the 'Administrator' role we'll keep a reference to it. It's worth noting that no Exception is normally thrown if the user has not been created, instead the object returned from the CreateAsync call will tell us and give a message if it has failed. The most common reason for this is that the password is not complicated enough, hence the '#' on the password above.</p>
<pre><code class="language-csharp">await userManager.SetLockoutEnabledAsync(adminUser, false);
</code></pre>
<p>Well we wouldn't want our administrator to be locked out of the system would we?</p>
<pre><code class="language-csharp">// Check AdminUserRoles
var adminRoles = await userManager.GetRolesAsync(adminUser);
if (!adminRoles.Any(r =&gt; string.Equals(r, &quot;Administrator&quot;)))
await userManager.AddToRoleAsync(adminUser, &quot;Administrator&quot;);
</code></pre>
<p>Finally we check that the user is in the admin role, and we're done with setting up our default user. Check the previous post for how ASP.NET Core configuration allows us to set some of the above defaults using .json files instead of hard coding them as we have here.</p>
<h1 id="adding-seed-data">Adding seed data</h1>
<p>Assuming you've added additional DbSets to your ApplicationDbContext for your models, they'll be available in the EnsureSeedData method. As above you'll want to check for each values existing before adding it, but otherwise create an instance of the model and .Add it to the relevant DbSet. The changes won't be saved until you call SaveChangesAsync and it has completed, so leave that until the end. Here's a quick sample that shows how to add some records and save the changes to finish.</p>
<pre><code class="language-csharp">// Ensure default MediaTypes
if (!MediaTypes.Any(t =&gt; string.Equals(t.Name, &quot;DVD&quot;, System.StringComparison.OrdinalIgnoreCase)))
{
var mediaType = new MediaType { Name = &quot;DVD&quot;, Description = &quot;Digital Versatile Disc&quot; }
MediaTypes.Add(mediaType);
}
await SaveChangesAsync();
</code></pre>
<h1 id="top-tips">Top tips</h1>
<p>Use ASP.NET's Configuration Options[2] to describe default values and allow them to be set by using .json files for different environments.
Move most of this additional configuration out to an extension method on the IServicesCollection. Keep the configure method clean.
Define the roles as auto properties in a static class so you don't need to use strings like �Administrator� to reference them everywhere</p>
</content:encoded>
</item>
<item>
<title>ASP.NET Core Configuration</title>
<link>https://wbates.net/posts/asp.net-core-configuration</link>
<description><p>Only just beginning to dive into ASP.NET Core (or ASP.NET 5 if you prefer), but there's a great deal to like. Configuration in Core is extremely simple. Values can simply be added to a json (or other format, but why bother) configuration file and are then available to your application where Configuration is instantiated.
The default projects will include the following lines in the Startup method of Startup.cs.</p></description>
<guid>https://wbates.net/posts/asp.net-core-configuration</guid>
<pubDate>Sun, 24 Apr 2016 00:00:00 GMT</pubDate>
<content:encoded><p>Only just beginning to dive into ASP.NET Core (or ASP.NET 5 if you prefer), but there's a great deal to like. Configuration in Core is extremely simple. Values can simply be added to a json (or other format, but why bother) configuration file and are then available to your application where Configuration is instantiated.
The default projects will include the following lines in the Startup method of Startup.cs.</p>
<pre><code class="language-csharp">var builder = new ConfigurationBuilder().AddJsonFile(&quot;appsettings.json&quot;);
Configuration = builder.Build().ReloadOnChanged(&quot;appsettings.json&quot;);
</code></pre>
<p>This will load the values from the appsettings.json file (if it exists) into the Configuration property. Multiple calls to this method can be chained together (see environment specific config below). If appsettings.json looked like this</p>
<pre><code class="language-json">{
&quot;Logging&quot;: {
&quot;IncludeScopes&quot;: false,
&quot;LogLevel&quot;: {
&quot;Default&quot;: &quot;Verbose&quot;,
&quot;System&quot;: &quot;Information&quot;,
&quot;Microsoft&quot;: &quot;Information&quot;
}
},
&quot;MyConfigValue&quot;: &quot;An important configuration string&quot;
}
</code></pre>
<p>The logging stuff is default, I've added the MyConfigValue key, which means that once Configuration is instantiated I can then access it's value within the Startup class with the following:</p>
<pre><code class="language-csharp">Configuration[&quot;MyConfigValue&quot;];
</code></pre>
<p>The Logging.LogLevel.Default value can be retrieved using</p>
<pre><code class="language-csharp">Configuration[&quot;Logging:LogLevel:Default&quot;];
</code></pre>
<p>By creating config classes specific to different areas of your application you can use the built in DI to inject that config only where it's needed.</p>
<p>If we replace MyConfigValue in the appsettings.json file above with the following key</p>
<pre><code class="language-json">&quot;MyConfig&quot;: {
&quot;Value1&quot;: &quot;This is the first config value&quot;,
&quot;Value2&quot;: &quot;And this is the second&quot;,
&quot;Number1&quot;: 15
}
</code></pre>
<p>Now add a new class to the project which can be called anything but I'm going with MyConfig.cs</p>
<pre><code class="language-csharp">public class MyConfig
{
public string Value1 { get; set; }
public string Value2 { get; set; }
public int Number1 { get; set; }
}
</code></pre>
<p>As you can see the properties match up. We now need to configure the options to be populated into a MyConfig object. Back to startup.cs and find the ConfigureServices method. Add the following lines</p>
<pre><code class="language-csharp">services.AddOptions();
services.Configure&lt;MyConfig&gt;(Configuration.GetSection(&quot;MyConfig&quot;);
</code></pre>
<p>This takes in the Configuration property and configures it so that when we ask for a MyConfig object it will provide it using whatever it has read from appsettings.json file by matching the classes properties to the keys with the same name. To see that in use create a new controller and add a constructor passing in IOptions as a parameter. This will provide an accessor to a populated config object. which is then accessible in the constructor and can be assigned as a property on that controller.</p>
<pre><code class="language-csharp">public MyController(IOptions&lt;MyConfig&gt; myConfig)
{
_myConfig = myConfig.Value;
}
</code></pre>
<p>The _myConfig object will now contain our config for this section.</p>
<h1 id="environment-specific-config">Environment specific config</h1>
<p>You can easily add additional config files for each environment by calling AddJsonFile on the ConfigurationBuilder multiple times. Since I wrote this the default configure method has the following code.</p>
<pre><code class="language-csharp">var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile(&quot;appsettings.json&quot;, optional: true, reloadOnChange: true)
.AddJsonFile($&quot;appsettings.{env.EnvironmentName}.json&quot;, optional: true);
</code></pre>
<p>The later files take priority over the earlier ones, overwriting any earlier values. See this page for info on how to set the EnvironmentName. I also recommend adding *.development.json to your .gitignore files so that your development environment configuration isn't committed.</p>
</content:encoded>
</item>
<item>
<title>Configuring for Pretty URLs in AngularJS and Visual Studio</title>
<link>https://wbates.net/posts/configuring-for-pretty-urls-in-angularjs-and-visual-studio</link>
<description><p>Pretty URLs look much better than some of the long query string type URLs we've gotten used to.
AngularJS makes it very simple to use pretty urls just follow the steps below and then configure for your server.</p></description>
<guid>https://wbates.net/posts/configuring-for-pretty-urls-in-angularjs-and-visual-studio</guid>
<pubDate>Mon, 08 Jun 2015 00:00:00 GMT</pubDate>
<content:encoded><p>Pretty URLs look much better than some of the long query string type URLs we've gotten used to.
AngularJS makes it very simple to use pretty urls just follow the steps below and then configure for your server.</p>
<p>You need to include the ngRoute module with the following line:</p>
<pre><code class="language-html">&lt;script src=&quot;//ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular-route.js&quot;&gt;&lt;/script&gt;
</code></pre>
<p>Our SPA also needs to know where to put the templates when a sub page is requested. Create a div in your page and add the ng-view attribute. That's it.</p>
<pre><code class="language-html">&lt;div ng-view&gt;
&lt;!-- Content will be injected here --&gt;
&lt;/div&gt;
</code></pre>
<p>Next you need to add <code>$routeProvider</code> and <code>$locationProvider</code> to your config and set them up.
You use the when() function to define which view to serve up depending on what the path is. For example:</p>
<pre><code class="language-javascript">mainApp.config(function ($routeProvider, $locationProvider) {
// Route for home page
$routeProvider.when('/', {
templateUrl: '_home/home.html',
controller: 'CarouselCtrl'
})
.when('/about', {
templateUrl: '_about/about.html',
controller: 'aboutCtrl'
})
.otherwise({
redirectTo: &quot;/&quot;
});
$locationProvider.html5Mode(true);
});
</code></pre>
<p>This means that if I go to <a href="http://yourserver.com/about">http://yourserver.com/about</a> the page will serve up the contents of the _about/about.html.
The $locationProvider line is required if you would like to also remove the hash from the URL. Without this your urls will appear like <a href="http://yourserver.com/#/about;">http://yourserver.com/#/about;</a> If you aren't worried about the # then you're finished and the following steps will not be necessary. Older browsers will also default back to using the # if they aren't fancy enough for pretty URLs.</p>
<p>Configuring your webserver to always route back to the index.html file, or whatever you main app's file is, is probably the trickiest aspect of this process. You also need to exclude any folders which actually do exist because otherwise the webserver will route back to index.html when trying to find each of your site assets (css files, images, etc.).</p>
<h1 id="for-iis-express">For IIS Express:</h1>
<p>(See below for IIS Web Core as used in VS)</p>
<ol>
<li>Download <a href="http://www.iis.net/downloads/microsoft/url-rewrite">URL Rewrite</a></li>
<li>Run IIS Manager (found in the start menu)</li>
<li>Expand the panel on the left to find your site</li>
<li>Load URL Rewrite</li>
</ol>
<p><img src="../assets/images/add-rule.png" class="img-fluid" alt="Add Url Rewrite rule" /></p>
<ol start="5">
<li><p>Click &quot;Add Rule&quot;</p>
</li>
<li><p>Choose a blank inbound rule
8 Call it Pretty URL Exclusions. These will be the folders that actually exist that you don't want to be rerouted.</p>
</li>
<li><p>Select &quot;does not match the pattern&quot;</p>
</li>
<li><p>Add in a regular expression to ignore all the folders that exists</p>
<p>For example:
To ignore the common folder and its contents use <code>common/.*</code>
Then use the pipe (|) to separate it from another folder
You might end up with something like
<code>css/.*|js/.*|images.*</code></p>
</li>
<li><p>For these folders we want the action type to be &quot;None&quot;</p>
</li>
<li><p>Check &quot;Stop processing of subsequent rules</p>
</li>