Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Developer docs #228

Merged
merged 6 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches: [ main ]
pull_request:
branches: [ main, developer ]
branches: [ main ]

jobs:
build-and-test-linux:
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# ARCtrl
Top level ARC DataModel and API function descriptions.

![Nuget](https://img.shields.io/nuget/dt/ARCtrl?label=nuget&link=https%3A%2F%2Fwww.nuget.org%2Fpackages%2FARCtrl)
![npm](https://img.shields.io/npm/dt/%40nfdi4plants%2Farctrl?label=npm&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F%40nfdi4plants%2Farctrl)

- [ARCtrl](#arctrl)
- [Jargon/Nomenclature](#jargonnomenclature)
- [API Design](#api-design)
Expand Down
192 changes: 192 additions & 0 deletions docs/ARC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# ARC

🔗 The script files for this documentation can be found here:
- [JavaScript](/scripts_js/ARC.js)
- [F#](/scripts_fsharp/ARC.fsx)

Table of Content
- [Create ARC](#create)
- [Write ARC](#write)
- [Read ARC](#read)

## Create

ARCtrl aims to provide an easy solution to create and manipulate ARCs in memory.

```fsharp
// F#
#r "nuget: FsSpreadsheet.ExcelIO, 4.1.0"
#r "nuget: ARCtrl, 1.0.0-alpha9"

open ARCtrl

/// Init a new empty ARC
let arc = ARC()
```

```js
// JavaScript
import {ARC} from "@nfdi4plants/arctrl";

let arc = new ARC()
```

This will initialize an ARC without metadata but with the basic ARC folder structure in `arc.FileSystem`

- 📁 ARC root
- 📄 isa.investigation.xlsx
- 📁 workflows
- 📁 runs
- 📁 assays
- 📁 studies

## Write

In .NET you can use [ARCtrl.NET][1] to handle any contract based read/write operations. For this documentation we will extract the relevant ARCtrl.NET functions.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we actually also add documentation for ARCtrl.Net here?


```fsharp
// F#
open ARCtrl.Contract
open FsSpreadsheet
open FsSpreadsheet.ExcelIO

let arcRootPath = @"path/where/you/want/the/NewTestARC"

// From ARCtrl.NET
let fulfillWriteContract basePath (c : Contract) =
let ensureDirectory (filePath : string) =
let file = new System.IO.FileInfo(filePath);
file.Directory.Create()
match c.DTO with
| Some (DTO.Spreadsheet wb) ->
let path = System.IO.Path.Combine(basePath, c.Path)
ensureDirectory path
FsWorkbook.toFile path (wb :?> FsWorkbook)
| Some (DTO.Text t) ->
let path = System.IO.Path.Combine(basePath, c.Path)
ensureDirectory path
System.IO.File.WriteAllText(path,t)
| None ->
let path = System.IO.Path.Combine(basePath, c.Path)
ensureDirectory path
System.IO.File.Create(path).Close()
| _ ->
printfn "Contract %s is not an ISA contract" c.Path

// From ARCtrl.NET
let write (arcPath: string) (arc:ARC) =
arc.GetWriteContracts()
|> Array.iter (fulfillWriteContract arcPath)

write arcRootPath arc
```

```js
// JavaScript
import {ARC} from "@nfdi4plants/arctrl";
import {Xlsx} from "fsspreadsheet";
import fs from "fs";
import path from "path";

const arcRootPath = "C:/Users/Kevin/Desktop/NewTestARCJS"

async function fulfillWriteContract (basePath, contract) {
function ensureDirectory (filePath) {
let dirPath = path.dirname(filePath)
if (!fs.existsSync(dirPath)){
fs.mkdirSync(dirPath, { recursive: true });
}
}
const p = path.join(basePath,contract.Path)
if (contract.Operation = "CREATE") {
if (contract.DTO == undefined) {
ensureDirectory(p)
fs.writeFileSync(p, "")
} else if (contract.DTOType == "ISA_Assay" || contract.DTOType == "ISA_Assay" || contract.DTOType == "ISA_Investigation") {
ensureDirectory(p)
await Xlsx.toFile(p, contract.DTO)
console.log("ISA", p)
} else if (contract.DTOType == "PlainText") {
ensureDirectory(p)
fs.writeFileSync(p, contract.DTO)
} else {
console.log("Warning: The given contract is not a correct ARC write contract: ", contract)
}
}
}

async function write(arcPath, arc) {
let contracts = arc.GetWriteContracts()
contracts.forEach(async contract => {
await fulfillWriteContract(arcPath,contract)
});
}

await write(arcRootPath,arc)
```

## Read

Read may look intimidating at first, until you notice that most of this is just setup which can be reused for any read you do.

Setup will be placed on top, with the actual read below.

```fsharp
// F#
open System.IO

// Setup

let normalizePathSeparators (str:string) = str.Replace("\\","/")

let getAllFilePaths (basePath: string) =
let options = EnumerationOptions()
options.RecurseSubdirectories <- true
Directory.EnumerateFiles(basePath, "*", options)
|> Seq.map (fun fp ->
Path.GetRelativePath(basePath, fp)
|> normalizePathSeparators
)
|> Array.ofSeq

// from ARCtrl.NET
// https://github.com/nfdi4plants/ARCtrl.NET/blob/ba3d2fabe007d9ca2c8e07b62d02ddc5264306d0/src/ARCtrl.NET/Contract.fs#L7
let fulfillReadContract basePath (c : Contract) =
match c.DTOType with
| Some DTOType.ISA_Assay
| Some DTOType.ISA_Investigation
| Some DTOType.ISA_Study ->
let path = System.IO.Path.Combine(basePath, c.Path)
let wb = FsWorkbook.fromXlsxFile path |> box |> DTO.Spreadsheet
{c with DTO = Some wb}
| Some DTOType.PlainText ->
let path = System.IO.Path.Combine(basePath, c.Path)
let text = System.IO.File.ReadAllText(path) |> DTO.Text
{c with DTO = Some text}
| _ ->
printfn "Contract %s is not an ISA contract" c.Path
c

// put it all together
let readARC(basePath: string) =
let allFilePaths = getAllFilePaths basePath
// Initiates an ARC from FileSystem but no ISA info.
let arcRead = ARC.fromFilePaths allFilePaths
// Read contracts will tell us what we need to read from disc.
let readContracts = arcRead.GetReadContracts()
let fulfilledContracts =
readContracts
|> Array.map (fulfillReadContract basePath)
arcRead.SetISAFromContracts(fulfilledContracts)
arcRead

// execution

readARC arcRootPath
```

```js

```

[1]: <https://www.nuget.org/packages/ARCtrl.NET> "ARCtrl.NET Nuget"
115 changes: 115 additions & 0 deletions docs/Contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Contracts - from a code perspective

**Table of contents**
- [WRITE](#write-contracts)
- [READ](#read-contracts)

**Code can be found here**
- [F#](/docs/scripts_fsharp/Contracts.fsx)
- [JavaScript](/docs/scripts_js/Contracts.js)


Contracts in ARCtrl are used to delegate *IO operations* to the api consumer to allow interoperability with any environment (.NET, Node, Browser..). Contracts use *Data Transfer Object"s* (DTOs) instead of the ARCtrl types to provide a higher interoperability.

A Contract object consists of the following fields:

```fsharp
// F#
// Using F# discriminate union types it is very easy to match for differen cases.
type Contract =
{
/// Determines what io operation should be done: CREATE, READ, DELETE, ...
Operation : Operation
/// The path where the io operation should be executed. The path is relative to ARC root.
Path: string
/// The type of DTO that is expected: JSON, PlainText, ISA_Assay, ISA_Study, ISA_Investigation,..
DTOType : DTOType option
/// The actual DTO, as discriminate union.
DTO: DTO option
}
```

```js
// JavaScript
export class Contract extends Record {
constructor(Operation, Path, DTOType, DTO) {
super();
// Can be any of: "CREATE", "UPDATE", "DELETE", "READ", "EXECUTE"
this.Operation = Operation;
// string, the path where the io operation should be executed. The path is relative to ARC root
this.Path = Path;
// Can be undefined or any of: "ISA_Assay", "ISA_Study", "ISA_Investigation", "JSON", "Markdown", "CWL", "PlainText", "Cli".
this.DTOType = DTOType;
// Can be undefined or any of: `FsWorkbook` (from fsspreadsheet), string (e.g. json,..), or a `CLITool`.
this.DTO = DTO;
}
```

Handling contracts can be generalized in a few functions.

## WRITE contracts

```fsharp
#r "nuget: FsSpreadsheet.ExcelIO, 4.1.0"
#r "nuget: ARCtrl, 1.0.0-alpha9"

open ARCtrl
open ARCtrl.Contract
open FsSpreadsheet
open FsSpreadsheet.ExcelIO

/// From ARCtrl.NET
let fulfillWriteContract basePath (c : Contract) =
let ensureDirectory (filePath : string) =
let file = new System.IO.FileInfo(filePath);
file.Directory.Create()
match c.DTO with
| Some (DTO.Spreadsheet wb) ->
let path = System.IO.Path.Combine(basePath, c.Path)
ensureDirectory path
FsWorkbook.toFile path (wb :?> FsWorkbook)
| Some (DTO.Text t) ->
let path = System.IO.Path.Combine(basePath, c.Path)
ensureDirectory path
System.IO.File.WriteAllText(path,t)
| None ->
let path = System.IO.Path.Combine(basePath, c.Path)
ensureDirectory path
System.IO.File.Create(path).Close()
| _ ->
printfn "Contract %s is not an ISA contract" c.Path
```

```js
import {Xlsx} from "fsspreadsheet";
import fs from "fs";
import path from "path";

export async function fulfillWriteContract (basePath, contract) {
function ensureDirectory (filePath) {
let dirPath = path.dirname(filePath)
if (!fs.existsSync(dirPath)){
fs.mkdirSync(dirPath, { recursive: true });
}
}
const p = path.join(basePath,contract.Path)
if (contract.Operation = "CREATE") {
if (contract.DTO == undefined) {
ensureDirectory(p)
fs.writeFileSync(p, "")
} else if (contract.DTOType == "ISA_Assay" || contract.DTOType == "ISA_Assay" || contract.DTOType == "ISA_Investigation") {
ensureDirectory(p)
await Xlsx.toFile(p, contract.DTO)
console.log("ISA", p)
} else if (contract.DTOType == "PlainText") {
ensureDirectory(p)
fs.writeFileSync(p, contract.DTO)
} else {
console.log("Warning: The given contract is not a correct ARC write contract: ", contract)
}
}
}
```


## READ contracts
Loading