I recently ran into the issue with synchronization log where it was reporting messages on missing reference files, something like this:
Error Desc : Unable to open file: ABC-3D.dgn (\\SomeServer\SomeShare\CAD Reference\DGN\PIPE\ABC-3D.dgn)
There were quite a few instances of this error and I needed to build a list of the filenames for further processing. Since I recently started learning F#, I thought it would be a good exercise to do this in it. One really good part of F# is – the interactive window, which lets you test code snippets without creating a full-blown .NET program, and that alone is a time saver.
This problem had basically two parts:
- First would be to find a way to parse the file so that we could get the line of interest (as shown above)
- Second would be to output the list – either write to a file or show on screen
Since I needed to parse the file, I resorted to using a regular expression as that was the easiest way in this case. So testing with Expresso, I came up with this expression:
“Error\s+Desc\s+[:]\sUnable\s+to\s+open\s+file[:]\s+(?<filename>.*)\s+\((?<filepath>.*)\)“
And as you can see, it gives me the values I need in “filename” and “filepath” groups.
I needed to now return a list of such matches, so enter – GetParsedLines:
open System open System.IO open System.Text open System.Text.RegularExpressions open System.Diagnostics let SyncLog_Missing_RefFile =
@"Error\s+Desc\s+[:]\sUnable\s+to\s+open\s+file[:]\s+(?<filename>.*)\s+\((?<filepath>.*)\)"
let GetParsedLines (fileToParse:string) (regExp:string) = seq { use reader:StreamReader = new StreamReader(fileToParse) while not reader.EndOfStream do let curLine = reader.ReadLine().Trim() let result = Regex.Match(curLine, regExp) if result.Success then match (List.tail [for g in result.Groups -> g.Value]) with | [] -> yield ["No groups found"] | lst -> yield lst else () }
What this does is, it first tests if the current line matches the regular expression, if so, it puts the group’s value in a list ([ for g in result.Groups -> g.value]) , then it omits the first entry in the list by taking the “tail” of the list since the first or zeroth group represent the entire match. After that if there are values (| lst -> yield lst), it yields or returns it.
Let’s test this: so fire up the Interactive window, from View->Other Windows->F# Interactive. Then highlight everything with the ALT key pressed, from the line, “open System“, till the last “}” in GetParsedLines, so you have something like this (the background color is light grey in my case):
Then right-click and select “Send to Interactive” and you will get the output in the Interactive window:
To test it, you only need to supply the path to a synch log which has errors related to missing reference files and use the regular expression, SyncLog_Missing_RefFile, that we defined above:
>GetParsedLines @”C:\Users\Public\Documents\synch.log” SyncLog_Missing_RefFile;;
And here’s the output:
seq
[[“trenches-405.dgn”;
“\\SomeServer\SymbolShares\CBI\PROJECT\3D CAD Reference\3DDGN\CIV\trenches-405.dgn”];
[“trenches-300.dgn”;
“\\SomeServer\SymbolShares\CBI\PROJECT\3D CAD Reference\3DDGN\CIV\trenches-300.dgn”];
[“trenches-400.dgn”;
“\\SomeServer\SymbolShares\CBI\PROJECT\3D CAD Reference\3DDGN\CIV\trenches-400.dgn”];
[“230-3D.dgn”;
“\\CustomerServer-ap1\ACBD\PROJECT\3D CAD Reference\3DDGN\PIPE\230-3D.dgn”];
…]
>
With this in place, the only thing remaining was is to add code to return a list of such matches and open it in notepad for review. Here’s that part:
let ParseFile (fileToParse:string) (regExp:string) = if not (File.Exists(fileToParse)) then failwith (sprintf "Invalid filename: %s" fileToParse) let results = Path.Combine(System.Environment.GetEnvironmentVariable("TEMP"), "ParseFileResults.log") use swr = new StreamWriter(results) (sprintf "Parsed lines from file %s\r\n" fileToParse) |> swr.WriteLine GetParsedLines fileToParse regExp |> Seq.iter (fun e -> String.Join(",", List.toArray(e)) |> swr.WriteLine ) Process.Start(@"C:\Program Files (x86)\Programmer's Notepad\pn.exe", results) |> ignore
To test this, just ALT-Copy the section above, right-click and “Send to Interactive”. Then use it by supplying a synch log and the regular expression to the ParseFile function:
In next part we will look at how to search for these files on the file server share and later on how to update the database with the new paths.
Leave a comment