Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I Use Multiple Properties in a StructuredFormatDisplayAttribute?

I'm playing around with StructuredFormatDisplay and I assumed I could use multiple properties for the Value, but it seems that is not the case. This question (and accepted answer) talk about customizing in general, but the examples given only use a single property. MSDN is not helpful when it comes to usage of this attribute.

Here's my example:

[<StructuredFormatDisplay("My name is {First} {Last}")>]
type Person = {First:string; Last:string}

If I then try this:

let johnDoe = {First="John"; Last="Doe"}

I end up with this error:

<StructuredFormatDisplay exception: Method 'FSI_0038+Person.First} {Last' not found.>

The error seems to hint at it only capturing the first property mentioned in my Value but it's hard for me to say that with any confidence.

I have figured out I can work around this by declaring my type like this:

[<StructuredFormatDisplay("My name is {Combined}")>]
type Person = {First:string; Last:string} with
    member this.Combined = this.First + " " + this.Last

But I was wondering if anyone could explain why I can't use more than one property, or if you can, what syntax I'm missing.

I did some digging in the source and found this comment:

In this version of F# the only valid values are of the form PreText {PropertyName} PostText

But I can't find where that limitation is actually implemented, so perhaps someone more familiar with the code base could simply point me to where this limitation is implemented and I'd admit defeat.

like image 459
Sven Grosen Avatar asked Feb 13 '15 17:02

Sven Grosen


1 Answers

The relevant code from the F# repository is in the file sformat.fs, around line 868. Omitting lots of details and some error handling, it looks something like this:

let p1 = txt.IndexOf ("{", StringComparison.Ordinal) 
  let p2 = txt.LastIndexOf ("}", StringComparison.Ordinal) 
  if p1 < 0 || p2 < 0 || p1+1 >= p2 then  
      None  
  else 
    let preText = if p1 <= 0 then "" else txt.[0..p1-1] 
    let postText = if p2+1 >= txt.Length then "" else txt.[p2+1..] 
    let prop = txt.[p1+1..p2-1] 
    match catchExn (fun () -> getProperty x prop) with 
    | Choice2Of2 e -> 
        Some (wordL ("<StructuredFormatDisplay exception: " + e.Message + ">")) 
    | Choice1Of2 alternativeObj -> 
        let alternativeObjL =  
          match alternativeObj with  
          | :? string as s -> sepL s 
          | _ -> sameObjL (depthLim-1) Precedence.BracketIfTuple alternativeObj 
        countNodes 0 // 0 means we do not count the preText and postText  
        Some (leftL preText ^^ alternativeObjL ^^ rightL postText) 

So, you can easily see that this looks for the first { and the last }, and then picks the text between them. So for foo {A} {B} bar, it extracts the text A} {B.

This does sound like a silly limitation and also one that would not be that hard to improve. So, feel free to open an issue on the F# GitHub page and consider sending a pull request!

like image 90
Tomas Petricek Avatar answered Oct 07 '22 03:10

Tomas Petricek