Coder Social home page Coder Social logo

fsharp.data.literalproviders's Introduction

FSharp.Data.LiteralProviders

Build Nuget

This is a collection of type providers that provide literals: compile-time constants that can be used in regular code, but also as parameters to other type providers or .NET attributes.

Reference

Env

FSharp.Data.LiteralProviders.Env contains literals for environment variables during compile time.

open FSharp.Data.LiteralProviders

/// The compile-time value of the "OS" environment variable
let compileOS = Env.OS.Value

match compileOS with
| "Windows_NT" -> printfn "This program was compiled on Windows!"
| "Unix" -> printfn "This program was compiled on OSX or Linux!"
| _ -> printfn "I don't know the platform this program was compiled on :("

Here is a more useful example, using it as a parameter to another type provider (namely, SQLProvider):

open FSharp.Data.Sql
open FSharp.Data.LiteralProviders

type Sql = SqlProvider<Common.DatabaseProviderTypes.MSSQLSERVER,
                       Env.CONNECTION_STRING.Value>

Note that when called this way, Env fails to compile if the environment variable is not set.

Alternatively, the environment variable's name can be passed as a string parameter. In this case, Env returns the empty string if the variable is not set.

open FSharp.Data.LiteralProviders

let vsVersion = Env<"VisualStudioEdition">.Value

match vsVersion with
| "" -> printfn "This program wasn't compiled with Visual Studio."
| v -> printfn "This program was built with Visual Studio %s." v

When used with a parameter, Env also provides a value IsSet : bool

Additional parameters can be passed:

  • DefaultValue : string will be used as the value if the environment variable isn't set, instead of the empty string.

    open FSharp.Data.Sql
    open FSharp.Data.LiteralProviders
    
    let [<Literal>] connString =
        Env<"CONNECTION_STRING", "Server=localhost;Integrated Security=true">.Value
    
    type Sql = SqlProvider<Common.DatabaseProviderTypes.MSSQLSERVER, connString>
  • EnsureExists : bool specifies the behavior when the environment variable isn't set.

    If false (the default), then Value is an empty string (or DefaultValue if provided).

    If true, then the type provider raises a compile-time error.

    /// Throws a compile-time error "Environment variable does not exist: CONNECTION_STRING".
    let [<Literal>] connString = Env<"CONNECTION_STRING", EnsureExists = true>.Text

TextFile

FSharp.Data.LiteralProviders.TextFile contains literals that are read from text files during compilation.

open FSharp.Data.LiteralProviders

/// The compile-time contents of the file <projectFolder>/build/version.txt
let [<Literal>] version = TextFile.build.``version.txt``.Text

Alternatively, the file path can be passed as a string parameter. In this case, TextFile returns the empty string if the file doesn't exist.

open FSharp.Data.LiteralProviders

/// The compile-time contents of the file <projectFolder>/build/version.txt
/// or "" if this file doesn't exist.
let [<Literal>] version = TextFile<"build/version.txt">.Text

Additional parameters can be passed:

  • DefaultValue : string will be used as the value if the file doesn't exist, instead of the empty string.

    open FSharp.Data.LiteralProviders
    
    /// The compile-time contents of the file <projectFolder>/build/version.txt
    /// or "1.0" if this file doesn't exist.
    let [<Literal>] version = TextFile<"build/version.txt", DefaultValue = "1.0">.Text
  • Encoding : string specifies the text encoding.

    The possible values are UTF-8, UTF-16-le, UTF-16-be, UTF-32-le and UTF-32-be.

    When not specified, TextFile tries to guess the encoding.

    open FSharp.Data.LiteralProviders
    
    let [<Literal>] script = TextFile<"LoadData.sql", Encoding = "UTF-16-le">.Text

    Note: regardless of the encoding, if the file starts with a byte order mark, then the BOM is stripped from the string.

  • EnsureExists : bool specifies the behavior when the file doesn't exist.

    If false (the default), then the Text value is an empty string (or DefaultValue if provided).

    If true, then the type provider raises a compile-time error.

    /// Throws a compile-time error "File does not exist: fileThatDoesntExist.txt".
    let [<Literal>] test = TextFile<"fileThatDoesntExist.txt", EnsureExists = true>.Text

Exec

FSharp.Data.LiteralProviders.Exec executes an external program during compilation and captures its output.

open FSharp.Data.LiteralProviders

let [<Literal>] currentBranch = Exec<"git", "branch --show-current">.Output

Additional parameters can be passed:

  • Input: string: text that is passed to the program's standard output.

  • Directory: string: the working directory. The default is the project directory.

  • EnsureSuccess: bool: if true, the provider ensures that the program exits successfully, and fails otherwise.
    If false, no error is raised.
    The default is true.

  • Timeout: int: timeout in milliseconds. Raise an error if the program takes longer to finish.
    The default is 10_000 (10 seconds).

The following values are provided:

  • Output: string: the program's standard output.

  • Error: string: the program's standard error.

  • ExitCode: int: the program's exit code. Only useful with EnsureSuccess = false, otherwise always 0.

Conditionals

FSharp.Data.LiteralProviders contains sub-namespaces String, Int and Bool for conditional operations on these types.

Equality

The providers EQ and NE contain Value: bool that checks whether the two parameters are equal / not equal, respectively.

open FSharp.Data.LiteralProviders

let [<Literal>] branch = Exec<"git", "branch --show-current">.Output

let [<Literal>] isMaster = String.EQ<branch, "master">.Value

Comparison

In sub-namespace Int, the providers LT, LE, GT and GE contain Value: bool that checks whether the first parameter is less than / less than or equal / greater than / greater than or equal to the second parameter, respectively.

open FSharp.Data.LiteralProviders

let [<Literal>] gitStatusCode = Exec<"git", "status", EnsureSuccess = false>.ExitCode

let [<Literal>] notInGitRepo = Int.GT<gitStatusCode, 0>.Value

Boolean operations

In sub-namespace Bool, the providers AND, OR, XOR and NOT contain Value: bool that performs the corresponding boolean operation on its parameter(s).

open FSharp.Data.LiteralProviders

type GithubAction = Env<"GITHUB_ACTION">

let [<Literal>] isLocalBuild = Bool.NOT<GithubAction.IsSet>.Value

If

The provider IF takes a condition and two values as parameters. It returns the first value if the condition is true, and the second value if the condition is false.

open FSharp.Data.LiteralProviders

let [<Literal>] versionSuffix = String.IF<isMaster, "", "-pre">.Value

Note that even though only one value is returned, both are evaluated. So if one branch fails, even though the other one is returned, the whole provider will fail.

open FSharp.Data.LiteralProviders

let [<Literal>] isCI = Env<"CI", "false">.ValueAsBool

// The following will fail, because when CI is false, GITHUB_REF_NAME is not defined.
let [<Literal>] badRef =
    String.IF<isCI,
        Env.GITHUB_REF_NAME.Value,
        const Exec<"git", "branch --current">.Value>.Value

// Instead, make sure to use a version that never fails.
// Here, Env returns an empty string if GITHUB_REF_NAME is not defined.
let [<Literal>] goodRef =
    String.IF<isCI,
        Env<"GITHUB_REF_NAME">.Value,
        const Exec<"git", "branch --current">.Value>.Value

// Even better, avoid using IF if you can achieve the same result with default values.
// For example, here, no need to check the CI variable:
// GITHUB_REF_NAME is set iff compiling on Github Actions anyway.
// So you can directly use GITHUB_REF_NAME, with `git branch` as default value.
let [<Literal>] betterRef =
    Env<"GITHUB_REF_NAME", const Exec<"git", "branch --current">.Value>.Value

BuildDate

FSharp.Data.LiteralProviders.BuildDate contains the build time as a literal string in ISO-8601 format ("o" format).

open FSharp.Data.LiteralProviders

let utcBuildDate = BuildDate.Utc      // "2019-08-24T19:45:03.2279236Z"
let localBuildDate = BuildDate.Local  // "2019-08-24T21:45:03.2279236+02:00"

It can be optionally parameterized by a date format.

open FSharp.Data.LiteralProviders

let buildTime = BuildDate<"hh:mm:ss">.Utc  // "21:45:03"

Parsed value

The providers try to parse string values as integer and as boolean. If any of these succeed, a value suffixed with AsInt or AsBool is provided.

open FSharp.Data.LiteralProviders

let runNumber = Env<"GITHUB_RUN_NUMBER">.Value // eg. "42"

let runNumber = Env<"GITHUB_RUN_NUMBER">.ValueAsInt // eg. 42

The following values are parsed this way:

  • Env.Value
  • TextFile.Text
  • Exec.Output
  • Exec.Error

Tips for combining type providers

One of the main use cases for FSharp.Data.LiteralProviders is to provide a literal to pass to another type provider. There are several ways to do so:

  • Declare each TP with a type alias:

    type ConnectionString = Env<"CONNECTION_STRING">
    
    type Sql = SqlProvider<Common.DatabaseProviderTypes.MSSQLSERVER, ConnectionString.Value>
  • Declare a TP's value as Literal then pass it to another TP:

    let [<Literal>] ConnectionString = Env<"CONNECTION_STRING">.Value
    
    type Sql = SqlProvider<Common.DatabaseProviderTypes.MSSQLSERVER, ConnectionString>
  • To use a TP entirely inside a parameter of another TP, prefix it with the keyword const:

    type Sql = SqlProvider<Common.DatabaseProviderTypes.MSSQLSERVER,
                           const Env<"CONNECTION_STRING">.Value>

Packaging

FSharp.Data.LiteralProviders is a compile-time only package: all of its provided values are baked into the compiled assembly. This means that if you are writing a library that uses FSharp.Data.LiteralProviders, your downstream users don't need to depend on it.

Here is how to exclude FSharp.Data.LiteralProviders from your NuGet dependencies.

Using NuGet

If you are using dotnet's built-in package management, then in your project file, replace the following:

<PackageReference Include="FSharp.Data.LiteralProviders" Version="..." />

with:

<PackageReference Include="FSharp.Data.LiteralProviders" Version="...">
    <PrivateAssets>All</PrivateAssets>
</PackageReference>

Using Paket

If you are packaging your library with paket pack, add the following to your paket.template:

excludeddependencies
    FSharp.Data.LiteralProviders

fsharp.data.literalproviders's People

Contributors

swoorup avatar tarmil avatar thinkbeforecoding avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fsharp.data.literalproviders's Issues

Exec is always retuning empty string.

I tried all examples regarding exec in MacOs, always getting empty string with no errors. Any ides

type DotnetListReference = Exec<"dotnet", "list reference">

printfn "test %s" DotnetListReference.Output

Exec: option to hide process window

Typing code is sometimes interrupted when windows are opened so that new literals can be established for Exec. To avoid this, it would be helpful if there was an option for Exec to not show a process window.

Feature request: fully qualified nameof

Background: https://stackoverflow.com/a/40856014/

The proposed trick only applies to C# (whose "constant expressions" include concatenations like nameof(Namespace) + "." + nameof(Namespace.Class))
Sadly we can't do the same with F# when you want some fully qualified type name to feed to e.g. [<assembly:AutoOpen(...)>]

If it's straightforward to add such a functionality to your Literal Providers suite, it would be awesome.

Fable support

First, super cool - think this is the only type provider I'd use in a production app.

The following code compiles fine in .net 6 but fails when compiled with fable.

module LanguageInfo = 
    let [<Literal>] json_blob = FSharp.Data.LiteralProviders.TextFile.``Language_ISO_list.json``.Text
error FSHARP: This is not a valid constant expression or custom attribute value (code 267)
error FSHARP: This is not a valid constant expression (code 837)
error FSHARP: The type 'TextFile' does not define the field, constructor or member 'Language_ISO_list.json'. (code 39)
error FSHARP: The block following this 'let' is unfinished. Every code block is an expression and must have a result. 'let' cannot be the final code element in a block. Consider giving this block an explicit result. (code 588)

I want a parameter in `TextFile` that will cause a compile error if the file specified in` Path` does not exist.

In the current implementation of TextFile, DefaultValue is used for the Text member if the file specified in Path does not exist. If DefaultValue is not specified, it will be an empty string.

If the contents of the file should always be used, but a non-existent file path is specified (for example, due to a mistake in the file path, a change in the file name, or a difference in the build environment), empty string is used as DefaultValue and no error occurs. This can cause unexpected bugs.

I think there is a way, for example, to add a bool type parameter named UseDefaultValue and raise an exception if the file is not found and this parameter is false.

Feature Request, TP based sprintf

Since this package is named literal providers, would it feasible to add sprintf like functionality using and get a string literal?

I am not entirely sure if it is possible to add specify varying arguments to TP though?

let [<Literal>] string = SPrintfTP<"Hello %s", "blalal">

Binary Files Support

This might sound ludicrous but I'm thinking about creating a PR in order to add the support for binary files which imho, still add some benefits over embedded resources.

Will create type yielding fields like akin to the TextFile generation:

  • Name
  • Path
  • Bytes

Wdyt?

[EDIT]
Just realized that it cannot really fit the "Literals" part of the project, thought about a default Encoding (eg. utf8) but it's a bit far fetched.

Since there is already TextFile, that would make this (idea of) TP a tad redundant.

Feature request: info from git

It would be cool to be able to include the branch name if on a branch, the tagname if there is a tag on the current commit, the commit hash, and also the number of uncommitted changes.

Exec<...>.Output does not preserve newlines in output

I love this library (just discovered it)! I can now get rid of some hacky MSBUILD stuff.

However, it seems the Exec Output property does not maintain newlines. I guess this is because the code uses StringBuilder.Append rather than StringBuilder.AppendLine:

proc.OutputDataReceived.Add(fun e -> output.Append(e.Data) |> ignore)

I have a use case like this:

let [<Literal>] PendingChanges = Exec<"git", "diff HEAD --name-only">.Output

Filenames are then concatenated into an alphabet soup. ๐Ÿ™‚

Should we emit a dummy Output when a command fails to execute?

Currently it's impossible to do something like this because one of the Execs will always fail:

let [<Literal>] IsUnix = String.EQ<Env.OS.Value, "Unix">.Value

// Dumb example of a command that needs to be different on Windows or Unix.
let [<Literal>] FileListing =
    String.IF<IsUnix,
        const Exec<"ls", "-la">.Output,
        const Exec<"cmd", "/c dir">.Output>.Value

Emitting a dummy Output property when the command fails to execute would make the above possible. But it may make the cause of an error harder to find in other unintended cases.

Some options I'm considering:

  1. Always emit Output, with a null or empty value if the command failed to run.
  2. Add a new parameter eg NullOnFailure = true that decides whether to emit a null Output if the command failed to run (false by default).
  3. Always emit Output, and when the command fails to run, its value is the exception message.

I'm leaning towards option 2: it makes the above example more verbose, but the basic use case remains as safe as today.

Licence?

This is a useful project.
Have you considered adding a licence?

Error when reading SQL files

I tried to use Text literal provider to load Sql files for queries using Dapper in F#. But I am having a strange behavior where the Sql file is loaded with a syntax error, altough the Sql file itself runs fine.

Trying to understand the problem, I copied the value of the Literal produced by the provider and pasted it into Sql Management Studio and tried executing it. I got the same syntax error: SqlException: Incorrect syntax near '๏ปฟ'.

Trying to understand the reason, I noticed that the Provider loads the Sql file with an unknown character before the first character. For example, a file with a content like select * from dbo.Customers is loaded with some unrecognized character before the s from select keyword.

To reproduce the problem, you can simply try loading a Sql script file into the provider and try executing the literal generated by it's text into SqlCommand .

For now, the workaround I am using is calling Substring(1) after the provider literal Text:

let sql = TextFile<"Implementations/GetParameterValue.sql">.Text.Substring(1)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.