Skip to content

Commit ead29a5

Browse files
committed
add comment column header and process converter
1 parent 7da50e4 commit ead29a5

7 files changed

Lines changed: 85 additions & 8 deletions

File tree

src/Core/Conversion.fs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,14 @@ module ProcessParsing =
317317
|> Some
318318
| _ -> None
319319

320+
let tryGetCommentGetter (generalI : int) (header : CompositeHeader) =
321+
match header with
322+
| CompositeHeader.Comment c ->
323+
fun (matrix : System.Collections.Generic.Dictionary<(int * int),CompositeCell>) i ->
324+
Comment(c,matrix.[generalI,i].AsFreeText)
325+
|> Some
326+
| _ -> None
327+
320328
/// Given the header sequence of an ArcTable, returns a function for parsing each row of the table to a process
321329
let getProcessGetter (processNameRoot : string) (headers : CompositeHeader seq) =
322330

@@ -366,6 +374,11 @@ module ProcessParsing =
366374
headers
367375
|> Seq.tryPick (fun (generalI,header) -> tryGetProtocolVersionGetter generalI header)
368376

377+
let commentGetters =
378+
headers
379+
|> Seq.choose (fun (generalI,header) -> tryGetCommentGetter generalI header)
380+
|> Seq.toList
381+
369382
// This is a little more complex, as data and material objects can't contain characteristics. So in the case where the input of the table is a data object but characteristics exist. An additional sample object with the same name is created to contain the characteristics.
370383
let inputGetter =
371384
match headers |> Seq.tryPick (fun (generalI,header) -> tryGetInputGetter generalI header) with
@@ -418,6 +431,8 @@ module ProcessParsing =
418431
let paramvalues = parameterValueGetters |> List.map (fun f -> f matrix i) |> Option.fromValueWithDefault []
419432
let parameters = paramvalues |> Option.map (List.map (fun pv -> pv.Category.Value))
420433

434+
let comments = commentGetters |> List.map (fun f -> f matrix i) |> Option.fromValueWithDefault []
435+
421436
let protocol : Protocol option =
422437
Protocol.make
423438
None
@@ -462,7 +477,7 @@ module ProcessParsing =
462477
None
463478
(Some inputs)
464479
(Some outputs)
465-
None
480+
comments
466481

467482
/// Groups processes by their name, or by the name of the protocol they execute
468483
///
@@ -492,20 +507,20 @@ module ProcessParsing =
492507
processes
493508
|> List.groupBy (fun x ->
494509
if x.Name.IsSome && (x.Name.Value |> Process.decomposeName |> snd).IsSome then
495-
(x.Name.Value |> Process.decomposeName |> fst), HashCodes.boxHashOption x.ExecutesProtocol, x.ParameterValues |> Option.map HashCodes.boxHashSeq
510+
(x.Name.Value |> Process.decomposeName |> fst), HashCodes.boxHashOption x.ExecutesProtocol, x.ParameterValues |> Option.map HashCodes.boxHashSeq, x.Comments |> Option.map HashCodes.boxHashSeq
496511
elif x.ExecutesProtocol.IsSome && x.ExecutesProtocol.Value.Name.IsSome then
497-
x.ExecutesProtocol.Value.Name.Value, HashCodes.boxHashOption x.ExecutesProtocol, x.ParameterValues |> Option.map HashCodes.boxHashSeq
512+
x.ExecutesProtocol.Value.Name.Value, HashCodes.boxHashOption x.ExecutesProtocol, x.ParameterValues |> Option.map HashCodes.boxHashSeq, x.Comments |> Option.map HashCodes.boxHashSeq
498513
else
499-
Identifier.createMissingIdentifier(), HashCodes.boxHashOption x.ExecutesProtocol, x.ParameterValues |> Option.map HashCodes.boxHashSeq
514+
Identifier.createMissingIdentifier(), HashCodes.boxHashOption x.ExecutesProtocol, x.ParameterValues |> Option.map HashCodes.boxHashSeq, x.Comments |> Option.map HashCodes.boxHashSeq
500515
)
501516
|> fun l ->
502517
l
503-
|> List.mapi (fun i ((n,_,_),processes) ->
518+
|> List.mapi (fun i ((n,_,_,_),processes) ->
504519
let pVs = processes.[0].ParameterValues
505520
let inputs = processes |> List.collect (fun p -> p.Inputs |> Option.defaultValue []) |> Option.fromValueWithDefault []
506521
let outputs = processes |> List.collect (fun p -> p.Outputs |> Option.defaultValue []) |> Option.fromValueWithDefault []
507522
let n = if l.Length > 1 then Process.composeName n i else n
508-
Process.create(Name = n,?ExecutesProtocol = processes.[0].ExecutesProtocol,?ParameterValues = pVs,?Inputs = inputs,?Outputs = outputs)
523+
Process.create(Name = n,?ExecutesProtocol = processes.[0].ExecutesProtocol,?ParameterValues = pVs,?Inputs = inputs,?Outputs = outputs,?Comments = processes.[0].Comments)
509524
)
510525

511526

@@ -530,6 +545,13 @@ module ProcessParsing =
530545
if prot.Version.IsSome then CompositeHeader.ProtocolVersion, CompositeCell.FreeText prot.Version.Value
531546
]
532547
| None -> []
548+
let comments =
549+
p.Comments
550+
|> Option.defaultValue []
551+
|> List.map (fun c ->
552+
CompositeHeader.Comment (Option.defaultValue "" c.Name),
553+
CompositeCell.FreeText (Option.defaultValue "" c.Value)
554+
)
533555
// zip the inputs and outpus so they are aligned as rows
534556
p.Outputs |> Option.defaultValue []
535557
|> List.zip (p.Inputs |> Option.defaultValue [])
@@ -565,6 +587,7 @@ module ProcessParsing =
565587
yield JsonTypes.decomposeProcessInput inputForType
566588
yield! protVals
567589
yield! vals
590+
yield! comments
568591
yield JsonTypes.decomposeProcessOutput outputForType
569592
]
570593
)

src/Core/Helper/Regex.fs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ module Pattern =
3030
[<Literal>]
3131
let iotype = "iotype"
3232

33+
[<Literal>]
34+
let commentKey = "commentKey"
35+
3336
/// This pattern is only used to remove any leftover #id attributes from previous Swate version.
3437
/// `"Parameter [biological replicate#2]"` This #id is deprecated but the pattern can still be used to remove any from files.
3538
/// Was deprecated before 2023.
@@ -95,6 +98,9 @@ module Pattern =
9598
/// This pattern is used to match Output column and capture the IOType as `iotype` group.
9699
let OutputPattern = $@"Output\s\[(?<{MatchGroups.iotype}>.+)\]"
97100

101+
/// This pattern is used to match Comment column and capture the comment key as `commentKey` group.
102+
let CommentPattern = $@"Comment\s\[(?<{MatchGroups.commentKey}>.+)\]"
103+
98104
/// This pattern matches any column header starting with some text, followed by one whitespace and a term name inside squared brackets.
99105
///
100106
/// Captures column type as named group: "termcolumntype" (e.g. Component, Characteristic .. ).
@@ -263,6 +269,12 @@ module ActivePatterns =
263269
r.Groups.["number"].Value |> int |> Some
264270
| _ -> None
265271

272+
let (|Comment|_|) input =
273+
match input with
274+
| Regex Pattern.CommentPattern r ->
275+
Some r.Groups.[Pattern.MatchGroups.commentKey].Value
276+
| _ -> None
277+
266278
open Pattern
267279
open ActivePatterns
268280
open System

src/Core/Table/CompositeHeader.fs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ type CompositeHeader =
151151
| Output of IOType
152152
// single - fallback
153153
| FreeText of string
154+
| Comment of string
154155

155156
with
156157

@@ -179,7 +180,7 @@ type CompositeHeader =
179180
// iotype as input
180181
| 11 | 12 -> 2
181182
// string as input
182-
| 13 -> 3
183+
| 13 | 14 -> 3
183184
| anyElse -> failwithf "Cannot assign input `Tag` (%i) to `CompositeHeader`" anyElse
184185

185186
override this.ToString() =
@@ -197,6 +198,7 @@ type CompositeHeader =
197198
| Date -> "Date"
198199
| Input io -> io.asInput
199200
| Output io -> io.asOutput
201+
| Comment key -> $"Comment [{key}]"
200202
| FreeText str -> str
201203

202204
/// If the column is a term column, returns the term as `OntologyAnnotation`. Otherwise returns an `OntologyAnnotation` with only the name.
@@ -215,6 +217,7 @@ type CompositeHeader =
215217
| Date -> OntologyAnnotation.create (this.ToString()) // use owl ontology in the future
216218
| Input _ -> OntologyAnnotation.create (this.ToString()) // use owl ontology in the future
217219
| Output _ -> OntologyAnnotation.create (this.ToString()) // use owl ontology in the future
220+
| Comment _ -> OntologyAnnotation.create (this.ToString()) // use owl ontology in the future
218221
| FreeText _ -> OntologyAnnotation.create (this.ToString()) // use owl ontology in the future
219222
// owl ontology: https://github.com/nfdi4plants/ARC_ontology/blob/main/ARC_v2.0.owl
220223

@@ -231,6 +234,8 @@ type CompositeHeader =
231234
| Regex.ActivePatterns.Regex Regex.Pattern.OutputPattern r ->
232235
let iotype = r.Groups.[Regex.Pattern.MatchGroups.iotype].Value
233236
Output <| IOType.ofString (iotype)
237+
| Regex.ActivePatterns.Regex Regex.Pattern.CommentPattern r ->
238+
Comment r.Groups.[Regex.Pattern.MatchGroups.commentKey].Value
234239
// Is term column
235240
| Regex.ActivePatterns.TermColumn r ->
236241
match r.TermColumnType with
@@ -332,6 +337,7 @@ type CompositeHeader =
332337
match this with
333338
| FreeText _
334339
| Input _ | Output _
340+
| Comment _
335341
| ProtocolREF | ProtocolDescription | ProtocolUri | ProtocolVersion | Performer | Date -> true
336342
| anythingElse -> false
337343

@@ -414,6 +420,11 @@ type CompositeHeader =
414420
| Date -> true
415421
| anythingElse -> false
416422

423+
member this.isComment =
424+
match this with
425+
| Comment _ -> true
426+
| anythingElse -> false
427+
417428
member this.isFreeText =
418429
match this with
419430
| FreeText _ -> true
@@ -475,6 +486,8 @@ type CompositeHeader =
475486
"Only one output column per table. E.g. experimental samples or files."
476487
| U2.Case1 (FreeText _) | U2.Case2 "FreeText" ->
477488
"Placeholder"
489+
| U2.Case1 (Comment _) | U2.Case2 "Comment" ->
490+
"Comment"
478491
| _ -> failwith $"Unable to parse combination to existing CompositeHeader: `{header}`"
479492

480493
#if FABLE_COMPILER

src/Json/Table/CompositeHeader.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module CompositeHeader =
1414
let t, v =
1515
match ch with
1616
| CompositeHeader.FreeText s -> s, []
17+
| CompositeHeader.Comment c -> "Comment", [Encode.string c]
1718
| CompositeHeader.Parameter oa -> "Parameter", [oaToJsonString oa]
1819
| CompositeHeader.Factor oa -> "Factor", [oaToJsonString oa]
1920
| CompositeHeader.Characteristic oa -> "Characteristic", [oaToJsonString oa]
@@ -37,6 +38,7 @@ module CompositeHeader =
3738
let headerType = get.Required.Field HeaderType Decode.string
3839
let oa() = get.Required.Field HeaderValues (Decode.index 0 OntologyAnnotation.decoder)
3940
let io() = get.Required.Field HeaderValues (Decode.index 0 IOType.decoder)
41+
let c() = get.Required.Field HeaderValues Decode.string
4042
match headerType with
4143
| "Characteristic" -> oa() |> CompositeHeader.Characteristic
4244
| "Parameter" -> oa() |> CompositeHeader.Parameter
@@ -51,6 +53,7 @@ module CompositeHeader =
5153
| "ProtocolVersion" -> CompositeHeader.ProtocolVersion
5254
| "Performer" -> CompositeHeader.Performer
5355
| "Date" -> CompositeHeader.Date
56+
| "Comment" -> c() |> CompositeHeader.Comment
5457
| anyelse -> CompositeHeader.FreeText anyelse
5558
)
5659

src/Spreadsheet/AnnotationTable/CompositeHeader.fs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ module ActivePattern =
9898
|> Some
9999
| _ -> None
100100

101+
let (|Comment|_|) (cells : FsCell list) =
102+
let cellValues = cells |> List.map (fun c -> c.ValueAsString())
103+
match cellValues with
104+
| [Comment key] -> Some (CompositeHeader.Comment key, CompositeCell.freeTextFromFsCells)
105+
| _ -> None
106+
101107
let (|ProtocolType|_|) (cells : FsCell list) =
102108
let parser s = if s = "Protocol Type" then Some s else None
103109
let header _ = CompositeHeader.ProtocolType
@@ -136,6 +142,7 @@ let fromFsCells (cells : list<FsCell>) : CompositeHeader*(FsCell list -> Composi
136142
| Output o -> o
137143
| ProtocolType pt -> pt
138144
| ProtocolHeader ph -> ph
145+
| Comment c -> c
139146
| FreeText ft -> ft
140147
| _ -> failwithf "Could not parse header group %O" cells
141148

tests/Core/ArcJsonConversion.Tests.fs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,24 @@ let private tests_ArcTableProcess =
410410
let t' = ArcTable.fromProcesses tableName1 processes
411411
Expect.arcTableEqual t' t "Table should be equal"
412412
)
413+
testCase "SingleRowIOAndComment GetAndFromProcesses" (fun () ->
414+
let t = ArcTable.init(tableName1)
415+
let commentKey = "MyCommentKey"
416+
let commentValue = "MyCommentValue"
417+
t.AddColumn(CompositeHeader.Input(IOType.Source),[|CompositeCell.createFreeText "Source"|])
418+
t.AddColumn(CompositeHeader.Comment(commentKey), [|CompositeCell.createFreeText commentValue|])
419+
t.AddColumn(CompositeHeader.Output(IOType.Sample),[|CompositeCell.createFreeText "Sample"|])
420+
let processes = t.GetProcesses()
421+
Expect.hasLength processes 1 ""
422+
let comments = Expect.wantSome processes.[0].Comments ""
423+
Expect.hasLength comments 1 ""
424+
let comment = comments.[0]
425+
Expect.equal comment (Comment(commentKey,commentValue)) ""
426+
let table = ArcTable.fromProcesses tableName1 processes
427+
let expectedTable = t
428+
Expect.arcTableEqual table expectedTable "Table should be equal"
429+
)
430+
413431
]
414432

415433
let private tests_ArcTablesProcessSeq =

tests/Core/CompositeHeader.Tests.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ let private tests_compositeHeader =
5151
testList "CompositeHeader" [
5252
testCase "Cases" <| fun _ ->
5353
let count = CompositeHeader.Cases.Length
54-
Expect.equal count 14 "count"
54+
Expect.equal count 15 "count"
5555
testCase "getExplanation" <| fun _ ->
5656
let cases = CompositeHeader.Cases |> Array.map snd
5757
for case in cases do
@@ -318,6 +318,7 @@ let tests_ToTerm = testList "ToTerm" [
318318
CompositeHeader.Input IOType.Source
319319
CompositeHeader.Output IOType.Sample
320320
CompositeHeader.FreeText "Hello World"
321+
CompositeHeader.Comment "MyComment"
321322
]
322323
|> List.distinct
323324
testCase "Ensure all headers listed" <| fun _ ->

0 commit comments

Comments
 (0)