Monday, January 23, 2012

HTML reporting for StyleCop

Based on the outputs from the earlier script; and using an appearance inspired by the output from the XSL report approach from codecampserver; but with the source in-lined into the report, and violation messages inserted after the affected lines. Each file (and the nested violation messages) are hidden by default, and can be expanded by clicking the file header or the source marked as violation (where the hand cursor shows):

N.B. Defined as it is within a PowerShell here-string, the jQuery script needs its $ symbols to be escaped to keep the PowerShell parser happy.

<# 
.SYNOPSIS 
    Stylecop HTML report generator. 
    
.DESCRIPTION 
    Creates *.html from *.StyleCop.xml in the current directory 
    These are reports with pretty reporting
        
.NOTES 
    File Name  : Transform-StyleCopReport.ps1 
    Requires   : PowerShell Version 2.0
#> 
function Insert-Newline([System.Xml.XmlElement] $element) {
    $break = $element.OwnerDocument.CreateSignificantWhitespace("`r`n");
    $element.AppendChild($break) | Out-Null
}

function Make-HTML([string] $file) {
    Write-Host "Processing report $file"
    $htmlfile = $file.Replace(".StyleCop.xml", ".html")
    [xml]$raw = Get-Content $file
    [xml]$report = @"
<!DOCTYPE html>
<html>
<head>
<title>StyleCop Report</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<style>
body {position: absolute; top: 0px; width: 100%;margin:0px;min-height:100%;padding:0}
body {font-family:Verdana, Helvetica, sans-serif;font-size:70%;background:#fff;color:#000;}
h1 { padding: 10px; width: 100%; background-color: #566077; color: #fff; }
h2 { margin:10px; padding: 10px; background-color: #ffc; border: #d7ce28 1px solid;  }
h3 { margin:25px; padding: 10px; background: #b9c9fe; color: #039; border: 1px solid #aabcfe; }
h3 span {float: right;}
div { margin:25px; border: #d7ce28 1px solid; }
div div { border: none; }
pre { font-family:Consolas, Inconsolata, "Lucida Console", "Courier New", monospace;font-size:100%; padding: 0; margin:0}
pre.odd { background: #eee; }
pre.even { background: #ddf; }
span.violation { background: #ff8; }
span.index { border-right : 1px solid black }
</style>
<script type="text/javascript">
`$(function(){
  // inner first, then outer
  `$('span.violation')
    .click(function(event) {
      if (this == event.target) {
        `$(this).parent().next().toggle();
      }
    return false;
  })
  .css('cursor', 'pointer')
  .click();  
  
  `$('h3')
    .click(function(event) {
      if (this == event.target) {
        `$(this).next().toggle();
      }
    return false;
  })
  .css('cursor', 'pointer')
  .click();
});
</script>

</head>

<body>
<h1>StyleCop Report</h1>

</body>

</html>
"@
    $body = (Select-Xml -Xml $report -XPath "//body").Node
    $doc = $body.OwnerDocument
    $doc.PreserveWhitespace = $true
    Insert-Newline $body
    
    $x = $doc.CreateElement("h2", "")
    $violations = (Select-Xml -Xml $raw -XPath "//Violation")
    
    $x.InnerText = "Total Violations : $($violations.Length)"
    $body.appendChild($x) | Out-Null
    Insert-Newline $body
    
    $vfiles = @($violations | % { $_.Node.Source } | Sort-Object -Unique)
    
    $vfiles | % { 
        $path = Resolve-Path $_
        $local = (Select-Xml -Xml $raw -XPath "//Violation[@Source='$_']")

        $h = $doc.CreateElement("h3", "")
        $body.AppendChild($h) | Out-Null
        Insert-Newline $body
        
        $t = $doc.CreateTextNode("SourceFile: $path")
        $span = $doc.CreateElement("span", "")
        
        $span.InnerText = "($($local.Length) violations)"
        $h.AppendChild($t)     | Out-Null
        $h.AppendChild($span)  | Out-Null
        
        $sdiv = $doc.CreateElement("div", "")
        $body.AppendChild($sdiv) | Out-Null
                    
        $source = Get-Content $path
        
        $line = 0
        $source | % {
            $line += 1
            $pre = $doc.CreateElement("pre", "")
            $num = $line.ToString()
            while ($num.Length -lt 4) { $num = " " + $num }
            $span1 = $doc.CreateElement("span", "")
            $span1.InnerText = "$num "
            $span1.SetAttribute("class", "index")
            $pre.AppendChild($span1) | Out-Null
            
            $span2 = $doc.CreateElement("span", "")
            $span2.InnerText = $_
            $pre.AppendChild($span2) | Out-Null
            
            $sdiv.AppendChild($pre) | Out-Null
            $pre.SetAttribute("class", "odd")
            if (0 -eq ($line % 2)) { $pre.SetAttribute("class", "even") }
            
            $localhere = @($local | % { $_.Node } | ? { $_.LineNumber -eq $line })
            if ($localhere.Length -gt 0) { 
                $span2.SetAttribute("class", "violation")
                $pdiv = $doc.CreateElement("div", "")
                Insert-Newline $sdiv
                $sdiv.AppendChild($pdiv) | Out-Null
                $ul = $doc.CreateElement("ul", "")
                $pdiv.AppendChild($ul) | Out-Null
                $localhere | % {
                    $li = $doc.CreateElement("li", "")
                    $li.InnerText = $_.InnerText
                    $ul.AppendChild($li) | Out-Null
                    Insert-Newline $ul
                }
            }
            
            Insert-Newline $sdiv
        }
    } # $vfiles
    
    $report.Save($htmlfile)
} # function

$files = @(dir *.StyleCop.xml)
$files | % { Make-HTML $_.FullName }

You will need to adjust the script to find the appropriate StyleCop output files from your build process, and create suitably named/located reports based on those files.

Sunday, January 22, 2012

Garden rubbish

Apart from a brief spell around last weekend, and into the start of the past week, where temperatures driving to work were in the -3C to -5C range, winter hasn't really arrived -- we've just had an extended autumn.

The cold finally did for the the antirrhinum flowers, and the last signs of life in the sunflowers, which are still serving as bird-feeders; but we still have overwintering escholzias; self-sown poached-egg plants from last summer's flowering already in bloom, primroses since before the end of last year, and now snowdrops and the first of the early crocuses.

I have at least been able to tend to a number of the tidy-up chores : pruning roses, fuchsias and apple trees; clearing out the straw from various dead annuals, and the last year's growth from the crocosmias and lemon balm.

And of course there are weeds already -- including a fine old crop of winter wheat from the porridge-like mass of spilt grain out of the bird feeder, as well as the usual stubborn perennials, misplaced self-seedings and the like. Which all means that the green bin is continually being put out full, even in the putative low season.

If it were drier, there'd even be grass clippings, as the mild wet weather is encouraging the lawn already!


Adding the missing pieces to the Standalone StyleCop script

Rather than post the whole ~200 lines, just the salient bits so that the original can be tidied up (but without obscuring everything with obsessive error handling). Start by giving it parameters like

<# 
.SYNOPSIS 
    Stand-alone stylecop runner. 
    
.DESCRIPTION 
    Runs StyleCop over a project or a solution
        
.NOTES 
    File Name  : Run-StyleCop.ps1 
    Requires   : PowerShell Version 2.0

.PARAMETER FilesToScan

Project or Solution file

.PARAMETER StyleCopFolder

To override the heuristic looking in $env:ProgramFiles for the latest version
e.g. for StyleCop in source control

.PARAMETER OnlyBuildIntegrated

Only scan project files which include <Import Project="something with 'stylecop' in it" />
#>
param ( 
    [Parameter(Mandatory = $true)] [string] $FilesToScan,
    [string] $StyleCopFolder,
    [switch] $OnlyBuildIntegrated)

Find StyleCop in the default install location by


if (-not $StyleCopFolder) {
    $styleCopFolder =  (dir "$env:programFiles\stylecop*" | Sort-object LastWriteTimeUtc | Select-Object -Last 1).FullName
}

Get the list of projects by either just the project, or using a regex on the project file from here: http://bytes.com/topic/c-sharp/answers/483959-enumerate-projects-solution


if ($FilesToScan -like "*.csproj") {
    $projectList = ,$FilesToScan
} else {
    if ($FilesToScan -like "*.sln") {
        $solutionDir = Split-Path $FilesToScan
        $matchProjectNameRegex =
            '^Project\("(?<PROJECTTYPEGUID>.*)"\)\s*=\s* "(?<PROJECTNAME>.*)"\s*,\s*"(?<PROJECTRELATIVEPATH>.*)"\s*,\s*"(?<PROJECTGUID>.*)"$'
        # or we could filter on project guid being {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
        $projectList = @(Get-Content $FilesToScan | % { 
                        $matches = $null; $_ -match $matchProjectNameRegex | Out-Null; $matches.PROJECTRELATIVEPATH } | ? {
                        $_ -like "*.csproj" } | % { Join-Path $solutionDir $_ } )
    } else {
        Write-Error "$FilesToScan isn't a solution file or a C# project"
    }
}

# Check if required for a StyleCop target
if ($OnlyBuildIntegrated) {
    $projectList = @($projectList | ? {
        [xml]$content = Get-Content $_
        $imports = @($content.Project.Import)
        $flagged = (@($imports | % { $_.Project } | ? { $_ -like "*stylecop*" })).Length
        if ($flagged -eq 0) { Write-Host "Skipping project $_" }
        ($flagged -gt 0)
})}

Find a Settings.StyleCop file by looking at the project folder and up, defaulting to one in the StyleCop folder; and then scan the C# files of interest by


[xml]$content = Get-Content $projectFile
        # All C# files
        $cs = @($content.Project.ItemGroup | % { $_.Compile } | ? {$_})
        
        # Filter excluded files; get absolute paths
        $projectDir = Split-Path $_
        $cs = @($cs | ? { -not (($cs[0].ExcludeFromStyleCop -like "true") -or ($cs[0].ExcludeFromSourceAnalysis -like "true")) 
            } | % { Join-Path $projectDir $_.Include })
        
        $cs | % { $console.Core.Environment.AddSourceCode($codeproject, $_, $null) | Out-Null }

and customise the output file as


$name = (Split-Path $projectFile -Leaf).Replace(".csproj", ".StyleCop.xml")
    $outputXml = Join-Path (Get-Location) $name

Saturday, January 21, 2012

Standalone StyleCop : a rough draft

Unlike FxCop, the StyleCop tool out of the box only comes with Visual Studio and MSBuild integration; there is no stand-alone command-line tool. Still, as it's now open-source, we can look at the workings of the MSBuild task and see how to drive it directly.

Here is a proof-of-concept PowerShell script which can be used as the basis for a proper command-line tool or script; and as a preliminary "how to" to write unit tests for your rules. Well, they aren't going to be proper unit tests, since they'd have to touch the file system, but they can crawl over test source files in a dummy project in the StyleCop rule solution.

# Load StyleCop
$path = "$($env:ProgramFiles)\StyleCop 4.5\StyleCop.dll"
Add-Type -Path $path

# arguments that must be non-null, non-empty
$settings = "$($env:UserProfile)\Documents\Visual Studio 2008\Projects\TestBed\Settings.StyleCop"
$here = Get-Location
$outputFile = Join-Path $here "output.xml"

# UNTESTED : the empty array should be filled with a list of directories containing 
# StyleCop add-ins that aren't in or beneath the StyleCop install folder
$console = new-object StyleCop.StyleCopConsole -arg  @($settings, $false, $outputFile, [string[]]@(), $true)

# Usual test case #defines
$defines = [string[]]@("DEBUG", "CODE_ANALYSIS")
$configuration = new-object StyleCop.Configuration -arg (,$defines)
$project = "$($env:UserProfile)\Documents\Visual Studio 2008\Projects\TestBed\TestBed.csproj"
$codeproject = new-object StyleCop.CodeProject -Arg @($project.GetHashCode(), $project, $configuration)
[StyleCop.CodeProject[]] $projects = ,$codeproject

## TODO -- get source files from project, do AddSourceCode for each file not excluded from StyleCop
$source = "$($env:UserProfile)\Documents\Visual Studio 2008\Projects\TestBed\Class1.cs"
$console.Core.Environment.AddSourceCode($codeproject, $source, $null) | Out-Null

# These are messages suitable for MSBuild e.g. "Output: (Low) Pass 1: Class1.cs"
$outputHandler = {
    $e = $event.SourceEventArgs
    $importance = $e.Importance
    $message = $e.Output

    $colour = "Green"
    if ($importance -eq [Microsoft.Build.Framework.MessageImportance]::Low) { $colour = "Gray" }
    if ($importance -eq [Microsoft.Build.Framework.MessageImportance]::High) { $colour = "Red" }
    Write-Host "Output: ($importance) $message" -Back White -Fore $colour
}

# These are the interesting bits, which also go into the XML file e.g.
# Violation SA1200 in ...\Class1.cs (1)
# All using directives must be placed inside of the namespace.
$violationHandler = {
    $e = $event.SourceEventArgs
    if ($e.SourceCode -and $e.SourceCode.Path) {
        $file = $e.SourceCode.Path;
    } else {
 if ($e.Element -and $e.Element.Document -and $e.Element.Document.SourceCode -and $e.Element.Document.SourceCode.Path)
        {
            $file = $e.Element.Document.SourceCode.Path;
        }
    }

    $colour = "Black"
    if ($e.Warning) { $colour = "Blue" }
    Write-Host "Violation $($e.Violation.Rule.CheckId) in $file ($($e.LineNumber))`r`n`t$($e.message)" -Back White -Fore $colour
}

try {
    Register-ObjectEvent -InputObject $console -EventName OutputGenerated -SourceIdentifier "Console.OutputGenerated" -Action $outputHandler | Out-Null
    Register-ObjectEvent -InputObject $console -EventName ViolationEncountered -SourceIdentifier "Console.ViolationEncountered" -Action $violationHandler | Out-Null
    $Console.Start($projects, $true) | Out-Null
} catch [System.Exception] {
    Write-Host $_.Exception.ToString()
} finally {
    Unregister-Event "Console.OutputGenerated"
    Unregister-Event "Console.ViolationEncountered"
}

## Could dump the XML format output here, like this; or run an XSLT report generation over it
##$values = Get-Content $outputFile
##Write-Host $values$outputFile

And that's all there is to it, really.


Saturday, December 31, 2011

3202.5

That's the cumulative cycling mileage at the end of the year, passing the 3200 target I wanted to hit (~2170 over the year, plus the 90-odd on a hired bike to make over 2250 total). That's omitting those miles where I'd forgotten to put the widget back in place to count the distance; but that is unlikely to be more than about 5 miles total, and could be made up with distances pushed anyway.

I think it goes to show how dry the weather was for much of the year.


Anime -- 2011 in review (part 1)

In total, I have watched all through just 3 series from the last year, am in the middle of two that have completed, one that hasn't and am about to start on one that has. Plus a scattering of older shows finished or in train. This post, old shows considered.


Mobile Suit Gundam


I remember when the future looked like Gundam - O'Neill colonies throughout the Earth-Moon system, but no internet. Now add brutal robot combat, the usual Tomino level of named character futile deaths, mixed with toy commercials. Like the following year's Space Runaway Ideon, this has an element of a treadmill, where each episode the enemy -- here the L2-based Principality of Zeon -- uprates its mobile suits each episode and our guys with their carrier and mobile suits continue to level up to meet them.

After ten episodes of this, the action moves ground-side, with a long meander from south-east Asia, through to the Black Sea (with a number of side trips -- "we need to reprovision with salt"; our hero has an angst attack and has wandered off with the Gundam; ...) thence to Ireland (where we meet a traditional red-headed Irish girl, with the traditional Irish name of Miharu), then across the Atlantic to a Federation base in South America, having circumnavigated the globe rather than going direct.

Then, finally, we return to space for a series of skirmishes, culminating in an escalating final battle where pretty much everything gets destroyed; but the power of Zeon is broken and all seems well (until the next series).

Even after 30 years, and with an indifferent American dub, the series holds up as more than just a piece of history. The anti-war message may be unsubtle, but there is a feeling of how even the significant players like the Gundam team are just a part of a larger concern; and the daring and charismatic recurring antagonist, Char Aznable is in turn just a minor, though equally significant, character on the Zeon side.


Catman


A quirky little flash series about a down-and-out in an alternate Canada peopled by anthropomorphic cats. It's slice of life anime done right. That means without any cute girls, school, or even cake, but with a whole lot of drinking, swearing, bowling, getting beaten up, and freezing to death on the street. And catchy ska BGM from the Planet Smashers.


Hyakko


A series I passed by back in the busy year of 2008, as another cute girls at school series like Hidamari Sketch or Sketchbook Full Colours; but which turned out to have rather more energy and enthusiasm about what it's doing. We start by having the ditzy bonde Ayumi as the first viewpoint character lost in her vast new school, then accumulate the oujo-sama Tatsuki (also lost), and the core group is then completed by the sudden eruption from an upper storey window of the tomboyish Torako, followed by her constant companion, Suzume (who are solving the navigation problem by heading in a straight line).

The series takes a little time finding its feet, and goes thematically all over the place, from out and out comedy, to amped-up melodrama; but as we learn about the quartet and the rest of the class of equally idiosyncratic types, everyone is refreshingly nice (except for the boys in the senior years, who are jerks, and Torako's family, who exist mainly for injecting drama).

Not an undying classic of the ages, but still a nice feel-good series to have watched over the holidays; especially as it was set in early summer, sunny and bright, a perfect antidote to the midwinter blues; the one exception, the April rainstorm that opens the final flashback episode, where the melancholy dawn of the first day of the school year breaks. That scene was the one where the incidental music was most noticeable, and it seemed to fit the bitter-sweet flavour of the whole episode.


Natsume Yuujinchou -- seasons 1 and 2


Another series from 2008 (and then 2009); somewhat like Mushishi, in that Natsume Takashi has the ability to see spirits that pass by normal people unobserved. However, unlike the competent adult Ginko, who has to deal with fungal or insectile mushi, the youkai that highschooler Natsume can see are people of a sort.

This unsettling aspect of his life, reactions to and conversations with the unseen, has left him shuffled from relative to relative. This time he also stumbles across some of his late grandmother's possessions -- including the eponymous book in which she inscribed the true names of many youkai; and into a shrine where a powerful spirit is bound who Natsume accidentally frees.

Having spent many years bound in the form of a lucky cat, the spirit now appears as a human-visible cat most of the time; and sets himself up as Natsume's bodyguard : no-one else is getting that book but him.

So with the rascally Nyanko-sensei to guide and protect him, he starts to find and unbind some of the youkai in the Book of Friends. The series thus starts as a simple monster of the week; but as the extended cast is gathered, it becomes as much about resolving problems amongst the youkai, and Natsume worrying about how his life is balanced between the two worlds.

While being much the typical anime protagonist most of the time, the show avoids getting Natsume into any of the usual romance clichés, without actively excluding girls from his social circle (such as it is). The fact that many of them are spirits, and one explicitly declares that she was much more interested in his grandmother, helps avoid that sort of thing.

So a quiet and delightful little series; and in the new year, I shall move onto the recently aired 3rd season, and the about to air 4th.




Wednesday, December 28, 2011

Film -- The Adventures Of Tintin: Secret Of The Unicorn

Last cinema visit of the year, catching the holiday re-run of the film.

Not having read any of the source material in decades, I didn't worry about anything that might offend purists, but enjoyed the cartoony inter-war era high adventure in the style of what I did recall.

We will know that the CGI medium, especially in its 3D expression, is mature when they stop showing off gratuitous mirrors and lenses.


Tuesday, December 27, 2011

Writing StyleCop rules in F#

Alas not "Writing StyleCop rules for F#" (or should that be StyleFop?), which would be nice, but a true job of work...

I don't see much need for more StyleCop rules for C# (except one to harness the FxCop spellcheck facilities to scan comments, which would have to be done via much dodgy reflection to winkle out internal types, and would anyway give issues for being tied to 32-bit native executables), so haven't tried this before. But, like mountains, it's there...


So, you have StyleCop 4.5 or 4.6 installed, and start by creating an empty class library targeted at the .net 3.5 environment, with added references to the StyleCop and StyleCop.CSharp assemblies. The bare rule class looks like

open StyleCop
open StyleCop.CSharp

namespace My.Namespace

[<SourceAnalyzer(typeof<CsParser>)>]
type MyRules() =
    inherit SourceAnalyzer()

and to go with it, add an XML file as an Embedded Resource with name My.Namespace.MyRules.xml -- first gotcha : the long name needs to be given in full because the default namespace doesn't get applied in the build like it would for C#. That can then be filled in as per the instructions in the StyleCop SDK documentation. Then just override the AnalyzeDocument method and go wild:

override self.AnalyzeDocument (document : CodeDocument) =
        let csharp = document :?> CsDocument
        self.CheckForTrailingSpaces csharp
        // add more rules here...

where I'm using the concept from the StyleCop+ rule SP2000 as an example of a new rule not yet present in the core tool.

Unlike FxCop, there is not a "one class = one rule" model here; the classes exist to group related rules, and you control how each scans the current source file of interest, and, indeed, how distinct they are in the code -- a closely related set of rules could be checked off a single scan, and in some ways StyleCop rules are more akin to the distinct named resolutions that can be defined within a single FxCop rule.

Another consequent difference from FxCop is how the object graph is visited. In FxCop, there are VisitXxx methods to override that will be called for every object of type Xxx, in an essentially stateless manner (your rule class is responsible for maintaining state between calls); here the equivalent methods are passed to a WalkXxx graph walker method as a callback, provide a mutable state object argument, and can signal through their return value whether the traversal should continue.

This approach of using a mutable object, needing down-casting, to carry inputs into each call, and contain the resultant outputs, feels a bit odd in a functional environment; fortunately it is possible to bypass this in many cases by representing the tree-walk as a seq and operating on that. For the trailing whitespace rule, where the offending lines are most easily found by scanning the raw source line by line, all we need the traversal for is to find an ICodeElement context object matching that line number. A simple walk of the code element tree looks like

let rec descent (x:CsElement) = seq {
                                                yield x
                                                if x.ChildElements <> null then
                                                    for c in x.ChildElements do
                                                        yield! descent c
                                            }

which we apply to the document RootElement and from the result find the last (so most deeply nested, and hence most constrained) item whose Location has a span that includes the line number of interest (skip while the end is before the line of interest, take while the start includes the line, reverse, get head), the line number being a value that the operation can close over.

For deployment onto machines that don't have F# installed, static linkage of the runtime via the --standalone build flag can be used rather than having to explicitly drop the F# runtime assembly into the StyleCop folder (second gotcha).

Monday, December 26, 2011

A mild Christmas, a welcome change

Friday, with the temperature about 5C, and strong sou'westerly blowing, I cycled to work, thinking all the while that I was mad for doing so, with a weather forecast for rain, mainly heavy, in the afternoon. Unlike last year, there wasn't the need to wear anything over my spandex shorts for warmth, so that would be enough to mitigate the worst of cycling in the rain. As it turned out, when I set off home a while after 2, it was starting to sprinkle with rain; eventually getting heavy enough to want a waterproof, but not really uncomfortable at all.

Saturday, a muntjac wandered into the garden, and ate some of the remaining windfall apples, the ones too small or damaged to have harvested. The cats were not amused by this thing invading their territory, and kept a wary eye on it from a distance, and well away as it wandered off again. At least it was too big to fit through the cat-flap, unlike pigeons or the various rodents whose entrails at times have decorated the kitchen floor; more like the pheasant that was left on the doorstep, with just a clear cat-inflicted bare patch on its neck a couple of weeks ago.

Yesterday began with the exchange of presents -- where I got Vinge's latest, novel, The Children of the Sky, the long awaited sequel to 1993 Hugo winner, A Fire Upon the Deep; and Tim Worstall's book of essays on economy and the environment Chasing Rainbows, which metaphorically in text is like suikawari without the blindfold; and then lunch.

Having not thought to book until 5 weeks ago, we had a simple enough choice of places where there were still slots to be had -- lunch at the Carpenters Arms.

This was a leisurely affair, 12:30 for 1, canapés, starters, then buffet style mains, where I was unable to take up the offer of a third helping, before the cheese board, a little Xmas pudding, and coffee with petites-fours; and so home in gentle fashion by not too long after 5pm.

So today could be pure R&R, with just a little bit of pottering in the garden for some fresh air -- clearing the tomato plants out of the greenhouse, hoovering the lawn (to get rid of fallen leaves), trimming where it had straggled over the borders, and clearing the few places where plants had stopped for the winter and obligingly dried up (the marigolds are still in flower, as are the antirrhinums; not just the viburnum which I'd expect to be flowering now); leaving a little space in the green bin for more gardening later in the week.

Some of the sunflower heads that haven't already been stripped of seeds, I hung up for the birds; and the last handful of rosebuds are in a vase by the fireplace. And so the garden remains in a late-autumn limbo, until I really can't postpone pruning the roses and apple trees any more.

Wednesday, December 14, 2011

An approximate type-based solution to the strings problem

As ever, neat ideas coded in Haskell can be almost but not quite ported to F#, foiled at the last moment by the fact that the .net type system cannot represent constructs that are expressed as Haskell type classes : there's always a run-time type coercion and some leakage of abstractions that have to be coded around.

One such is this idea of type-safe strings for representing structured data like XML -- which would require a little more mutually recursive and self-encapsulating type definitions than I think F# can manage. It gets close, though, and could probably be embellished a little further:

#light

open System

module TypeStrings =

    let rec foldr f z l =
        match l with
        | [] -> z
        | x::xs -> f x (foldr f z xs)

    [<AbstractClass>]
    type SafeStringBase(content : String) =
        member this.AsString with get() = content
        override this.ToString() = content
        
    // Now here #SafeStringBase really means SafeString of the corresponding 
    // concrete language type but there isn't a way to express that
    type ILanguage =
        abstract member LiteralFragment         : String -> #SafeStringBase // String is a literal language fragment
        abstract member LiteralText             : String -> #SafeStringBase // String is literal text
        abstract member NativeRepresentation    : #SafeStringBase -> String   // Gets the native-language representation
        abstract member Language                : unit -> String             // Gets the name of the language  
        abstract member Empty                   : unit -> #SafeStringBase     // creates an empty SafeString in the Language
        abstract member Add                     : #SafeStringBase -> #SafeStringBase -> #SafeStringBase
        
    type SafeString<'TLanguage when 'TLanguage :> ILanguage and 'TLanguage : (new : unit -> 'TLanguage) > (content : String) =
        inherit SafeStringBase(content)
        static let language : 'TLanguage = new 'TLanguage()
        
        // this is ugly
        static member private ToType (x:#SafeStringBase) =
            x :> SafeStringBase :?> SafeString<'TLanguage>
        
        static member Empty : SafeString<'TLanguage> =
            language.Empty() |> SafeString<'TLanguage>.ToType
        
        // takes a string that you certify as representing a fragment in the Language 
        // and returns a corresponding SafeString
        static member Fragment fragment =
            language.LiteralFragment fragment |> SafeString<'TLanguage>.ToType
        
        // takes a string that you certify as representing text and returns a 
        // corresponding SafeString in the Language
        static member Text text =
            language.LiteralText text |> SafeString<'TLanguage>.ToType
            
        static member Join (strings : seq<SafeString<'TLanguage>>) =
          let l = Seq.toList strings
          (foldr language.Add (language.Empty()) l) |> SafeString<'TLanguage>.ToType
        
        static member (+) ((self: SafeString<'TLanguage>), (other: SafeString<'TLanguage>)) : SafeString<'TLanguage> =
            (language.Add self other)  |> SafeString<'TLanguage>.ToType
            
          
    type Xml () =
      // this too is ugly
      static member private ToType (x:#SafeStringBase) =
            x :> SafeStringBase :?> SafeString<Xml>
      static member private ToHashType (x:#SafeStringBase) =
            x :> SafeStringBase :?> #SafeStringBase
            
      interface ILanguage with
        member self.LiteralFragment s = Xml.ToHashType (new SafeString<Xml>(s)) 
        member self.LiteralText s = Xml.ToHashType (new SafeString<Xml>(System.Security.SecurityElement.Escape(s)))
        member self.NativeRepresentation s =
            s.AsString.Replace("&apos;", "'").
              Replace("&quot;", "\"").Replace("&gt;", ">").
              Replace("&lt;", "<").Replace("&amp;", "&")
        member self.Language () = "XML"
        member self.Empty () = Xml.ToHashType (new SafeString<Xml>(String.Empty))
        
        // Don't think we can force this at compile time
        // The best we can do is encapsulate this operation
        member self.Add x y = 
            let left = Xml.ToType x
            let right = Xml.ToType y
            Xml.ToHashType (new SafeString<Xml>(x.AsString + y.AsString)) 
      
    [<EntryPoint>]
    let main a =
        let frag = SafeString<Xml>.Fragment "<em>wow!</em>"
        printfn "%s" frag.AsString
        let text = SafeString<Xml>.Text "ham & eggs"
        printfn "%s" text.AsString
        
        // We're safe from doing
        //
        // frag + "ham & eggs"
        //
        // Error FS0001: Type constraint mismatch. The type      string     is not compatible 
        // with type     SafeString<Xml>     
        // The type 'string' is not compatible with the type 'SafeString<Xml>'
        
        let sum = frag + text
        printfn "%s" sum.AsString
        0    

Deleting files, keeping a few -- in Python

And the same again, only as a script, just because:

import glob
import os
  
def fcompare(f1, f2):
  t1 = os.path.getmtime(f1)
  t2 = os.path.getmtime(f2)
  if t1 < t2:
    return -1
  if t1 > t2:
    return 1
  return 0

# TODO: parametrise the pattern and the number to retain
for f in sorted(glob.iglob('*.log'), cmp = fcompare, reverse = True)[3:]:
  os.remove(f)

If there wasn't that comparison function to write, it'd be terser...


Tuesday, December 13, 2011

Deleting files, keeping a few

Inspired by this post, how it works in PowerShell:

Get-ChildItem *.log | Sort-Object lastWriteTimeUtc -descending | Select-Object -skip 3 | Remove-Item

will delete all but the most recent 3 files ending in ".log"; and it would be the same sort of thing in any scripting language.

Sunday, December 04, 2011

Lovely weather for the time of year

The garden today

The garden today

A year ago

A year ago

Yesterday was a mild and sunny day, so I took a spin out in the countryside, stopping at the Carpenters Arms for a light lunch, of pumpkin and parsnip soup and a ham and cheese toasted sandwich. It was great to enjoy the chance to get out on the bike again -- as it seemed did a number of the lycra crowd, though fortunately they don't seem to hunt in packs at this time of year.

What a contrast with a year ago, when needing to go into town, I wrapped up with long-johns, heavy gloves, multiple layers, goggles, just leaving my nose exposed -- and regretting that for the first couple of miles before getting properly warmed up.

Monday, November 28, 2011

PowerShell and GTK-Server

Another little five-finger exercise, porting the example VBScript stdin driver to PowerShell. The only annoying thing is that the Start-Process cmdlet doesn't give you direct access to the streams, so we have to drop into .net to fire up the GTK server process.

<# 
.SYNOPSIS 
    demo to use the GTK-server using STDIN. 
    
.DESCRIPTION 
    This script is based on the VBScript example by
    Peter van Eerten published at http://www.gtk-server.org/demo-stdin.vbs.txt
        
.NOTES 
    File Name  : Try-GTKServer.ps1 
    Requires   : PowerShell Version 1.0

.PARAMETER Help

Show this help text.
#> 
param ( 
    [switch] $Help)

if ($help)
{
    Get-Help $MyInvocation.MyCommand.Definition
    return
}

# hard-coded rather than passed in as a parameter
# I'm feeling lazy tonight...
$gtkpath = "path\to\gtk-server.exe"

## As we want to get at the process stdin, stdout we can't use the Start-Process cmdlet
## $gtkserver = Start-Process -FilePath $gtkpath -ArgumentList "-stdin" -PassThru
## which only allows file I/O
$gtkinfo = new-object System.Diagnostics.ProcessStartInfo @($gtkpath, "-stdin")
$gtkinfo.UseShellExecute = $false
$gtkinfo.RedirectStandardInput = $true
$gtkinfo.RedirectStandardOutput = $true
$gtkinfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden

$gtkserver = [System.Diagnostics.Process]::start($gtkinfo)

Function Invoke-GTK 
{
Param([string] $command)
$gtkserver.StandardInput.WriteLine($command)
$line = $gtkserver.StandardOutput.ReadLine()
if ($line -cne "ok") { $line }
}


#Define GUI
Invoke-GTK("gtk_init NULL NULL")
$win = Invoke-GTK("gtk_window_new 0")
Invoke-GTK("gtk_window_set_title $win `"PowerShell Script demo program using STDIN`"")
Invoke-GTK("gtk_widget_set_usize $win 450 400")
$table = Invoke-GTK("gtk_table_new 50 50 1")
Invoke-GTK("gtk_container_add $win $table" )
$button = Invoke-GTK("gtk_button_new_with_label Exit")
Invoke-GTK("gtk_table_attach_defaults $table $button 41 49 45 49")
$entry = Invoke-GTK("gtk_entry_new")
Invoke-GTK("gtk_table_attach_defaults $table $entry 1 40 45 49")
$text = Invoke-GTK("gtk_text_new NULL NULL")
Invoke-GTK("gtk_table_attach_defaults $table $text 1 49 8 44")

$radio1 = Invoke-GTK("gtk_radio_button_new_with_label_from_widget NULL Yes")
Invoke-GTK("gtk_table_attach_defaults $table $radio1 1 10 1 4")
$radio2 = Invoke-GTK("gtk_radio_button_new_with_label_from_widget $radio1 No")
Invoke-GTK("gtk_table_attach_defaults $table $radio2 1 10 4 7")
Invoke-GTK("gtk_widget_show_all $win" )
Invoke-GTK("gtk_widget_grab_focus $entry" )

# message/event loop
do {
    $event = Invoke-GTK("gtk_server_callback wait")
    if ($event -ceq $entry) {
    $tmp = Invoke-GTK("gtk_entry_get_text $entry" )
    if($tmp.Length -gt 1) {
            Invoke-GTK("gtk_text_insert $text NULL NULL NULL `"$tmp`n`" -1")
       }
    # Empty entry field
       Invoke-GTK("gtk_editable_delete_text $entry 0 -1")
    }
} while ($event -cne $button)

Invoke-GTK("gtk_server_exit")


Friday, November 18, 2011

Another PowerShell egg-timer (using events)

Recording a learning exercise for how PowerShell handles events, and how to communicate into event handlers

Note that every registered event must be unregistered (otherwise running the script leaves droppings in your PowerShell session in the form of those claims on events). The events that are handled -- the timer 250ms wake-ups -- are consumed by being handled; but the one that is merely waited on remains latched after triggering the wait to release and must be explicitly cleared.

Additional AV frills would be the same as any of the polling loop timer examples out there.

<# 
.SYNOPSIS 
    This script provides simple egg-timer like functionality. 
    
.DESCRIPTION 
    This script counts down a time interval then speaks some text
        
.NOTES 
    File Name  : Run-Timer.ps1 
    Requires   : PowerShell Version 2.0
    
.PARAMETER Minutes

Delay in minutes

.PARAMETER Seconds

Additional delay in seconds

.PARAMETER Text

What to say at the end of the time

.PARAMETER Help

Show this help text.
#>
param ( 
    [ValidateRange(0,59)] [int] $Minutes,
    [ValidateRange(0,59)] [int] $Seconds,
    [string] $Text="It is time",
    [switch] $Help)

if ($help)
{
    Get-Help $MyInvocation.MyCommand.Definition
    return
}

$delay = new-object "system.timespan" @(0, $Minutes, $Seconds)
$fireAt = [System.DateTime]::UtcNow + $delay
Write-Host "Requested alarm time = `t$fireAt"

# Do the heavy lifting up front
$Voice = new-object -com SAPI.SpVoice
$Voice.Rate = -5
$timer = new-object "system.timers.timer" @(250.0)
$timer.autoreset = $true

$handler =  {
    # extract arguments (no closures here)
    $fireAt = $Event.MessageData
    $timer = $Sender
  
    # round time to wait to integer seconds and display
    $delta = $fireAt - [System.DateTime]::UtcNow
    $delta = new-object "system.timespan" @(0, $delta.Minutes, [System.Math]::Round($delta.Seconds))
    [System.Console]::Write("$delta`r")
  
    # After the time allotted, kill the timer
    if ([System.DateTime]::UtcNow -gt $fireAt) { 
        $timer.enabled = $false
        $timer.autoreset = $false
        $timer.Dispose() 
    }
}

Register-ObjectEvent -InputObject $timer -EventName Elapsed -SourceIdentifier "Timer.Elapsed" -Action $handler -MessageData $fireAt | Out-Null
Register-ObjectEvent -InputObject $timer -EventName Disposed -SourceIdentifier "Timer.Disposed"

$timer.Start() 

# Wait until done
if (-not (Wait-Event "Timer.Disposed" -Timeout 3600 )) {
    Write-Host "Timed out"
    $timer.Stop() 
    $timer.Dispose()
} else {
    Write-Host "Completed"
}
Write-Host "Actual alarm time = `t$([System.DateTime]::UtcNow)"

# Tidy event registrations and event queue
# The Elapsed events are spent in the handler
# but the one we wait on must be explicitly cleared
Remove-Event "Timer.Disposed"
Unregister-Event "Timer.Elapsed"
Unregister-Event "Timer.Disposed"

#audible alarm
$Voice.Speak($Text) | Out-Null
Write-Host "`r`nDone"