Initial commit
This commit is contained in:
12
.config/dotnet-tools.json
Normal file
12
.config/dotnet-tools.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"fantomas": {
|
||||
"version": "6.2.0",
|
||||
"commands": [
|
||||
"fantomas"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
41
.editorconfig
Normal file
41
.editorconfig
Normal file
@@ -0,0 +1,41 @@
|
||||
root=true
|
||||
|
||||
[*]
|
||||
charset=utf-8
|
||||
end_of_line=crlf
|
||||
trim_trailing_whitespace=true
|
||||
insert_final_newline=true
|
||||
indent_style=space
|
||||
indent_size=4
|
||||
|
||||
# ReSharper properties
|
||||
resharper_xml_indent_size=2
|
||||
resharper_xml_max_line_length=100
|
||||
resharper_xml_tab_width=2
|
||||
|
||||
[*.{csproj,fsproj,sqlproj,targets,props,ts,tsx,css,json}]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[*.{fs,fsi}]
|
||||
fsharp_bar_before_discriminated_union_declaration=true
|
||||
fsharp_space_before_uppercase_invocation=true
|
||||
fsharp_space_before_class_constructor=true
|
||||
fsharp_space_before_member=true
|
||||
fsharp_space_before_colon=true
|
||||
fsharp_space_before_semicolon=true
|
||||
fsharp_multiline_bracket_style=aligned
|
||||
fsharp_newline_between_type_definition_and_members=true
|
||||
fsharp_align_function_signature_to_indentation=true
|
||||
fsharp_alternative_long_member_definitions=true
|
||||
fsharp_multi_line_lambda_closing_newline=true
|
||||
fsharp_experimental_keep_indent_in_branch=true
|
||||
fsharp_max_value_binding_width=80
|
||||
fsharp_max_record_width=0
|
||||
max_line_length=120
|
||||
end_of_line=lf
|
||||
|
||||
[*.{appxmanifest,build,dtd,nuspec,xaml,xamlx,xoml,xsd}]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
tab_width=2
|
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
* eol=auto
|
||||
*.sh text eol=lf
|
||||
*.nix text eol=lf
|
||||
hooks/pre-push text eol=lf
|
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
.idea/
|
||||
*.user
|
||||
*.DotSettings
|
||||
.DS_Store
|
||||
result
|
||||
.profile*
|
||||
|
||||
node_modules/
|
||||
package.json
|
||||
package-lock.json
|
10
.woodpecker/.all-checks-complete.yml
Normal file
10
.woodpecker/.all-checks-complete.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
steps:
|
||||
echo:
|
||||
image: alpine
|
||||
commands:
|
||||
- echo "All required checks complete"
|
||||
|
||||
depends_on:
|
||||
- build
|
||||
|
||||
skip_clone: true
|
22
.woodpecker/.build.yml
Normal file
22
.woodpecker/.build.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
steps:
|
||||
build:
|
||||
image: nixos/nix
|
||||
commands:
|
||||
- echo 'experimental-features = flakes nix-command' >> /etc/nix/nix.conf
|
||||
# Lint
|
||||
- "nix flake check"
|
||||
# Test
|
||||
- nix build
|
||||
- nix run . -- --help
|
||||
- nix run . -- auth --help
|
||||
- nix run . -- lookup-gym --help
|
||||
- nix run . -- fullness --help
|
||||
- nix run . -- activity --help
|
||||
- nix develop --command markdown-link-check README.md
|
||||
- nix develop --command dotnet test
|
||||
- nix develop --command dotnet test --configuration Release
|
||||
|
||||
when:
|
||||
- event: "push"
|
||||
evaluate: 'CI_COMMIT_BRANCH == CI_REPO_DEFAULT_BRANCH'
|
||||
- event: "pull_request"
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Patrick Stevens
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
21
PureGym.App/ArgsCrate.fs
Normal file
21
PureGym.App/ArgsCrate.fs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace PureGym.App
|
||||
|
||||
open System.Threading.Tasks
|
||||
open Argu
|
||||
|
||||
type ArgsEvaluator<'ret> =
|
||||
abstract Eval<'a, 'b when 'b :> IArgParserTemplate> :
|
||||
(ParseResults<'b> -> Result<'a, ArguParseException>) -> ('a -> Task<int>) -> 'ret
|
||||
|
||||
type ArgsCrate =
|
||||
abstract Apply<'ret> : ArgsEvaluator<'ret> -> 'ret
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module ArgsCrate =
|
||||
let make<'a, 'b when 'b :> IArgParserTemplate>
|
||||
(ofResult : ParseResults<'b> -> Result<'a, ArguParseException>)
|
||||
(run : 'a -> Task<int>)
|
||||
=
|
||||
{ new ArgsCrate with
|
||||
member _.Apply e = e.Eval ofResult run
|
||||
}
|
41
PureGym.App/AuthArg.fs
Normal file
41
PureGym.App/AuthArg.fs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace PureGym.App
|
||||
|
||||
open Argu
|
||||
open PureGym
|
||||
|
||||
type AuthArg =
|
||||
| [<Unique ; CustomAppSettings "PUREGYM_BEARER_TOKEN">] Bearer_Token of string
|
||||
| [<Unique>] User_Email of string
|
||||
| [<Unique>] Pin of string
|
||||
| [<GatherUnrecognized>] Others of string
|
||||
|
||||
interface IArgParserTemplate with
|
||||
member s.Usage =
|
||||
match s with
|
||||
| AuthArg.Bearer_Token _ -> "A bearer token for the PureGym API"
|
||||
| AuthArg.User_Email _ -> "PureGym user's email address"
|
||||
| AuthArg.Pin _ -> "Eight-digit PureGym user's PIN"
|
||||
| AuthArg.Others _ -> "<specific args for command>"
|
||||
|
||||
static member Parse (args : ParseResults<AuthArg>) : Result<Auth * string[], ArguParseException> =
|
||||
let unmatchedArgs = args.GetResults AuthArg.Others |> List.toArray
|
||||
|
||||
match
|
||||
args.TryGetResult AuthArg.User_Email, args.TryGetResult AuthArg.Pin, args.TryGetResult AuthArg.Bearer_Token
|
||||
with
|
||||
| Some email, Some pin, _ ->
|
||||
let auth =
|
||||
Auth.User
|
||||
{
|
||||
Username = email
|
||||
Pin = pin
|
||||
}
|
||||
|
||||
Ok (auth, unmatchedArgs)
|
||||
| Some _email, None, _ -> failwith "Supplied --user-email but no --pin; either both or neither are required."
|
||||
| None, Some _pin, _ -> failwith "Supplied --pin but no --user-email; either both or neither are required."
|
||||
| None, None, None ->
|
||||
failwith "No creds given: expected at least one of `--bearer-token` or `--user-email --pin`"
|
||||
| None, None, Some token ->
|
||||
let auth = Auth.Token (AuthToken.ofBearerToken token)
|
||||
Ok (auth, unmatchedArgs)
|
40
PureGym.App/Authenticate.fs
Normal file
40
PureGym.App/Authenticate.fs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace PureGym.App
|
||||
|
||||
open Argu
|
||||
open System
|
||||
open PureGym
|
||||
|
||||
type GetTokenArg =
|
||||
| [<ExactlyOnce>] User_Email of string
|
||||
| [<ExactlyOnce>] Pin of string
|
||||
|
||||
interface IArgParserTemplate with
|
||||
member s.Usage =
|
||||
match s with
|
||||
| GetTokenArg.Pin _ -> "Eight-digit PureGym user's PIN"
|
||||
| GetTokenArg.User_Email _ -> "PureGym user's email address"
|
||||
|
||||
static member Parse (args : ParseResults<GetTokenArg>) : Result<UsernamePin, ArguParseException> =
|
||||
try
|
||||
{
|
||||
Username = args.GetResult GetTokenArg.User_Email
|
||||
Pin = args.GetResult GetTokenArg.Pin
|
||||
}
|
||||
|> Ok
|
||||
with :? ArguParseException as e ->
|
||||
Error e
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Authenticate =
|
||||
|
||||
let run (creds : UsernamePin) =
|
||||
task {
|
||||
let! cred = AuthToken.get creds
|
||||
Console.WriteLine cred.AccessToken
|
||||
|
||||
match cred.ExpiryTime with
|
||||
| None -> ()
|
||||
| Some expiry -> Console.Error.WriteLine $"Expires at {expiry}"
|
||||
|
||||
return 0
|
||||
}
|
11
PureGym.App/Exception.fs
Normal file
11
PureGym.App/Exception.fs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace PureGym.App
|
||||
|
||||
open System.Runtime.ExceptionServices
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Exception =
|
||||
|
||||
let reraiseWithOriginalStackTrace<'a> (e : exn) : 'a =
|
||||
let edi = ExceptionDispatchInfo.Capture e
|
||||
edi.Throw ()
|
||||
failwith "unreachable"
|
61
PureGym.App/Fullness.fs
Normal file
61
PureGym.App/Fullness.fs
Normal file
@@ -0,0 +1,61 @@
|
||||
namespace PureGym.App
|
||||
|
||||
open Argu
|
||||
open PureGym
|
||||
|
||||
type FullnessArgsFragment =
|
||||
| [<Unique>] Gym_Id of int
|
||||
| [<Unique>] Gym_Name of string
|
||||
| [<Unique>] Terse of bool
|
||||
|
||||
interface IArgParserTemplate with
|
||||
member s.Usage =
|
||||
match s with
|
||||
| FullnessArgsFragment.Gym_Id _ -> "ID of the gym to look up, according to PureGym's internal mapping"
|
||||
| FullnessArgsFragment.Gym_Name _ -> "Name of a gym (best-guess fuzzy matching)"
|
||||
| FullnessArgsFragment.Terse _ -> "If true, output only the single number 'how many people are in the gym'."
|
||||
|
||||
type FullnessArgs =
|
||||
{
|
||||
Creds : Auth
|
||||
Gym : GymSelector
|
||||
Terse : bool
|
||||
}
|
||||
|
||||
static member Parse
|
||||
(auth : Auth)
|
||||
(args : FullnessArgsFragment ParseResults)
|
||||
: Result<FullnessArgs, ArguParseException>
|
||||
=
|
||||
let gym =
|
||||
match args.TryGetResult FullnessArgsFragment.Gym_Id, args.TryGetResult FullnessArgsFragment.Gym_Name with
|
||||
| Some _, Some _ -> failwith "--gym-id and --gym-name are mutually exclusive"
|
||||
| None, None ->
|
||||
System.Console.Error.WriteLine "No gym ID given and no gym named; assuming the user's home gym"
|
||||
GymSelector.Home
|
||||
| Some id, None -> GymSelector.Id id
|
||||
| None, Some name -> GymSelector.Name name
|
||||
|
||||
{
|
||||
Creds = auth
|
||||
Gym = gym
|
||||
Terse = args.TryGetResult FullnessArgsFragment.Terse |> Option.defaultValue false
|
||||
}
|
||||
|> Ok
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Fullness =
|
||||
|
||||
let run (args : FullnessArgs) =
|
||||
task {
|
||||
let! client = Api.make args.Creds
|
||||
let! id = GymSelector.canonicalId client args.Gym
|
||||
let! attendance = client.GetGymAttendance id
|
||||
|
||||
if args.Terse then
|
||||
System.Console.WriteLine attendance.TotalPeopleInGym
|
||||
else
|
||||
System.Console.WriteLine (string<GymAttendance> attendance)
|
||||
|
||||
return 0
|
||||
}
|
51
PureGym.App/LookupGym.fs
Normal file
51
PureGym.App/LookupGym.fs
Normal file
@@ -0,0 +1,51 @@
|
||||
namespace PureGym.App
|
||||
|
||||
open Argu
|
||||
open PureGym
|
||||
|
||||
type LookupGymArgsFragment =
|
||||
| [<Unique>] Gym_Id of int
|
||||
| [<Unique>] Gym_Name of string
|
||||
|
||||
interface IArgParserTemplate with
|
||||
member s.Usage =
|
||||
match s with
|
||||
| LookupGymArgsFragment.Gym_Id _ -> "ID of the gym to look up, according to PureGym's internal mapping"
|
||||
| LookupGymArgsFragment.Gym_Name _ -> "Name of a gym (best-guess fuzzy matching)"
|
||||
|
||||
type LookupGymArgs =
|
||||
{
|
||||
Creds : Auth
|
||||
Gym : GymSelector
|
||||
}
|
||||
|
||||
static member Parse
|
||||
(auth : Auth)
|
||||
(args : LookupGymArgsFragment ParseResults)
|
||||
: Result<LookupGymArgs, ArguParseException>
|
||||
=
|
||||
let gym =
|
||||
match args.TryGetResult LookupGymArgsFragment.Gym_Id, args.TryGetResult LookupGymArgsFragment.Gym_Name with
|
||||
| Some _, Some _ -> failwith "--gym-id and --gym-name are mutually exclusive"
|
||||
| None, None ->
|
||||
System.Console.Error.WriteLine "No gym ID given and no gym named; assuming the user's home gym"
|
||||
GymSelector.Home
|
||||
| Some id, None -> GymSelector.Id id
|
||||
| None, Some name -> GymSelector.Name name
|
||||
|
||||
{
|
||||
Creds = auth
|
||||
Gym = gym
|
||||
}
|
||||
|> Ok
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module LookupGym =
|
||||
|
||||
let run (args : LookupGymArgs) =
|
||||
task {
|
||||
let! client = Api.make args.Creds
|
||||
let! s = client.GetGym 19
|
||||
System.Console.WriteLine (string<Gym> s)
|
||||
return 0
|
||||
}
|
38
PureGym.App/MemberActivity.fs
Normal file
38
PureGym.App/MemberActivity.fs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace PureGym.App
|
||||
|
||||
open Argu
|
||||
open PureGym
|
||||
|
||||
type MemberActivityArgsFragment =
|
||||
| MemberActivityArgsFragment of bool
|
||||
|
||||
interface IArgParserTemplate with
|
||||
member s.Usage =
|
||||
match s with
|
||||
| MemberActivityArgsFragment _ -> "dummy argument: this subcommand has no args"
|
||||
|
||||
type MemberActivityArgs =
|
||||
{
|
||||
Creds : Auth
|
||||
}
|
||||
|
||||
static member Parse
|
||||
(auth : Auth)
|
||||
(_ : MemberActivityArgsFragment ParseResults)
|
||||
: Result<MemberActivityArgs, ArguParseException>
|
||||
=
|
||||
{
|
||||
Creds = auth
|
||||
}
|
||||
|> Ok
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module MemberActivity =
|
||||
|
||||
let run (args : MemberActivityArgs) =
|
||||
task {
|
||||
let! client = Api.make args.Creds
|
||||
let! activity = client.GetMemberActivity ()
|
||||
System.Console.WriteLine (string<MemberActivity> activity)
|
||||
return 0
|
||||
}
|
118
PureGym.App/Program.fs
Normal file
118
PureGym.App/Program.fs
Normal file
@@ -0,0 +1,118 @@
|
||||
namespace PureGym.App
|
||||
|
||||
open System
|
||||
open Argu
|
||||
|
||||
type Subcommand =
|
||||
| RequiresAuth of (PureGym.Auth -> ArgsCrate)
|
||||
| NoAuth of ArgsCrate
|
||||
|
||||
module Program =
|
||||
let subcommands =
|
||||
[|
|
||||
"auth", ("Get an authentication token", NoAuth (ArgsCrate.make GetTokenArg.Parse Authenticate.run))
|
||||
|
||||
"lookup-gym",
|
||||
("Get information about the physical instantiation of a gym",
|
||||
RequiresAuth (fun auth -> ArgsCrate.make (LookupGymArgs.Parse auth) LookupGym.run))
|
||||
|
||||
"fullness",
|
||||
("Determine how full a gym is",
|
||||
RequiresAuth (fun auth -> ArgsCrate.make (FullnessArgs.Parse auth) Fullness.run))
|
||||
|
||||
"activity",
|
||||
("Get information about your gym usage",
|
||||
RequiresAuth (fun auth -> ArgsCrate.make (MemberActivityArgs.Parse auth) MemberActivity.run))
|
||||
|]
|
||||
|> Map.ofArray
|
||||
|
||||
[<EntryPoint>]
|
||||
let main argv =
|
||||
// It looks like Argu doesn't really support the combination of subcommands and read-from-env-vars, so we just
|
||||
// roll our own.
|
||||
|
||||
match Array.tryHead argv with
|
||||
| None
|
||||
| Some "--help" ->
|
||||
subcommands.Keys
|
||||
|> String.concat ","
|
||||
|> eprintfn "Subcommands (try each with `--help`): %s"
|
||||
|
||||
0
|
||||
|
||||
| Some commandName ->
|
||||
|
||||
match Map.tryFind commandName subcommands with
|
||||
| None ->
|
||||
subcommands.Keys
|
||||
|> String.concat ","
|
||||
|> eprintfn "Unrecognised command '%s'. Subcommands (try each with `--help`): %s" commandName
|
||||
|
||||
127
|
||||
|
||||
| Some (_help, command) ->
|
||||
|
||||
let argv = Array.tail argv
|
||||
let config = ConfigurationReader.FromEnvironmentVariables ()
|
||||
|
||||
let argv, command =
|
||||
match command with
|
||||
| RequiresAuth command ->
|
||||
let authParser = ArgumentParser.Create<AuthArg> ()
|
||||
|
||||
let extractedAuthParse, helpRequested =
|
||||
try
|
||||
authParser.Parse (argv, config, raiseOnUsage = true, ignoreUnrecognized = true), false
|
||||
with :? ArguParseException as e ->
|
||||
if e.Message.StartsWith ("USAGE:", StringComparison.OrdinalIgnoreCase) then
|
||||
authParser.Parse (argv, config, raiseOnUsage = false, ignoreUnrecognized = true), true
|
||||
else
|
||||
reraise ()
|
||||
|
||||
let authArgs, argv =
|
||||
if helpRequested then
|
||||
let subcommandArgs =
|
||||
("--help" :: extractedAuthParse.GetResults AuthArg.Others) |> List.toArray
|
||||
|
||||
Unchecked.defaultof<_>, subcommandArgs
|
||||
else
|
||||
match AuthArg.Parse extractedAuthParse with
|
||||
| Ok a -> a
|
||||
| Error e -> Exception.reraiseWithOriginalStackTrace e
|
||||
|
||||
argv, command authArgs
|
||||
| NoAuth command -> argv, command
|
||||
|
||||
{ new ArgsEvaluator<_> with
|
||||
member _.Eval<'a, 'b when 'b :> IArgParserTemplate> (ofResult : ParseResults<'b> -> Result<'a, _>) run =
|
||||
let parser = ArgumentParser.Create<'b> ()
|
||||
|
||||
let parsed =
|
||||
try
|
||||
parser.Parse (argv, config, raiseOnUsage = true) |> Ok
|
||||
with :? ArguParseException as e ->
|
||||
e.Message.Replace ("PureGym.App ", $"PureGym.App %s{commandName} ")
|
||||
|> Console.Error.WriteLine
|
||||
|
||||
if e.Message.StartsWith ("USAGE:", StringComparison.OrdinalIgnoreCase) then
|
||||
Error true
|
||||
else
|
||||
Error false
|
||||
|
||||
match parsed with
|
||||
| Error false -> Error 127
|
||||
| Error true -> Error 0
|
||||
| Ok parsed ->
|
||||
|
||||
match ofResult parsed with
|
||||
| Error e ->
|
||||
e.Message.Replace ("PureGym.App ", $"PureGym.App %s{commandName} ")
|
||||
|> Console.Error.WriteLine
|
||||
|
||||
Error 127
|
||||
| Ok args ->
|
||||
|
||||
run args |> Ok
|
||||
}
|
||||
|> command.Apply
|
||||
|> Result.cata (fun t -> t.Result) id
|
28
PureGym.App/PureGym.App.fsproj
Normal file
28
PureGym.App/PureGym.App.fsproj
Normal file
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Result.fs" />
|
||||
<Compile Include="Exception.fs" />
|
||||
<Compile Include="ArgsCrate.fs" />
|
||||
<Compile Include="AuthArg.fs" />
|
||||
<Compile Include="Authenticate.fs" />
|
||||
<Compile Include="Fullness.fs" />
|
||||
<Compile Include="LookupGym.fs" />
|
||||
<Compile Include="MemberActivity.fs" />
|
||||
<Compile Include="Program.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PureGym\PureGym.fsproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Argu" Version="6.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
9
PureGym.App/Result.fs
Normal file
9
PureGym.App/Result.fs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace PureGym.App
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Result =
|
||||
|
||||
let cata<'ok, 'err, 'result> onOk onError (r : Result<'ok, 'err>) : 'result =
|
||||
match r with
|
||||
| Ok ok -> onOk ok
|
||||
| Error e -> onError e
|
27
PureGym.Test/PureGym.Test.fsproj
Normal file
27
PureGym.Test/PureGym.Test.fsproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="TestSurface.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ApiSurface" Version="4.0.12" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
|
||||
<PackageReference Include="NUnit" Version="3.13.3"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
|
||||
<PackageReference Include="coverlet.collector" Version="3.2.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PureGym\PureGym.fsproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
21
PureGym.Test/TestSurface.fs
Normal file
21
PureGym.Test/TestSurface.fs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace PureGym.Test
|
||||
|
||||
open PureGym
|
||||
open NUnit.Framework
|
||||
open ApiSurface
|
||||
|
||||
[<TestFixture>]
|
||||
module TestSurface =
|
||||
|
||||
let assembly = typeof<Gym>.Assembly
|
||||
|
||||
[<Test>]
|
||||
let ``Ensure API surface has not been modified`` () = ApiSurface.assertIdentical assembly
|
||||
|
||||
[<Test ; Explicit>]
|
||||
let ``Update API surface`` () =
|
||||
ApiSurface.writeAssemblyBaseline assembly
|
||||
|
||||
[<Test ; Explicit "This isn't done yet">]
|
||||
let ``Ensure public API is fully documented`` () =
|
||||
DocCoverage.assertFullyDocumented assembly
|
28
PureGym.sln
Normal file
28
PureGym.sln
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "PureGym", "PureGym\PureGym.fsproj", "{BDF66CFC-5F2C-4AC4-A9FE-FB3B135E47FF}"
|
||||
EndProject
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "PureGym.App", "PureGym.App\PureGym.App.fsproj", "{B4848668-4CB2-4919-987D-8C95F865C81E}"
|
||||
EndProject
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "PureGym.Test", "PureGym.Test\PureGym.Test.fsproj", "{F09DF609-5F53-4BB3-BD64-DDB136CD4D2E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BDF66CFC-5F2C-4AC4-A9FE-FB3B135E47FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BDF66CFC-5F2C-4AC4-A9FE-FB3B135E47FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BDF66CFC-5F2C-4AC4-A9FE-FB3B135E47FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BDF66CFC-5F2C-4AC4-A9FE-FB3B135E47FF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B4848668-4CB2-4919-987D-8C95F865C81E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B4848668-4CB2-4919-987D-8C95F865C81E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B4848668-4CB2-4919-987D-8C95F865C81E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B4848668-4CB2-4919-987D-8C95F865C81E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F09DF609-5F53-4BB3-BD64-DDB136CD4D2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F09DF609-5F53-4BB3-BD64-DDB136CD4D2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F09DF609-5F53-4BB3-BD64-DDB136CD4D2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F09DF609-5F53-4BB3-BD64-DDB136CD4D2E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
272
PureGym/Api.fs
Normal file
272
PureGym/Api.fs
Normal file
@@ -0,0 +1,272 @@
|
||||
namespace PureGym
|
||||
|
||||
open System
|
||||
open System.Net.Http
|
||||
open System.Text.Json.Serialization
|
||||
open System.Threading.Tasks
|
||||
open RestEase
|
||||
|
||||
/// Describes the opening hours of a given gym.
|
||||
type GymOpeningHours =
|
||||
{
|
||||
/// If this is true, there should be no OpeningHours (but nothing enforces that).
|
||||
IsAlwaysOpen : bool
|
||||
/// This is a pretty unstructured list, which is in general not really parseable: it's human-readable only.
|
||||
OpeningHours : string list
|
||||
}
|
||||
|
||||
/// Human-readable representation
|
||||
override this.ToString () =
|
||||
if this.IsAlwaysOpen then
|
||||
"always open"
|
||||
else
|
||||
this.OpeningHours |> String.concat ", "
|
||||
|
||||
/// How a human can authenticate with a gym when they physically try to enter it
|
||||
type GymAccessOptions =
|
||||
{
|
||||
/// This gym has PIN entry pads
|
||||
PinAccess : bool
|
||||
/// This gym has a QR code scanner. QR codes can be generated with the PureGym app.
|
||||
QrCodeAccess : bool
|
||||
}
|
||||
|
||||
/// Human-readable representation
|
||||
override this.ToString () =
|
||||
$"Pin access: %c{Char.emoji this.PinAccess}; QR code access: %c{Char.emoji this.QrCodeAccess}"
|
||||
|
||||
/// Where a gym is on the Earth
|
||||
type GymLocation =
|
||||
{
|
||||
/// Measured in degrees
|
||||
Longitude : float
|
||||
/// Measured in degrees
|
||||
Latitude : float
|
||||
}
|
||||
|
||||
/// The postal address of a gym
|
||||
type GymAddress =
|
||||
{
|
||||
/// E.g. "Canterbury Court"
|
||||
[<JsonRequired>]
|
||||
AddressLine1 : string
|
||||
/// E.g. "Units 4, 4A, 5 And 5A"
|
||||
AddressLine2 : string
|
||||
/// E.g. "Kennington Park"
|
||||
AddressLine3 : string
|
||||
/// E.g. "LONDON"
|
||||
[<JsonRequired>]
|
||||
Town : string
|
||||
County : string
|
||||
/// E.g. "SW9 6DE"
|
||||
[<JsonRequired>]
|
||||
Postcode : string
|
||||
}
|
||||
|
||||
/// Human-readable statement of the address
|
||||
override this.ToString () =
|
||||
[
|
||||
yield Some this.AddressLine1
|
||||
yield this.AddressLine2 |> Option.ofObj
|
||||
yield this.AddressLine3 |> Option.ofObj
|
||||
match this.County with
|
||||
| null -> yield Some $"%s{this.Town} %s{this.Postcode}"
|
||||
| county ->
|
||||
yield Some this.Town
|
||||
yield Some $"%s{county} %s{this.Postcode}"
|
||||
]
|
||||
|> Seq.choose id
|
||||
|> String.concat "\n"
|
||||
|
||||
/// Metadata about a physical gym
|
||||
type Gym =
|
||||
{
|
||||
// The following fields are returned but are always null
|
||||
// ReasonsToJoin : string
|
||||
// VirtualTourUrl : Uri
|
||||
// PersonalTrainersUrl : Uri
|
||||
// WebViewUrl : Uri
|
||||
// FloorPlanUrl : Uri
|
||||
// StaffMembers : string
|
||||
|
||||
/// The name of this gym, e.g. "London Oval"
|
||||
[<JsonRequired>]
|
||||
Name : string
|
||||
/// This gym's ID in the PureGym system, e.g. 19
|
||||
[<JsonRequired>]
|
||||
Id : int
|
||||
/// I don't know what this status is. Please tell me if you know!
|
||||
[<JsonRequired>]
|
||||
Status : int
|
||||
/// Postal address of this gym
|
||||
[<JsonRequired>]
|
||||
Address : GymAddress
|
||||
/// Phone number of this gym, e.g. "+44 1234 567890"
|
||||
[<JsonRequired>]
|
||||
PhoneNumber : string
|
||||
/// Contact email address for this gym's staff
|
||||
[<JsonRequired>]
|
||||
EmailAddress : string
|
||||
/// When this gym is open
|
||||
[<JsonRequired>]
|
||||
GymOpeningHours : GymOpeningHours
|
||||
/// How a human can physically authenticate when they physically enter this gym
|
||||
[<JsonRequired>]
|
||||
AccessOptions : GymAccessOptions
|
||||
/// Where this gym is physically located
|
||||
[<JsonRequired>]
|
||||
Location : GymLocation
|
||||
/// The IANA time zone this gym observes, e.g. "Europe/London"
|
||||
[<JsonRequired>]
|
||||
TimeZone : string
|
||||
/// This is a date-time in the format yyyy-MM-ddTHH:mm:ss+01 Europe/London
|
||||
ReopenDate : string
|
||||
}
|
||||
|
||||
/// Human-readable representation of the most important information about this gym
|
||||
override this.ToString () =
|
||||
$"""%s{this.Name} (%i{this.Id})
|
||||
{this.Address}
|
||||
%s{this.EmailAddress} %s{this.PhoneNumber}
|
||||
Opening hours: %s{string<GymOpeningHours> this.GymOpeningHours}
|
||||
%s{string<GymAccessOptions> this.AccessOptions}
|
||||
"""
|
||||
|
||||
/// A human member of PureGym
|
||||
type Member =
|
||||
{
|
||||
/// This member's ID. This is a fairly large number.
|
||||
Id : int
|
||||
/// No idea what this is - please tell me if you know!
|
||||
CompoundMemberId : string
|
||||
/// First name, e.g. "Patrick"
|
||||
FirstName : string
|
||||
/// Last name, e.g. "Stevens"
|
||||
LastName : string
|
||||
/// ID of the gym designated as this user's home gym. This is also the "Id" field of the appropriate Gym object.
|
||||
HomeGymId : int
|
||||
/// The name of the gym designated as this user's home gym. This is also the "Name" field of the appropriate
|
||||
/// Gym object.
|
||||
HomeGymName : string
|
||||
/// This user's email address
|
||||
EmailAddress : string
|
||||
/// This user's gym access pin, probably 8 digits
|
||||
GymAccessPin : string
|
||||
/// This user's recorded date of birth
|
||||
DateOfBirth : DateOnly
|
||||
/// This user's phone number, human-readable
|
||||
MobileNumber : string
|
||||
/// This user's registered home postcode
|
||||
Postcode : string
|
||||
/// E.g. "Corporate"
|
||||
MembershipName : string
|
||||
MembershipLevel : int
|
||||
SuspendedReason : int
|
||||
MemberStatus : int
|
||||
}
|
||||
|
||||
/// Statistics for how many people are currently at a gym
|
||||
type GymAttendance =
|
||||
{
|
||||
[<JsonRequired>]
|
||||
Description : string
|
||||
/// How many people are in the gym as of this statistics snapshot
|
||||
[<JsonRequired>]
|
||||
TotalPeopleInGym : int
|
||||
/// How many people are in classes at the gym as of this statistics snapshot
|
||||
[<JsonRequired>]
|
||||
TotalPeopleInClasses : int
|
||||
TotalPeopleSuffix : string
|
||||
[<JsonRequired>]
|
||||
IsApproximate : bool
|
||||
/// When the query was received (I think)
|
||||
AttendanceTime : DateTime
|
||||
/// When the "total people in gym" snapshot was taken that is reported here
|
||||
LastRefreshed : DateTime
|
||||
/// When the "number of people in classes" snapshot was taken that is reported here
|
||||
LastRefreshedPeopleInClasses : DateTime
|
||||
/// Maximum capacity of the gym, or 0 if no listed capacity
|
||||
MaximumCapacity : int
|
||||
}
|
||||
|
||||
/// Human-readable representation
|
||||
override this.ToString () =
|
||||
if not (Object.ReferenceEquals (this.TotalPeopleSuffix, null)) then
|
||||
failwith $"Unexpectedly got Total People Suffix: %s{this.TotalPeopleSuffix}"
|
||||
|
||||
let capacity =
|
||||
if this.MaximumCapacity = 0 then
|
||||
""
|
||||
else
|
||||
$" out of %i{this.MaximumCapacity} maximum"
|
||||
|
||||
let classes =
|
||||
if this.TotalPeopleInClasses = 0 then
|
||||
""
|
||||
else
|
||||
$"\n%i{this.TotalPeopleInClasses} in classes"
|
||||
|
||||
$"""%i{this.TotalPeopleInGym} in gym%s{capacity} (is exact: %c{Char.emoji (not this.IsApproximate)})%s{classes}
|
||||
Query made at %s{this.AttendanceTime.ToString ("s")}%s{this.AttendanceTime.ToString ("zzz")}
|
||||
Snapshot correct as of %s{this.LastRefreshed.ToString ("s")}%s{this.LastRefreshed.ToString ("zzz")}
|
||||
Classes info correct as of %s{this.LastRefreshedPeopleInClasses.ToString ("s")}%s{this.LastRefreshedPeopleInClasses.ToString ("zzz")}"""
|
||||
|
||||
/// The visit statistics for a particular human to a particular gym.
|
||||
/// The semantics of this class are basically unknown.
|
||||
type MemberActivity =
|
||||
{
|
||||
/// ??? semantics unknown; this was 2852 for me
|
||||
[<JsonPropertyName "totalDuration">]
|
||||
TotalDurationMinutes : int
|
||||
[<JsonPropertyName "averageDuration">]
|
||||
AverageDurationMinutes : int
|
||||
TotalVisits : int
|
||||
TotalClasses : int
|
||||
IsEstimated : bool
|
||||
LastRefreshed : DateTime
|
||||
}
|
||||
|
||||
/// The PureGym REST API. You probably want to instantiate one of these with `Api.make`.
|
||||
[<Header("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")>]
|
||||
type IPureGymApi =
|
||||
/// Get the complete list of all gyms known to PureGym.
|
||||
[<Get "v1/gyms/">]
|
||||
abstract GetGyms : unit -> Task<Gym list>
|
||||
|
||||
/// Get information about the PureGym human whose credentials this client is authenticated with.
|
||||
[<Get "v1/member">]
|
||||
abstract GetMember : unit -> Task<Member>
|
||||
|
||||
/// Get information about how full the given gym currently is. The gym ID can be found from `GetGyms`.
|
||||
[<Get "v1/gyms/{gym_id}/attendance">]
|
||||
abstract GetGymAttendance : [<Path "gym_id">] gymId : int -> Task<GymAttendance>
|
||||
|
||||
/// Get information about a specific gym.
|
||||
[<Get "v1/gyms/{gym_id}">]
|
||||
abstract GetGym : [<Path "gym_id">] gymId : int -> Task<Gym>
|
||||
|
||||
/// Get information about the activities logged against the currently authenticated PureGym human.
|
||||
[<Get "v1/member/activity">]
|
||||
abstract GetMemberActivity : unit -> Task<MemberActivity>
|
||||
|
||||
/// Methods for interacting with the PureGym REST API.
|
||||
[<RequireQualifiedAccess>]
|
||||
module Api =
|
||||
/// Create a REST client, authenticated as the specified user.
|
||||
let make (auth : Auth) : IPureGymApi Task =
|
||||
task {
|
||||
let! token =
|
||||
match auth with
|
||||
| Auth.Token t -> Task.FromResult<_> t
|
||||
| Auth.User cred -> AuthToken.get cred
|
||||
|
||||
let client = new HttpClient ()
|
||||
client.BaseAddress <- Uri "https://capi.puregym.com/api"
|
||||
|
||||
client.DefaultRequestHeaders.Authorization <-
|
||||
Headers.AuthenticationHeaderValue ("Bearer", token.AccessToken)
|
||||
|
||||
client.DefaultRequestHeaders.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")
|
||||
|
||||
return RestClient.For<IPureGymApi> client
|
||||
}
|
97
PureGym/Auth.fs
Normal file
97
PureGym/Auth.fs
Normal file
@@ -0,0 +1,97 @@
|
||||
namespace PureGym
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
open System.Net.Http
|
||||
open System.Text.Json
|
||||
open System.Text.Json.Serialization
|
||||
open System.Threading.Tasks
|
||||
|
||||
// System.Text.Json does not support internal F# records as of .NET 8, presumably because it can't find the constructor.
|
||||
// So we end up with this gruesome soup of C#.
|
||||
// TODO(net8): make this internal
|
||||
type AuthResponseRaw [<JsonConstructor>] (access_token : string, expires_in : int, token_type : string, scope : string)
|
||||
=
|
||||
member _.access_token = access_token
|
||||
member _.expires_in = expires_in
|
||||
member _.token_type = token_type
|
||||
member _.scope = scope
|
||||
|
||||
/// A token which can be used to authenticate with the PureGym API.
|
||||
type AuthToken =
|
||||
{
|
||||
/// A string which can be passed as the "Bearer" in an Authorization header
|
||||
AccessToken : string
|
||||
/// Time that this token expires, if known
|
||||
ExpiryTime : DateTime option
|
||||
}
|
||||
|
||||
static member internal Parse (response : AuthResponseRaw) : AuthToken =
|
||||
if response.scope <> "pgcapi" then
|
||||
failwithf $"unexpected scope: %s{response.scope}"
|
||||
|
||||
if response.token_type <> "Bearer" then
|
||||
failwithf $"unexpected type: %s{response.token_type}"
|
||||
|
||||
{
|
||||
AccessToken = response.access_token
|
||||
ExpiryTime = Some (DateTime.Now + TimeSpan.FromSeconds response.expires_in)
|
||||
}
|
||||
|
||||
/// Authentication credentials as known to a human user
|
||||
type UsernamePin =
|
||||
{
|
||||
/// The user's email address
|
||||
Username : string
|
||||
/// Eight-digit PIN
|
||||
Pin : string
|
||||
}
|
||||
|
||||
/// Any way to authenticate with the PureGym API.
|
||||
type Auth =
|
||||
/// Authenticate with credentials which are known to the user
|
||||
| User of UsernamePin
|
||||
/// An AuthToken (that is, a bearer token)
|
||||
| Token of AuthToken
|
||||
|
||||
/// Methods for constructing PureGym authentication tokens.
|
||||
[<RequireQualifiedAccess>]
|
||||
module AuthToken =
|
||||
/// Construct an AuthToken given that you already have a bearer token.
|
||||
let ofBearerToken (token : string) : AuthToken =
|
||||
{
|
||||
AccessToken = token
|
||||
ExpiryTime = None
|
||||
}
|
||||
|
||||
let private options = JsonSerializerOptions (IncludeFields = true)
|
||||
|
||||
/// Get an AuthToken for the given user email address with the given eight-digit PureGym PIN.
|
||||
let get (creds : UsernamePin) : Task<AuthToken> =
|
||||
task {
|
||||
use client = new HttpClient ()
|
||||
client.BaseAddress <- Uri "https://auth.puregym.com"
|
||||
client.DefaultRequestHeaders.Add ("User-Agent", "PureGym/1523 CFNetwork/1312 Darwin/21.0.0")
|
||||
|
||||
let request =
|
||||
[
|
||||
"grant_type", "password"
|
||||
"username", creds.Username
|
||||
"password", creds.Pin
|
||||
"scope", "pgcapi"
|
||||
"client_id", "ro.client"
|
||||
]
|
||||
|> List.map KeyValuePair
|
||||
|
||||
use content = new FormUrlEncodedContent (request)
|
||||
let! response = client.PostAsync (Uri "https://auth.puregym.com/connect/token", content)
|
||||
|
||||
if response.IsSuccessStatusCode then
|
||||
let! content = response.Content.ReadAsStreamAsync ()
|
||||
let! response = JsonSerializer.DeserializeAsync<AuthResponseRaw> (content, options)
|
||||
// let! response = JsonSerializer.DeserializeAsync<AuthResponseRaw> (content, options)
|
||||
return AuthToken.Parse response
|
||||
else
|
||||
let! content = response.Content.ReadAsStringAsync ()
|
||||
return failwithf $"bad status code: %+A{response.StatusCode}\n%s{content}"
|
||||
}
|
48
PureGym/GymSelector.fs
Normal file
48
PureGym/GymSelector.fs
Normal file
@@ -0,0 +1,48 @@
|
||||
namespace PureGym
|
||||
|
||||
open System.Threading.Tasks
|
||||
open Fastenshtein
|
||||
|
||||
/// Identifies a gym, possibly non-uniquely and possibly ambiguously.
|
||||
[<RequireQualifiedAccess>]
|
||||
type GymSelector =
|
||||
/// The ID of this gym, according to the PureGym internal mapping.
|
||||
| Id of int
|
||||
/// The user-specified name of this gym.
|
||||
| Name of string
|
||||
/// The home gym of the authenticated user.
|
||||
| Home
|
||||
|
||||
/// Methods for manipulating GymSelector.
|
||||
[<RequireQualifiedAccess>]
|
||||
module GymSelector =
|
||||
|
||||
/// Get the canonical PureGym ID for this user-specified gym.
|
||||
let canonicalId (client : IPureGymApi) (gym : GymSelector) : int Task =
|
||||
match gym with
|
||||
| GymSelector.Home ->
|
||||
task {
|
||||
let! self = client.GetMember ()
|
||||
return self.HomeGymId
|
||||
}
|
||||
| GymSelector.Id i -> Task.FromResult<_> i
|
||||
| GymSelector.Name name ->
|
||||
task {
|
||||
let! allGyms = client.GetGyms ()
|
||||
|
||||
if allGyms.IsEmpty then
|
||||
return failwith "PureGym API returned no gyms!"
|
||||
else
|
||||
let distance = Levenshtein (name.ToLowerInvariant ())
|
||||
|
||||
let bestDistance, bestGym =
|
||||
allGyms
|
||||
|> Seq.map (fun gym -> distance.DistanceFrom (gym.Name.ToLowerInvariant ()), gym)
|
||||
|> Seq.sortBy fst
|
||||
|> Seq.head
|
||||
|
||||
if bestDistance <> 0 then
|
||||
System.Console.Error.WriteLine $"Autocorrected gym from %s{name} to %s{bestGym.Name}"
|
||||
|
||||
return bestGym.Id
|
||||
}
|
24
PureGym/PureGym.fsproj
Normal file
24
PureGym/PureGym.fsproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="String.fs" />
|
||||
<Compile Include="Auth.fs" />
|
||||
<Compile Include="Api.fs" />
|
||||
<Compile Include="GymSelector.fs" />
|
||||
<EmbeddedResource Include="SurfaceBaseline.txt" />
|
||||
<EmbeddedResource Include="version.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RestEase" Version="1.6.4" />
|
||||
<PackageReference Update="FSharp.Core" Version="6.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="7.0.3" />
|
||||
<PackageReference Include="Fastenshtein" Version="1.0.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
5
PureGym/String.fs
Normal file
5
PureGym/String.fs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace PureGym
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module internal Char =
|
||||
let emoji bool = if bool then '✅' else '❌'
|
198
PureGym/SurfaceBaseline.txt
Normal file
198
PureGym/SurfaceBaseline.txt
Normal file
@@ -0,0 +1,198 @@
|
||||
PureGym.Api inherit obj
|
||||
PureGym.Api.make [static method]: PureGym.Auth -> PureGym.IPureGymApi System.Threading.Tasks.Task
|
||||
PureGym.Auth inherit obj, implements PureGym.Auth System.IEquatable, System.Collections.IStructuralEquatable, PureGym.Auth System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 2 cases
|
||||
PureGym.Auth+Tags inherit obj
|
||||
PureGym.Auth+Tags.Token [static field]: int = 1
|
||||
PureGym.Auth+Tags.User [static field]: int = 0
|
||||
PureGym.Auth+Token inherit PureGym.Auth
|
||||
PureGym.Auth+Token.get_Item [method]: unit -> PureGym.AuthToken
|
||||
PureGym.Auth+Token.Item [property]: [read-only] PureGym.AuthToken
|
||||
PureGym.Auth+User inherit PureGym.Auth
|
||||
PureGym.Auth+User.get_Item [method]: unit -> PureGym.UsernamePin
|
||||
PureGym.Auth+User.Item [property]: [read-only] PureGym.UsernamePin
|
||||
PureGym.Auth.get_IsToken [method]: unit -> bool
|
||||
PureGym.Auth.get_IsUser [method]: unit -> bool
|
||||
PureGym.Auth.get_Tag [method]: unit -> int
|
||||
PureGym.Auth.IsToken [property]: [read-only] bool
|
||||
PureGym.Auth.IsUser [property]: [read-only] bool
|
||||
PureGym.Auth.NewToken [static method]: PureGym.AuthToken -> PureGym.Auth
|
||||
PureGym.Auth.NewUser [static method]: PureGym.UsernamePin -> PureGym.Auth
|
||||
PureGym.Auth.Tag [property]: [read-only] int
|
||||
PureGym.AuthResponseRaw inherit obj
|
||||
PureGym.AuthResponseRaw..ctor [constructor]: (string, int, string, string)
|
||||
PureGym.AuthResponseRaw.access_token [property]: [read-only] string
|
||||
PureGym.AuthResponseRaw.expires_in [property]: [read-only] int
|
||||
PureGym.AuthResponseRaw.get_access_token [method]: unit -> string
|
||||
PureGym.AuthResponseRaw.get_expires_in [method]: unit -> int
|
||||
PureGym.AuthResponseRaw.get_scope [method]: unit -> string
|
||||
PureGym.AuthResponseRaw.get_token_type [method]: unit -> string
|
||||
PureGym.AuthResponseRaw.scope [property]: [read-only] string
|
||||
PureGym.AuthResponseRaw.token_type [property]: [read-only] string
|
||||
PureGym.AuthToken inherit obj, implements PureGym.AuthToken System.IEquatable, System.Collections.IStructuralEquatable, PureGym.AuthToken System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||
PureGym.AuthToken..ctor [constructor]: (string, System.DateTime option)
|
||||
PureGym.AuthToken.AccessToken [property]: [read-only] string
|
||||
PureGym.AuthToken.ExpiryTime [property]: [read-only] System.DateTime option
|
||||
PureGym.AuthToken.get_AccessToken [method]: unit -> string
|
||||
PureGym.AuthToken.get_ExpiryTime [method]: unit -> System.DateTime option
|
||||
PureGym.AuthTokenModule inherit obj
|
||||
PureGym.AuthTokenModule.get [static method]: PureGym.UsernamePin -> PureGym.AuthToken System.Threading.Tasks.Task
|
||||
PureGym.AuthTokenModule.ofBearerToken [static method]: string -> PureGym.AuthToken
|
||||
PureGym.Gym inherit obj, implements PureGym.Gym System.IEquatable, System.Collections.IStructuralEquatable, PureGym.Gym System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||
PureGym.Gym..ctor [constructor]: (string, int, int, PureGym.GymAddress, string, string, PureGym.GymOpeningHours, PureGym.GymAccessOptions, PureGym.GymLocation, string, string)
|
||||
PureGym.Gym.AccessOptions [property]: [read-only] PureGym.GymAccessOptions
|
||||
PureGym.Gym.Address [property]: [read-only] PureGym.GymAddress
|
||||
PureGym.Gym.EmailAddress [property]: [read-only] string
|
||||
PureGym.Gym.get_AccessOptions [method]: unit -> PureGym.GymAccessOptions
|
||||
PureGym.Gym.get_Address [method]: unit -> PureGym.GymAddress
|
||||
PureGym.Gym.get_EmailAddress [method]: unit -> string
|
||||
PureGym.Gym.get_GymOpeningHours [method]: unit -> PureGym.GymOpeningHours
|
||||
PureGym.Gym.get_Id [method]: unit -> int
|
||||
PureGym.Gym.get_Location [method]: unit -> PureGym.GymLocation
|
||||
PureGym.Gym.get_Name [method]: unit -> string
|
||||
PureGym.Gym.get_PhoneNumber [method]: unit -> string
|
||||
PureGym.Gym.get_ReopenDate [method]: unit -> string
|
||||
PureGym.Gym.get_Status [method]: unit -> int
|
||||
PureGym.Gym.get_TimeZone [method]: unit -> string
|
||||
PureGym.Gym.GymOpeningHours [property]: [read-only] PureGym.GymOpeningHours
|
||||
PureGym.Gym.Id [property]: [read-only] int
|
||||
PureGym.Gym.Location [property]: [read-only] PureGym.GymLocation
|
||||
PureGym.Gym.Name [property]: [read-only] string
|
||||
PureGym.Gym.PhoneNumber [property]: [read-only] string
|
||||
PureGym.Gym.ReopenDate [property]: [read-only] string
|
||||
PureGym.Gym.Status [property]: [read-only] int
|
||||
PureGym.Gym.TimeZone [property]: [read-only] string
|
||||
PureGym.GymAccessOptions inherit obj, implements PureGym.GymAccessOptions System.IEquatable, System.Collections.IStructuralEquatable, PureGym.GymAccessOptions System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||
PureGym.GymAccessOptions..ctor [constructor]: (bool, bool)
|
||||
PureGym.GymAccessOptions.get_PinAccess [method]: unit -> bool
|
||||
PureGym.GymAccessOptions.get_QrCodeAccess [method]: unit -> bool
|
||||
PureGym.GymAccessOptions.PinAccess [property]: [read-only] bool
|
||||
PureGym.GymAccessOptions.QrCodeAccess [property]: [read-only] bool
|
||||
PureGym.GymAddress inherit obj, implements PureGym.GymAddress System.IEquatable, System.Collections.IStructuralEquatable, PureGym.GymAddress System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||
PureGym.GymAddress..ctor [constructor]: (string, string, string, string, string, string)
|
||||
PureGym.GymAddress.AddressLine1 [property]: [read-only] string
|
||||
PureGym.GymAddress.AddressLine2 [property]: [read-only] string
|
||||
PureGym.GymAddress.AddressLine3 [property]: [read-only] string
|
||||
PureGym.GymAddress.County [property]: [read-only] string
|
||||
PureGym.GymAddress.get_AddressLine1 [method]: unit -> string
|
||||
PureGym.GymAddress.get_AddressLine2 [method]: unit -> string
|
||||
PureGym.GymAddress.get_AddressLine3 [method]: unit -> string
|
||||
PureGym.GymAddress.get_County [method]: unit -> string
|
||||
PureGym.GymAddress.get_Postcode [method]: unit -> string
|
||||
PureGym.GymAddress.get_Town [method]: unit -> string
|
||||
PureGym.GymAddress.Postcode [property]: [read-only] string
|
||||
PureGym.GymAddress.Town [property]: [read-only] string
|
||||
PureGym.GymAttendance inherit obj, implements PureGym.GymAttendance System.IEquatable, System.Collections.IStructuralEquatable, PureGym.GymAttendance System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||
PureGym.GymAttendance..ctor [constructor]: (string, int, int, string, bool, System.DateTime, System.DateTime, System.DateTime, int)
|
||||
PureGym.GymAttendance.AttendanceTime [property]: [read-only] System.DateTime
|
||||
PureGym.GymAttendance.Description [property]: [read-only] string
|
||||
PureGym.GymAttendance.get_AttendanceTime [method]: unit -> System.DateTime
|
||||
PureGym.GymAttendance.get_Description [method]: unit -> string
|
||||
PureGym.GymAttendance.get_IsApproximate [method]: unit -> bool
|
||||
PureGym.GymAttendance.get_LastRefreshed [method]: unit -> System.DateTime
|
||||
PureGym.GymAttendance.get_LastRefreshedPeopleInClasses [method]: unit -> System.DateTime
|
||||
PureGym.GymAttendance.get_MaximumCapacity [method]: unit -> int
|
||||
PureGym.GymAttendance.get_TotalPeopleInClasses [method]: unit -> int
|
||||
PureGym.GymAttendance.get_TotalPeopleInGym [method]: unit -> int
|
||||
PureGym.GymAttendance.get_TotalPeopleSuffix [method]: unit -> string
|
||||
PureGym.GymAttendance.IsApproximate [property]: [read-only] bool
|
||||
PureGym.GymAttendance.LastRefreshed [property]: [read-only] System.DateTime
|
||||
PureGym.GymAttendance.LastRefreshedPeopleInClasses [property]: [read-only] System.DateTime
|
||||
PureGym.GymAttendance.MaximumCapacity [property]: [read-only] int
|
||||
PureGym.GymAttendance.TotalPeopleInClasses [property]: [read-only] int
|
||||
PureGym.GymAttendance.TotalPeopleInGym [property]: [read-only] int
|
||||
PureGym.GymAttendance.TotalPeopleSuffix [property]: [read-only] string
|
||||
PureGym.GymLocation inherit obj, implements PureGym.GymLocation System.IEquatable, System.Collections.IStructuralEquatable, PureGym.GymLocation System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||
PureGym.GymLocation..ctor [constructor]: (float, float)
|
||||
PureGym.GymLocation.get_Latitude [method]: unit -> float
|
||||
PureGym.GymLocation.get_Longitude [method]: unit -> float
|
||||
PureGym.GymLocation.Latitude [property]: [read-only] float
|
||||
PureGym.GymLocation.Longitude [property]: [read-only] float
|
||||
PureGym.GymOpeningHours inherit obj, implements PureGym.GymOpeningHours System.IEquatable, System.Collections.IStructuralEquatable, PureGym.GymOpeningHours System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||
PureGym.GymOpeningHours..ctor [constructor]: (bool, string list)
|
||||
PureGym.GymOpeningHours.get_IsAlwaysOpen [method]: unit -> bool
|
||||
PureGym.GymOpeningHours.get_OpeningHours [method]: unit -> string list
|
||||
PureGym.GymOpeningHours.IsAlwaysOpen [property]: [read-only] bool
|
||||
PureGym.GymOpeningHours.OpeningHours [property]: [read-only] string list
|
||||
PureGym.GymSelector inherit obj, implements PureGym.GymSelector System.IEquatable, System.Collections.IStructuralEquatable, PureGym.GymSelector System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 3 cases
|
||||
PureGym.GymSelector+Id inherit PureGym.GymSelector
|
||||
PureGym.GymSelector+Id.get_Item [method]: unit -> int
|
||||
PureGym.GymSelector+Id.Item [property]: [read-only] int
|
||||
PureGym.GymSelector+Name inherit PureGym.GymSelector
|
||||
PureGym.GymSelector+Name.get_Item [method]: unit -> string
|
||||
PureGym.GymSelector+Name.Item [property]: [read-only] string
|
||||
PureGym.GymSelector+Tags inherit obj
|
||||
PureGym.GymSelector+Tags.Home [static field]: int = 2
|
||||
PureGym.GymSelector+Tags.Id [static field]: int = 0
|
||||
PureGym.GymSelector+Tags.Name [static field]: int = 1
|
||||
PureGym.GymSelector.get_Home [static method]: unit -> PureGym.GymSelector
|
||||
PureGym.GymSelector.get_IsHome [method]: unit -> bool
|
||||
PureGym.GymSelector.get_IsId [method]: unit -> bool
|
||||
PureGym.GymSelector.get_IsName [method]: unit -> bool
|
||||
PureGym.GymSelector.get_Tag [method]: unit -> int
|
||||
PureGym.GymSelector.Home [static property]: [read-only] PureGym.GymSelector
|
||||
PureGym.GymSelector.IsHome [property]: [read-only] bool
|
||||
PureGym.GymSelector.IsId [property]: [read-only] bool
|
||||
PureGym.GymSelector.IsName [property]: [read-only] bool
|
||||
PureGym.GymSelector.NewId [static method]: int -> PureGym.GymSelector
|
||||
PureGym.GymSelector.NewName [static method]: string -> PureGym.GymSelector
|
||||
PureGym.GymSelector.Tag [property]: [read-only] int
|
||||
PureGym.GymSelectorModule inherit obj
|
||||
PureGym.GymSelectorModule.canonicalId [static method]: PureGym.IPureGymApi -> PureGym.GymSelector -> int System.Threading.Tasks.Task
|
||||
PureGym.IPureGymApi - interface with 5 member(s)
|
||||
PureGym.IPureGymApi.GetGym [method]: int -> PureGym.Gym System.Threading.Tasks.Task
|
||||
PureGym.IPureGymApi.GetGymAttendance [method]: int -> PureGym.GymAttendance System.Threading.Tasks.Task
|
||||
PureGym.IPureGymApi.GetGyms [method]: unit -> PureGym.Gym list System.Threading.Tasks.Task
|
||||
PureGym.IPureGymApi.GetMember [method]: unit -> PureGym.Member System.Threading.Tasks.Task
|
||||
PureGym.IPureGymApi.GetMemberActivity [method]: unit -> PureGym.MemberActivity System.Threading.Tasks.Task
|
||||
PureGym.Member inherit obj, implements PureGym.Member System.IEquatable, System.Collections.IStructuralEquatable, PureGym.Member System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||
PureGym.Member..ctor [constructor]: (int, string, string, string, int, string, string, string, System.DateOnly, string, string, string, int, int, int)
|
||||
PureGym.Member.CompoundMemberId [property]: [read-only] string
|
||||
PureGym.Member.DateOfBirth [property]: [read-only] System.DateOnly
|
||||
PureGym.Member.EmailAddress [property]: [read-only] string
|
||||
PureGym.Member.FirstName [property]: [read-only] string
|
||||
PureGym.Member.get_CompoundMemberId [method]: unit -> string
|
||||
PureGym.Member.get_DateOfBirth [method]: unit -> System.DateOnly
|
||||
PureGym.Member.get_EmailAddress [method]: unit -> string
|
||||
PureGym.Member.get_FirstName [method]: unit -> string
|
||||
PureGym.Member.get_GymAccessPin [method]: unit -> string
|
||||
PureGym.Member.get_HomeGymId [method]: unit -> int
|
||||
PureGym.Member.get_HomeGymName [method]: unit -> string
|
||||
PureGym.Member.get_Id [method]: unit -> int
|
||||
PureGym.Member.get_LastName [method]: unit -> string
|
||||
PureGym.Member.get_MembershipLevel [method]: unit -> int
|
||||
PureGym.Member.get_MembershipName [method]: unit -> string
|
||||
PureGym.Member.get_MemberStatus [method]: unit -> int
|
||||
PureGym.Member.get_MobileNumber [method]: unit -> string
|
||||
PureGym.Member.get_Postcode [method]: unit -> string
|
||||
PureGym.Member.get_SuspendedReason [method]: unit -> int
|
||||
PureGym.Member.GymAccessPin [property]: [read-only] string
|
||||
PureGym.Member.HomeGymId [property]: [read-only] int
|
||||
PureGym.Member.HomeGymName [property]: [read-only] string
|
||||
PureGym.Member.Id [property]: [read-only] int
|
||||
PureGym.Member.LastName [property]: [read-only] string
|
||||
PureGym.Member.MembershipLevel [property]: [read-only] int
|
||||
PureGym.Member.MembershipName [property]: [read-only] string
|
||||
PureGym.Member.MemberStatus [property]: [read-only] int
|
||||
PureGym.Member.MobileNumber [property]: [read-only] string
|
||||
PureGym.Member.Postcode [property]: [read-only] string
|
||||
PureGym.Member.SuspendedReason [property]: [read-only] int
|
||||
PureGym.MemberActivity inherit obj, implements PureGym.MemberActivity System.IEquatable, System.Collections.IStructuralEquatable, PureGym.MemberActivity System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||
PureGym.MemberActivity..ctor [constructor]: (int, int, int, int, bool, System.DateTime)
|
||||
PureGym.MemberActivity.AverageDurationMinutes [property]: [read-only] int
|
||||
PureGym.MemberActivity.get_AverageDurationMinutes [method]: unit -> int
|
||||
PureGym.MemberActivity.get_IsEstimated [method]: unit -> bool
|
||||
PureGym.MemberActivity.get_LastRefreshed [method]: unit -> System.DateTime
|
||||
PureGym.MemberActivity.get_TotalClasses [method]: unit -> int
|
||||
PureGym.MemberActivity.get_TotalDurationMinutes [method]: unit -> int
|
||||
PureGym.MemberActivity.get_TotalVisits [method]: unit -> int
|
||||
PureGym.MemberActivity.IsEstimated [property]: [read-only] bool
|
||||
PureGym.MemberActivity.LastRefreshed [property]: [read-only] System.DateTime
|
||||
PureGym.MemberActivity.TotalClasses [property]: [read-only] int
|
||||
PureGym.MemberActivity.TotalDurationMinutes [property]: [read-only] int
|
||||
PureGym.MemberActivity.TotalVisits [property]: [read-only] int
|
||||
PureGym.UsernamePin inherit obj, implements PureGym.UsernamePin System.IEquatable, System.Collections.IStructuralEquatable, PureGym.UsernamePin System.IComparable, System.IComparable, System.Collections.IStructuralComparable
|
||||
PureGym.UsernamePin..ctor [constructor]: (string, string)
|
||||
PureGym.UsernamePin.get_Pin [method]: unit -> string
|
||||
PureGym.UsernamePin.get_Username [method]: unit -> string
|
||||
PureGym.UsernamePin.Pin [property]: [read-only] string
|
||||
PureGym.UsernamePin.Username [property]: [read-only] string
|
7
PureGym/version.json
Normal file
7
PureGym/version.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"publicReleaseRefSpec": [
|
||||
"^refs/heads/main$"
|
||||
],
|
||||
"pathFilters": null
|
||||
}
|
30
README.md
Normal file
30
README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Unofficial PureGym client
|
||||
|
||||

|
||||
|
||||
*With thanks to [Tom Hollingsworth](https://github.com/2t6h/puregym-attendance/blob/64dcd830bd874dc0150c7767f5cc6c75ed0b9dad/puregym.py).*
|
||||
|
||||
## Usage
|
||||
|
||||
* With Nix: `nix run` (you can refer to this flake).
|
||||
* Manually: `git clone` and then `dotnet run --project PureGym.App/PureGym.App.fsproj`.
|
||||
|
||||
(Something is up on Darwin: `nix run` currently produces an executable which dies instantly.
|
||||
Workaround: `nix build` and then `nix develop --command dotnet exec ./result/lib/puregym/PureGym.App.dll`.)
|
||||
|
||||
The available subcommands can be viewed in the `subcommands` map defined in [Program.fs](./PureGym.App/Program.fs).
|
||||
As of this writing, the following are implemented:
|
||||
|
||||
* `activity`: get the logged-in user's activity stats. I have no idea what the semantics of these numbers are!
|
||||
* `fullness`: determine how full a given gym is right now.
|
||||
* `lookup-gym`: give information about the gym's physical instantiation (e.g. its address).
|
||||
|
||||
### Authentication
|
||||
|
||||
You can authenticate with your PureGym email address and PIN combination, or (probably better) you can call `PureGym.App auth` to obtain a token.
|
||||
Use this token in subsequent commands by setting the `PUREGYM_BEARER_TOKEN` environment variable or supplying it as `--bearer-token`.
|
||||
|
||||
## Structure
|
||||
|
||||
* The REST client is at [Api.fs](./PureGym/Api.fs).
|
||||
* The standalone application is at [PureGym.App](./PureGym.App). It uses a ghastly mix of hand-rolled argument parsing and [Argu](https://fsprojects.github.io/Argu/), because Argu does not *quite* want to do what I want an argument parser to do.
|
60
flake.lock
generated
Normal file
60
flake.lock
generated
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1696981262,
|
||||
"narHash": "sha256-YaCOjdqhbjBeyMjxlgFWt4XD/b9pGKWURgS3uEwNLtc=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a2b87a4f66f309d2f4b789fd0457f5fc5db0a9a6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
114
flake.nix
Normal file
114
flake.nix
Normal file
@@ -0,0 +1,114 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs";
|
||||
flake-utils = {
|
||||
url = "github:numtide/flake-utils";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = inputs @ {
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
...
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
projectFile = "./PureGym.App/PureGym.App.fsproj";
|
||||
testProjectFile = "./PureGym.Test/PureGym.Test.fsproj";
|
||||
pname = "puregym";
|
||||
dotnet-sdk = pkgs.dotnet-sdk_7;
|
||||
dotnet-runtime = pkgs.dotnetCorePackages.runtime_7_0;
|
||||
version = "0.1";
|
||||
dotnetTool = toolName: toolVersion: sha256:
|
||||
pkgs.stdenvNoCC.mkDerivation rec {
|
||||
name = toolName;
|
||||
version = toolVersion;
|
||||
nativeBuildInputs = [pkgs.makeWrapper];
|
||||
src = pkgs.fetchNuGet {
|
||||
pname = name;
|
||||
version = version;
|
||||
sha256 = sha256;
|
||||
installPhase = ''mkdir -p $out/bin && cp -r tools/net6.0/any/* $out/bin'';
|
||||
};
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p "$out/lib"
|
||||
cp -r ./bin/* "$out/lib"
|
||||
makeWrapper "${dotnet-runtime}/bin/dotnet" "$out/bin/${name}" --add-flags "$out/lib/${name}.dll"
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
fantomas = dotnetTool "fantomas" (builtins.fromJSON (builtins.readFile ./.config/dotnet-tools.json)).tools.fantomas.version "sha256-83RodORaC3rkYfbFMHsYLEtl0+8+akZXcKoSJdgwuUo=";
|
||||
in {
|
||||
packages = {
|
||||
fantomas = fantomas;
|
||||
fetchDeps = let
|
||||
flags = [];
|
||||
runtimeIds = ["win-x64"] ++ map (system: pkgs.dotnetCorePackages.systemToDotnetRid system) dotnet-sdk.meta.platforms;
|
||||
in
|
||||
pkgs.writeShellScriptBin "fetch-${pname}-deps" (builtins.readFile (pkgs.substituteAll {
|
||||
src = ./nix/fetchDeps.sh;
|
||||
pname = pname;
|
||||
binPath = pkgs.lib.makeBinPath [pkgs.coreutils dotnet-sdk (pkgs.nuget-to-nix.override {inherit dotnet-sdk;})];
|
||||
projectFiles = toString (pkgs.lib.toList projectFile);
|
||||
testProjectFiles = toString (pkgs.lib.toList testProjectFile);
|
||||
rids = pkgs.lib.concatStringsSep "\" \"" runtimeIds;
|
||||
packages = dotnet-sdk.packages;
|
||||
storeSrc = pkgs.srcOnly {
|
||||
src = ./.;
|
||||
pname = pname;
|
||||
version = version;
|
||||
};
|
||||
}));
|
||||
default = pkgs.buildDotnetModule {
|
||||
pname = pname;
|
||||
name = "puregym";
|
||||
version = version;
|
||||
src = ./.;
|
||||
projectFile = projectFile;
|
||||
nugetDeps = ./nix/deps.nix;
|
||||
doCheck = true;
|
||||
dotnet-sdk = dotnet-sdk;
|
||||
dotnet-runtime = dotnet-runtime;
|
||||
};
|
||||
};
|
||||
apps = {
|
||||
default = {
|
||||
type = "app";
|
||||
program = "${self.packages.${system}.default}/bin/PureGym.App";
|
||||
};
|
||||
};
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs =
|
||||
[pkgs.alejandra pkgs.dotnet-sdk_7 pkgs.python3 pkgs.nodePackages.markdown-link-check]
|
||||
++ (
|
||||
if pkgs.stdenv.isDarwin
|
||||
then [pkgs.darwin.apple_sdk.frameworks.CoreServices]
|
||||
else []
|
||||
);
|
||||
};
|
||||
checks = {
|
||||
alejandra = pkgs.stdenvNoCC.mkDerivation {
|
||||
name = "alejandra-check";
|
||||
src = ./.;
|
||||
checkPhase = ''
|
||||
${pkgs.alejandra}/bin/alejandra --check .
|
||||
'';
|
||||
installPhase = "mkdir $out";
|
||||
dontBuild = true;
|
||||
doCheck = true;
|
||||
};
|
||||
fantomas = pkgs.stdenvNoCC.mkDerivation {
|
||||
name = "fantomas-check";
|
||||
src = ./.;
|
||||
checkPhase = ''
|
||||
${fantomas}/bin/fantomas --check .
|
||||
'';
|
||||
installPhase = "mkdir $out";
|
||||
dontBuild = true;
|
||||
doCheck = true;
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
364
nix/deps.nix
Normal file
364
nix/deps.nix
Normal file
@@ -0,0 +1,364 @@
|
||||
# This file was automatically generated by passthru.fetch-deps.
|
||||
# Please don't edit it manually, your changes might get overwritten!
|
||||
{fetchNuGet}: [
|
||||
(fetchNuGet {
|
||||
pname = "Fastenshtein";
|
||||
version = "1.0.0.8";
|
||||
sha256 = "1rvw27rz7qb2n68i0jvvcr224fcpy5yzzxaj1bp89jw41cpdabp2";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Argu";
|
||||
version = "6.1.1";
|
||||
sha256 = "1v996g0760qhiys2ahdpnvkldaxr2jn5f1falf789glnk4a6f3xl";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Configuration.ConfigurationManager";
|
||||
version = "4.4.0";
|
||||
sha256 = "1hjgmz47v5229cbzd2pwz2h0dkq78lb2wp9grx8qr72pb5i0dk7v";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "fantomas";
|
||||
version = "6.2.0";
|
||||
sha256 = "sha256-83RodORaC3rkYfbFMHsYLEtl0+8+akZXcKoSJdgwuUo=";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "ApiSurface";
|
||||
version = "4.0.12";
|
||||
sha256 = "0v56sv4cz8bgrfqjjg0q96619qs9dvvi0a6lp7hzz2mi82i1inmq";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "coverlet.collector";
|
||||
version = "3.2.0";
|
||||
sha256 = "1qxpv8v10p5wn162lzdm193gdl6c5f81zadj8h889dprlnj3g8yr";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "FSharp.Core";
|
||||
version = "6.0.0";
|
||||
sha256 = "1hjhvr39c1vpgrdmf8xln5q86424fqkvy9nirkr29vl2461d2039";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "FSharp.Core";
|
||||
version = "7.0.400";
|
||||
sha256 = "1pl6iqqcpm9djfn7f6ms5j1xbcyz00nb808qd6pmsjrnylflalgp";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Ref";
|
||||
version = "6.0.22";
|
||||
sha256 = "0fqpl1fr213b4fb3c6xw3fy6669yxqcp1bzcnayw80yrskw8lpxs";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64";
|
||||
version = "6.0.22";
|
||||
sha256 = "1xvqqc7bzj764g3scp0saqxlfiv866crgi8chz57vhjp9sgd61jw";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.linux-arm64";
|
||||
version = "7.0.11";
|
||||
sha256 = "0hmsqy4yc3023mcp5rg0h59yv3f8cnjhxw1g4i8md67vm5y04lfv";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.linux-x64";
|
||||
version = "6.0.22";
|
||||
sha256 = "1gcv99y295fnhy12fyx8wqvbhbj6mz8p5bm66ppwdxb3zykjg2l8";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.linux-x64";
|
||||
version = "7.0.11";
|
||||
sha256 = "18sk9wka8z5354ca77q43hi0615yjssdjbyi0hqq92w6zmg43vgc";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64";
|
||||
version = "6.0.22";
|
||||
sha256 = "1ib0x1w33wqy7lgzjf14dvgx981xpjffjqd800d7wgxisgmakrmr";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.osx-arm64";
|
||||
version = "7.0.11";
|
||||
sha256 = "1j0zbd4rmmd3ylgixsvyj145g2r6px6b9d9k4yxxg6d61x90c165";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.osx-x64";
|
||||
version = "6.0.22";
|
||||
sha256 = "026r38a7by7wdfd3virjdaah3y2sjjmnabgf5l25vdnwpwc7c31d";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.osx-x64";
|
||||
version = "7.0.11";
|
||||
sha256 = "0wxw7vgygg6hqzq479n0pfjizr69wq7ja03a0qh8bma8b9q2mn6f";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.win-x64";
|
||||
version = "6.0.22";
|
||||
sha256 = "0ygdqsd312kqpykwb0k2942n45q1w3yn1nia6m1ahf7b74926qb5";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.AspNetCore.App.Runtime.win-x64";
|
||||
version = "7.0.11";
|
||||
sha256 = "05ywwfn5lzx6y999f7gwmablkxi2zvska4sg20ihmjzp3xakcmk0";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.CodeCoverage";
|
||||
version = "17.6.0";
|
||||
sha256 = "02s98d8nwz5mg4mymcr86qdamy71a29g2091xg452czmd3s3x2di";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NET.Test.Sdk";
|
||||
version = "17.6.0";
|
||||
sha256 = "1bnwpwg7k72z06027ip4yi222863r8sv14ck9nj8h64ckiw2r256";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.linux-arm64";
|
||||
version = "6.0.22";
|
||||
sha256 = "0gri1gqznm5c8fsb6spqb3j88a3b0br0iy50y66fh4hz9wc4fwzm";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.linux-arm64";
|
||||
version = "7.0.11";
|
||||
sha256 = "03nkxjn4wq30rw0163rqi8sngfxmcvwgm0wg7sgyb1cdh0q1ai68";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.linux-x64";
|
||||
version = "6.0.22";
|
||||
sha256 = "0k1i74wn6j7nq0bd8m6jrpl65wda6qc9pglppvz4ybk0n2ab1rbi";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.linux-x64";
|
||||
version = "7.0.11";
|
||||
sha256 = "12hh69sr4wf8sjcw3q71vky51sn854ffahbq6rgz3njzvbvc0dbj";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.osx-arm64";
|
||||
version = "6.0.22";
|
||||
sha256 = "0166gwarhhnary19lf80ff33bkx00mkm24f17bc8j6v7g3a7zvq6";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.osx-x64";
|
||||
version = "6.0.22";
|
||||
sha256 = "038bjwk201p2kzs3jflrkhlnszf7cwalafq0nvs2v8bp7jlnx5ib";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.osx-x64";
|
||||
version = "7.0.11";
|
||||
sha256 = "1j1k735gkwba93n5yck87wppfpsbny979hppcygwrk81myf3fv03";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.win-x64";
|
||||
version = "6.0.22";
|
||||
sha256 = "1bjy3zmrmaq97xp0f3nzs3ax330ji632avrfpg8xz4vc5p8s1xpc";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Host.win-x64";
|
||||
version = "7.0.11";
|
||||
sha256 = "0ifshdx19bgnbgynbk6iy6gybnxmp63nylrn7068x66hvcavh7kh";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Ref";
|
||||
version = "6.0.22";
|
||||
sha256 = "0km8184kma8kgz7iyl3j6apj1n7vskzdhzmq3myy3y36ysqrb4wf";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.linux-arm64";
|
||||
version = "6.0.22";
|
||||
sha256 = "01gbl9dgky4h7ijxryz3527l39v23lkcvk4fs4w91ra4pris2n8p";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.linux-arm64";
|
||||
version = "7.0.11";
|
||||
sha256 = "1gzwc96fs222ddia0k1924cn7gxm2a4anqgcxhmavx56x76wsy6f";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.linux-x64";
|
||||
version = "6.0.22";
|
||||
sha256 = "09gfqdxbh36bjx20fw9k94b9qa9bwffhrq0ldwn834mx31bgrfs8";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.linux-x64";
|
||||
version = "7.0.11";
|
||||
sha256 = "0vxza49wwiia0d3m887yiaprp3xnax2bgzhj5bf080b4ayapzkf9";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.osx-arm64";
|
||||
version = "6.0.22";
|
||||
sha256 = "1x7wclv93q8wp7rip5nwnsxbqcami92yilvzbp0yn42ddkw177ds";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.osx-arm64";
|
||||
version = "7.0.11";
|
||||
sha256 = "15b62hxrpfy19xvyxlyligixxpa9sysfgi47xi4imx5055fhwphh";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.osx-x64";
|
||||
version = "6.0.22";
|
||||
sha256 = "1sq1ygsrpv2sl85wrs8382wgkjic0zylaj1y8kcvhczcmkpk3wr5";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.osx-x64";
|
||||
version = "7.0.11";
|
||||
sha256 = "018qf23b0jixfh3fm74zqaakk01qx6yq21gk2mdn68b0xhnvlzma";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.win-x64";
|
||||
version = "6.0.22";
|
||||
sha256 = "1nn254xv1hi5c4rg38fbfkln3031vv545lv9f4df31i8c1yfzz24";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.App.Runtime.win-x64";
|
||||
version = "7.0.11";
|
||||
sha256 = "12xmw2kcpf5rh8sv4y0mqzp917f7q8g4mfh5navqw4jmnxyb26qq";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.Platforms";
|
||||
version = "1.1.0";
|
||||
sha256 = "08vh1r12g6ykjygq5d3vq09zylgb84l63k49jc4v8faw9g93iqqm";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.NETCore.Platforms";
|
||||
version = "2.0.0";
|
||||
sha256 = "1fk2fk2639i7nzy58m9dvpdnzql4vb8yl8vr19r2fp8lmj9w2jr0";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.TestPlatform.ObjectModel";
|
||||
version = "17.6.0";
|
||||
sha256 = "1rz22chnis11dwjrqrcvvmfw80fi2a7756a7ahwy6jlnr250zr61";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Microsoft.TestPlatform.TestHost";
|
||||
version = "17.6.0";
|
||||
sha256 = "16vpicp4q2kbpgr3qwpsxg7srabxqszx23x6smjvvrvz7qmr5v8i";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NETStandard.Library";
|
||||
version = "2.0.0";
|
||||
sha256 = "1bc4ba8ahgk15m8k4nd7x406nhi0kwqzbgjk2dmw52ss553xz7iy";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "Newtonsoft.Json";
|
||||
version = "13.0.1";
|
||||
sha256 = "0fijg0w6iwap8gvzyjnndds0q4b8anwxxvik7y8vgq97dram4srb";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Common";
|
||||
version = "6.6.1";
|
||||
sha256 = "1q7k5rqwchxgs5pnrn22d1rkdb7l2qblvsb9hy046ll69i71vv45";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Configuration";
|
||||
version = "6.6.1";
|
||||
sha256 = "0pw4ikd8784iya920wxigacqn5g2v0zlpwxjlswyq5mnj2ha7gpk";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Frameworks";
|
||||
version = "5.11.0";
|
||||
sha256 = "0wv26gq39hfqw9md32amr5771s73f5zn1z9vs4y77cgynxr73s4z";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Frameworks";
|
||||
version = "6.6.1";
|
||||
sha256 = "1zq79mklzq7qyiyhcv3w8pznw6rq1ddcl8fvy7j1c6n8qh3mglhx";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Packaging";
|
||||
version = "6.6.1";
|
||||
sha256 = "1lmx8kgpg220q8kic4wm8skccj53cbkdqggirq9js34gnxxi9b88";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Protocol";
|
||||
version = "6.6.1";
|
||||
sha256 = "01n8cw114npvzfk3m3803lb8plk0wm1zg496gpq9az8hw20nmd8g";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NuGet.Versioning";
|
||||
version = "6.6.1";
|
||||
sha256 = "0n2p05y8ciw6jc5s238rlnx6q4dgxvm14v06pcd84ji5j1iirc30";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NUnit";
|
||||
version = "3.13.3";
|
||||
sha256 = "0wdzfkygqnr73s6lpxg5b1pwaqz9f414fxpvpdmf72bvh4jaqzv6";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NUnit.Analyzers";
|
||||
version = "3.6.1";
|
||||
sha256 = "16dw5375k2wyhiw9x387y7pjgq6zms30y036qb8z7idx4lxw9yi9";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "NUnit3TestAdapter";
|
||||
version = "4.4.2";
|
||||
sha256 = "1n2jlc16vjdd81cb1by4qbp75sq73zsjz5w3zc61ssmbdci1q2ri";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "RestEase";
|
||||
version = "1.6.4";
|
||||
sha256 = "1mvi3nbrr450g3fgd1y4wg3bwl9k1agyjfd9wdkqk12714bsln8l";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Formats.Asn1";
|
||||
version = "5.0.0";
|
||||
sha256 = "1axc8z0839yvqi2cb63l73l6d9j6wd20lsbdymwddz9hvrsgfwpn";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.IO.Abstractions";
|
||||
version = "4.2.13";
|
||||
sha256 = "0s784iphsmj4vhkrzq9q3w39vsn76w44zclx3hsygsw458zbyh4y";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.IO.FileSystem.AccessControl";
|
||||
version = "4.5.0";
|
||||
sha256 = "1gq4s8w7ds1sp8f9wqzf8nrzal40q5cd2w4pkf4fscrl2ih3hkkj";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Reflection.Metadata";
|
||||
version = "1.6.0";
|
||||
sha256 = "1wdbavrrkajy7qbdblpbpbalbdl48q3h34cchz24gvdgyrlf15r4";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Runtime.CompilerServices.Unsafe";
|
||||
version = "6.0.0";
|
||||
sha256 = "0qm741kh4rh57wky16sq4m0v05fxmkjjr87krycf5vp9f0zbahbc";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Security.AccessControl";
|
||||
version = "4.5.0";
|
||||
sha256 = "1wvwanz33fzzbnd2jalar0p0z3x0ba53vzx1kazlskp7pwyhlnq0";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Security.Cryptography.Cng";
|
||||
version = "5.0.0";
|
||||
sha256 = "06hkx2za8jifpslkh491dfwzm5dxrsyxzj5lsc0achb6yzg4zqlw";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Security.Cryptography.Pkcs";
|
||||
version = "5.0.0";
|
||||
sha256 = "0hb2mndac3xrw3786bsjxjfh19bwnr991qib54k6wsqjhjyyvbwj";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Security.Cryptography.ProtectedData";
|
||||
version = "4.4.0";
|
||||
sha256 = "1q8ljvqhasyynp94a1d7jknk946m20lkwy2c3wa8zw2pc517fbj6";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Security.Principal.Windows";
|
||||
version = "4.5.0";
|
||||
sha256 = "0rmj89wsl5yzwh0kqjgx45vzf694v9p92r4x4q6yxldk1cv1hi86";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Text.Encodings.Web";
|
||||
version = "6.0.0";
|
||||
sha256 = "06n9ql3fmhpjl32g3492sj181zjml5dlcc5l76xq2h38c4f87sai";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Text.Encodings.Web";
|
||||
version = "7.0.0";
|
||||
sha256 = "1151hbyrcf8kyg1jz8k9awpbic98lwz9x129rg7zk1wrs6vjlpxl";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Text.Json";
|
||||
version = "6.0.0";
|
||||
sha256 = "1si2my1g0q0qv1hiqnji4xh9wd05qavxnzj9dwgs23iqvgjky0gl";
|
||||
})
|
||||
(fetchNuGet {
|
||||
pname = "System.Text.Json";
|
||||
version = "7.0.3";
|
||||
sha256 = "0zjrnc9lshagm6kdb9bdh45dmlnkpwcpyssa896sda93ngbmj8k9";
|
||||
})
|
||||
]
|
73
nix/fetchDeps.sh
Executable file
73
nix/fetchDeps.sh
Executable file
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This file was adapted from
|
||||
# https://github.com/NixOS/nixpkgs/blob/b981d811453ab84fb3ea593a9b33b960f1ab9147/pkgs/build-support/dotnet/build-dotnet-module/default.nix#L173
|
||||
set -euo pipefail
|
||||
export PATH="@binPath@"
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--keep-sources|-k)
|
||||
keepSources=1
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
echo "usage: $0 [--keep-sources] [--help] <output path>"
|
||||
echo " <output path> The path to write the lockfile to. A temporary file is used if this is not set"
|
||||
echo " --keep-sources Don't remove temporary directories upon exit, useful for debugging"
|
||||
echo " --help Show this help message"
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
done
|
||||
tmp=$(mktemp -td "@pname@-tmp-XXXXXX")
|
||||
export tmp
|
||||
HOME=$tmp/home
|
||||
exitTrap() {
|
||||
test -n "${ranTrap-}" && return
|
||||
ranTrap=1
|
||||
if test -n "${keepSources-}"; then
|
||||
echo -e "Path to the source: $tmp/src\nPath to the fake home: $tmp/home"
|
||||
else
|
||||
rm -rf "$tmp"
|
||||
fi
|
||||
# Since mktemp is used this will be empty if the script didnt succesfully complete
|
||||
if ! test -s "$depsFile"; then
|
||||
rm -rf "$depsFile"
|
||||
fi
|
||||
}
|
||||
trap exitTrap EXIT INT TERM
|
||||
dotnetRestore() {
|
||||
local -r project="${1-}"
|
||||
local -r rid="$2"
|
||||
dotnet restore "${project-}" \
|
||||
-p:ContinuousIntegrationBuild=true \
|
||||
-p:Deterministic=true \
|
||||
--packages "$tmp/nuget_pkgs" \
|
||||
--runtime "$rid" \
|
||||
--no-cache \
|
||||
--force
|
||||
}
|
||||
declare -a projectFiles=( @projectFiles@ )
|
||||
declare -a testProjectFiles=( @testProjectFiles@ )
|
||||
export DOTNET_NOLOGO=1
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
depsFile=$(realpath "${1:-$(mktemp -t "@pname@-deps-XXXXXX.nix")}")
|
||||
mkdir -p "$tmp/nuget_pkgs"
|
||||
storeSrc="@storeSrc@"
|
||||
src="$tmp/src"
|
||||
cp -rT "$storeSrc" "$src"
|
||||
chmod -R +w "$src"
|
||||
cd "$src"
|
||||
echo "Restoring project..."
|
||||
rids=("@rids@")
|
||||
for rid in "${rids[@]}"; do
|
||||
(( ${#projectFiles[@]} == 0 )) && dotnetRestore "" "$rid"
|
||||
for project in "${projectFiles[@]-}" "${testProjectFiles[@]-}"; do
|
||||
dotnetRestore "$project" "$rid"
|
||||
done
|
||||
done
|
||||
echo "Successfully restored project"
|
||||
echo "Writing lockfile..."
|
||||
echo -e "# This file was automatically generated by passthru.fetch-deps.\n# Please don't edit it manually, your changes might get overwritten!\n" > "$depsFile"
|
||||
nuget-to-nix "$tmp/nuget_pkgs" "@packages@" >> "$depsFile"
|
||||
echo "Successfully wrote lockfile to $depsFile"
|
Reference in New Issue
Block a user