Skip to content

Commit

Permalink
hierarchy: category > task > recipe
Browse files Browse the repository at this point in the history
- URL we serve this under is docs/cookbook/[task slug]/[recipe slug]
- multiple recipes per task
- recipe page displays links to other recipes for the same task
  • Loading branch information
sabine committed Feb 17, 2024
1 parent 6c059f6 commit b3bde63
Show file tree
Hide file tree
Showing 14 changed files with 330 additions and 105 deletions.
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ We've provided a list of community-driven content below. When adding content to
- [Success Stories](#content-success-story)
- [Academic and Industrial Users](#content-user)
- [OCaml Books](#content-book)
- [OCaml Cookbook Recipes](#content-cookbook)
- [Community Meetups](#content-meetup)
- [Upcoming Events](#content-upcoming_event)
- [The OCaml Changelog](#content-changelog)
Expand Down Expand Up @@ -112,6 +113,12 @@ You can add a new industrial user by creating a new Markdown file in [data/acade
You can add a new OCaml book by creating a new Markdown file in [data/books/](data/books/). For instance: [ocaml-from-the-very-beginning.md](data/books/ocaml-from-the-very-beginning.md).

### <a name="content-cookbook"></a>Add a Recipe to the OCaml Cookbook

The OCaml cookbook is a place where OCaml developers share how to solve practical-minded tasks in OCaml using packages from the OCaml ecosystem. To contribute a recipe, you need to choose a category for the recipe (see [cookbook_categories.yml](https://)) and write a markdown file with YAML header (for an example, see []).

Every cookbook recipe consists of one or more files with explanations. In the markdown body of the document, you can provide further context or links to the packages used. But: keep this short, the point is to get people back to building things, not keep them here reading.

### <a name="content-meetup"></a>Add A meetup

> Contribute to the [Community Meetups](https://ocaml.org/community).
Expand Down
61 changes: 61 additions & 0 deletions data/cookbook/cookbook_categories.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
cookbook_categories:
- title: Command Line
folder: command-line
tasks: []
- title: Compression
folder: compression
tasks: []
- title: Concurrency
folder: concurrency
tasks: []
- title: Cryptography
folder: cryptography
tasks: []
- title: Database
folder: database
tasks: []
- title: Date and Time
folder: date-and-time
tasks:
- title: Get Today's Date
folder: get-today-date
- title: Debugging
folder: debugging
tasks: []
- title: Encoding
folder: encoding
tasks: []
- title: File System
folder: file-system
tasks: []
- title: Garbage Collector
folder: garbage-collector
tasks: []
- title: Generate Random Values
folder: generate-random-values
tasks:
- title: Generate random numbers
folder: generate-random-numbers
- title: Generate random strings and arrays
folder: generate-random-strings-and-arrays
- title: Hash Tables and Maps
folder: hash-tables-and-maps
tasks: []
- title: Mathematics
folder: mathematics
tasks: []
- title: Networking
folder: networking
tasks: []
- title: Operating System
folder: operating-system
tasks: []
- title: Sorting
folder: sorting
tasks: []
- title: Text Processing
folder: text-processing
tasks: []
- title: Web Programming
folder: web-programming
tasks: []
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
---
title: Get Current Date (Stdlib)
problem: "You need to find the year, month, and day values for today's date."
category: "Date and Time"
packages: []
sections:
- filename: main.ml
Expand All @@ -23,8 +20,6 @@ sections:
year month day;;
---

## Discussion

- **Understanding `Unix.localtime` and `Unix.time`:** The `Unix.localtime` function converts a timestamp obtained from `Unix.time` (which returns the current time since the Unix epoch) into a local time, represented by a `tm` structure. This structure includes fields like `tm_year`, `tm_mon`, and `tm_mday` for year, month, and day, respectively.
- **Month and Year Adjustments:** In OCaml's `Unix` module, the month is zero-indexed (0 for January, 11 for December), and the year is the number of years since 1900. Don't forget to adjust these values to get a human-readable date.
- **Alternative Libraries:** For more complex date-time operations, consider using external libraries like `calendar` or `timedesc`, which offer more functionalities like time zone handling and date arithmetic.
35 changes: 0 additions & 35 deletions data/cookbook/generate-random-numbers/00-mirage-crypto-rng.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
packages:
- name: "mirage-crypto-rng"
version: "0.11.2"
- name: "randomconv"
version: "0.1.3"
sections:
- filename: main.ml
language: ocaml
code_blocks:
- explanation: |
Initialize the RNG with the default entropy source.
code: |
let () = Mirage_crypto_rng_unix.initialize
(module Mirage_crypto_rng.Fortuna)
- explanation: |
Generate a `Cstruct.t` with random data.
code: |
let cstruct n = Mirage_crypto_rng.generate n
- explanation: |
Use the `Randomconv` module from the `randomconv` package to convert to various integer types.
code: |
let int8 () = Randomconv.int8 cstruct
let int16 () = Randomconv.int16 cstruct
let int32 () = Randomconv.int32 cstruct
let int64 () = Randomconv.int64 cstruct
- explanation:
Generate a random `int` or `float` less than or equal to `max`.
code: |
let int ?max () =
Randomconv.int ?bound:max cstruct
let float ?max () =
Randomconv.float ?bound:max cstruct
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
packages:
- name: "mirage-crypto-rng"
version: "0.11.2"
- name: "randomconv"
version: "0.1.3"
sections:
- filename: main.ml
language: ocaml
code_blocks:
- explanation: |
Initialize the RNG with the default entropy source.
code: |
let () = Mirage_crypto_rng_unix.initialize
(module Mirage_crypto_rng.Fortuna)
- explanation: |
Generate a `Cstruct.t` with random data.
code: |
let cstruct n = Mirage_crypto_rng.generate n
- explanation: |
To get a random `char`, convert a random `int8`:
code: |
let char () = Char.chr (int8 ())
- explanation: |
Use `Cstruct`'s conversion methods to obtain random byte arrays, bigarrays or strings.
code: |
let bytes n = cstruct n |> Cstruct.to_bytes
let bigarray n = cstruct n |> Cstruct.to_bigarray
let string n = cstruct n |> Cstruct.to_string
- explanation: |
You can also create alphanumeric random characters:
code: |
let alphanum () =
Char.chr (48 + Randomconv.int ~bound:74 cstruct)
- explanation: |
Wrap your random value generator into a sequence to generate as many random values as you want.
code: |
let seq gen =
Seq.unfold (fun () -> Some (gen (), ())) ()
let list n gen =
seq gen |> Seq.take n |> List.of_seq
---
2 changes: 1 addition & 1 deletion src/global/url.ml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ let exercises = "/exercises"
let outreachy = "/outreachy"
let logos = "/logo"
let cookbook = "/docs/cookbook"
let cookbook_recipe recipe = "/docs/cookbook/" ^ recipe
let cookbook_recipe ~task_slug slug = "/docs/cookbook/" ^ task_slug ^ "/" ^ slug

let github_opam_file package_name package_version =
Printf.sprintf
Expand Down
13 changes: 12 additions & 1 deletion src/ocamlorg_data/data.ml
Original file line number Diff line number Diff line change
Expand Up @@ -217,5 +217,16 @@ end
module Cookbook = struct
include Cookbook

let get_by_slug slug = List.find_opt (fun x -> String.equal slug x.slug) all
let get_tasks_by_category ~category_slug =
tasks
|> List.filter (fun (x : task) ->
String.equal category_slug x.category.slug)

let get_by_task ~task_slug =
all |> List.filter (fun (x : t) -> String.equal task_slug x.task.slug)

let get_by_slug ~task_slug slug =
List.find_opt
(fun x -> String.equal slug x.slug && String.equal task_slug x.task.slug)
all
end
17 changes: 11 additions & 6 deletions src/ocamlorg_data/data.mli
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,10 @@ module Governance : sig
end

module Cookbook : sig
type category = { title : string; slug : string }
type task = { title : string; slug : string; category : category }
type code_block_with_explanation = { code : string; explanation : string }
type package = { name : string; version : string }

type section = {
filename : string;
Expand All @@ -529,15 +532,17 @@ module Cookbook : sig

type t = {
slug : string;
group_id : string;
title : string;
problem : string;
category : string;
packages : string list;
filepath : string;
task : task;
packages : package list;
sections : section list;
body_html : string;
}

val categories : category list
val tasks : task list
val all : t list
val get_by_slug : string -> t option
val get_tasks_by_category : category_slug:string -> task list
val get_by_task : task_slug:string -> t list
val get_by_slug : task_slug:string -> string -> t option
end
53 changes: 33 additions & 20 deletions src/ocamlorg_frontend/pages/cookbook.eml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
let render (recipes: Data.Cookbook.t list) =
let render (categories: Data.Cookbook.category list) =
Learn_layout.single_column_layout
~title:"OCaml Cookbook"
~description:"A collection of recipes to get things done in OCaml."
Expand All @@ -14,31 +14,44 @@ Learn_layout.single_column_layout
common tasks using packages from the OCaml ecosystem.
</p>
</div>
<div class="prose max-w-full flex flex-col gap-x-4 gap-y-2">
<div class="grid grid-cols-[1fr_30%_20%] items-center">
<div><strong>Recipe</strong></div>
<div><strong>Packages Used</strong></div>
<div><strong>Category</strong></div>
</div>
% recipes |> List.iter (fun (recipe : Data.Cookbook.t) ->
<div class="grid grid-cols-[1fr_30%_20%] items-center">
<a href="<%s Url.cookbook_recipe recipe.slug %>" class="underline">
<%s recipe.title %>
</a>
<div class="flex flex-wrap gap-2 items-center">
% (if List.length recipe.packages = 0 then
-
<div class="flex flex-col gap-6">
<% categories |> List.iter (fun (category: Data.Cookbook.category) -> %>
<div class="max-w-4xl">
<div class="prose">
<h2 class="text-xl font-semibold mb-6"><%s category.title %></h2>
<% let tasks = Data.Cookbook.get_tasks_by_category ~category_slug:category.slug in %>
% if tasks = [] then (
<p>There's nothing in this category yet, maybe you want to
<a target="_blank" href="https://github.com/ocaml/ocaml.org/blob/main/CONTRIBUTING.md#content-cookbook">contribute a recipe</a>!
</p>
% );
% recipe.packages |> List.iter (fun (package: string) ->
<a class="tag" href="<%s Url.Package.overview package %>"><%s package %></a>
%);
</div>
% if List.length tasks > 0 then (
<div class="max-w-4xl">
<div class="grid grid-cols-[1fr_40%] items-center bg-primary text-white rounded-t">
<div class="p-2"><strong>Task</strong></div>
<div class="p-2"><strong>Packages Used</strong></div>
</div>
<div>
<%s recipe.category %>
<div class="flex flex-col gap-2 bg-sand rounded-b py-2">
<% tasks |> List.iter (fun (task : Data.Cookbook.task) -> %>
<% let recipe = List.nth (Data.Cookbook.get_by_task ~task_slug:task.slug) 0 in %>
<div class="grid grid-cols-[1fr_40%] items-center">
<a href="<%s Url.cookbook_recipe ~task_slug:recipe.task.slug recipe.slug %>" class="p-2 underline hover:no-underline">
<%s recipe.task.title %>
</a>
<div class="flex flex-wrap gap-2 items-center">
<%s if List.length recipe.packages = 0 then "-" else "" %>
<% recipe.packages |> List.iter (fun (package: Data.Cookbook.package) -> %>
<a class="tag" href="<%s Url.Package.overview ~version:package.version package.name %>"><%s package.name %>.<%s package.version %></a>
<% ); %>
</div>
</div>
<% ); %>
</div>
</div>
% );
</div>
<% ); %>
</div>
</div>
</div>
33 changes: 29 additions & 4 deletions src/ocamlorg_frontend/pages/cookbook_recipe.eml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
let render (recipe: Data.Cookbook.t) =
let render (recipe: Data.Cookbook.t) (other_recipes_for_this_task: Data.Cookbook.t list) =
Learn_layout.single_column_layout
~title:"OCaml Cookbook"
~description:"A collection of recipes to get things done in OCaml."
Expand All @@ -8,9 +8,8 @@ Learn_layout.single_column_layout
<div class="bg-legacy-default dark:bg-legacy-dark-default">
<div class="container-fluid pt-10">
<div class="prose mx-auto max-w-5xl">
<h1 class="font-normal text-4xl mb-10"><%s recipe.title %></h2>
<h2>Problem</h2>
<p><%s recipe.problem %></p>
<h1 class="font-normal text-4xl mb-10"><%s recipe.task.title %></h2>
<h2>Files</h2>
% recipe.sections |> List.iter (fun (section: Data.Cookbook.section) ->
<div class="grid grid-cols-1 lg:grid-cols-2 lg:gap-x-10">
<div></div>
Expand All @@ -23,7 +22,33 @@ Learn_layout.single_column_layout
% );
</div>
% );
% if String.length recipe.body_html > 0 then (
<h2>Discussion</h2>
<%s! recipe.body_html %>
% );
<h2>Recipe not working? Want to Upgrade a Package Version of this Recipe?</h2>
<a target="_blank" href="https://github.com/ocaml/ocaml.org/issues">Open an issue</a>
or <a target="_blank" href="https://github.com/ocaml/ocaml.org/tree/main/data/<%s recipe.filepath %>">contribute to this recipe</a>!
% if List.length other_recipes_for_this_task > 0 then (
<h2>Other Recipes for this Task</h2>
<ul>
% other_recipes_for_this_task |> List.iter (fun (recipe : Data.Cookbook.t) ->
<li>
<div class="grid grid-cols-[1fr_40%] items-center">
<a href="<%s Url.cookbook_recipe ~task_slug:recipe.task.slug recipe.slug %>" class="p-2 underline hover:no-underline">
<%s recipe.task.title %>
</a>
<div class="flex flex-wrap gap-2 items-center">
<%s if List.length recipe.packages = 0 then "-" else "" %>
<% recipe.packages |> List.iter (fun (package: Data.Cookbook.package) -> %>
<a class="tag" href="<%s Url.Package.overview ~version:package.version package.name %>"><%s package.name %>.<%s package.version %></a>
<% ); %>
</div>
</div>
</li>
% );
</ul>
% );
</div>
</div>
</div>
Loading

0 comments on commit b3bde63

Please sign in to comment.