While working on a package manager for CMake I came across the problem of finding a suitable format for my package descriptor files. First I thought about and tried using plain old CMake file that simply sets variables and can be included to access its contents.  It was very quick and easy to implement. Hierarchical data structures however where hard to implement and a none standard format makes it harder to exchange data with a webservice/other programs. So I opted for using JSON as my import/export format.  JSON is alot easier to parse than XML and is still ubiquitous in software developement so I cast aside XML (for now).

You can grab my implementation for reading and writing JSON with CMake  at  https://github.com/toeb/oo-cmake.git.  Its part of my object oriented CMake library and uses my implementation for CMake maps and references.

Note: Because CMake only understands strings the deserializer only accepts string , array or object values.  Also all keys and values have to be double quoted.

As always: feedback is most welcome if you feel like it you can tell me if you find it usefull, have ideas, or think that I have reinvented the wheel.

Here are some examples for serialization and deserialization:

Code Snippet
  1. # serialize empty value
  2. json_serialize(res “”)
  3. assert(NOT res)
  4. # serialze simple value
  5. json_serialize(res “hello!”)
  6. assert(res)
  7. assert(“\”hello!\”” STREQUAL ${res})
  8. #empty object
  9. element(uut MAP)
  10. element(END)
  11. json_serialize(res ${uut})
  12. assert(“{}” STREQUAL ${res})
  13. # empty list
  14. element(uut LIST)
  15. element(END)
  16. json_serialize(res ${uut})
  17. assert(“[]” STREQUAL ${res})
  18. # ref
  19. ref_new(uut)
  20. ref_set(${uut} “a b c”)
  21. json_serialize(res ${uut})
  22. assert(“\”a b c\”” STREQUAL ${res})
  23. # list with one element
  24. element(uut LIST)
  25. value(1)
  26. element(END)
  27. json_serialize(res ${uut})
  28. assert(“[\”1\”]” STREQUAL ${res})
  29. # list with multiple elements element
  30. element(uut LIST)
  31. value(1)
  32. value(2)
  33. element(END)
  34. json_serialize(res ${uut})
  35. assert(“[\”1\”,\”2\”]” STREQUAL ${res})
  36. # object with single value
  37. element(uut MAP)
  38. value(KEY k1 val1)
  39. element(END)
  40. json_serialize(res ${uut})
  41. assert(“{\”k1\”:\”val1\”}” STREQUAL ${res})
  42. # object with multiple value
  43. element(uut MAP)
  44. value(KEY k1 val1)
  45. value(KEY k2 val2)
  46. element(END)
  47. json_serialize(res ${uut})
  48. assert(“{\”k1\”:\”val1\”,\”k2\”:\”val2\”}” STREQUAL ${res})
  49. # list with single map
  50. element(uut LIST)
  51. element()
  52. value(KEY k1 1)
  53. element(END)
  54. element(END)
  55. json_serialize(res ${uut})
  56. assert(“[{\”k1\”:\”1\”}]” STREQUAL ${res})
  57. # list with differnt elements map
  58. element(uut LIST)
  59.     element(MAP)
  60.         value(KEY k1 1)
  61.         value(KEY k2 2)
  62.         value(KEY k3 3)
  63.     element(END)
  64.     element(LIST)
  65.         value(1)
  66.         value(2)
  67.     element(END)
  68.     value(a)
  69.     value(b)
  70. element(END)
  71. json_serialize(res ${uut})
  72. assert(“[{\”k1\”:\”1\”,\”k2\”:\”2\”,\”k3\”:\”3\”},[\”1\”,\”2\”],\”a\”,\”b\”]” STREQUAL ${res})
  73. # deserialize a empty value
  74. json_deserialize(res “”)
  75. assert(NOT res)
  76. # deserialize a empty object
  77. json_deserialize(res “{}”)
  78. assert(res)
  79. ref_isvalid(is_ref ${res} )
  80. assert(is_ref MESSAGE “expected res to be a ref”)
  81. # desirialize a empty array
  82. json_deserialize(res “[]”)
  83. assert(res)
  84. ref_isvalid(is_ref ${res})
  85. assert(is_ref MESSAGE “expected res to be a ref”)
  86. # deserialize a simple value
  87. json_deserialize(res “\”teststring\””)
  88. assert(${res} STREQUAL “teststring”)
  89. # deserialize a array with one value
  90. json_deserialize(res “[\”1\”]”)
  91. map_navigate(${res} val “[0]”)
  92. assert(${val} STREQUAL “1”)
  93. #deserialize a array with multiple values
  94. json_deserialize(res “[\”1\”, \”2\”]”)
  95. map_navigate(${res} val “[0]”)
  96. assert(${val} STREQUAL “1”)
  97. map_navigate(${res} val “[1]”)
  98. assert(${val} STREQUAL “2”)
  99. # deserialize a simple object with one value
  100. json_deserialize(res “{ \”key\” : \”value\”}”)
  101. map_navigate(${res} val “key”)
  102. assert(${val} STREQUAL “value”)
  103. # deserialize a simple object with multiple values
  104. json_deserialize(res “{ \”key\” : \”value\”, \”key2\” : \”val2\”}”)
  105. map_navigate(${res} val “key”)
  106. assert(${val} STREQUAL “value”)
  107. map_navigate(${res} val “key2”)
  108. assert(${val} STREQUAL “val2”)
  109. # deserialize a simple nested structure
  110. json_deserialize(res “{ \”key\” : {\”key3\”:\”myvalue\” }, \”key2\” : \”val2\”}”)
  111. map_navigate(${res} val “key2”)
  112. assert(${val} STREQUAL “val2”)
  113. map_navigate(${res} val “key.key3”)
  114. assert(${val} STREQUAL “myvalue”)
  115. # deserialize a nested structure containing both arrays and objects
  116. json_deserialize(res “{ \”key\” : [\”1\”, \”2\”], \”key2\” : \”val2\”}”)
  117. map_navigate(${res} val “key2”)
  118. assert(${val} STREQUAL “val2”)
  119. map_navigate(${res} val “key[0]”)
  120. assert(${val} STREQUAL “1”)
  121. map_navigate(${res} val “key[1]”)
  122. assert(${val} STREQUAL “2”)
  123. # deserialize a ‘deeply’ nested structure and get a value
  124. json_deserialize(res “{ \”key\” : [{\”k1\”:\”v1\”}, \”2\”], \”key2\” : \”val2\”}”)
  125. map_navigate(${res} val “key[0].k1”)
  126. assert(${val} STREQUAL “v1”)

I worked on my object oriented extensions for CMake  and am on a much higher level of usability than before!

try it at https://github.com/toeb/oo-cmake 

and as always: Feedback is most welcome

See the following object tutorial for ease of use: (for the current version look at the readme.md on github)


### using objects
    ### =============
    # oo-cmake is very similar to javascript
    # I actually used the javascript reference to figure out how things could be done :)
    # oo-cmake is a pure object oriented language like javascript (only objects no types per se)
    # oo-cmake is currently file based and relies heavily on dynamic functions to be upfron about it:
    # oo-cmake is very slow (depending on what your doing)

    ## creating a object
    ## =================
    obj_new(myobject)
    # ${myobject} now is a reference to an object
    obj_exists(${myobject} _exists)
    assert(_exists)

    ## deleting a object
    ## =================
    # oo-cmake does not contains automatic memory management
    # you can however remove all objects by calling obj_cleanup 
    # (a very crude way of garbage collection) I would suggest calling it at the end of cmake.
    obj_new(object_to_delete)
    obj_delete(${object_to_delete})
    # object_to_delete still contains the same reference
    # but the object does not exists anymore and the following returns false
    obj_exists(${object_to_delete} _exists)
    assert(NOT _exists)

    ## setting and setting property
    ## ==================
    obj_new(myobject)
    # call obj_set passing the object reference
    # the propertyname 'key1' and the value 'val1'
    # everything beyond 'key1' is saved (as a list)
    obj_set(${myobject} key1 "val1")
    #call obj_get passing the object refernce the result variable and
    # the key which is to be gotten
    obj_get(${myobject} theValue key1)
    assert(theValue)
    assert(${theValue} STREQUAL "val1")

    ## setting a function and calling it
    ## =================================
    obj_new(obj)
    obj_set(${obj} last_name "Becker")
    obj_set(${obj} first_name "Tobias")
    #obj_setfunction accepts any function (cmake command, string function, file function, unique function (see function tutorial))
    # if you use a cmake function be sure that it will not be overwritten
    # the safest way to add a function is to use obj_declarefunction
    # you can either specify the key where it is to be stored or not
    # if you do not specify it the name of the function is used
    function(greet result)
        # in the function you have read access to all fields of the proeprty
        # as will as to 'this' which contains the reference to the object

        # this sets the variable ${result} in PARENT_SCOPE (returning values in cmake)
        set(${result} "Hello ${first_name} ${last_name}!" PARENT_SCOPE)

    endfunction()
    obj_setfunction(${obj} greet)
    obj_callmember(${obj} greet res)
    assert(res)
    assert(${res} STREQUAL "Hello Tobias Becker!")
    # alternatively you can also use obj_declarefunction
    # this is actually the easiest way to define a function in code
    obj_declarefunction(${obj} say_goodbye)
    function(${say_goodbye} result)
        set(${result} "Goodbye ${first_name} ${last_name}!" PARENT_SCOPE)
    endfunction()
    obj_callmember(${obj} say_goodbye res)
    assert(res)
    assert(res STREQUAL "Goodbye Tobias Becker!")

    ## built in methods
    ## ================
    # obj_new creates a object which automatically inherits Object
    # Object contains some functions e.g. to_string, print, ...
    # 
    obj_new(obj)
    obj_callmember(${obj} print)

    # this prints all members
    # ie
    #{
    # print: [function], 
    # to_string: [function], 
    # __ctor__: [object :object_Y3dVWkChKi], 
    # __proto__: [object :object_AztQwnKoE7], 
    #}

    ## constructors
    ## ============
    # you can easily define a object type via constructor
    function(MyObject)
        # declare a function on the prototype (which is accessible for all objects)
        # inheriting from MyObject
        obj_declarefunction(${__proto__} myMethod)
        function(${myMethod} result)
            set(${result} "myMethod: ${myValue}" PARENT_SCOPE)
            this_set(myNewProperty "this is a text ${myValue}")
        endfunction()

        # set a field for the object
        this_set(myValue "hello")
    endfunction()

    obj_new(obj MyObject)
    # type of object will now be MyObject
    obj_gettype(${obj} type)
    assert(type)
    assert(${type} STREQUAL MyObject)
    # call
    obj_callmember(${obj} myMethod res)
    assert(res)
    assert(${res} STREQUAL "myMethod: hello")
    obj_set(${obj} myValue "othervalue")
    obj_callmember(${obj} myMethod res)
    assert(res)
    assert(${res} STREQUAL "myMethod: othervalue")
    obj_get(${obj} res myNewProperty)
    assert(res)
    assert(${res} STREQUAL "this is a text othervalue")

    ## functors
    ## ========

    ## binding a function
    ## ==================
    # you can bind any function to an object without
    # setting it as property
    # causing the function to get access to 'this'
    # and all defined properties
    function(anyfunction)
        this_callmember(print)
    endfunction()
    obj_new(obj)
    obj_bindcall(${obj} anyfunction)
    ## print the object
    # alternatively you can bind the function to the object
    obj_bind(boundfu ${obj} anyfunction)
    call_function(${boundfu})
    # prints the object
    # alternatively you can bind and import then function
    # beware that you might overwrite a defined function if you append REDEFINE
    # 
    obj_bindimport(${obj} anyfunction myBoundFunc REDEFINE)
    myBoundFunc()
    # print the object

Get it at https://github.com/toeb/oo-cmake and tell me if you like it

For more complex build system features oo programming would be a major boon. So I took it upon myself to create a usable and tested oo-extension for cmake (in pure CMake) so I could continue developing and extending my plattform independent tooling support (cutil) .

It’s too bad that Kitware decided to use their own language (with very little high level language features) for their build system product because CMakes project generation capabilties are unparalleled .  I can understand that it seemed like a good idea at first ( the CMake scripting Language is platform independent and easier to use than make or bash or cmd also it was not intended to do complicated things). I however strongly believe that building a programm should be as easy as possible and therefore a high level language should have been used. CMake’s creators Bill Hoffman and Kenneth Martin have said that they would have done this differently if they had to do it again (http://www.aosabook.org/en/cmake.html)

Of course CMake does not offer language features for a very sleak usage but the with these functions you can use prototypical inheritance, maps, methods, objects, lists, properties, references, etc. which are at the basis of every oo language.

Even if you do not want to do inheritance in CMake you can use these functions for Maps (Dictionaries)

People familiar with JavaScript will be familiar with this approach to object orientation.

Here is a simple example

cmake_minimum_required(VERSION 2.8)
# include standalone version of oo-cmake
# when not using cutil this  includes the function files as well as
# the dependent functions
include("../oo-cmake.cmake")

message("creating a simple inheritance example")

#create animal object
obj_create(animal)

# set object method eat
# this method prints out which food the animal is eating
# existing functions can also be appliead via obj_bindcall(${ref} func arg1 arg2 arg3)
obj_set(
    ${animal}   #reference to animal object
    "eat"       #name of property
    RAW         # RAW indictates that the data is to be treated as a raw string not a list (hinders evaluation)
    "function(eat this)
      obj_get(\${this} food food) 
      message(\"I am eating \${food}\") 
     endfunction()"
)

# create mamal object
obj_create(mamal)
obj_setprototype(${mamal} ${animal})

#create bird object
obj_create(bird)                    #create instance
obj_setprototype(${bird} ${animal}) #set prototype animal
obj_set(${bird} food Birdfood)      #set bird's food property to "Birdfood"

#create dog object
obj_create(dog)                     #create instance
obj_setprototype(${dog} ${mamal})   #set prototype mamal
obj_set(${dog} food Dogfood)        #set dogs food property to "Dogfood"

#create cat object
obj_create(cat)                     #create instance
obj_setprototype(${cat} ${mamal})   #set prototype mamal
obj_set(${cat} food Catfood)        #set cat's food property to "Catfood"

# call eat function for different objects
obj_callmember(${bird} eat) 
obj_callmember(${dog} eat)
obj_callmember(${cat} eat)

#output should be
#I am eating Birdfood
#I am eating Dogfood
#I am eating Catfood

You can use the following commands

 
obj_create(out_ref [objectpath]) #returns a ref to a new object. this object is persistent and will exist until obj_delete is called, internally the object is represented by a folder
obj_delete(in_ref) # deletes a object (deletes the folder internally)
obj_find(refOrId) # searches for a named object or id
obj_get(ref value key) # gets a property ${value} = ref[key] the prototype chain is considered
obj_set(ref key value) # sets a property ref[key] = value
obj_getref(ref prop_ref key) # returns the reference to a property (a file)
obj_getownproperty(ref value key) # returns the value for property if the property belongs to ref 
obj_setownproperty(ref key value) # sets the own property ref[key] =value
obj_has(ref result key) # sets result to true if ref[key] exists
obj_hasownproperty(ref result key) # sets result to true if ref[key] exists and is owned by ref
obj_callmember(ref methodname args...) # calls a stored memberfunction (first argument passed is ref)
obj_bindcall(ref methodname args...) # calls a defined function setting first argument to ref
obj_setprototype(ref proto_ref) # sets the prototype for ref
obj_getprototype(ref proto_ref) # returns the prototyp for ref
obj_settype(ref type) # sets the type for ref
obj_gettype(ref type) # gets the type for ref
obj_getkeys(ref keys) # returns all defined properties for ref
obj_getownkeys(ref keys) # returns all defined properties that ref owns

I have been doing some hard directive/ html markup work and I came across a problem where I needed the normalized html tag attribute names.   (I was trying to create a html element but needed access to the normalized names without turning it into a directive)

Angular.js normalizes names like “test-name” and “data-test-name” to “testName”.  So I went ahead and extracted and wrapped the functions needed into the following function:

/** normalizes attribute names ,  stolen from angular.js */
   function normalizeAttributeName(attributeName) {
     // copied from angular.js v1.2.2
     // (c) 2010-2012 Google, Inc. http://angularjs.org
     // License: MIT
     // modified by me 😀
     var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
     var MOZ_HACK_REGEXP = /^moz([A-Z])/;

     function camelCase(name) {
       return name.
         replace(SPECIAL_CHARS_REGEXP, function (_, separator, letter, offset) {
           return offset ? letter.toUpperCase() : letter;
         }).
         replace(MOZ_HACK_REGEXP, 'Moz$1');
     }

     var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;

     function directiveNormalize(name) {
       return camelCase(name.replace(PREFIX_REGEXP, ''));
     }
     return directiveNormalize(attributeName);
   }

I did not find out how this could be done by just calling an angular api so  if you know how, please let me know 😀

I’ve been searching and searching the internet for someone, somewhere who might know how to enable correct IntelliSense for cshtml (MVC 5.0) in VS2013 when developing in a ClassLibrary type project.

This is the problem I was encountering:

Missing Intellisense

It’s all thanks to Mohammad Chehab, who was also stumped for a while (probably not as long as me), that I was able to find the solution.  His blog post explains that one must change the output path for the class library to bin/ (instead of bin/<release>)  for Intellisense  to work.

So now I can show you the steps for creating a ClassLibrary with cshtml (MVC5 Style):

  1. Create or open an existing class library project (if you open an existing one be sure to remove the MVC5 nuget package)
  2. Add the MVC (5.0) nuget package (right click project in solution explorer -> Manage NuGet Packages -> search for MVC and install “Microsoft ASP.NET MVC”)
  3. Close any and all open .cshtml files
  4. Right click project -> Properties -> Build -> change Output path to “bin/”
  5. Add the following minimal Web.config to the root of your class library project ( the web config file is solely needed for intellisense. Configuration (via Web.config) should be done in the WebApplication hosting your ClassLibrary assembly)
  6. Clean and Build the solution.
  7. Open cshtml file
  8. Voila

Web.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
      <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
    </sectionGroup>
  </configSections>
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
  </appSettings>

  <system.web>
    <compilation debug="true" targetFramework="4.5" />
  </system.web
  
  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization"/>
        <add namespace="System.Web.Routing" />
        <!-- add other namespaces for views here -->
        <!-- e.g. your own project's, Lib.Views.Etc -->
      </namespaces>
    </pages>
  </system.web.webPages.razor>
</configuration>

One would expect for intellisense for a .cshtml files to work after step 2. However the other steps need to be performed before Intellisense works for  directives like @model or the base page (which contains Model, ViewBag, etc. )

The result looks like it is supposed to:

Correct Intellisense

You may download an example solution here: http://goo.gl/Jx2cXg

As to the reason why this does not work correctly:  I believe the intellisense provider for cshtml files compiles these in the background using the web.config s it finds in the solution and expects the resulting .dll s to be stored in the bin directory (which is the default output directory for WebApplication projects) However Class libraries have different subfolders depending on the build type  (bin/Debug, bin/Release, bin/<buildtype> ) and thus the IntellisenseProvider cannot find these files.  I think the Intellisense/MVC/Razor/ASP.NET developement team could fix this easily, however it is a rare bug because putting Views into separate assemblies is currently an edge case for MVC.  (hopefully not in the future)

I know its a little obscure, but I recently needed to find out how to enable Typescript in a Visual Studio 2013 Class Library project, because per default this is not enabled. (probably a bug)

Vanilla way of enabling TypeScript:

  1. Create a “Class Library” project
  2. Add a Typescript file (.ts)

This is were the bug occurs, normally the TypeScript file should compile on save.  However it does not. So after searching around and looking at the csproj file and comparing it to a MvcApplication csproj file I found out what the problem is:

Visual Studio correctly adds the TypeScript props file at the top of your csproj. file:

<?xml version="1.0" encoding="utf-8"?>
<Project 
  ToolsVersion="12.0" 
  DefaultTargets="Build" 
  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.Default.props" />
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />

(see first project import)

This allows your csproj file to use the <TypeScriptCompile> tag

e.g.

<TypeScriptCompile Include="Angular\Accounting\AccountingTestDirective.ts" />

Enabling compile on save is done by adding the missing

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" />
 

after

  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />


at the end of the csproj file, before your own custom build tasks.

I believe this should have been done automatically. But now I know how to do it manually >D

A 40 minute timelapse deep in the night

While doing some filesystem work I encountered alot of AccessViolationExceptions because a file is being opened to write multiple times.  Even after closing the file explicitly a short timeframe exists in which the file could not be opened again.  The simplest approach for me was to retry openening the file until it worked.  To allow a more easy and more general way to handle exceptions I developed a small extensible try-repeat-framework which allows me to configure waiting and repeating strategies and also to only react to specific exceptions.  I have written it with a fluent interface which allows easy usage:

New: I created a Repository on Github : https://github.com/toeb/fluentexceptions
Even newer: you can now install it via nuget:  PM> Install-Package Core.Trying

Example 1:

var configuration = new Try()
        // do not throw exceptions 
        // when action continouosly fails
      .BeQuiet(true)
        // only expectAccessViolationExceptions
      .Expect<AccessViolationException>()
        // repeat at most 5 times or until 
        // 1s of waiting time has passed
      .Repeat(5, 1000)
        // when actions fails backoff exponentially
        // e.g. first wait 10 ms then 100 ms then 1000ms
      .BackoffExponentially();
      //configuration is reusable
      var result = configuration.Execute(() =>
      {
        /*something which potentially throws */
      });

      if (result)
      {
        // success
        Console.Write("number of retries: ");
        Console.WriteLine(result.FailedCount);
      }
      else
      {
        // failure
        Console.Write("time spent waiting [ms]: ");
        Console.WriteLine(result.WaitedTime);
        Console.Write("number of retries: ");
        Console.WriteLine(result.FailedCount);
        Console.Write("last throw exception message: ");
        Console.WriteLine(result.LastException.Message);
      }

Example 2:

// default configuration handles any exception
  // repeats 4 times at most and exponentially backs off
  Try.Default.Execute(() => {
    /*some action which potentially throws*/
  });

let me know if you think it is useful…  Be warned that this type of repeat on exception strategy should not be applied blindly …

I developed the library because I needed it but some terminology was researched.  consulted sources:

To parse versioned web asset filenames ala
jquery-1.4.min.js
I wrote a simple class 

/// <summary>
/// simple class for describing a file library like 
/// jquery-1.4.min.js
/// bootstrap.min.css
/// libname-1.0.0.0.tag1.tag2.tag3.extension
/// ... etc
/// contains methods for parsing these kind of filenames
/// </summary>
public class FileLib
{
  /// <summary>
  /// regular expression for parsing the filename
  /// </summary>
  public static readonly string libRegex = @"((?<libname>[^\.-]*)-?)((?<major>\d+))?(\.(?<minor>\d+)(\.(?<revision>\d+)(\.(?<build>\d+))?)?)?(\.(?<tags>.+))*";
  /// <summary>
  /// parses a path as a lib
  /// </summary>
  /// <param name="path"></param>
  /// <returns></returns>
  public static FileLib ParsePath(string path)
  {
    var filename = Path.GetFileName(path);
    return Parse(filename);
  }
  /// <summary>
  /// parses a filename as a file lib 
  /// </summary>
  /// <param name="filename"></param>
  /// <returns></returns>
  public static FileLib Parse(string filename)
  {
    var match = Regex.Match(filename, libRegex);
    match.NextMatch();
    var lib = match.Groups["libname"].Value ?? "";
    var major = match.Groups["major"].Value ?? "";
    var minor = match.Groups["minor"].Value ?? "";
    var revision = match.Groups["revision"].Value ?? "";
    var build = match.Groups["build"].Value ?? "";
    var tags = match.Groups["tags"].Value.Split('.');
    var extension = tags.LastOrDefault() ?? "";
    tags = tags.Reverse().Skip(1).Reverse().ToArray();
    var result = new FileLib()
    {
      Extension = extension,
      LibName = lib,
      Major = major,
      Minor = minor,
      Revision = revision,
      Build = build,
      Tags = tags

    };
    return result;
  }
  /// <summary>
  /// Major version number
  /// </summary>
  public string Major { getset; }
  /// <summary>
  /// Minor version number
  /// </summary>
  public string Minor { getset; }
  /// <summary>
  /// Revision Number
  /// </summary>
  public string Revision { getset; }

  /// <summary>
  /// Build number
  /// </summary>
  public string Build { getset; }

  /// <summary>
  /// Creates a version object for the Filelib. returns null if this is not possible
  /// </summary>
  /// <returns></returns>
  public Version GetVersion()
  {

    Version parsedVersion = null;
    try
    {
      var version = Major + "." + Minor + "." + Revision + "." + Build;
      while (version.EndsWith(".")) version = version.Substring(0, version.Length - 1);
      if (version == ""return parsedVersion;
      parsedVersion = new Version(version);
    }
    catch (Exception exception) { }
    return parsedVersion;

  }
  /// <summary>
  /// the name of the lib
  /// </summary>
  public string LibName { getset; }
  /// <summary>
  /// the tags of the lib.  e.g. min, intellisense, etc
  /// </summary>
  public IEnumerable<string> Tags { getset; }
  /// <summary>
  /// the extension of the lib
  /// </summary>
  public string Extension { getset; }
}

Tableroom

Table set for two