├── .gitattributes ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── BlogContent ├── blog │ ├── 2013 │ │ ├── 11-08-hello-fsblog.md │ │ └── 11-09-getting-started.md │ └── index.cshtml └── index.cshtml ├── Code ├── .paket │ ├── Paket.Restore.targets │ ├── paket.bootstrapper.exe │ └── paket.targets ├── FsBlog.nuspec ├── FsBlog.sln ├── RELEASE_NOTES.md ├── build ├── build.cmd ├── build.fsx ├── config │ └── config.yml ├── content │ ├── favicon.png │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ ├── fsharp.formatting │ │ ├── tooltips.css │ │ └── tooltips.js │ ├── javascripts │ │ └── scale.fix.js │ ├── layouts │ │ ├── default.cshtml │ │ ├── page.cshtml │ │ └── post.cshtml │ └── stylesheets │ │ ├── pygment_trac.css │ │ └── styles.css ├── fake ├── fake.cmd ├── fsblog.fsx ├── paket.dependencies ├── paket.lock ├── tests │ └── FsBlogLib.Tests │ │ ├── BlogPostsTest.fs │ │ ├── BlogTests.fs │ │ ├── FsBlogLib.Tests.fsproj │ │ ├── app.config │ │ └── paket.references ├── themes │ ├── casper │ │ ├── css │ │ │ └── screen.css │ │ ├── fonts │ │ │ ├── casper-icons.eot │ │ │ ├── casper-icons.svg │ │ │ ├── casper-icons.ttf │ │ │ └── casper-icons.woff │ │ ├── fsharp.formatting │ │ │ ├── tooltips.css │ │ │ └── tooltips.js │ │ ├── js │ │ │ ├── index.js │ │ │ └── jquery.fitvids.js │ │ ├── layouts │ │ │ ├── default.cshtml │ │ │ ├── page.cshtml │ │ │ └── post.cshtml │ │ └── source │ │ │ └── index.cshtml │ ├── default │ │ ├── favicon.png │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ │ ├── fsharp.formatting │ │ │ ├── tooltips.css │ │ │ └── tooltips.js │ │ ├── javascripts │ │ │ └── scale.fix.js │ │ ├── layouts │ │ │ ├── default.cshtml │ │ │ ├── page.cshtml │ │ │ └── post.cshtml │ │ ├── source │ │ │ ├── blog │ │ │ │ └── index.cshtml │ │ │ └── index.cshtml │ │ └── stylesheets │ │ │ ├── pygment_trac.css │ │ │ └── styles.css │ └── singlepaged │ │ ├── css │ │ └── screen.css │ │ ├── fsharp.formatting │ │ ├── tooltips.css │ │ └── tooltips.js │ │ ├── js │ │ ├── index.js │ │ └── jquery.fitvids.js │ │ ├── layouts │ │ ├── default.cshtml │ │ ├── page.cshtml │ │ └── post.cshtml │ │ └── source │ │ └── index.cshtml └── tools │ ├── FsBlogLib │ ├── AssemblyInfo.fs │ ├── Blog.fs │ ├── BlogCustomizations.fs │ ├── BlogPosts.fs │ ├── FileHelpers.fs │ ├── FileHelpersOperators.fs │ ├── FsBlogLib.fsproj │ ├── Razor.fs │ ├── Template.fs │ └── paket.references │ ├── empty-template.html │ └── paket.references └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | *.sh text eol=lf 15 | 16 | ############################################################################### 17 | # Set the merge driver for project and solution files 18 | # 19 | # Merging from the command prompt will add diff markers to the files if there 20 | # are conflicts (Merging from VS is not affected by the settings below, in VS 21 | # the diff markers are never inserted). Diff markers may cause the following 22 | # file extensions to fail to load in VS. An alternative would be to treat 23 | # these files as binary and thus will always conflict and require user 24 | # intervention with every merge. To do so, just uncomment the entries below 25 | ############################################################################### 26 | #*.sln merge=binary 27 | #*.csproj merge=binary 28 | #*.vbproj merge=binary 29 | #*.vcxproj merge=binary 30 | #*.vcproj merge=binary 31 | #*.dbproj merge=binary 32 | #*.fsproj merge=binary 33 | #*.lsproj merge=binary 34 | #*.wixproj merge=binary 35 | #*.modelproj merge=binary 36 | #*.sqlproj merge=binary 37 | #*.wwaproj merge=binary 38 | 39 | ############################################################################### 40 | # behavior for image files 41 | # 42 | # image files are treated as binary by default. 43 | ############################################################################### 44 | #*.jpg binary 45 | #*.png binary 46 | #*.gif binary 47 | 48 | ############################################################################### 49 | # diff behavior for common document formats 50 | # 51 | # Convert binary document formats to text before diffing them. This feature 52 | # is only available from the command line. Turn it on by uncommenting the 53 | # entries below. 54 | ############################################################################### 55 | #*.doc diff=astextplain 56 | #*.DOC diff=astextplain 57 | #*.docx diff=astextplain 58 | #*.DOCX diff=astextplain 59 | #*.dot diff=astextplain 60 | #*.DOT diff=astextplain 61 | #*.pdf diff=astextplain 62 | #*.PDF diff=astextplain 63 | #*.rtf diff=astextplain 64 | #*.RTF diff=astextplain 65 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | Please provide a succinct description of your issue. 4 | 5 | ### Repro steps 6 | 7 | Please provide the steps required to reproduce the problem 8 | 9 | 1. Step A 10 | 11 | 2. Step B 12 | 13 | ### Expected behavior 14 | 15 | Please provide a description of the behavior you expect. 16 | 17 | ### Actual behavior 18 | 19 | Please provide a description of the actual behavior you observe. 20 | 21 | ### Known workarounds 22 | 23 | Please provide a description of any known workarounds. 24 | 25 | ### Related information 26 | 27 | * Operating system 28 | * Branch 29 | * .NET Runtime, CoreCLR or Mono Version 30 | * Performance information, links to performance testing scripts 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # FsBlog-specifics 5 | [Oo]utput/ 6 | [Dd]eploy/ 7 | 8 | # User-specific files 9 | .vs 10 | *.suo 11 | *.user 12 | *.sln.docstates 13 | 14 | # Build results 15 | [Dd]ebug/ 16 | [Rr]elease/ 17 | x64/ 18 | build/ 19 | [Bb]in/ 20 | [Oo]bj/ 21 | 22 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 23 | !packages/*/build/ 24 | 25 | # MSTest test Results 26 | [Tt]est[Rr]esult*/ 27 | [Bb]uild[Ll]og.* 28 | tests/source/ 29 | 30 | # FAKE temp dir 31 | .fake/ 32 | 33 | # NUnit test 34 | TestResult.xml 35 | 36 | # Paket 37 | paket-files 38 | paket.exe 39 | .paket/paket.exe 40 | 41 | *_i.c 42 | *_p.c 43 | *.ilk 44 | *.meta 45 | *.obj 46 | *.pch 47 | *.pdb 48 | *.pgc 49 | *.pgd 50 | *.rsp 51 | *.sbr 52 | *.tlb 53 | *.tli 54 | *.tlh 55 | *.tmp 56 | *.tmp_proj 57 | *.log 58 | *.vspscc 59 | *.vssscc 60 | .builds 61 | *.pidb 62 | *.log 63 | *.scc 64 | 65 | # Visual C++ cache files 66 | ipch/ 67 | *.aps 68 | *.ncb 69 | *.opensdf 70 | *.sdf 71 | *.cachefile 72 | 73 | # Visual Studio profiler 74 | *.psess 75 | *.vsp 76 | *.vspx 77 | 78 | # Guidance Automation Toolkit 79 | *.gpState 80 | 81 | # ReSharper is a .NET coding add-in 82 | _ReSharper*/ 83 | *.[Rr]e[Ss]harper 84 | 85 | # TeamCity is a build add-in 86 | _TeamCity* 87 | 88 | # DotCover is a Code Coverage Tool 89 | *.dotCover 90 | 91 | # NCrunch 92 | *.ncrunch* 93 | .*crunch*.local.xml 94 | 95 | # Installshield output folder 96 | [Ee]xpress/ 97 | 98 | # DocProject is a documentation generator add-in 99 | DocProject/buildhelp/ 100 | DocProject/Help/*.HxT 101 | DocProject/Help/*.HxC 102 | DocProject/Help/*.hhc 103 | DocProject/Help/*.hhk 104 | DocProject/Help/*.hhp 105 | DocProject/Help/Html2 106 | DocProject/Help/html 107 | 108 | # Click-Once directory 109 | publish/ 110 | 111 | # Publish Web Output 112 | *.Publish.xml 113 | 114 | # NuGet Packages Directory 115 | packages/ 116 | 117 | # Generated documentation folder 118 | docs/output/ 119 | 120 | # Enable nuget.exe in the .nuget folder (though normally executables are not tracked) 121 | !.nuget/NuGet.exe 122 | 123 | # Windows Azure Build Output 124 | csx 125 | *.build.csdef 126 | 127 | # Windows Store app package directory 128 | AppPackages/ 129 | 130 | # Others 131 | sql/ 132 | *.Cache 133 | ClientBin/ 134 | [Ss]tyle[Cc]op.* 135 | ~$* 136 | *~ 137 | *.dbmdl 138 | *.[Pp]ublish.xml 139 | *.pfx 140 | *.publishsettings 141 | 142 | # RIA/Silverlight projects 143 | Generated_Code/ 144 | 145 | # Backup & report files from converting an old project file to a newer 146 | # Visual Studio version. Backup files are not needed, because we have git ;-) 147 | _UpgradeReport_Files/ 148 | Backup*/ 149 | UpgradeLog*.XML 150 | UpgradeLog*.htm 151 | 152 | # SQL Server files 153 | App_Data/*.mdf 154 | App_Data/*.ldf 155 | 156 | 157 | #LightSwitch generated files 158 | GeneratedArtifacts/ 159 | _Pvt_Extensions/ 160 | ModelManifest.xml 161 | 162 | # ========================= 163 | # Windows detritus 164 | # ========================= 165 | 166 | # Windows image file caches 167 | Thumbs.db 168 | ehthumbs.db 169 | 170 | # Folder config file 171 | Desktop.ini 172 | 173 | # Recycle Bin used on file shares 174 | $RECYCLE.BIN/ 175 | 176 | # Mac desktop service store files 177 | .DS_Store 178 | *.orig 179 | -------------------------------------------------------------------------------- /BlogContent/blog/2013/11-08-hello-fsblog.md: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "post"; 3 | Title = "Hello, FsBlog!"; 4 | Date = "2013-11-08T11:27:31"; 5 | Tags = ""; 6 | Description = "Introducing FsBlog - a blog-aware static site generator using F#."; 7 | } 8 | 9 | **FsBlog** aims to be a blog-aware static site generator, mostly built in `F#` for .NET and Mono developers. 10 | 11 | 12 | 13 | But don't worry, you won't even need to know any F# to get up and running. So long as you are comfortable using a command line or terminal, and have a degree of familiarity with Markdown and Razor syntax - you're already most of the way there! 14 | 15 | Inspired by the likes of **Jekyll** and **Octopress** - and greatly helped along the way by the scripts behind [@@tomaspetricek](https://twitter.com/tomaspetricek)'s personal website; FsBlog gives the average .NET or Mono dev the tools that they need to get a statically built website up and running quickly and hosted somewhere like [Github Pages](http://pages.github.com/). 16 | 17 | This set of tools have been pulled together using some of the following community projects: 18 | 19 | * [FAKE](http://fsharp.github.io/FAKE/) for automation and scripting of the different tasks. 20 | * [F# Formatting](http://tpetricek.github.io/FSharp.Formatting/) for Markdown and F# literate programming, processing and colorization. 21 | * [RazorEngine](https://github.com/Antaris/RazorEngine) which is used for the templating and embedded `C#` code. 22 | * Some of the code in **FsBlogLib** that calls the **RazorEngine** has been based on F# code in [Tilde](https://github.com/aktowns/tilde). 23 | * [Github Pages](http://pages.github.com/) for our default theme. 24 | 25 | ## Development 26 | 27 | It is very early days and we haven't yet reached anything vaguely resembling a version 1.0 of the software. 28 | 29 | You can track the progress of the development, outstanding issues and check what we're working towards by checking out the [Issues](https://github.com/fsprojects/FsBlog/issues) and [Milestones](https://github.com/fsprojects/FsBlog/milestones) sections of our repository. 30 | 31 | If you find a bug, or have a request or whatever - please raise a new issue there! or even better make the change and open a pull request. -------------------------------------------------------------------------------- /BlogContent/blog/2013/11-09-getting-started.md: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "post"; 3 | Title = "Getting Started"; 4 | Date = "2013-11-09T12:38:14"; 5 | Tags = ""; 6 | Description = ""; 7 | } 8 | 9 | If you've much experience with other static site generators like *Octopress* or *Jekyll* most of this should be fairly familiar, if not, hopefully it won't be too difficult to pick up. 10 | 11 | 12 | 13 | ## Command line 14 | 15 | FsBlog tends to rely on the fact that you're using the command line or terminal - almost all interaction with FsBlog and git are documented as such. 16 | 17 | ## Dependencies 18 | 19 | Although X-Platform is a goal for the [not-too-distant-future](https://github.com/fsprojects/FsBlog/issues?milestone=2&state=open) - at the moment you'll need: 20 | 21 | * .NET 4.5 22 | * F# 3.0 23 | * VS2012 or VS2013 24 | * A git client 25 | 26 | If you follow the instructions for [using F# on Windows](http://fsharp.org/use/windows/) you'll probably be good to go. 27 | 28 | ## 1. Setup FsBlog 29 | 30 | First you'll need to clone the repo. On your command line, using git: 31 | 32 | [lang=sh] 33 | git clone git://github.com/fsprojects/FsBlog.git FsBlog 34 | cd FsBlog 35 | 36 | Next, run the build: 37 | 38 | [lang=sh] 39 | ./build 40 | 41 | This should install a few [NuGet](http://www.nuget.org/) dependencies that FsBlog makes use of. 42 | 43 | ## 2. Configuration 44 | 45 | Using your favourite F# code editor, edit the following section of the `./fsblog.fsx` file in your repo: 46 | 47 | [lang=fsharp] 48 | // -------------------------------------------------------------------------------------- 49 | // Configuration. 50 | // -------------------------------------------------------------------------------------- 51 | Environment.CurrentDirectory <- __SOURCE_DIRECTORY__ 52 | let root = "http://fsprojects.github.io/FsBlog" 53 | let title = "FsBlog - F# static site generation" 54 | let description = """ 55 | FsBlog aims to be a blog-aware static site generator, mostly built in F#. But don't worry, 56 | you won't even need to know any F# to get up and running. So long as you are comfortable 57 | using a command line or terminal, and have a degree of familiarity with Markdown and Razor 58 | syntax - you're good to go!""" 59 | 60 | There isn't a whole lot to change right now, but at some point you'll be able to configure a Twitter handle, GitHub account etc. 61 | 62 | ## 3. Create a post 63 | 64 | Again on your command line: 65 | 66 | [lang=sh] 67 | ./fake new post="post title" 68 | 69 | This will create a new file in the following location, ready for you to edit: `./source/blog/yyyy/MM-dd-title.md`. 70 | 71 | ## 4. Generate and preview 72 | 73 | You can generate your website at any time from the command line using the following command: 74 | 75 | [lang=sh] 76 | ./fake generate 77 | 78 | If you also want to preview your website, you can run the following command: 79 | 80 | [lang=sh] 81 | ./fake preview 82 | 83 | The `preview` command will also `generate` the website too. -------------------------------------------------------------------------------- /BlogContent/blog/index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "default"; 3 | Title = "FsBlog"; 4 | Description = "A blog aware static site generator in mostly F#"; 5 | } 6 | @using System.Linq 7 | 8 | @foreach (var post in Enumerable.Take(Model.Posts, 10)) 9 | { 10 | @post.Title 11 | } -------------------------------------------------------------------------------- /BlogContent/index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "default"; 3 | Title = "FsBlog"; 4 | Description = "A blog aware static site generator in mostly F#"; 5 | } 6 | 7 |

Hello, FsBlog!

8 | 9 |

FsBlog aims to be a blog-aware static site generator, mostly built in F# for .NET and Mono developers. But don't worry, you won't even need to know any F# to get up and running. So long as you are comfortable using a command line or terminal, and have a degree of familiarity with Markdown and Razor syntax - you're already most of the way there!

10 | 11 |

Inpired by the likes of Jekyll and Octopress - and greatly helped along the way by the scripts behind @@tomaspetricek's personal website; FsBlog gives the average .NET or Mono dev the tools that they need to get a statically built website up and running quickly and hosted somewhere like Github Pages.

12 | 13 |

This set of tools have been pulled together using some of the following community projects:

14 | 15 | 22 | 23 |

Development

24 | 25 |

It is very early days and we haven't yet reached anything vaguely resembling a version 1.0 of the software.

26 | 27 |

You can track the progress of the development, outstanding issues and check what we're working towards by checking out the Issues and Milestones section of our repository.

28 | 29 |

If you find a bug, or have a request or whatever - please raise a new issue there! or even better make the change and open a pull request.

30 | -------------------------------------------------------------------------------- /Code/.paket/Paket.Restore.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 8 | 9 | true 10 | $(MSBuildThisFileDirectory) 11 | $(MSBuildThisFileDirectory)..\ 12 | $(PaketRootPath)paket-files\paket.restore.cached 13 | $(PaketRootPath)paket.lock 14 | /Library/Frameworks/Mono.framework/Commands/mono 15 | mono 16 | 17 | $(PaketRootPath)paket.exe 18 | $(PaketToolsPath)paket.exe 19 | "$(PaketExePath)" 20 | $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" 21 | $(PaketRootPath)paket.bootstrapper.exe 22 | $(PaketToolsPath)paket.bootstrapper.exe 23 | "$(PaketBootStrapperExePath)" 24 | $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)" 25 | 26 | 27 | 28 | 29 | true 30 | true 31 | 32 | 33 | 34 | 35 | 36 | 37 | true 38 | $(NoWarn);NU1603 39 | 40 | 41 | 42 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) 43 | $([System.IO.File]::ReadAllText('$(PaketLockFilePath)')) 44 | true 45 | false 46 | true 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | $(MSBuildProjectDirectory)\obj\$(MSBuildProjectFile).paket.references.cached 56 | 57 | $(MSBuildProjectFullPath).paket.references 58 | 59 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references 60 | 61 | $(MSBuildProjectDirectory)\paket.references 62 | $(MSBuildProjectDirectory)\obj\$(MSBuildProjectFile).$(TargetFramework).paket.resolved 63 | true 64 | references-file-or-cache-not-found 65 | 66 | 67 | 68 | 69 | $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)')) 70 | $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)')) 71 | references-file 72 | false 73 | 74 | 75 | 76 | 77 | false 78 | 79 | 80 | 81 | 82 | true 83 | target-framework '$(TargetFramework)' 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) 101 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) 102 | 103 | 104 | %(PaketReferencesFileLinesInfo.PackageVersion) 105 | 106 | 107 | 108 | 109 | $(MSBuildProjectDirectory)/obj/$(MSBuildProjectFile).paket.clitools 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0]) 119 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1]) 120 | 121 | 122 | %(PaketCliToolFileLinesInfo.PackageVersion) 123 | 124 | 125 | 126 | 127 | $(MSBuildProjectDirectory)/obj/$(MSBuildProjectFile).NuGet.Config 128 | 129 | 130 | 131 | 132 | 133 | 134 | false 135 | 136 | 137 | 138 | 139 | 140 | $(MSBuildProjectDirectory)/$(MSBuildProjectFile) 141 | true 142 | false 143 | true 144 | 145 | 146 | 147 | <_NuspecFiles Include="$(BaseIntermediateOutputPath)*.nuspec"/> 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 199 | 200 | 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /Code/.paket/paket.bootstrapper.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsprojects/FsBlog/fd4ad6512e4c1d2a2216e37a155b45366474317b/Code/.paket/paket.bootstrapper.exe -------------------------------------------------------------------------------- /Code/.paket/paket.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | 7 | true 8 | $(MSBuildThisFileDirectory) 9 | $(MSBuildThisFileDirectory)..\ 10 | /Library/Frameworks/Mono.framework/Commands/mono 11 | mono 12 | 13 | 14 | 15 | $(PaketToolsPath)paket.exe 16 | $(PaketToolsPath)paket.bootstrapper.exe 17 | "$(PaketExePath)" 18 | $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" 19 | "$(PaketBootStrapperExePath)" 20 | $(MonoPath) --runtime=v4.0.30319 $(PaketBootStrapperExePath) 21 | 22 | $(MSBuildProjectDirectory)\paket.references 23 | $(MSBuildStartupDirectory)\paket.references 24 | $(MSBuildProjectFullPath).paket.references 25 | $(PaketCommand) restore --references-files "$(PaketReferences)" 26 | $(PaketBootStrapperCommand) 27 | 28 | RestorePackages; $(BuildDependsOn); 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Code/FsBlog.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | FsBlog 5 | 1.0.1-alpha 6 | Matt Ball, Dave Thomas,Tomas Petricek 7 | Matt Ball, Dave Thomas,Tomas Petricek 8 | https://github.com/fsprojects/FsBlog 9 | https://raw.github.com/fsharp/FSharp.Compiler.Service/master/misc/logo.png 10 | false 11 | FsBlog aims to be a blog-aware static site generator, mostly built in F# for .NET and Mono developers. But don't worry, you won't even need to know any F# to get up and running. So long as you are comfortable using a command line or terminal, and have a degree of familiarity with Markdown and Razor syntax - you're already most of the way there! 12 | 13 | Inpired by the likes of Jekyll and Octopress - and greatly helped along the way by the scripts behind @tomaspetricek's personal website; FsBlog gives the average .NET or Mono dev the tools that they need to get a statically built website up and running quickly and hosted somewhere like Github Pages. 14 | 15 | This set of tools have been pulled together using some of the following community projects: 16 | 17 | FAKE for automation and scripting of the different tasks. 18 | F# Formatting for Markdown and F# literate programming, processing and colorization. 19 | RazorEngine which is used for the templating and embedded C# code. 20 | Some of the code in FsBlogLib that calls the RazorEngine has been based on F# code in Tilde. 21 | Github Pages for our default theme. 22 | Blog aware, static site generation using F#. 23 | Fix symbols for named union fields in patterns 24 | Copyright 2014 25 | F# fsharp blog 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Code/FsBlog.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 14 3 | VisualStudioVersion = 14.0.24720.0 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{ACDC1C0A-1528-4BAC-BFE2-57BF1BEE5154}" 6 | ProjectSection(SolutionItems) = preProject 7 | paket.dependencies = paket.dependencies 8 | paket.lock = paket.lock 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{8E03BE4F-38F8-4701-AF77-B49950F254F5}" 12 | ProjectSection(SolutionItems) = preProject 13 | FsBlog.nuspec = FsBlog.nuspec 14 | EndProjectSection 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{14C8B79A-1276-4B1C-BE97-4ED715860DE8}" 17 | ProjectSection(SolutionItems) = preProject 18 | build.fsx = build.fsx 19 | fsblog.fsx = fsblog.fsx 20 | EndProjectSection 21 | EndProject 22 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsBlogLib", "tools\FsBlogLib\FsBlogLib.fsproj", "{B83F5E3E-8CEC-4F4E-B251-D2C853816C62}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{22965072-5FEA-4A09-BCF8-975E5EB7942B}" 25 | EndProject 26 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsBlogLib.Tests", "tests\FsBlogLib.Tests\FsBlogLib.Tests.fsproj", "{27E210E1-D9C9-4661-9CF5-4B7EDCB3335F}" 27 | EndProject 28 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{76023851-00A1-494C-8928-3E61E90258AB}" 29 | ProjectSection(SolutionItems) = preProject 30 | config\config.yml = config\config.yml 31 | EndProjectSection 32 | EndProject 33 | Global 34 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 35 | Debug|Any CPU = Debug|Any CPU 36 | Release|Any CPU = Release|Any CPU 37 | EndGlobalSection 38 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 39 | {B83F5E3E-8CEC-4F4E-B251-D2C853816C62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {B83F5E3E-8CEC-4F4E-B251-D2C853816C62}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {B83F5E3E-8CEC-4F4E-B251-D2C853816C62}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {B83F5E3E-8CEC-4F4E-B251-D2C853816C62}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {27E210E1-D9C9-4661-9CF5-4B7EDCB3335F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {27E210E1-D9C9-4661-9CF5-4B7EDCB3335F}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {27E210E1-D9C9-4661-9CF5-4B7EDCB3335F}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {27E210E1-D9C9-4661-9CF5-4B7EDCB3335F}.Release|Any CPU.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | GlobalSection(SolutionProperties) = preSolution 49 | HideSolutionNode = FALSE 50 | EndGlobalSection 51 | GlobalSection(NestedProjects) = preSolution 52 | {B83F5E3E-8CEC-4F4E-B251-D2C853816C62} = {8E03BE4F-38F8-4701-AF77-B49950F254F5} 53 | {27E210E1-D9C9-4661-9CF5-4B7EDCB3335F} = {22965072-5FEA-4A09-BCF8-975E5EB7942B} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /Code/RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | * 0.0.0-beta - Initial release 2 | * 0.1.0-beta - Ability to *new up* blog posts from command line 3 | * 0.2.0-beta - Ability to *new up* pages from the command line 4 | * 0.2.1-beta - Generates website, but with errors/missing files 5 | * 0.3.0-beta - Generates it's own `gh-pages` content 6 | * 0.3.1-beta - fixed a bug in urls pointing to styles etc 7 | * 0.3.2-beta - fixed a bug with mismatched fsharp.formatting css/js and libs 8 | * 0.3.3-beta - minor change to fix bug in default layout 9 | * 0.3.4-beta - fixed a bug where inline code styles were not displaying properly 10 | * 0.3.5-beta - changed the way in which pages and posts were templated (both use the default layout now) 11 | * 0.4.0-beta - added 'clean' command 12 | * 0.4.1-beta - markers can be added to markdown and fsx posts to create abstracts that are processed -------------------------------------------------------------------------------- /Code/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if test "$OS" = "Windows_NT" 3 | then 4 | # use .Net 5 | [ ! -f .paket/paket.exe ] && .paket/paket.bootstrapper.exe 6 | .paket/paket.exe restore 7 | packages/FAKE/tools/FAKE.exe build.fsx $@ 8 | else 9 | # use mono 10 | [ ! -f .paket/paket.exe ] && mono --runtime=v4.0 .paket/paket.bootstrapper.exe 11 | mono --runtime=v4.0 .paket/paket.exe restore 12 | mono --runtime=v4.0 packages/FAKE/tools/FAKE.exe build.fsx -d:MONO $@ 13 | fi 14 | -------------------------------------------------------------------------------- /Code/build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if not exist .paket\paket.exe ( 4 | .paket\paket.bootstrapper.exe 5 | ) 6 | 7 | .paket\paket.exe restore 8 | packages\FAKE\tools\FAKE.exe build.fsx %* 9 | -------------------------------------------------------------------------------- /Code/build.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | # Build Script 3 | 4 | This script is intended for use with [FAKE][fake] for the build process of the 5 | **FsBlog** tools themselves. 6 | 7 | [fake]: http://fsharp.github.io/FAKE/ 8 | *) 9 | 10 | #I "packages/FAKE/tools/" 11 | #r "packages/FAKE/tools/FakeLib.dll" 12 | open Fake 13 | open Fake.Testing 14 | open Fake.Testing.NUnit3 15 | open Fake.AssemblyInfoFile 16 | open Fake.ReleaseNotesHelper 17 | open System 18 | 19 | 20 | // -------------------------------------------------------------------------------------- 21 | // Important variables. 22 | // -------------------------------------------------------------------------------------- 23 | Environment.CurrentDirectory <- __SOURCE_DIRECTORY__ 24 | let release = parseReleaseNotes (IO.File.ReadAllLines "RELEASE_NOTES.md") 25 | 26 | // Information about the project to be used 27 | // - by NuGet 28 | // - in AssemblyInfo files 29 | // - in FAKE tasks 30 | let solution = "FsBlog" 31 | let project = "FsBlogLib" 32 | let authors = [ "matt ball"; "tomas petricek"; ] 33 | let summary = "Blog aware, static site generation using F#." 34 | let description = """ 35 | FsBlog aims to be a blog-aware static site generator, mostly built in F#. But don't worry, 36 | you won't even need to know any F# to get up and running. So long as you are comfortable 37 | using a command line or terminal, and have a degree of familiarity with Markdown and Razor 38 | syntax - you're good to go!""" 39 | 40 | // Pattern specifying assemblies to be tested using xunit 41 | let testAssemblies = "bin/FsBlogLib/*Tests*.dll" 42 | 43 | let tags = "F# fsharp blog website generation" 44 | 45 | // Generate assembly info files with the right version & up-to-date information 46 | Target "AssemblyInfo" (fun _ -> 47 | 48 | let fileName = "tools/" + project + "/AssemblyInfo.fs" 49 | CreateFSharpAssemblyInfo fileName 50 | [ Attribute.Title project 51 | Attribute.Product project 52 | Attribute.Description summary 53 | Attribute.Version release.AssemblyVersion 54 | Attribute.FileVersion release.AssemblyVersion ] 55 | ) 56 | 57 | 58 | // -------------------------------------------------------------------------------------- 59 | // Tasks for running the build of tools. 60 | // -------------------------------------------------------------------------------------- 61 | 62 | Target "Clean" (fun _ -> 63 | CleanDirs ["bin"] 64 | ) 65 | 66 | Target "Build" (fun _ -> 67 | { BaseDirectory = __SOURCE_DIRECTORY__ 68 | Includes = [ solution + ".sln" ] 69 | Excludes = [] } 70 | |> MSBuildRelease "bin/FsBlogLib" "Rebuild" 71 | |> ignore 72 | ) 73 | 74 | // -------------------------------------------------------------------------------------- 75 | // Run the unit tests using test runner 76 | 77 | Target "RunTests" (fun _ -> 78 | !! testAssemblies 79 | |> NUnit3 (fun p -> 80 | { p with 81 | ShadowCopy = false 82 | TimeOut = TimeSpan.FromMinutes 20. 83 | }) 84 | ) 85 | 86 | // -------------------------------------------------------------------------------------- 87 | // Build dependencies. 88 | // -------------------------------------------------------------------------------------- 89 | "Clean" 90 | ==> "AssemblyInfo" 91 | ==> "Build" 92 | ==> "RunTests" 93 | 94 | RunTargetOrDefault "Build" 95 | -------------------------------------------------------------------------------- /Code/config/config.yml: -------------------------------------------------------------------------------- 1 | # ----------------------- # 2 | # Main Configs # 3 | # ----------------------- # 4 | 5 | url: http://fsprojects.github.io/FsBlog 6 | gitlocation: https://github.com/fsprojects/FsBlog.git 7 | gitbranch: gh-pages 8 | title: FsBlog 9 | subtitle: Blog aware, static site generation using F#. 10 | description: > 11 | FsBlog aims to be a blog-aware static site generator, mostly built in F#. But don't worry, 12 | you won't even need to know any F# to get up and running. So long as you are comfortable 13 | using a command line or terminal, and have a degree of familiarity with Markdown and Razor 14 | syntax - you're good to go! 15 | 16 | source: ../BlogContent/ 17 | blog: ../BlogContent/blog/ 18 | blogIndex: ../BlogContent/blog/index.cshtml 19 | layouts: layouts 20 | content: content 21 | template: tools/empty-template.html 22 | output: ../Output/ 23 | deploy: deploy 24 | themes: themes 25 | -------------------------------------------------------------------------------- /Code/content/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsprojects/FsBlog/fd4ad6512e4c1d2a2216e37a155b45366474317b/Code/content/favicon.png -------------------------------------------------------------------------------- /Code/content/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsprojects/FsBlog/fd4ad6512e4c1d2a2216e37a155b45366474317b/Code/content/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /Code/content/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsprojects/FsBlog/fd4ad6512e4c1d2a2216e37a155b45366474317b/Code/content/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /Code/content/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsprojects/FsBlog/fd4ad6512e4c1d2a2216e37a155b45366474317b/Code/content/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /Code/content/fsharp.formatting/tooltips.css: -------------------------------------------------------------------------------- 1 | @import url(//fonts.googleapis.com/css?family=Droid+Sans|Droid+Sans+Mono|Actor); 2 | 3 | /*-------------------------------------------------------------------------- 4 | Formatting for F# code snippets 5 | /*--------------------------------------------------------------------------*/ 6 | 7 | /* identifier */ 8 | span.i { color:#d1d1d1; } 9 | /* string */ 10 | span.s { color:#d4b43c; } 11 | /* keywords */ 12 | span.k { color:#4e98dc; } 13 | /* comment */ 14 | span.c { color:#96C71D; } 15 | /* operators */ 16 | span.o { color:#af75c1; } 17 | /* numbers */ 18 | span.n { color:#96C71D; } 19 | /* line number */ 20 | span.l { color:#80b0b0; } 21 | 22 | /* inactive code */ 23 | span.inactive { color:#808080; } 24 | /* preprocessor */ 25 | span.prep { color:#af75c1; } 26 | /* fsi output */ 27 | span.fsi { color:#808080; } 28 | 29 | /* omitted */ 30 | span.omitted { 31 | background:#3c4e52; 32 | border-radius:5px; 33 | color:#808080; 34 | padding:0px 0px 1px 0px; 35 | } 36 | /* tool tip */ 37 | div.tip { 38 | background:#475b5f; 39 | border-radius:4px; 40 | font:11pt 'Droid Sans', arial, sans-serif; 41 | padding:6px 8px 6px 8px; 42 | display:none; 43 | color:#d1d1d1; 44 | } 45 | table.pre pre { 46 | padding:0px; 47 | margin:0px; 48 | border:none; 49 | } 50 | table.pre, pre.fssnip, pre { 51 | line-height:13pt; 52 | border:1px solid #d8d8d8; 53 | border-collapse:separate; 54 | white-space:pre; 55 | font: 9pt 'Droid Sans Mono',consolas,monospace; 56 | width:90%; 57 | margin:10px 20px 20px 20px; 58 | background-color:#212d30; 59 | padding:10px; 60 | border-radius:5px; 61 | color:#d1d1d1; 62 | } 63 | table.pre pre { 64 | padding:0px; 65 | margin:0px; 66 | border-radius:0px; 67 | width: 100%; 68 | } 69 | table.pre td { 70 | padding:0px; 71 | white-space:normal; 72 | margin:0px; 73 | } 74 | table.pre td.lines { 75 | width:30px; 76 | } 77 | 78 | /*-------------------------------------------------------------------------- 79 | Formatting for page & standard document content 80 | /*--------------------------------------------------------------------------*/ 81 | 82 | body { 83 | font-family: Actor, serif; 84 | padding-top: 0px; 85 | padding-bottom: 40px; 86 | } 87 | 88 | pre { 89 | word-wrap: inherit; 90 | } 91 | 92 | /* Format the heading - nicer spacing etc. */ 93 | .masthead { 94 | overflow: hidden; 95 | } 96 | .masthead ul, .masthead li { 97 | margin-bottom:0px; 98 | } 99 | .masthead .nav li { 100 | margin-top: 15px; 101 | font-size:110%; 102 | } 103 | .masthead h3 { 104 | margin-bottom:5px; 105 | font-size:170%; 106 | } 107 | hr { 108 | margin:0px 0px 20px 0px; 109 | } 110 | 111 | /* Make table headings and td.title bold */ 112 | td.title, thead { 113 | font-weight:bold; 114 | } 115 | 116 | /* Format the right-side menu */ 117 | #menu { 118 | margin-top:50px; 119 | font-size:11pt; 120 | padding-left:20px; 121 | } 122 | 123 | #menu .nav-header { 124 | font-size:12pt; 125 | color:#606060; 126 | margin-top:20px; 127 | } 128 | 129 | #menu li { 130 | line-height:25px; 131 | } 132 | 133 | /* Change font sizes for headings etc. */ 134 | #main h1 { font-size: 26pt; margin:10px 0px 15px 0px; } 135 | #main h2 { font-size: 20pt; margin:20px 0px 0px 0px; } 136 | #main h3 { font-size: 14pt; margin:15px 0px 0px 0px; } 137 | #main p { font-size: 12pt; margin:5px 0px 15px 0px; } 138 | #main ul { font-size: 12pt; margin-top:10px; } 139 | #main li { font-size: 12pt; margin: 5px 0px 5px 0px; } 140 | 141 | /*-------------------------------------------------------------------------- 142 | Formatting for API reference 143 | /*--------------------------------------------------------------------------*/ 144 | 145 | #main h2 { 146 | font-size: 160%; 147 | margin-top:40px; 148 | } 149 | #main h3 { 150 | font-size: 130%; 151 | font-weight: bold; 152 | margin-top: 20px; 153 | } 154 | .type-list .type-name, .module-list .module-name { 155 | width:25%; 156 | font-weight:bold; 157 | } 158 | .member-list .member-name { 159 | width:35%; 160 | } 161 | #main .xmldoc h2 { 162 | font-size:14pt; 163 | margin:10px 0px 0px 0px; 164 | } 165 | #main .xmldoc h3 { 166 | font-size:12pt; 167 | margin:10px 0px 0px 0px; 168 | } 169 | /*-------------------------------------------------------------------------- 170 | Additional formatting for the homepage 171 | /*--------------------------------------------------------------------------*/ 172 | 173 | #nuget { 174 | margin-top:20px; 175 | font-size: 11pt; 176 | padding:20px; 177 | } 178 | 179 | #nuget pre { 180 | font-size:11pt; 181 | -moz-border-radius: 0px; 182 | -webkit-border-radius: 0px; 183 | border-radius: 0px; 184 | background: #404040; 185 | border-style:none; 186 | color: #e0e0e0; 187 | margin-top:15px; 188 | } 189 | 190 | /* Hide snippets on the home page snippet & nicely format table */ 191 | #hp-snippet td.lines { 192 | display: none; 193 | } 194 | #hp-snippet .table { 195 | width:80%; 196 | margin-left:30px; 197 | } 198 | -------------------------------------------------------------------------------- /Code/content/fsharp.formatting/tooltips.js: -------------------------------------------------------------------------------- 1 | var currentTip = null; 2 | var currentTipElement = null; 3 | 4 | function hideTip(evt, name, unique) { 5 | var el = document.getElementById(name); 6 | el.style.display = "none"; 7 | currentTip = null; 8 | } 9 | 10 | function findPos(obj) { 11 | // no idea why, but it behaves differently in webbrowser component 12 | if (window.location.search == "?inapp") 13 | return [obj.offsetLeft + 10, obj.offsetTop + 30]; 14 | 15 | var curleft = 0; 16 | var curtop = obj.offsetHeight; 17 | while (obj) { 18 | curleft += obj.offsetLeft; 19 | curtop += obj.offsetTop; 20 | obj = obj.offsetParent; 21 | }; 22 | return [curleft, curtop]; 23 | } 24 | 25 | function hideUsingEsc(e) { 26 | if (!e) { e = event; } 27 | hideTip(e, currentTipElement, currentTip); 28 | } 29 | 30 | function showTip(evt, name, unique, owner) { 31 | document.onkeydown = hideUsingEsc; 32 | if (currentTip == unique) return; 33 | currentTip = unique; 34 | currentTipElement = name; 35 | 36 | var pos = findPos(owner ? owner : (evt.srcElement ? evt.srcElement : evt.target)); 37 | var posx = pos[0]; 38 | var posy = pos[1]; 39 | 40 | var el = document.getElementById(name); 41 | var parent = (document.documentElement == null) ? document.body : document.documentElement; 42 | el.style.position = "absolute"; 43 | el.style.left = posx + "px"; 44 | el.style.top = posy + "px"; 45 | el.style.display = "block"; 46 | } -------------------------------------------------------------------------------- /Code/content/javascripts/scale.fix.js: -------------------------------------------------------------------------------- 1 | var metas = document.getElementsByTagName('meta'); 2 | var i; 3 | if (navigator.userAgent.match(/iPhone/i)) { 4 | for (i=0; i@rawDate.ToString("MMM d, yyyy") 4 | } 5 | 6 | 7 | 8 | 9 | 10 | @Title 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 31 | 32 | 33 |
34 |
35 | @Model.SiteTitle 36 |

@Model.SiteSubtitle

37 | 38 |

View the Project on GitHub

39 | 44 |

Recent posts

45 | @foreach (var post in Enumerable.Take(Model.Posts, 5)) 46 | { 47 | 48 |

@PostDate(post.Date)
@post.Abstract

49 | } 50 |
51 |
52 | @RenderBody() 53 |
54 | 58 |
59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Code/content/layouts/page.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "default"; 3 | } 4 | @RenderBody() -------------------------------------------------------------------------------- /Code/content/layouts/post.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "default"; 3 | } 4 |

@Title

5 | @RenderBody() -------------------------------------------------------------------------------- /Code/content/stylesheets/pygment_trac.css: -------------------------------------------------------------------------------- 1 | .highlight { background: #ffffff; } 2 | .highlight .c { color: #999988; font-style: italic } /* Comment */ 3 | .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 4 | .highlight .k { font-weight: bold } /* Keyword */ 5 | .highlight .o { font-weight: bold } /* Operator */ 6 | .highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ 7 | .highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ 8 | .highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ 9 | .highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ 10 | .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ 11 | .highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #aa0000 } /* Generic.Error */ 14 | .highlight .gh { color: #999999 } /* Generic.Heading */ 15 | .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ 16 | .highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ 17 | .highlight .go { color: #888888 } /* Generic.Output */ 18 | .highlight .gp { color: #555555 } /* Generic.Prompt */ 19 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 20 | .highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */ 21 | .highlight .gt { color: #aa0000 } /* Generic.Traceback */ 22 | .highlight .kc { font-weight: bold } /* Keyword.Constant */ 23 | .highlight .kd { font-weight: bold } /* Keyword.Declaration */ 24 | .highlight .kn { font-weight: bold } /* Keyword.Namespace */ 25 | .highlight .kp { font-weight: bold } /* Keyword.Pseudo */ 26 | .highlight .kr { font-weight: bold } /* Keyword.Reserved */ 27 | .highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ 28 | .highlight .m { color: #009999 } /* Literal.Number */ 29 | .highlight .s { color: #d14 } /* Literal.String */ 30 | .highlight .na { color: #008080 } /* Name.Attribute */ 31 | .highlight .nb { color: #0086B3 } /* Name.Builtin */ 32 | .highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ 33 | .highlight .no { color: #008080 } /* Name.Constant */ 34 | .highlight .ni { color: #800080 } /* Name.Entity */ 35 | .highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ 36 | .highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ 37 | .highlight .nn { color: #555555 } /* Name.Namespace */ 38 | .highlight .nt { color: #000080 } /* Name.Tag */ 39 | .highlight .nv { color: #008080 } /* Name.Variable */ 40 | .highlight .ow { font-weight: bold } /* Operator.Word */ 41 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 42 | .highlight .mf { color: #009999 } /* Literal.Number.Float */ 43 | .highlight .mh { color: #009999 } /* Literal.Number.Hex */ 44 | .highlight .mi { color: #009999 } /* Literal.Number.Integer */ 45 | .highlight .mo { color: #009999 } /* Literal.Number.Oct */ 46 | .highlight .sb { color: #d14 } /* Literal.String.Backtick */ 47 | .highlight .sc { color: #d14 } /* Literal.String.Char */ 48 | .highlight .sd { color: #d14 } /* Literal.String.Doc */ 49 | .highlight .s2 { color: #d14 } /* Literal.String.Double */ 50 | .highlight .se { color: #d14 } /* Literal.String.Escape */ 51 | .highlight .sh { color: #d14 } /* Literal.String.Heredoc */ 52 | .highlight .si { color: #d14 } /* Literal.String.Interpol */ 53 | .highlight .sx { color: #d14 } /* Literal.String.Other */ 54 | .highlight .sr { color: #009926 } /* Literal.String.Regex */ 55 | .highlight .s1 { color: #d14 } /* Literal.String.Single */ 56 | .highlight .ss { color: #990073 } /* Literal.String.Symbol */ 57 | .highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ 58 | .highlight .vc { color: #008080 } /* Name.Variable.Class */ 59 | .highlight .vg { color: #008080 } /* Name.Variable.Global */ 60 | .highlight .vi { color: #008080 } /* Name.Variable.Instance */ 61 | .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ 62 | 63 | .type-csharp .highlight .k { color: #0000FF } 64 | .type-csharp .highlight .kt { color: #0000FF } 65 | .type-csharp .highlight .nf { color: #000000; font-weight: normal } 66 | .type-csharp .highlight .nc { color: #2B91AF } 67 | .type-csharp .highlight .nn { color: #000000 } 68 | .type-csharp .highlight .s { color: #A31515 } 69 | .type-csharp .highlight .sc { color: #A31515 } 70 | -------------------------------------------------------------------------------- /Code/content/stylesheets/styles.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700); 2 | 3 | body { 4 | padding:50px; 5 | font:14px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif; 6 | color:#777; 7 | font-weight:300; 8 | } 9 | 10 | h1, h2, h3, h4, h5, h6, .falseheader { 11 | color:#222; 12 | margin:0 0 20px; 13 | } 14 | 15 | p, ul, ol, table, pre, dl { 16 | margin:0 0 20px; 17 | } 18 | 19 | h1, h2, h3, .falseheader { 20 | line-height:1.1; 21 | } 22 | 23 | h1, .falseheader { 24 | font-size:28px; 25 | } 26 | 27 | h2 { 28 | color:#393939; 29 | } 30 | 31 | h3, h4, h5, h6 { 32 | color:#494949; 33 | } 34 | 35 | a { 36 | color:#39c; 37 | font-weight:400; 38 | text-decoration:none; 39 | } 40 | 41 | a small { 42 | font-size:11px; 43 | color:#777; 44 | margin-top:-0.6em; 45 | display:block; 46 | } 47 | 48 | .wrapper { 49 | width:860px; 50 | margin:0 auto; 51 | } 52 | 53 | blockquote { 54 | border-left:1px solid #e5e5e5; 55 | margin:0; 56 | padding:0 0 0 20px; 57 | font-style:italic; 58 | } 59 | 60 | code, pre { 61 | font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; 62 | font-size:12px; 63 | } 64 | 65 | p code { 66 | font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; 67 | font-size:12px; 68 | padding:2px 4px; 69 | color:#d14; 70 | background-color:#f7f7f9; 71 | border:1px solid #e1e1e8; 72 | -webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px; 73 | } 74 | 75 | pre { 76 | padding:8px 15px; 77 | background: #f8f8f8; 78 | border-radius:5px; 79 | border:1px solid #e5e5e5; 80 | overflow-x: auto; 81 | } 82 | 83 | table { 84 | width:100%; 85 | border-collapse:collapse; 86 | } 87 | 88 | th, td { 89 | text-align:left; 90 | padding:5px 10px; 91 | } 92 | 93 | dt { 94 | color:#444; 95 | font-weight:700; 96 | } 97 | 98 | th { 99 | color:#444; 100 | } 101 | 102 | img { 103 | max-width:100%; 104 | } 105 | 106 | header { 107 | width:270px; 108 | float:left; 109 | position:fixed; 110 | } 111 | 112 | header ul { 113 | list-style:none; 114 | height:40px; 115 | 116 | padding:0; 117 | 118 | background: #eee; 119 | background: -moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%); 120 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd)); 121 | background: -webkit-linear-gradient(top, #f8f8f8 0%,#dddddd 100%); 122 | background: -o-linear-gradient(top, #f8f8f8 0%,#dddddd 100%); 123 | background: -ms-linear-gradient(top, #f8f8f8 0%,#dddddd 100%); 124 | background: linear-gradient(top, #f8f8f8 0%,#dddddd 100%); 125 | 126 | border-radius:5px; 127 | border:1px solid #d2d2d2; 128 | box-shadow:inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0; 129 | width:270px; 130 | } 131 | 132 | header li { 133 | width:89px; 134 | float:left; 135 | border-right:1px solid #d2d2d2; 136 | height:40px; 137 | } 138 | 139 | header ul a { 140 | line-height:1; 141 | font-size:11px; 142 | color:#999; 143 | display:block; 144 | text-align:center; 145 | padding-top:6px; 146 | height:40px; 147 | } 148 | 149 | strong { 150 | color:#222; 151 | font-weight:700; 152 | } 153 | 154 | header ul li + li { 155 | width:88px; 156 | border-left:1px solid #fff; 157 | } 158 | 159 | header ul li + li + li { 160 | border-right:none; 161 | width:89px; 162 | } 163 | 164 | header ul a strong { 165 | font-size:14px; 166 | display:block; 167 | color:#222; 168 | } 169 | 170 | section { 171 | width:500px; 172 | float:right; 173 | padding-bottom:50px; 174 | } 175 | 176 | small { 177 | font-size:11px; 178 | } 179 | 180 | hr { 181 | border:0; 182 | background:#e5e5e5; 183 | height:1px; 184 | margin:0 0 20px; 185 | } 186 | 187 | footer { 188 | width:270px; 189 | float:left; 190 | position:fixed; 191 | bottom:50px; 192 | } 193 | 194 | @media print, screen and (max-width: 960px) { 195 | 196 | div.wrapper { 197 | width:auto; 198 | margin:0; 199 | } 200 | 201 | header, section, footer { 202 | float:none; 203 | position:static; 204 | width:auto; 205 | } 206 | 207 | header { 208 | padding-right:320px; 209 | } 210 | 211 | section { 212 | border:1px solid #e5e5e5; 213 | border-width:1px 0; 214 | padding:20px 0; 215 | margin:0 0 20px; 216 | } 217 | 218 | header a small { 219 | display:inline; 220 | } 221 | 222 | header ul { 223 | position:absolute; 224 | right:50px; 225 | top:52px; 226 | } 227 | } 228 | 229 | @media print, screen and (max-width: 720px) { 230 | body { 231 | word-wrap:break-word; 232 | } 233 | 234 | header { 235 | padding:0; 236 | } 237 | 238 | header ul, header p.view { 239 | position:static; 240 | } 241 | 242 | pre, code { 243 | word-wrap:normal; 244 | } 245 | } 246 | 247 | @media print, screen and (max-width: 480px) { 248 | body { 249 | padding:15px; 250 | } 251 | 252 | header ul { 253 | display:none; 254 | } 255 | } 256 | 257 | @media print { 258 | body { 259 | padding:0.4in; 260 | font-size:12pt; 261 | color:#444; 262 | } 263 | } 264 | 265 | .h2link { 266 | margin: 0 0 8px; 267 | } -------------------------------------------------------------------------------- /Code/fake: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if test "$OS" = "Windows_NT" 3 | then 4 | # use .Net 5 | [ ! -f .paket/paket.exe ] && .paket/paket.bootstrapper.exe 6 | .paket/paket.exe restore 7 | packages/FAKE/tools/FAKE.exe fsblog.fsx $@ 8 | else 9 | # use mono 10 | [ ! -f .paket/paket.exe ] && mono --runtime=v4.0 .paket/paket.bootstrapper.exe 11 | mono --runtime=v4.0 .paket/paket.exe restore 12 | mono --runtime=v4.0 packages/FAKE/tools/FAKE.exe fsblog.fsx -d:MONO $@ 13 | fi 14 | -------------------------------------------------------------------------------- /Code/fake.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if not exist .paket\paket.exe ( 4 | .paket\paket.bootstrapper.exe 5 | ) 6 | .paket\paket.exe restore 7 | packages\FAKE\tools\FAKE.exe fsblog.fsx %* 8 | -------------------------------------------------------------------------------- /Code/fsblog.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | # FsBlog Script 3 | 4 | This script is the main workhorse of FsBlog that just coordinates the commands 5 | and tasks that operate with the static site generation. 6 | *) 7 | 8 | #I @"packages/FAKE/tools/" 9 | #I @"packages/FSharp.Configuration/lib/net45" 10 | #I @"packages/RazorEngine/lib/net45" 11 | #I @"packages/Suave/lib/net40" 12 | #I @"bin/FsBlogLib" 13 | 14 | #r "FakeLib.dll" 15 | #r "RazorEngine.dll" 16 | #r "FsBlogLib.dll" 17 | #r "FSharp.Configuration.dll" 18 | #r "Suave.dll" 19 | 20 | open Fake 21 | open Fake.Git 22 | open System 23 | open System.IO 24 | open System.Net 25 | open System.Diagnostics 26 | open System.Threading 27 | open FsBlogLib 28 | open FSharp.Configuration 29 | open Suave 30 | open Suave.Web 31 | open Suave.Files 32 | open Suave.Sockets 33 | open Suave.Sockets.Control 34 | open Suave.WebSocket 35 | open Suave.Operators 36 | 37 | 38 | // -------------------------------------------------------------------------------------- 39 | // Configuration. 40 | // -------------------------------------------------------------------------------------- 41 | Environment.CurrentDirectory <- __SOURCE_DIRECTORY__ 42 | type Config = YamlConfig<"config/config.yml"> 43 | 44 | let config = new Config() 45 | let title = config.title 46 | let subtitle = config.subtitle 47 | let description = config.description 48 | let gitLocation = config.gitlocation 49 | let gitbranch = config.gitbranch 50 | 51 | let source = __SOURCE_DIRECTORY__ ++ config.source 52 | let blog = __SOURCE_DIRECTORY__ ++ config.blog 53 | let blogIndex = __SOURCE_DIRECTORY__ ++ config.blogIndex 54 | let themes = __SOURCE_DIRECTORY__ ++ config.themes 55 | let content = __SOURCE_DIRECTORY__ ++ config.content 56 | let layouts = content ++ config.layouts 57 | let template = __SOURCE_DIRECTORY__ ++ config.template 58 | 59 | let output = __SOURCE_DIRECTORY__ ++ config.output 60 | let deploy = __SOURCE_DIRECTORY__ ++ config.deploy 61 | 62 | let tagRenames = List.empty |> dict 63 | let exclude = [] 64 | 65 | let special = 66 | [ source ++ "index.cshtml" 67 | source ++ "blog" ++ "index.cshtml" ] 68 | let rsscount = 20 69 | 70 | // -------------------------------------------------------------------------------------- 71 | // Regenerates the site 72 | // -------------------------------------------------------------------------------------- 73 | 74 | type RoutingMode = 75 | | Production 76 | | Preview 77 | 78 | let buildSite routing updateTagArchive = 79 | 80 | let root = 81 | match routing with 82 | | Production -> config.url.AbsoluteUri 83 | | Preview -> "http://localhost:8080" 84 | 85 | let dependencies = [ yield! Directory.GetFiles(layouts) ] 86 | let noModel = { Root = root; SiteTitle = title; SiteSubtitle = subtitle; MonthlyPosts = [||]; Posts = [||]; TaglyPosts = [||]; GenerateAll = true } 87 | let razor = new Razor(layouts, Model = noModel) 88 | let model = Blog.LoadModel(tagRenames, Blog.TransformAsTemp (template, source) razor, root, blog, title, subtitle) 89 | 90 | // Generate RSS feed 91 | Blog.GenerateRss root title description model rsscount (output ++ "rss.xml") 92 | 93 | let uk = System.Globalization.CultureInfo.GetCultureInfo("en-GB") 94 | Blog.GeneratePostListing 95 | layouts template blogIndex model model.MonthlyPosts 96 | (fun (y, m, _) -> output ++ "blog" ++ "archive" ++ (m.ToLower() + "-" + (string y)) ++ "index.html") 97 | (fun (y, m, _) -> y = DateTime.Now.Year && m = uk.DateTimeFormat.GetMonthName(DateTime.Now.Month)) 98 | (fun (y, m, _) -> sprintf "%d %s" y m) 99 | (fun (_, _, p) -> p) 100 | 101 | if updateTagArchive then 102 | Blog.GeneratePostListing 103 | layouts template blogIndex model model.TaglyPosts 104 | (fun (_, u, _) -> output ++ "blog" ++ "tag" ++ u ++ "index.html") 105 | (fun (_, _, _) -> true) 106 | (fun (t, _, _) -> t) 107 | (fun (_, _, p) -> p) 108 | 109 | let filesToProcess = 110 | FileHelpers.GetSourceFiles source output 111 | |> FileHelpers.SkipExcludedFiles exclude 112 | |> FileHelpers.TransformOutputFiles output source 113 | |> FileHelpers.FilterChangedFiles dependencies special 114 | 115 | let razor = new Razor(layouts, Model = model) 116 | for current, target in filesToProcess do 117 | FileHelpers.EnsureDirectory(Path.GetDirectoryName(target)) 118 | printfn "Processing file: %s" (current.Substring(source.Length)) 119 | Blog.TransformFile template true razor None current target 120 | 121 | FileHelpers.CopyFiles content output 122 | 123 | // -------------------------------------------------------------------------------------- 124 | // Webserver stuff 125 | // -------------------------------------------------------------------------------------- 126 | 127 | let refreshEvent = new Event<_>() 128 | 129 | let handleWatcherEvents (events:FileChange seq) = 130 | for e in events do 131 | let fi = fileInfo e.FullPath 132 | traceImportant <| sprintf "%s was changed." fi.Name 133 | match fi.Attributes.HasFlag FileAttributes.Hidden || fi.Attributes.HasFlag FileAttributes.Directory with 134 | | true -> () 135 | | _ -> buildSite Preview false // TODO optimize based on which file has changed 136 | refreshEvent.Trigger() 137 | 138 | let socketHandler (webSocket : WebSocket) = 139 | fun cx -> socket { 140 | while true do 141 | let! refreshed = 142 | Control.Async.AwaitEvent(refreshEvent.Publish) 143 | |> Suave.Sockets.SocketOp.ofAsync 144 | do! webSocket.send Text (new ByteSegment(System.Text.Encoding.UTF8.GetBytes "refreshed")) true 145 | } 146 | 147 | let startWebServer () = 148 | printfn "starting webserver: %s" (FullName output) 149 | let serverConfig = 150 | { defaultConfig with 151 | homeFolder = Some (FullName output) 152 | bindings = [HttpBinding.create HTTP IPAddress.Loopback 8080us] 153 | } 154 | let app = 155 | choose [ 156 | Filters.path "/websocket" >=> handShake socketHandler 157 | Writers.setHeader "Cache-Control" "no-cache, no-store, must-revalidate" 158 | >=> Writers.setHeader "Pragma" "no-cache" 159 | >=> Writers.setHeader "Expires" "0" 160 | >=> browseHome ] 161 | startWebServerAsync serverConfig app |> snd |> Async.Start 162 | Process.Start "http://localhost:8080/index.html" |> ignore 163 | 164 | // -------------------------------------------------------------------------------------- 165 | // Static site tooling as a set of targets. 166 | // -------------------------------------------------------------------------------------- 167 | 168 | /// Regenerates the entire static website from source files (markdown and fsx). 169 | Target "Generate" (fun _ -> 170 | buildSite Production true 171 | ) 172 | 173 | Target "Preview" (fun _ -> 174 | 175 | buildSite Preview true 176 | 177 | use watcherDynamic = !! (source + "**/*.*") |> WatchChanges (fun changes -> 178 | printfn "Dynamic: %A" changes 179 | handleWatcherEvents changes 180 | ) 181 | 182 | use watcherStatic = !! (content + "/**/*.*") |> WatchChanges (fun changes -> 183 | printfn "Static: %A" changes 184 | handleWatcherEvents changes 185 | ) 186 | 187 | startWebServer () 188 | 189 | traceImportant "Press Ctrl+C to stop!" 190 | // wat!? 191 | Thread.Sleep(-1) 192 | ) 193 | 194 | Target "New" (fun _ -> 195 | let post, fsx, page = 196 | getBuildParam "post", 197 | getBuildParam "fsx", 198 | getBuildParam "page" 199 | 200 | match page, post, fsx with 201 | | "", "", "" -> traceError "Please specify either a new 'page', 'post' or 'fsx'." 202 | | _, "", "" -> BlogPosts.CreateMarkdownPage source page 203 | | "", _, "" -> BlogPosts.CreateMarkdownPost blog post 204 | | "", "", _ -> BlogPosts.CreateFsxPost blog fsx 205 | | _, _, _ -> traceError "Please specify only one argument, 'post' or 'fsx'." 206 | ) 207 | 208 | Target "Clean" (fun _ -> 209 | CleanDirs [output] 210 | ) 211 | 212 | Target "Deploy" DoNothing 213 | 214 | Target "Commit" DoNothing 215 | 216 | Target "DoNothing" DoNothing 217 | 218 | Target "GitClone" (fun _ -> 219 | if(FileSystemHelper.directoryExists(deploy ++ ".git")) then 220 | () 221 | else 222 | Repository.cloneSingleBranch __SOURCE_DIRECTORY__ gitLocation.AbsoluteUri gitbranch deploy 223 | ) 224 | 225 | Target "GitPublish" (fun _ -> 226 | CopyRecursive output deploy true |> ignore 227 | CommandHelper.runSimpleGitCommand deploy "add ." |> printfn "%s" 228 | let cmd = sprintf """commit -a -m "Update generated web site (%s)" """ (DateTime.Now.ToString("dd MMMM yyyy")) 229 | CommandHelper.runSimpleGitCommand deploy cmd |> printfn "%s" 230 | Branches.push deploy 231 | ) 232 | 233 | Target "Install" (fun _ -> 234 | let theme = getBuildParam "theme" 235 | 236 | match theme with 237 | | "" -> traceError "Please specify theme" 238 | | _ -> 239 | CleanDir content 240 | CopyDir content (themes ++ theme) (fun file -> not(file.StartsWith(themes ++ theme ++ "source"))) |> ignore 241 | CopyRecursive (themes ++ theme ++ "source") source true |> ignore 242 | ) 243 | 244 | "Clean" =?> 245 | ("Install", hasBuildParam "theme") ==> 246 | "Generate" 247 | 248 | "Clean" =?> 249 | ("Install", hasBuildParam "theme") ==> 250 | "Preview" 251 | 252 | "Generate" ==> "GitClone" ==> "GitPublish" 253 | 254 | // -------------------------------------------------------------------------------------- 255 | // Run a specified target. 256 | // -------------------------------------------------------------------------------------- 257 | RunTargetOrDefault "Preview" 258 | -------------------------------------------------------------------------------- /Code/paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://nuget.org/api/v2 2 | 3 | nuget FSharp.Configuration 4 | nuget FSharp.Formatting 5 | nuget NuGet.CommandLine 6 | nuget FAKE 7 | nuget RazorEngine 8 | nuget suave 9 | nuget NUnit 10 | nuget NUnit.Runners 11 | github forki/FsUnit FsUnit.fs 12 | -------------------------------------------------------------------------------- /Code/tests/FsBlogLib.Tests/BlogPostsTest.fs: -------------------------------------------------------------------------------- 1 | module FsBlogLib.Tests.BlogPostsTests 2 | 3 | open FsBlogLib 4 | open System 5 | open System.IO 6 | open NUnit.Framework 7 | 8 | [] 9 | let ``Create blog post`` () = 10 | let path = __SOURCE_DIRECTORY__ ++ "..\\source\\blog" 11 | let title = "FsharpTest" 12 | let dateFormat = DateTime.Now 13 | let expectedPath = path ++ dateFormat.Year.ToString() ++ sprintf "%02i-%02i-%s" dateFormat.Month dateFormat.Day "fsharptest.md" 14 | BlogPosts.CreateMarkdownPost path title 15 | Assert.True(File.Exists(expectedPath)) 16 | -------------------------------------------------------------------------------- /Code/tests/FsBlogLib.Tests/BlogTests.fs: -------------------------------------------------------------------------------- 1 | module FsBlogLib.Tests.BlogTests 2 | 3 | open FsBlogLib.FileHelpers 4 | open FsBlogLib 5 | open System.IO 6 | open NUnit.Framework 7 | 8 | let layouts = "content/layouts" 9 | let root = "http://localhost:8080" 10 | let template = "tools/empty-template.html" 11 | 12 | [] 13 | let basicPost = " 14 | This is a heading 15 | =================" 16 | 17 | [] 18 | let basicExpectedHtml = "

This is a heading

\r\n\r\n" 19 | 20 | [] 21 | let youtubePost = "https://www.youtube.com/watch?v=6q_IIWZwEhs" 22 | 23 | [] 24 | let youtubeExpectedHtml = "