Hey guys,
So this week I worked on a small code kata. The purpose of this project is to simply be able to format text into a bullet point like any text editor allows you to do it. To complete this challenge, I decide to use F# since it’s so great for prototyping and get fast results. The project had the following requirements:
- Produce an outline of headings
- Heading values are provided
Here’s an example of what should be printed in console :
- Software development is
A. An awesome thing to do
2. Why do code katas ?
A. They are
i. Entertaining
ii. Challenging
So as we can, we have three different level of formatting to do on the string and heading values are provided for the formatting. Those requirements led to the following code :
code language="fsharp"] open System open System.Linq type Indexes = { Primary: int Secondary : int Third : int } type BulletPointStyle = | NumberedStyle | LetteredStyle type HeadingWeight = | HW1 | HW2 | HW3 type Heading = { Weight : HeadingWeight Text : string } type Node = { Line : Heading } type Outline = { Text : string HeadingIndexes : Indexes } with member x.addContent str = { x with Text = x.Text + Environment.NewLine + str } let updateIndexes (o: Outline) (h: Heading) = match h.Weight with | HW1 -> let indexes = { o.HeadingIndexes with Primary = o.HeadingIndexes.Primary + 1; Secondary = 1 ;Third = 1 } { o with HeadingIndexes = indexes } | HW2 -> let indexes = { o.HeadingIndexes with Secondary = o.HeadingIndexes.Secondary + 1; Third = 1 } { o with HeadingIndexes = indexes } | HW3 -> let indexes = { o.HeadingIndexes with Third = o.HeadingIndexes.Third + 1 } { o with HeadingIndexes = indexes } let determineBulletStyle (hw: HeadingWeight) = match hw with | HW1 -> NumberedStyle | HW2 | HW3 -> LetteredStyle let getLetter (index: int)= (("ABCDEFGHIJKLMNOPQRSTUVWXYZ".[index-1]).ToString()) let formatTextNode(n: Node) (o: Outline)= let header = n.Line let mutable text = "" let style = determineBulletStyle header.Weight let indexer = o.HeadingIndexes match style with | NumberedStyle -> text <- (indexer.Primary.ToString()) + ". " + header.Text | LetteredStyle -> let head = match header.Weight with | HW2 -> String.Join("", Enumerable.Repeat(" ",4)) + getLetter indexer.Secondary | HW3 -> String.Join("", Enumerable.Repeat(" ", 8)) + String.Join("", Enumerable.Repeat("i", indexer.Third)) | _ -> "" text <- head + ". " + header.Text text // Folds an Outline and a list Nodes to an Outline let formatTextOutline(nodeList: Node list) = ( { Text = ""; HeadingIndexes = { Primary = 1; Secondary = 1; Third = 1 } }, nodeList) ||> Seq.fold (fun outline node -> let text = formatTextNode node outline let outline = outline.addContent text updateIndexes outline node.Line ) [<EntryPoint>] let main argv = let nList = [ { Line = { Weight = HW1; Text = "Software development is" } } { Line ={ Weight = HW2; Text = "Super fun" } } { Line ={ Weight = HW3; Text = "But challenging" } } { Line ={ Weight = HW3; Text = "And rewarding" } } ] let outline = formatTextOutline nList printf "%s" outline.Text 0 // return an integer exit code
[/code]
So basically, the process is made thanks to formatTextOutline and formatTextNode. Through those functions, I can create the Outline record that holds the formatted text and the indices for the level of formatting (Indexes). When I get in formatTextNode, I can first establish the heading style of the line thanks BulletPointStyle discriminated union(DU) type. Using the style enables me to know if I only have to take care of numbers or the letters. When I have to deal with characters, the HeadingWeight DU becomes handy to see whether or not I’ll be using a single tab (4 spaces) or a 2 tabs (8 spaces).
There you go ! 🙂
Kevin out