Skip to content

Commit 934770d

Browse files
ErikSchierboomrobkeim
authored andcommitted
Include status of all exercises (#464)
* Show status of all exercises * Fix template path cross-platform issue On Windows the correct embedded resources path was generated, but not on macOS. This works around that by using a local file system instead. * Update exercises * Add diamond exercise as custom exercise
1 parent d197d62 commit 934770d

File tree

11 files changed

+268
-136
lines changed

11 files changed

+268
-136
lines changed

exercises/bob/BobTest.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// This file was auto-generated based on version 1.0.0 of the canonical data.
1+
// This file was auto-generated based on version 1.1.0 of the canonical data.
22

33
module BobTest
44

@@ -41,7 +41,7 @@ let ``Using acronyms in regular speech`` () =
4141

4242
[<Fact(Skip = "Remove to run test")>]
4343
let ``Forceful question`` () =
44-
response "WHAT THE HELL WERE YOU THINKING?" |> should equal "Whoa, chill out!"
44+
response "WHAT THE HELL WERE YOU THINKING?" |> should equal "Calm down, I know what I'm doing!"
4545

4646
[<Fact(Skip = "Remove to run test")>]
4747
let ``Shouting numbers`` () =

exercises/bob/Example.fs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ let response (input: string) =
88
let isQuestion = input.Trim().EndsWith "?"
99

1010
match input with
11-
| _ when isEmpty -> "Fine. Be that way!"
12-
| _ when isYell -> "Whoa, chill out!"
13-
| _ when isQuestion -> "Sure."
14-
| _ -> "Whatever."
11+
| _ when isEmpty ->
12+
"Fine. Be that way!"
13+
| _ when isYell && isQuestion ->
14+
"Calm down, I know what I'm doing!"
15+
| _ when isYell ->
16+
"Whoa, chill out!"
17+
| _ when isQuestion ->
18+
"Sure."
19+
| _ ->
20+
"Whatever."

exercises/book-store/BookStoreTest.fs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// This file was auto-generated based on version 1.0.1 of the canonical data.
1+
// This file was auto-generated based on version 1.1.0 of the canonical data.
22

33
module BookStoreTest
44

@@ -59,3 +59,7 @@ let ``Three copies of first book and 2 each of remaining`` () =
5959
let ``Three each of first 2 books and 2 each of remaining books`` () =
6060
total [1; 1; 2; 2; 3; 3; 4; 4; 5; 5; 1; 2] |> should equal 75.20
6161

62+
[<Fact(Skip = "Remove to run test")>]
63+
let ``Four groups of four are cheaper than two groups each of five and three`` () =
64+
total [1; 1; 2; 2; 3; 3; 4; 5; 1; 1; 2; 2; 3; 3; 4; 5] |> should equal 102.40
65+

exercises/rna-transcription/RnaTranscriptionTest.fs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// This file was auto-generated based on version 1.0.1 of the canonical data.
1+
// This file was auto-generated based on version 1.1.0 of the canonical data.
22

33
module RnaTranscriptionTest
44

@@ -27,15 +27,3 @@ let ``RNA complement of adenine is uracil`` () =
2727
let ``RNA complement`` () =
2828
toRna "ACGTGGTCTTAA" |> should equal (Some "UGCACCAGAAUU")
2929

30-
[<Fact(Skip = "Remove to run test")>]
31-
let ``Correctly handles invalid input (RNA instead of DNA)`` () =
32-
toRna "U" |> should equal None
33-
34-
[<Fact(Skip = "Remove to run test")>]
35-
let ``Correctly handles completely invalid DNA input`` () =
36-
toRna "XXX" |> should equal None
37-
38-
[<Fact(Skip = "Remove to run test")>]
39-
let ``Correctly handles partially invalid DNA input`` () =
40-
toRna "ACGTXXXCTTAA" |> should equal None
41-

generators/CanonicalData.fs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,11 @@ let private updateToLatestVersion options =
3737
Log.Information("Updated repository to latest version.");
3838

3939
let private downloadData options =
40-
if options.SkipUpdateCanonicalData then
40+
if options.CacheCanonicalData then
4141
()
4242
else
4343
cloneRepository options
44-
updateToLatestVersion options
45-
46-
let private readCanonicalData options exercise =
47-
let exerciseCanonicalDataPath = Path.Combine(options.CanonicalDataDirectory, "exercises", exercise, "canonical-data.json")
48-
File.ReadAllText(exerciseCanonicalDataPath)
44+
updateToLatestVersion options
4945

5046
type CanonicalDataConverter() =
5147
inherit JsonConverter()
@@ -87,7 +83,18 @@ type CanonicalDataConverter() =
8783
override __.CanConvert(objectType: Type) = objectType = typeof<CanonicalData>
8884

8985
let private convertCanonicalData canonicalDataContents =
90-
JsonConvert.DeserializeObject<CanonicalData>(canonicalDataContents, CanonicalDataConverter())
86+
JsonConvert.DeserializeObject<CanonicalData>(canonicalDataContents, CanonicalDataConverter())
87+
88+
let private canonicalDataFile options exercise =
89+
Path.Combine(options.CanonicalDataDirectory, "exercises", exercise, "canonical-data.json")
90+
91+
let private readCanonicalData options exercise =
92+
canonicalDataFile options exercise
93+
|> File.ReadAllText
94+
95+
let hasCanonicalData options exercise =
96+
canonicalDataFile options exercise
97+
|> File.Exists
9198

9299
let parseCanonicalData options =
93100
downloadData options

generators/Exercise.fs

Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@ module Generators.Exercise
33
open System
44
open System.IO
55
open System.Reflection
6+
open Newtonsoft.Json
67
open Newtonsoft.Json.Linq
78
open Humanizer
89
open Serilog
910
open Formatting
1011
open Rendering
12+
open CanonicalData
13+
14+
let private exerciseNameFromType (exerciseType: Type) = exerciseType.Name.Kebaberize()
1115

1216
[<AbstractClass>]
13-
type Exercise() =
17+
type GeneratorExercise() =
18+
1419
// Allow changes in canonical data
1520
abstract member MapCanonicalData : CanonicalData -> CanonicalData
1621
abstract member MapCanonicalDataCase : CanonicalDataCase -> CanonicalDataCase
@@ -56,7 +61,7 @@ type Exercise() =
5661
abstract member UseFullMethodName : CanonicalDataCase -> bool
5762
abstract member AdditionalNamespaces : string list
5863

59-
member this.Name = this.GetType().Name.Kebaberize()
64+
member this.Name = this.GetType() |> exerciseNameFromType
6065
member this.TestModuleName = this.GetType().Name.Pascalize() |> sprintf "%sTest"
6166
member this.TestedModuleName = this.GetType().Name.Pascalize()
6267

@@ -66,8 +71,6 @@ type Exercise() =
6671
Directory.CreateDirectory(Path.GetDirectoryName(testClassPath)) |> ignore
6772
File.WriteAllText(testClassPath, contents)
6873

69-
Log.Information("Generated tests for {Exercise} exercise in {TestClassPath}", this.Name, testClassPath);
70-
7174
member this.Regenerate(canonicalData) =
7275
canonicalData
7376
|> this.MapCanonicalData
@@ -233,23 +236,86 @@ type Exercise() =
233236
default __.UseFullMethodName _ = false
234237

235238
default __.AdditionalNamespaces = []
239+
240+
type CustomExercise() =
241+
242+
member this.Name = this.GetType().Name.Kebaberize()
243+
244+
type MissingDataExercise = { Name: string }
245+
246+
type UnimplementedExercise = { Name: string }
247+
248+
type Exercise =
249+
| Generator of GeneratorExercise
250+
| Custom of CustomExercise
251+
| MissingData of MissingDataExercise
252+
| Unimplemented of UnimplementedExercise
253+
254+
let exerciseName exercise =
255+
match exercise with
256+
| Generator generator -> generator.Name
257+
| Custom custom -> custom.Name
258+
| Unimplemented unimplemented -> unimplemented.Name
259+
| MissingData missingData -> missingData.Name
260+
261+
type ConfigExercise = { Slug: string }
262+
263+
type Config = { Exercises: ConfigExercise[] }
264+
265+
let private exerciseNames =
266+
let configFilePath = "../config.json"
267+
let configFileContents = File.ReadAllText configFilePath
268+
let config = JsonConvert.DeserializeObject<Config>(configFileContents)
269+
270+
config.Exercises
271+
|> Seq.map (fun exercise -> exercise.Slug)
272+
|> Seq.sort
273+
|> Seq.toList
236274

237-
let createExercises filteredExercise =
275+
let private isConcreteType (ty: Type) = not ty.IsAbstract
238276

239-
let isConcreteExercise (exerciseType: Type) =
240-
not exerciseType.IsAbstract && typeof<Exercise>.IsAssignableFrom(exerciseType)
277+
let private isGeneratorExercise (ty: Type) = typeof<GeneratorExercise>.IsAssignableFrom(ty)
241278

242-
let isFilteredExercise exercise (exerciseType: Type) =
243-
String.equals exercise exerciseType.Name ||
244-
String.equals exercise (exerciseType.Name.Kebaberize())
279+
let private isCustomExercise (ty: Type) = typeof<CustomExercise>.IsAssignableFrom(ty)
245280

246-
let includeExercise (exerciseType: Type) =
247-
match filteredExercise with
248-
| None -> isConcreteExercise exerciseType
249-
| Some exercise -> isConcreteExercise exerciseType && isFilteredExercise exercise exerciseType
281+
let private concreteAssemblyTypes =
282+
Assembly.GetEntryAssembly().GetTypes()
283+
|> Array.filter isConcreteType
250284

251-
let assemblyTypes = Assembly.GetEntryAssembly().GetTypes()
285+
let private exerciseTypeByName<'T> exerciseType =
286+
(exerciseNameFromType exerciseType, Activator.CreateInstance(exerciseType) :?> 'T)
252287

253-
seq { for exerciseType in assemblyTypes do
254-
if includeExercise exerciseType then
255-
yield Activator.CreateInstance(exerciseType) :?> Exercise }
288+
let private exerciseTypesByName<'T> predicate =
289+
concreteAssemblyTypes
290+
|> Array.filter predicate
291+
|> Array.map exerciseTypeByName<'T>
292+
|> Map.ofArray
293+
294+
let private generatorExercises = exerciseTypesByName<GeneratorExercise> isGeneratorExercise
295+
296+
let private customExercises = exerciseTypesByName<CustomExercise> isCustomExercise
297+
298+
let private tryFindGeneratorExercise exerciseName =
299+
generatorExercises
300+
|> Map.tryFind exerciseName
301+
|> Option.map Generator
302+
303+
let private tryFindCustomExercise exerciseName =
304+
customExercises
305+
|> Map.tryFind exerciseName
306+
|> Option.map Custom
307+
308+
let private tryFindUnimplementedExercise options exerciseName =
309+
match hasCanonicalData options exerciseName with
310+
| true -> Unimplemented { Name = exerciseName } |> Some
311+
| false -> None
312+
313+
let private createExercise options exerciseName =
314+
tryFindGeneratorExercise exerciseName
315+
|> Option.orElse (tryFindCustomExercise exerciseName)
316+
|> Option.orElse (tryFindUnimplementedExercise options exerciseName)
317+
|> Option.orElse (MissingData { Name = exerciseName } |> Some)
318+
319+
let createExercises options =
320+
exerciseNames
321+
|> List.choose (createExercise options)

0 commit comments

Comments
 (0)