Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fsdocs init command #875

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft

fsdocs init command #875

wants to merge 9 commits into from

Conversation

kMutagene
Copy link
Contributor

This PR intends to implement a basic version of #872

additionally, i tried to refactor the way default files and folders are accessed from the build command, because the init command needs essentially the same code to copy the relevant files.

@kMutagene
Copy link
Contributor Author

kMutagene commented Nov 18, 2023

Allright @nojaf @nhirschey i think this is ready for a first review.

I took #875 as an opportunity to get a little familiar with the repo and how the cli tool works, so if anything is not in the correct place please correct me.

currently, the new fsdocs init command creates this scaffold:

└───docs
    │   Dockerfile
    │   index.md
    │   literate_sample.fsx
    │   Nuget.config
    │   _template.html
    │   _template.ipynb
    │   _template.tex
    │
    └───img
            logo.png

i think the _template.* files should be customizable via a flag, but would love some feedback before digging further.

Also, i included just a simple markdown and literate script file to have something there when you run fsdocs watch, this can also serve as a starting point for creating documentation content.

As the init command must copy-paste some of the files that are also searched in the build command, i refactored that as the InRepoLocations and InPackageLocations classes, so those can be accessed across both commands in the same way.

Happy for any feedback

@kMutagene kMutagene marked this pull request as ready for review November 18, 2023 19:48
@kMutagene
Copy link
Contributor Author

Note that this can also be easily used to init fsdocs with a custom template by also adding a fsdocs-theme.css file (see #861 ). we could add a set of curated templates to this repo so people can choose from these e.g. via fsdocs init --theme fslab

@nojaf nojaf mentioned this pull request Nov 20, 2023
Copy link
Collaborator

@nojaf nojaf left a comment

Choose a reason for hiding this comment

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

I think this isn't a bad start but I wasn't able to see it in action yet.

I also wonder if we shouldn't prompt the user a bit more about some questions.

We could use Spectr for this.

I thinking about a flow like:

> We notice that you don't have a Build.Directory.props file, do you want to create one (y/n)?

y

> Who are the authors of this project?
Barry & Dave

We could adjust a lot of the often missing substitutions this way.

Of course, we then need a --yes flag to also provide users a way to scaffold everything with all the defaults.

member this.Execute() =

let outputPath = Path.GetFullPath(this.output)
let repoRoot = Path.GetFullPath(Path.Combine(outputPath, ".."))
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a bit of an assumption you are making here. If the user is running dotnet fsdocs init in a nested subfolder, this won't be accurate.
Maybe we can run git rev-parse --show-toplevel first and use Path.GetFullPath(Path.Combine(outputPath, "..")) as fallback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So if i didn't miss anything, the build/watch commands seem to just take user input as given. Should i adjust the init command to the same behavior?

let rootOutputFolderAsGiven =
if this.output = "" then
if watch then "tmp/watch" else "output"
else
this.output

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, I think repoRoot and rootOutputFolderAsGiven are a little different.
I don't think these need to align.

(inRepoLocations.index_md_template, Path.GetFullPath(Path.Combine(initLocations.docs, "index.md")))
(inRepoLocations.literate_sample_template,
Path.GetFullPath(Path.Combine(initLocations.docs, "literate_sample.fsx"))) ]
|> List.iter (fun (src, dst) -> File.Copy(src, dst, true))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, I'm not sure if we should overwrite existing files.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How about a --force flag to opt-in to overwriting files?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah maybe. Do you think this would often be used? Running init twice?

printfn "check it out by running 'dotnet fsdocs watch' !"

0
with _ as exn ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
with _ as exn ->
with exn ->

///
/// Note that the path of these files will always be combined with the given `assemblyPath` because the cli tool will query it's own path on runtime via reflection.
/// </summary>
type InPackageLocations(relAssemblyPath) =
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can use InNugetPackageLocations maybe? I'm struggling to wrap my head around this.

printfn "Error: %s" exn.Message
1
else
printfn
Copy link
Collaborator

Choose a reason for hiding this comment

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

I got this while trying out the local tool in a different repository:
image

member _.RelAssemblyPath = relAssemblyPath

// default folder locations relative to the assembly path
member this.docs = Path.Combine(this.RelAssemblyPath, "docs") |> Path.GetFullPath
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: I'm not sure how I feel about the lowercase member names.
I get why you went with them. Maybe be we should use ticks and go for something like

member this.``docs/templates`` = ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this was my initial approach actually, but i find auto-suggestions in my IDE lacking for these members. A small issue though, i'll adjust this accordingly.

member this.docs_templates_init = Path.Combine(this.docs_templates, "init") |> Path.GetFullPath
member this.docs_img = Path.Combine(this.docs, "img") |> Path.GetFullPath
member this.docs_content = Path.Combine(this.docs, "content") |> Path.GetFullPath
member this.docs_content_img = Path.Combine(this.docs_content, "img") |> Path.GetFullPath
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also a nit but I like a custom operator when using Path.Combine extensively.
Something like let (</>) a b = Path.Combine(a, b), so we can do this.docs_content </> "img"

///
/// Note that the path of these files will always be combined with the given `assemblyPath` because the cli tool will query it's own path on runtime via reflection.
/// </summary>
type InPackageLocations(relAssemblyPath) =
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we even need two classes like this? They look so similar, if they only differ by the starting folder, we perhaps should extract that logic and reuse this type for both cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My exact thoughts initially, but the project does pack the files slightly different into the nuget package than the actual paths in the repo, see here for example:

<Content Include="..\..\docs\_template.html" PackagePath="templates" />
<Content Include="..\..\docs\_template.tex" PackagePath="templates" />
<Content Include="..\..\docs\_template.ipynb" PackagePath="templates" />

a "templates" folder does not exist in the repo, but will contain the template files in the nuget package. I guess the reason here is that in the repo, we need an intact docs folder. It just felt wrong putting this on the same class when it could contain paths that do not exist.

ensureOutputDirs ()

try
[ (inPackageLocations.template_html, initLocations.template_html)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm very opposed that we copy the _template.html file by default.
This should only be done when users are interested in creating their custom theme.
Which, in a sense we want to avoid. 90% of our user base should be able to use or customize the modern theme instead of writing one from scratch.

So, I would not include _template.html by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry I think i do not really get the difference - when the user does not put a _template.html file into their docs folder, isn't the file i am copying here the exact one that will be used by the tool per default anyways?

My rationale would be that you will need a template file once you want to make even slight changes to the layout, but want to keep most of the default file. Where would i get that file other than looking up the file in the FSharp.Formatting repo?

Copy link
Contributor Author

@kMutagene kMutagene Nov 21, 2023

Choose a reason for hiding this comment

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

On the other hand, this could be circumvented by putting a good descriptive interactive text on the setup, so users that will need to make adjustments just opt-in to copy the template file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh i think i get the issue. If we update the file for a new version of the tool, the repos that use the copied template file will have an outdated version. So i agree this should be opt-in

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, indeed. If the user doesn't have a _template.html, the one from NuGet is used.
Which will stay updated if they bump the version.

printfn "check it out by running 'dotnet fsdocs watch' !"

0
with _ as exn ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
with _ as exn ->
with exn ->

@kMutagene
Copy link
Contributor Author

kMutagene commented Nov 21, 2023

Allright a quick summary of how i would proceed:

  • Make the command interactive by default, letting the user decide what files to use accompanied by a descriptive text of what the files do and in which scenario it makes sense to use them as defaults.
  • Use the same behavior for output path as the other commands
  • More descriptive name for InNugetPackageLocations, and adjust member names with double ticks to fully represent the locations
  • Overall better defaults for the non-interactive variant of the command (no template files)

sounds good @nojaf ?

@kMutagene
Copy link
Contributor Author

I would like it more if there would be a way to unify InPackageLocations and InRepoLocations though, so it could just be DefaultLocations determined from a base path. However i think this would need adjusting the way the templates and extras are packaged into the nuget package, see my other comments

@nojaf
Copy link
Collaborator

nojaf commented Nov 21, 2023

sounds good @nojaf ?

Yup!

@kMutagene
Copy link
Contributor Author

Hey @nojaf, where should I put tests that check whether the location classes construct correct paths? None of the test projects seem to match the scope of testing 'Common' functions used across commands in the tool.

@kMutagene kMutagene marked this pull request as draft November 28, 2023 09:26
@nojaf
Copy link
Collaborator

nojaf commented Nov 28, 2023

Hmm, this does seem like a rather new area when it comes to the existing test projects.
I'm not sure really, you could create a new project I suppose.
On the other hand, will this be easy to write unit tests for?
Could you give an example of what you have in mind you want to test?

@kMutagene
Copy link
Contributor Author

you could create a new project I suppose

This is what i feared, because all the existing test projects look really arcane to me - i usually just use expecto, here it seems to be FsUnit + NUnit - i can roll with that, but every test i looked at also makes heavy use of compiler directives and i have absolutely no clue on what to use from that.

Could you give an example of what you have in mind you want to test?

A really simple case would be verifying that all default paths created from a base directory are correct

@nojaf
Copy link
Collaborator

nojaf commented Jan 2, 2024

Well, the whole solution is rather arcane, isn't it?
Anyway, I don't think all test files are that bad.

You could also consider using https://github.com/TestableIO/System.IO.Abstractions in your implementation. That could make writing such tests easier.

@kMutagene
Copy link
Contributor Author

https://github.com/TestableIO/System.IO.Abstractions

oh wow, that will come in handy in other projects as well, thanks! I'll copy stuff together until i have working tests i guess ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants