Showing preview only (1,678K chars total). Download the full file or copy to clipboard to get everything.
Repository: zanaptak/TypedCssClasses
Branch: main
Commit: ee7a84f3314f
Files: 77
Total size: 1.6 MB
Directory structure:
gitextract_1rqpensr/
├── .editorconfig
├── .gitattributes
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── doc/
│ └── configuration.md
├── invoke.build.ps1
├── sample/
│ ├── FableSass/
│ │ ├── .config/
│ │ │ └── dotnet-tools.json
│ │ ├── .editorconfig
│ │ ├── .gitignore
│ │ ├── FableSass.fsproj
│ │ ├── FableSass.sln
│ │ ├── README.md
│ │ ├── content/
│ │ │ ├── _base.sass
│ │ │ ├── index.html
│ │ │ └── styles.sass
│ │ ├── package.json
│ │ ├── sass-process.js
│ │ ├── src/
│ │ │ ├── App.fs
│ │ │ └── Main.fs
│ │ └── webpack.config.js
│ └── FableTailwind/
│ ├── .config/
│ │ └── dotnet-tools.json
│ ├── .editorconfig
│ ├── .gitignore
│ ├── FableTailwind.fsproj
│ ├── FableTailwind.sln
│ ├── README.md
│ ├── content/
│ │ ├── index.html
│ │ └── tailwind-source.css
│ ├── package.json
│ ├── postcss.config.js
│ ├── src/
│ │ ├── App.fs
│ │ └── Main.fs
│ ├── tailwind-process.js
│ ├── tailwind.config.js
│ └── webpack.config.js
├── src/
│ ├── CssClassesTypeProvider.fs
│ ├── Fable.Core.fs
│ ├── TypedCssClasses.fsproj
│ ├── TypedCssClasses.sln
│ ├── Types.fs
│ ├── Utils.fs
│ └── vendor/
│ ├── FSharp.Data/
│ │ ├── Caching.fs
│ │ ├── Helpers.fs
│ │ ├── Http.fs
│ │ ├── IO.fs
│ │ └── LICENSE.md
│ ├── FSharp.TypeProviders.SDK/
│ │ ├── LICENSE.md
│ │ ├── ProvidedTypes.fs
│ │ └── ProvidedTypes.fsi
│ └── README.md
└── test/
├── TestWithFable/
│ ├── .gitignore
│ ├── README.md
│ ├── TestWithFable.sln
│ ├── package.json
│ ├── public/
│ │ └── index.html
│ ├── src/
│ │ ├── TestWithFable.fsproj
│ │ ├── folder/
│ │ │ ├── folder.fs
│ │ │ ├── folder1.css
│ │ │ └── folder2.css
│ │ ├── import/
│ │ │ ├── import.fs
│ │ │ ├── import.js
│ │ │ ├── import1.css
│ │ │ └── import2.css
│ │ ├── main.css
│ │ ├── main.fs
│ │ └── sass/
│ │ ├── sass.fs
│ │ └── sass.sass
│ └── webpack.config.js
└── TypedCssClasses.Tests/
├── Tests.fs
├── TypedCssClasses.Tests.fsproj
├── TypedCssClasses.Tests.sln
└── testdata/
├── bootstrap-431-classes-reference.txt
├── bootstrap-431-min-css.txt
├── tailwind-10-classes-reference.txt
└── tailwind-10-min-css.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
[*.{sln,fsproj,csproj,vbproj}]
charset = utf-8-bom
indent_size = 2
================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
================================================
FILE: .gitignore
================================================
*.log.*
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
================================================
FILE: CHANGELOG.md
================================================
# Changelog - Zanaptak.TypedCssClasses
[](https://github.com/zanaptak/TypedCssClasses) [](https://www.nuget.org/packages/Zanaptak.TypedCssClasses)
## 1.0.0 (2021-07-30)
- Add [`fableCssModule`](https://github.com/zanaptak/TypedCssClasses/blob/main/doc/configuration.md#fablecssmodule) parameter for CSS Module support in Fable projects ([#11](https://github.com/zanaptak/TypedCssClasses/pull/11)) @alfonsogarciacaro
- Fix stale cache data when changing `naming` and `nameCollisions` parameters ([#12](https://github.com/zanaptak/TypedCssClasses/issues/12))
## 0.4.0 (2020-07-26)
- Add caching of parsed results from files and process execution ([#7](https://github.com/zanaptak/TypedCssClasses/issues/7), [#8](https://github.com/zanaptak/TypedCssClasses/pull/8))
- Change from properties to fields for better completion list performance
- See [note on performance](https://github.com/zanaptak/TypedCssClasses/pull/8#issue-456779399) with large class counts
## 0.3.1 (2020-07-16)
- Add process id to log messages
## 0.3.0 (2020-07-14)
- Add configurable [external command](https://github.com/zanaptak/TypedCssClasses/blob/main/doc/configuration.md#external-command-support-for-css-preprocessing) capability for CSS preprocessing ([#4](https://github.com/zanaptak/TypedCssClasses/issues/4))
- Support environment variables in parameters
- Support OS-specific values in parameters
- Add [`logFile`](https://github.com/zanaptak/TypedCssClasses/blob/main/doc/configuration.md#logfile) option
- Enable Source Link
- __Breaking change__: Target .NET Standard 2.0 only, per Type Provider SDK guidance (may affect very old build chains)
- __Breaking change__: The environment variable and OS-specific parameter support may affect existing parameter values if they use the same syntax. See the [`osDelimiters`](https://github.com/zanaptak/TypedCssClasses/blob/main/doc/configuration.md#osdelimiters) and [`expandVariables`](https://github.com/zanaptak/TypedCssClasses/blob/main/doc/configuration.md#expandvariables) options to address this.
## 0.2.0 (2019-09-27)
- Add [`nameCollisions`](https://github.com/zanaptak/TypedCssClasses/blob/main/doc/configuration.md#namecollisions) parameter for handling duplicate property names ([#1](https://github.com/zanaptak/TypedCssClasses/issues/1))
## 0.1.0 (2019-08-21)
- Packaging update (no change in functionality):
- Remove System.ValueTuple dependency
## 0.0.3 (2019-08-10)
- Add [`getProperties`](https://github.com/zanaptak/TypedCssClasses/blob/main/doc/configuration.md#getproperties) option providing seq of generated properties, useful for code generation
- Add .NET Framework target
## 0.0.2 (2019-08-06)
- Recognize classes inside `@supports` rules
- Provide duplicate property names with `_2`, `_3`, etc. suffixes
- Preserve non-ASCII letters with `Naming.Underscores`
- Add `_` prefix to names with invalid identifier start characters (except with `Naming.Verbatim`)
- Add `_` suffix to names that are F# keywords (except with `Naming.Verbatim`)
## 0.0.1 (2019-07-29)
- Initial release
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 zanaptak
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.
================================================
FILE: README.md
================================================
# Zanaptak.TypedCssClasses
[](https://github.com/zanaptak/TypedCssClasses) [](https://www.nuget.org/packages/Zanaptak.TypedCssClasses)
A CSS class type provider for F# web development.
Bring external stylesheet classes into your F# code as design-time discoverable compiler-verified properties.
## Code examples
The following code examples show how the type provider enables type-safe CSS classes. Anywhere you would have previously used a class name string, you can now use something like a `Bootstrap`-, `FA`-, or `tw`-prefixed property, with your IDE providing completion for valid classes.
The syntax in these examples is [Fable.React](https://fable.io/blog/Announcing-Fable-React-5.html) view syntax, but any web framework can be used because the provided properties just compile to the underlying class names as strings.
### Bootstrap CSS
```fs
// A "Bootstrap" type pointing at a remote Bootstrap CSS file.
type Bootstrap = CssClasses<"https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css", Naming.PascalCase>
// All CSS classes become Bootstrap.* properties, with design-time completion.
div [ ClassName Bootstrap.Card ] [
div [ ClassName Bootstrap.CardBody ] [
h5 [ ClassName Bootstrap.CardTitle ] [ str "A clever title" ]
p [ ClassName Bootstrap.CardText ] [ str "Lorem ipsum dolor sit amet." ]
]
]
```
### Font Awesome CSS
```fs
// An "FA" type pointing at a local Font Awesome CSS file.
type FA = CssClasses<"static/font-awesome/css/all.css", Naming.Underscores>
// All CSS classes become FA.* properties, with design-time completion.
i [ classList [
FA.far, true
FA.fa_grin, true ] ] []
i [ classList [
FA.fas, true
FA.fa_thumbs_up, true ] ] []
```
### Tailwind CSS
```fs
// A "tw" type pointing at a remote Tailwind CSS file.
type tw = CssClasses<"https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css", Naming.Verbatim>
// All CSS classes become tw.* properties, with design-time completion.
div [ ClassName <| String.concat " " [
tw.``bg-red-200``
tw.``hover:bg-red-500``
tw.``hover:font-bold``
tw.``sm:text-2xl``
tw.``sm:bg-green-200``
tw.``sm:hover:bg-green-500``
tw.``lg:text-4xl``
tw.``lg:bg-blue-200``
tw.``lg:hover:bg-blue-500`` ]
] [ str "Resize me! Hover me!" ]
```
## Samples
Preconfigured sample projects to see it in action and use as a starting point:
- [Fable Sass sample](https://github.com/zanaptak/TypedCssClasses/tree/main/sample/FableSass) - Demonstrates TypedCssClasses with Sass compilation in a Fable project.
- [Fable Tailwind sample](https://github.com/zanaptak/TypedCssClasses/tree/main/sample/FableTailwind) - Demonstrates TypedCssClasses with Tailwind CSS in a Fable project.
## Getting started
Add the [NuGet package](https://www.nuget.org/packages/Zanaptak.TypedCssClasses) to your project:
```
dotnet add package Zanaptak.TypedCssClasses
```
If the project was already open in an IDE, you might want to close and restart it to make sure the type provider loads into the process correctly.
Write some code:
```fs
open Zanaptak.TypedCssClasses
// Define a type for a CSS source.
// Can be file path, web URL, or CSS text.
type css = CssClasses<"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
// Enjoy your typed properties!
let x = css.``display-1``
```
It should work with any IDE that supports type providers. (Tested in Visual Studio Code with Ionide-fsharp extension on Windows and Linux, and in Visual Studio on Windows.)
## Configuration
See the [configuration instructions](https://github.com/zanaptak/TypedCssClasses/blob/main/doc/configuration.md) for full details on configuration parameters to customize the behavior of the type provider.
## Notes
This type provider does not use formal CSS parsing; it identifies classes by typical selector patterns using (somewhat complex) regular expressions. It tests fine against several major CSS frameworks but is not guaranteed foolproof in case there is some obscure CSS syntax not accounted for.
Web URLs are expected to use static CDN or otherwise unchanging content and are cached on the filesystem with a 90-day expiration. If tracking of CSS changes is required, you must use local CSS files in your project.
If using Fable 2.x, update fable-compiler to version 2.3.17 or later to avoid an issue with the type provider failing to resolve relative file paths.
CSS `@import` rules are not processed internally by the type provider. If desired, they can be processed via external command; see the [TestWithFable test project](https://github.com/zanaptak/TypedCssClasses/tree/main/test/TestWithFable) for an example using [PostCSS](https://postcss.org/) with the [postcss-import](https://github.com/postcss/postcss-import) plugin.
================================================
FILE: doc/configuration.md
================================================
# Configuration - Zanaptak.TypedCssClasses
[](https://github.com/zanaptak/TypedCssClasses) [](https://www.nuget.org/packages/Zanaptak.TypedCssClasses)
## Getting started
Add the [NuGet package](https://www.nuget.org/packages/Zanaptak.TypedCssClasses) to your project:
```
dotnet add package Zanaptak.TypedCssClasses
```
If the project was already open in an IDE, you might want to close and restart it to make sure the type provider loads into the process correctly.
Write some code:
```fs
open Zanaptak.TypedCssClasses
// Define a type for a CSS source.
// Can be file path, web URL, or CSS text.
type css = CssClasses<"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
// Enjoy your typed properties!
let x = css.``display-1``
```
It should work with any IDE that supports type providers. (Tested in Visual Studio Code with Ionide-fsharp extension on Windows and Linux, and in Visual Studio on Windows.)
## External command support for CSS preprocessing
The internal logic of this type provider requires standard CSS syntax to extract class names from. For alternate syntaxes (such as Sass/SCSS), you can specify an external command to compile the source into standard CSS with the `commandFile` parameter:
```
// Assuming a "sass" command is available in PATH.
type css = CssClasses<"example.sass", commandFile="sass">
```
The command can be an executable file in your PATH, a file path relative to the resolution folder (see [`resolutionFolder`](#resolutionfolder) parameter), or an absolute path. The working directory of the process will be the resolution folder of the type provider. On Windows, the .exe extension can be omitted, but other extensions such as .cmd/.bat must be specified.
The `source` parameter will be passed as an argument to the command. The standard output returned by the command will be used as the CSS data for extracting class names. A non-zero exit code returned by the command indicates an error and the standard error text will be reported as an exception.
In addition to the CSS output, the command can optionally return leading lines to indicate additional files to watch beyond the `source` parameter (see [File watching](#file-watching) section below), such as Sass partials. Any initial full line that exactly matches a local path will be interpreted as a file to watch; the type provider will only start processing CSS on the first line that doesn't match a local path. You would likely accomplish this via custom scripts; see the sample projects for examples.
Two additional parameters are available to further customize the command: `argumentPrefix` and `argumentSuffix`. If specified, these strings will be placed before and after the `source` argument, separated by a space. That is, the full command will be the result of the following parameters:
```
commandFile [argumentPrefix ][source][ argumentSuffix]
```
Source and arguments are concatenated as-is (after OS-specific and environment variable processing described below); you are responsible for any quoting or escaping if necessary.
## Multiple development environments
To support development in different environments, you can include operating system-specific alternatives in parameters using comma-separated OS=value pairs after an initially-specified default, in the form of `defaultvalue,OS1=value1,OS2=value2`:
```
// Use "sass.cmd" on Windows, "sass" everywhere else.
type css = CssClasses<"example.sass", commandFile="sass,Windows=sass.cmd">
```
Supported values are (case-insensitive): `FREEBSD`, `LINUX`, `OSX`, and `WINDOWS`. (Based on [OSPlatform.Create()](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.osplatform.create?view=netstandard-2.0) as of .NET Standard 2.0.)
If this syntax conflicts with your parameter values, you can use the `osDelimiters` parameter. It takes a two-character string specifying alternatives for the comma and the equals sign in that order, or an empty string to disable OS parsing.
## Environment variables
You can use environment variables in parameters using `%VARIABLE%` syntax. Note that the Windows-style `%...%` syntax must be used, even for non-Windows systems (e.g. Linux must use `%HOME%` rather than `$HOME`). Variables not found in the environment will not be processed and the original syntax will be left in the string.
You can disable environment variable expansion by setting the `expandVariables` parameter to false.
## File watching
If the `source` parameter specifies a local file, the type provider will monitor the file and refresh the CSS classes when it changes (rerunning any specified command if applicable).
If a `commandFile` is specified, any leading lines from the output of the command that exactly specify a local file path will also be watched.
## Parameters
### source
The source CSS to process. Can be a file path, web URL, or CSS text.
### naming
* `Naming.Verbatim`: (default) use class names verbatim from source CSS, requiring backtick-quotes for names with special characters.
* `Naming.Underscores`: replace all non-alphanumeric characters with underscores.
* `Naming.CamelCase`: convert to camel case names with all non-alphanumeric characters removed.
* `Naming.PascalCase`: convert to Pascal case names with all non-alphanumeric characters removed.
Note that non-verbatim naming options can produce name collisions. See the [`nameCollisions`](#nameCollisions) parameter for details.
### resolutionFolder
A custom folder to use for resolving relative file paths. The default is the project root folder.
To have nested code files referencing CSS files in the same directory without having to specify the entire path from project root, you can use the built-in F# `__SOURCE_DIRECTORY__` value:
```fs
type css = CssClasses<"file-in-same-dir.css", resolutionFolder=__SOURCE_DIRECTORY__>
```
### getProperties
If true, the type will include a `GetProperties()` method that returns a sequence of the generated property name/value pairs. This can be used, for example, to generate hard-coded CSS class bindings via `.fsx` script:
```fs
// Before release of F# 5, use preview flag: dotnet fsi --langversion:preview SCRIPT_NAME.fsx
#r "nuget: Zanaptak.TypedCssClasses"
open Zanaptak.TypedCssClasses
type css = CssClasses<"https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css", Naming.PascalCase, getProperties=true>
printfn "module Bootstrap"
css.GetProperties() |> Seq.iter (fun p -> printfn "let [<Literal>] %s = \"%s\"" p.Name p.Value)
```
Example output:
```fs
module Bootstrap
let [<Literal>] Accordion = "accordion"
let [<Literal>] Active = "active"
let [<Literal>] Alert = "alert"
let [<Literal>] AlertDanger = "alert-danger"
...
```
### nameCollisions
If a naming option produces collisions, such as `card-text` and `card_text` CSS classes both mapping to a `CardText` property in Pascal case, then the duplicate names will be handled according to this option.
- `NameCollisions.BasicSuffix`: (default) The base property name will refer to the closest text match, and additional properties will receive `_2`, `_3`, etc. suffixes as needed. Note that if this option is used during ongoing CSS development, it can cause existing properties to silently change to refer to different classes if collisions are introduced that affect the base name and number determination.
- `NameCollisions.ExtendedSuffix`: All property names involved in a collision will receive an extended numbered suffix such as `__1_of_2`, `__2_of_2`. This option is safer for ongoing development since any introduced collision will change all involved names and produce immediate compiler errors where the previous names were used.
- `NameCollisions.Omit`: All colliding properties will be omitted from the generated type. This option is safer for ongoing development since any introduced collision will remove the original property and produce immediate compiler errors wherever it was used.
### logFile
Path to a log file the type provider should write to.
### commandFile
An executable file to run that will process the `source` file before extracting CSS class names. See [External command support](#external-command-support-for-css-preprocessing).
### argumentPrefix
An argument string to include before the `source` parameter, separated by a space, when running a command. Only applicable when `commandFile` is specified.
### argumentSuffix
An argument string to include after the `source` parameter, separated by a space, when running a command. Only applicable when `commandFile` is specified.
### osDelimiters
A two-character string specifying the delimiter characters used to indicate operating system-specific parameter values. Default is `,=` as in `defaultvalue,OS1=value1,OS2=value2`. If set to empty string, disables parsing for OS values.
Applies to parameters: `source`, `resolutionFolder`, `logFile`, `commandFile`, `argumentPrefix`, `argumentSuffix`
### expandVariables
Boolean to indicate whether evironment variables in the form of `%VARIABLE%` in parameter values should be expanded. Default true.
Applies to parameters: `source`, `resolutionFolder`, `logFile`, `commandFile`, `argumentPrefix`, `argumentSuffix`
### fableCssModule
In a Fable project, this parameter causes the provided properties to be compiled to Fable import expressions instead of class name strings, allowing processing as a [CSS Module](https://github.com/css-modules/css-modules). (This only affects the type provider property substitutions; you must also configure your JavaScript bundler for CSS Module support.)
Enabling this option will cause the `source` parameter to be used in a JavaScript `import` statement after Fable compilation, so it must be specified in a way that both the type provider and the JavaScript bundler can resolve. It is recommended to use a path relative to the source file, with current directory dot prefix, as follows:
```fs
type css = CssClasses<"./style.module.css", resolutionFolder=__SOURCE_DIRECTORY__, fableCssModule=true>
```
================================================
FILE: invoke.build.ps1
================================================
# Install PowerShell Core:
# https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell
# Install Invoke-Build:
# https://github.com/nightroman/Invoke-Build
# (Optional) Add "Set-Alias ib Invoke-Build" to your PS profile.
# At a PS prompt, run any build task (optionally use "ib" alias):
# Invoke-Build build
# Invoke-Build ? # this lists available tasks
param (
$NuGetApiPushKey = ( property NuGetApiPushKey 'MISSING' ) ,
$LocalPackageDir = ( property LocalPackageDir 'C:/code/LocalPackages' ) ,
$Configuration = "Release"
)
[ System.Environment ]::CurrentDirectory = $BuildRoot
$baseProjectName = "TypedCssClasses"
$basePackageName = "Zanaptak.$baseProjectName"
$mainProjectFilePath = "src/$baseProjectName.fsproj"
function trimLeadingZero {
param ( $item )
$item = $item.TrimStart( '0' )
if ( $item -eq "" ) { "0" } else { $item }
}
function combinePrefixSuffix {
param ( $prefix , $suffix )
"$prefix-$suffix".TrimEnd( '-' )
}
function writeProjectFileProperty {
param ( $projectFile , $propertyName , $propertyValue )
$xml = New-Object System.Xml.XmlDocument
$xml.PreserveWhitespace = $true
$xml.Load( $projectFile )
$nodePath = '/Project/PropertyGroup/' + $propertyName
$node = $xml.SelectSingleNode( $nodePath )
$node.InnerText = $propertyValue
$settings = New-Object System.Xml.XmlWriterSettings
$settings.OmitXmlDeclaration = $true
$settings.Encoding = New-Object System.Text.UTF8Encoding( $true )
$writer = [ System.Xml.XmlWriter ]::Create( $projectFile , $settings )
try {
$xml.Save( $writer )
} finally {
$writer.Dispose()
}
}
function readProjectFileProperty {
param ( $projectFile , $propertyName )
$nodePath = '/Project/PropertyGroup/' + $propertyName
$propertyValue =
Select-Xml -Path $projectFile -XPath $nodePath |
Select-Object -First 1 |
& { process { $_.Node.InnerXml.Trim() } }
$propertyValue
}
function changelogTopVersionAndDate {
$topSectionVersion = ""
$topSectionDate = ""
foreach ( $line in ( Get-Content .\CHANGELOG.md ) ) {
$versionMatch = ( [ regex ] "^#+ (\d+(?:\.\d+){2,}(?:-\S+)?) \((\S+)\)" ).Match( $line )
if ( $versionMatch.Success ) {
$topSectionVersion , $topSectionDate = $versionMatch.Groups[ 1 ].Value , $versionMatch.Groups[ 2 ].Value
break
}
}
$topSectionVersion , $topSectionDate
}
task . Build
task Clean {
exec { dotnet clean ./src -c $Configuration }
}
task Build {
exec { dotnet build ./src -c $Configuration }
}
task Test {
$script:Configuration = "ReleaseTest"
} , Clean , Build , {
exec { dotnet test ./test/TypedCssClasses.Tests -c $Configuration }
}
task IncrementMajor LoadVersion , {
$version = [ System.Version ] $VersionPrefix
$newVersionPrefix = [ System.Version ]::new( ( $version.Major + 1 ) , 0 , 0 ).ToString( 3 )
writeProjectFileProperty $mainProjectFilePath "VersionPrefix" $newVersionPrefix
writeProjectFileProperty $mainProjectFilePath "VersionSuffix" ""
} , ReportProjectFileVersion
task IncrementMinor LoadVersion , {
$version = [ System.Version ] $VersionPrefix
$newVersionPrefix = [ System.Version ]::new( $version.Major , ( $version.Minor + 1 ) , 0 ).ToString( 3 )
writeProjectFileProperty $mainProjectFilePath "VersionPrefix" $newVersionPrefix
writeProjectFileProperty $mainProjectFilePath "VersionSuffix" ""
} , ReportProjectFileVersion
task IncrementPatch LoadVersion , {
$version = [ System.Version ] $VersionPrefix
$newVersionPrefix = [ System.Version ]::new( $version.Major , $version.Minor , ( $version.Build + 1 ) ).ToString( 3 )
writeProjectFileProperty $mainProjectFilePath "VersionPrefix" $newVersionPrefix
writeProjectFileProperty $mainProjectFilePath "VersionSuffix" ""
} , ReportProjectFileVersion
task IncrementPre LoadVersion , {
$now = Get-Date
$pre1 = $now.ToString( "yyyyMMdd" )
$pre2 = trimLeadingZero $now.ToString( "HHmmssff" )
$newVersionSuffix = "pre.{0}.{1}" -f $pre1 , $pre2
writeProjectFileProperty $mainProjectFilePath "VersionSuffix" $newVersionSuffix
} , ReportProjectFileVersion
task ClearPre LoadVersion , {
writeProjectFileProperty $mainProjectFilePath "VersionSuffix" ""
} , ReportProjectFileVersion
task ReportProjectFileVersion {
$actualVersionPrefix = readProjectFileProperty $mainProjectFilePath "VersionPrefix"
$actualVersionSuffix = readProjectFileProperty $mainProjectFilePath "VersionSuffix"
$actualFullVersion = combinePrefixSuffix $actualVersionPrefix $actualVersionSuffix
Write-Build Green "Version: $actualFullVersion"
}
task LoadVersion {
$script:VersionPrefix = readProjectFileProperty $mainProjectFilePath "VersionPrefix"
$script:VersionSuffix = readProjectFileProperty $mainProjectFilePath "VersionSuffix"
$script:FullVersion = combinePrefixSuffix $VersionPrefix $VersionSuffix
}
task Pack {
$script:Configuration = "Release"
} , Clean , Build , {
exec { dotnet pack ./src -c Release }
}
task PackInternal {
$script:Configuration = "Debug"
} , Clean , Build , LoadVersion , {
$yearStart = Get-Date -Year ( ( Get-Date ).Year ) -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0
$now = Get-Date
$seconds = [ int ] ( $now - $yearStart ).TotalSeconds
if ( $VersionSuffix ) {
$internalVersionPrefix = $VersionPrefix
$internalVersionSuffix = "$VersionSuffix.$seconds"
}
else {
$internalVersionPrefix = "$VersionPrefix.$seconds"
$internalVersionSuffix = $VersionSuffix
}
exec { dotnet pack ./src -c $Configuration -p:VersionPrefix=$internalVersionPrefix -p:VersionSuffix=$internalVersionSuffix }
$internalFullVersion = combinePrefixSuffix $internalVersionPrefix $internalVersionSuffix
$filename = "$basePackageName.$internalFullVersion.nupkg"
Copy-Item ./src/bin/$Configuration/$filename $LocalPackageDir
Write-Build Green "Copied $filename to $LocalPackageDir"
}
task UploadNuGet EnsureCommitted , LoadVersion , {
if ( $NuGetApiPushKey -eq "MISSING" ) { throw "NuGet key not provided" }
Set-Location ./src/bin/Release
$filename = "$basePackageName.$FullVersion.nupkg"
if ( -not ( Test-Path $filename ) ) { throw "nupkg file not found" }
$lastHour = ( Get-Date ).AddHours( -1 )
if ( ( Get-ChildItem $filename ).LastWriteTime -lt $lastHour ) { throw "nupkg file too old" }
exec { dotnet nuget push $filename -k $NuGetApiPushKey -s https://api.nuget.org/v3/index.json }
}
task EnsureCommitted {
$gitoutput = exec { git status -s -uall }
if ( $gitoutput ) { throw "uncommitted changes exist in working directory" }
}
task UpdateProjectFromChangelog {
$version , $date = changelogTopVersionAndDate
if ( $version -match '-' ) {
$prefix , $suffix = $version -split '-'
}
else {
$prefix , $suffix = $version , ""
}
writeProjectFileProperty $mainProjectFilePath "VersionPrefix" $prefix
writeProjectFileProperty $mainProjectFilePath "VersionSuffix" $suffix
$anchor = ( $version -replace '\.','' ) + "-$date"
$url = "https://github.com/zanaptak/TypedCssClasses/blob/main/CHANGELOG.md#$anchor"
writeProjectFileProperty $mainProjectFilePath "PackageReleaseNotes" $url
Write-Build Green "****"
Write-Build Green "**** Assumed changelog URL (VERIFY): $url"
Write-Build Green "****"
} , ReportProjectFileVersion
================================================
FILE: sample/FableSass/.config/dotnet-tools.json
================================================
{
"version": 1,
"isRoot": true,
"tools": {
"fable": {
"version": "3.2.9",
"commands": [
"fable"
]
}
}
}
================================================
FILE: sample/FableSass/.editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
[*.{sln,fsproj,csproj,vbproj}]
charset = utf-8-bom
indent_size = 2
================================================
FILE: sample/FableSass/.gitignore
================================================
dist/
*.fs.js
*.fs.js.map
TypedCssClasses.log
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
================================================
FILE: sample/FableSass/FableSass.fsproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Content Include="content\index.html" />
<Content Include="content\_base.sass" />
<Content Include="content\styles.sass" />
<Compile Include="src\App.fs" />
<Compile Include="src\Main.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fable.Core" Version="3.2.8" />
<PackageReference Include="Feliz" Version="1.49.0" />
<PackageReference Include="Zanaptak.TypedCssClasses" Version="1.0.0" />
</ItemGroup>
</Project>
================================================
FILE: sample/FableSass/FableSass.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29123.88
MinimumVisualStudioVersion = 10.0.40219.1
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FableSass", "FableSass.fsproj", "{E842D90F-BD83-4DB4-BC96-459B3D772A03}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3E6CCADF-250E-4646-84F3-3BEE70E243D4}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
package.json = package.json
README.md = README.md
sass-process.js = sass-process.js
webpack.config.js = webpack.config.js
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E842D90F-BD83-4DB4-BC96-459B3D772A03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E842D90F-BD83-4DB4-BC96-459B3D772A03}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E842D90F-BD83-4DB4-BC96-459B3D772A03}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E842D90F-BD83-4DB4-BC96-459B3D772A03}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1E39F012-820E-49EF-B8DC-5DDBF51C7178}
EndGlobalSection
EndGlobal
================================================
FILE: sample/FableSass/README.md
================================================
# TypedCssClasses Fable Sass Sample

This sample illustrates the following concepts:
- Using [TypedCssClasses](https://github.com/zanaptak/TypedCssClasses) for type-safe CSS class properties in a [Fable](https://fable.io/) single page application.
- Using the [Sass](https://sass-lang.com/) CSS preprocessor to generate CSS. The sample uses Sass syntax; SCSS syntax would also work with the same setup.
- Using [Feliz](https://github.com/Zaid-Ajaj/Feliz/) for type-safe HTML and React components.
## Getting started
Install [.NET Core SDK](https://dotnet.microsoft.com/download).
Install [Node.js](https://nodejs.org/).
Run `dotnet tool restore`.
Run `dotnet restore`.
Run `npm install`.
Run `npm start`, and then browse to `http://localhost:8080`. You should get a counter with the header styled as white monospace text on blue background.
Optionally, run `npm run build` to produce a production deployment version.
## Project structure
* `src/App.fs`
The application code, consisting of `App` and `Counter` React components.
In the code, note the `type Css = CssClasses<...>` declaration for the TypedCssClasses type provider and the `Css`-prefixed CSS class properties.
* `content/styles.sass` and `content/_base.sass`
The source Sass files that are used to generate the CSS.
* `sass-process.js`
The Node-based script file executed by the TypedCssClasses type provider at design and compile time to preprocess the Sass source file into standard CSS, which is then used to generate the CSS class properties.
This script is used instead of the Sass CLI in order to additionally communicate any referenced files (`_base.sass` in this case) to the type provider as files to watch for changes.
* `dist/`
The folder containing the final distributable output after running `npm run build`. You can look at the `style.[some hash value].css` file to see the final generated CSS, and you can open `index.html` in the browser to run the application.
================================================
FILE: sample/FableSass/content/_base.sass
================================================
$font-stack: monospace
$primary-color: blue
$secondary-color: white
div
.base
font: 1.25rem $font-stack
color: $primary-color
background-color: $secondary-color
.number
display: inline-block
width: 5rem
text-align: center
button
width: 3rem
================================================
FILE: sample/FableSass/content/index.html
================================================
<!doctype html>
<html>
<head>
<title>FableSass</title>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="fable.ico" />
</head>
<body>
<div id="app"></div>
</body>
</html>
================================================
FILE: sample/FableSass/content/styles.sass
================================================
@use 'base'
div
.inverse
background-color: base.$primary-color
color: base.$secondary-color
================================================
FILE: sample/FableSass/package.json
================================================
{
"private": true,
"scripts": {
"start": "dotnet fable watch FableSass.fsproj --sourceMaps --run webpack serve",
"build": "dotnet fable FableSass.fsproj --run webpack --node-env production",
"browsers": "browserslist --update-db"
},
"browserslist": [
"> 0.25%",
"not dead"
],
"dependencies": {
"@babel/core": "^7.14.8",
"@babel/preset-env": "^7.14.8",
"@babel/preset-react": "^7.14.5",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-rc.2",
"babel-loader": "^8.2.2",
"browserslist": "^4.16.6",
"copy-webpack-plugin": "^9.0.1",
"core-js": "^3.15.2",
"css-loader": "^6.2.0",
"html-webpack-plugin": "^5.3.2",
"mini-css-extract-plugin": "^2.1.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-refresh": "^0.10.0",
"sass": "^1.36.0",
"sass-loader": "^12.1.0",
"source-map-loader": "^3.0.0",
"style-loader": "^3.2.1",
"webpack": "^5.47.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^4.0.0-rc.0"
}
}
================================================
FILE: sample/FableSass/sass-process.js
================================================
const sass = require('sass')
const fs = require('fs')
const inputFile = fs.realpathSync(process.argv[2])
// run the preprocessor
const result = sass.renderSync({ file: inputFile })
// write included files as leading output lines
result.stats.includedFiles
.filter(f => f !== inputFile)
.forEach(f => console.log(f))
// write the final CSS output
console.log("/* ---- end included files, begin generated CSS ---- */")
console.log(result.css.toString())
================================================
FILE: sample/FableSass/src/App.fs
================================================
module App
open Zanaptak.TypedCssClasses
// Configure type provider to use result of sass transformation.
type Css =
CssClasses<
"content/styles.sass"
, Naming.PascalCase
, commandFile = "node"
, argumentPrefix = "sass-process.js"
//, logFile = "TypedCssClasses.log" // uncomment to enable logging
>
open Feliz
[<ReactComponent>]
let Counter() =
let (count, setCount) = React.useState(0)
Html.div [
prop.classes [
Css.Base
]
prop.children [
// Header row
Html.div [
prop.classes [
Css.Inverse
]
prop.text "Counter"
]
// Counter row
Html.div [
Html.button [
prop.onClick (fun _ -> setCount(count + 1))
prop.text "+"
]
Html.div [
prop.classes [
Css.Number
]
prop.text count
]
Html.button [
prop.onClick (fun _ -> setCount(count - 1))
prop.text "-"
]
]
]
]
[<ReactComponent>]
let App() =
Html.div [
Counter()
]
================================================
FILE: sample/FableSass/src/Main.fs
================================================
module Main
open Feliz
ReactDOM.render(App.App(), Browser.Dom.document.getElementById "app")
================================================
FILE: sample/FableSass/webpack.config.js
================================================
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); // Inject <script> or <link> tags for generated bundles into index.html template
const CopyWebpackPlugin = require('copy-webpack-plugin'); // Copy static assets to output directory
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // Extract CSS from bundle to a different file
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); // Enable React Fast Refresh
// Development mode if running 'webpack serve'
const isDevelopment = process.env.WEBPACK_SERVE === 'true'
console.log('Bundling for ' + (isDevelopment ? 'development' : 'production') + '...');
module.exports = {
mode: isDevelopment ? 'development' : 'production',
entry: {
app: ['./src/Main.fs.js', './content/styles.sass']
},
// Add a hash to the output file name in production to prevent browser caching if code changes
output: {
filename: isDevelopment ? '[name].js' : '[name].[contenthash].js',
},
// Enable source maps in development mode
...(isDevelopment && { devtool: 'eval-source-map' }),
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: './content/index.html'
}),
!isDevelopment && new CopyWebpackPlugin({ patterns: [{ from: './content/assets' }] }),
!isDevelopment && new MiniCssExtractPlugin({ filename: 'style.[contenthash].css' }),
isDevelopment && new ReactRefreshWebpackPlugin(),
].filter(Boolean),
devServer: {
static: {
directory: path.resolve(__dirname, './content/assets'),
}
},
module: {
rules: [
{
test: /\.js$/,
enforce: "pre",
use: ["source-map-loader"],
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
// Use babel-preset-env to generate JS compatible with most-used browsers.
// More info at https://babeljs.io/docs/en/babel-preset-env.html
'@babel/preset-env',
{
// This adds polyfills when needed. Requires core-js dependency.
// See https://babeljs.io/docs/en/babel-preset-env#usebuiltins
// Note that you still need to add custom polyfills if necessary (e.g. whatwg-fetch)
useBuiltIns: 'usage',
corejs: '3.15',
}
],
'@babel/preset-react'
],
plugins: [
isDevelopment && require.resolve('react-refresh/babel'),
].filter(Boolean),
}
}
},
{
test: /\.s[ac]ss$/i,
use: [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader',
].filter(Boolean),
},
]
}
};
================================================
FILE: sample/FableTailwind/.config/dotnet-tools.json
================================================
{
"version": 1,
"isRoot": true,
"tools": {
"fable": {
"version": "3.2.9",
"commands": [
"fable"
]
}
}
}
================================================
FILE: sample/FableTailwind/.editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
[*.{sln,fsproj,csproj,vbproj}]
charset = utf-8-bom
indent_size = 2
================================================
FILE: sample/FableTailwind/.gitignore
================================================
dist/
*.fs.js
*.fs.js.map
tailwind-generated.css
TypedCssClasses.log
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
================================================
FILE: sample/FableTailwind/FableTailwind.fsproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Content Include="content\index.html" />
<Content Include="content\tailwind-source.css" />
<Compile Include="src\App.fs" />
<Compile Include="src\Main.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fable.Core" Version="3.2.8" />
<PackageReference Include="Feliz" Version="1.49.0" />
<PackageReference Include="Zanaptak.TypedCssClasses" Version="1.0.0" />
</ItemGroup>
</Project>
================================================
FILE: sample/FableTailwind/FableTailwind.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29123.88
MinimumVisualStudioVersion = 10.0.40219.1
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FableTailwind", "FableTailwind.fsproj", "{E842D90F-BD83-4DB4-BC96-459B3D772A03}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3E6CCADF-250E-4646-84F3-3BEE70E243D4}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
package.json = package.json
postcss.config.js = postcss.config.js
README.md = README.md
tailwind-process.js = tailwind-process.js
tailwind.config.js = tailwind.config.js
webpack.config.js = webpack.config.js
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E842D90F-BD83-4DB4-BC96-459B3D772A03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E842D90F-BD83-4DB4-BC96-459B3D772A03}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E842D90F-BD83-4DB4-BC96-459B3D772A03}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E842D90F-BD83-4DB4-BC96-459B3D772A03}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1E39F012-820E-49EF-B8DC-5DDBF51C7178}
EndGlobalSection
EndGlobal
================================================
FILE: sample/FableTailwind/README.md
================================================
# TypedCssClasses Fable Tailwind Sample

This sample illustrates the following concepts:
- Using [TypedCssClasses](https://github.com/zanaptak/TypedCssClasses) for type-safe CSS class properties in a [Fable](https://fable.io/) single page application.
- Using a locally generated [Tailwind CSS](https://tailwindcss.com/) file with custom configuration options and extracted CSS components.
- Using Tailwind's [PurgeCSS](https://www.purgecss.com/) integration to [reduce the CSS bundle size](https://tailwindcss.com/docs/optimizing-for-production). In this sample, it reduces from an initial minified size of over 500 KiB to a purged and minified size of about 5 KiB due to the small number of CSS classes referenced.
- Using [Feliz](https://github.com/Zaid-Ajaj/Feliz/) for type-safe HTML and React components.
## Getting started
Install [.NET Core SDK](https://dotnet.microsoft.com/download).
Install [Node.js](https://nodejs.org/).
Run `dotnet tool restore`.
Run `dotnet restore`.
Run `npm install`.
Run `npm start`, and then browse to `http://localhost:8080`. You should get a counter with rounded blue buttons styled with Tailwind CSS.
Optionally, run `npm run build` to produce a production deployment version.
## Project structure
* `src/App.fs`
The application code, consisting of `App` and `Counter` React components.
In the code, note the `type tw = CssClasses<...>` declaration for the TypedCssClasses type provider and the `tw`-prefixed CSS class properties.
* `content/tailwind-source.css`
The source CSS file that is used to generate the Tailwind utility classes as well as any custom CSS you have added. In this sample, custom CSS has been added for a rounded blue button style.
* `content/tailwind-generated.css`
An optional file that can be manually generated by running `npm run css`. This is not needed to build or run the application. It can be used to view the generated Tailwind CSS classes when modifying the Tailwind configuration.
* `tailwind.config.js`
The options file for configuring Tailwind customizations and purging.
In this sample, the number of responsive screen breakpoints has been reduced from 4 to 1 in order to reduce the total number of CSS classes (more classes can result in slower IDE completion list performance). Also, some of the numeric values have been changed to be zero-padded. This improves sorting in completion lists, since numbers are sorted by leading character, so we get a sorted _01 02 03 ... 10 11 12_ list instead of a mixed _1 10 11 12 2 3 ..._ list.
The purge option is configured to look at all `src/*.fs` and `content/*.html` files for strings matching the syntax characteristics of Tailwind class names to retain in the final bundle, purging all others.
* `tailwind-process.js`
The Node-based script file executed by the TypedCssClasses type provider at design and compile time to process the Tailwind source file into standard CSS, which is then used to generate the CSS class properties.
This script is used instead of the Tailwind CLI in order to additionally communicate any referenced files (`tailwind.config.js` in this case) to the type provider as files to watch for changes.
* `postcss.config.js`
The file that configures PostCSS to run the Tailwind plugin during webpack builds.
* `dist/`
The folder containing the final distributable output after running `npm run build`. You can look at the `style.[some hash value].css` file to see the final purged and minified CSS, and you can open `index.html` in the browser to run the application.
================================================
FILE: sample/FableTailwind/content/index.html
================================================
<!doctype html>
<html>
<head>
<title>FableTailwind</title>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="fable.ico" />
</head>
<body>
<div id="app"></div>
</body>
</html>
================================================
FILE: sample/FableTailwind/content/tailwind-source.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
/* Custom component classes: https://tailwindcss.com/docs/extracting-components/#extracting-css-components-with-apply */
.custom-blue-button {
@apply bg-blue-500 text-white text-lg font-bold py-02 px-04 rounded-full border-2 border-blue-500
}
.custom-blue-button:hover {
@apply bg-blue-700
}
.custom-blue-button:focus {
@apply outline-none border-blue-700
}
}
================================================
FILE: sample/FableTailwind/package.json
================================================
{
"private": true,
"scripts": {
"start": "dotnet fable watch FableTailwind.fsproj --sourceMaps --run webpack serve",
"build": "dotnet fable FableTailwind.fsproj --run webpack --node-env production",
"css": "tailwind build -i content/tailwind-source.css -o content/tailwind-generated.css",
"browsers": "browserslist --update-db"
},
"browserslist": [
"> 0.25%",
"not dead"
],
"dependencies": {
"@babel/core": "^7.14.8",
"@babel/preset-env": "^7.14.8",
"@babel/preset-react": "^7.14.5",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.0-rc.2",
"autoprefixer": "^10.3.1",
"babel-loader": "^8.2.2",
"browserslist": "^4.16.6",
"copy-webpack-plugin": "^9.0.1",
"core-js": "^3.15.2",
"css-loader": "^6.2.0",
"cssnano": "^5.0.7",
"html-webpack-plugin": "^5.3.2",
"mini-css-extract-plugin": "^2.1.0",
"postcss": "^8.3.6",
"postcss-loader": "^6.1.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-refresh": "^0.10.0",
"source-map-loader": "^3.0.0",
"style-loader": "^3.2.1",
"tailwindcss": "^2.2.7",
"webpack": "^5.47.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^4.0.0-rc.0"
}
}
================================================
FILE: sample/FableTailwind/postcss.config.js
================================================
const isDevelopment = process.env.WEBPACK_SERVE === 'true';
const cssnano = require('cssnano')({
preset: ['default', {
discardComments: { removeAll: true }
}]
})
module.exports = {
plugins: [
require('tailwindcss')
, require('autoprefixer')
, !isDevelopment && cssnano
].filter(Boolean)
};
================================================
FILE: sample/FableTailwind/src/App.fs
================================================
module App
open Zanaptak.TypedCssClasses
// Configure type provider to use generated Tailwind classes.
// Naming.Verbatim is required for PurgeCSS bundle reduction, see https://tailwindcss.com/docs/optimizing-for-production
type private tw =
CssClasses<
"content/tailwind-source.css"
, Naming.Verbatim
, commandFile = "node"
, argumentPrefix = "tailwind-process.js tailwind.config.js"
//, logFile = "TypedCssClasses.log" // uncomment to enable logging
>
open Feliz
[<ReactComponent>]
let Counter() =
let (count, setCount) = React.useState(0)
Html.div [
prop.classes [
tw.container
tw.``mx-auto``
]
prop.children [
// Header row
Html.div [
prop.classes [
tw.``text-3xl``
tw.``mb-01``
tw.``text-green-600``
]
prop.text "Counter (tailwind)"
]
// Counter row
Html.div [
prop.classes [
tw.flex
tw.``flex-col``
tw.``items-center``
tw.``wide:flex-row-reverse``
tw.``wide:justify-around``
]
prop.children [
// Increment button
Html.button [
prop.classes [
tw.``custom-blue-button``
tw.``w-02/05``
tw.``wide:w-01/06``
tw.``transition-colors``
tw.``duration-200``
]
prop.onClick (fun _ -> setCount(count + 1))
prop.text "+"
]
// Display current counter value
Html.div [
prop.classes [
tw.``text-3xl``
tw.``text-red-600``
tw.``text-center``
tw.``w-01/06``
]
prop.text count
]
// Decrement button
Html.button [
prop.classes [
tw.``custom-blue-button``
tw.``w-02/05``
tw.``wide:w-01/06``
tw.``transition-colors``
tw.``duration-200``
]
prop.onClick (fun _ -> setCount(count - 1))
prop.text "-"
]
]
]
]
]
[<ReactComponent>]
let App() =
Html.div [
Counter()
]
================================================
FILE: sample/FableTailwind/src/Main.fs
================================================
module Main
open Feliz
ReactDOM.render(App.App(), Browser.Dom.document.getElementById "app")
================================================
FILE: sample/FableTailwind/tailwind-process.js
================================================
const fs = require('fs')
const tailwindConfigFile = fs.realpathSync(process.argv[2])
const inputFile = fs.realpathSync(process.argv[3])
const postcss = require('postcss')
const tailwindcss = require('tailwindcss')(tailwindConfigFile)
const css = fs.readFileSync(inputFile)
postcss()
.use(tailwindcss)
.process(css, { from: inputFile })
.then(result => {
// write included files as leading output lines
result.messages
.filter(msg => msg.type === "dependency" && msg.file !== inputFile)
.forEach(msg => console.log(msg.file))
// write the final CSS output
console.log("/* ---- end included files, begin generated CSS ---- */")
console.log(result.css)
})
.catch(reason => {
console.error(reason)
process.exit(1)
})
================================================
FILE: sample/FableTailwind/tailwind.config.js
================================================
const colors = require('tailwindcss/colors')
module.exports = {
purge: [
'./content/**/*.html',
'./src/**/*.fs',
],
theme: {
colors: {
transparent: 'transparent',
current: 'currentColor',
black: colors.black,
white: colors.white,
red: colors.red,
green: colors.emerald,
blue: colors.blue,
},
spacing: {
px: '1px',
'00': '0',
'01': '0.25rem',
'02': '0.5rem',
'03': '0.75rem',
'04': '1rem',
'05': '1.25rem',
'06': '1.5rem',
'08': '2rem',
'10': '2.5rem',
'12': '3rem',
'16': '4rem',
'20': '5rem',
'24': '6rem',
'32': '8rem',
'40': '10rem',
'48': '12rem',
'56': '14rem',
'64': '16rem',
},
inset: (theme, { negative }) => ({
auto: 'auto',
...theme('spacing'),
...negative(theme('spacing')),
'01/03': '33.333333%',
'02/03': '66.666667%',
'01/04': '25%',
'02/04': '50%',
'03/04': '75%',
half: '50%',
full: '100%',
}),
order: {
first: '-9999',
last: '9999',
none: '0',
'01': '1',
'02': '2',
'03': '3',
'04': '4',
'05': '5',
'06': '6',
'07': '7',
'08': '8',
'09': '9',
'10': '10',
'11': '11',
'12': '12',
},
width: theme => ({
auto: 'auto',
...theme('spacing'),
'01/03': '33.333333%',
'02/03': '66.666667%',
'01/04': '25%',
'02/04': '50%',
'03/04': '75%',
'01/05': '20%',
'02/05': '40%',
'03/05': '60%',
'04/05': '80%',
'01/06': '16.666667%',
'02/06': '33.333333%',
'03/06': '50%',
'04/06': '66.666667%',
'05/06': '83.333333%',
'01/12': '8.333333%',
'02/12': '16.666667%',
'03/12': '25%',
'04/12': '33.333333%',
'05/12': '41.666667%',
'06/12': '50%',
'07/12': '58.333333%',
'08/12': '66.666667%',
'09/12': '75%',
'10/12': '83.333333%',
'11/12': '91.666667%',
half: '50%',
full: '100%',
screen: '100vw',
}),
screens: {
wide: '800px',
},
},
}
================================================
FILE: sample/FableTailwind/webpack.config.js
================================================
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); // Inject <script> or <link> tags for generated bundles into index.html template
const CopyWebpackPlugin = require('copy-webpack-plugin'); // Copy static assets to output directory
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // Extract CSS from bundle to a different file
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); // Enable React Fast Refresh
// Development mode if running 'webpack serve'
const isDevelopment = process.env.WEBPACK_SERVE === 'true'
console.log('Bundling for ' + (isDevelopment ? 'development' : 'production') + '...');
// Note: Tailwind performs CSS purging based on NODE_ENV environment variable, not webpack mode,
// so production builds should be launched with 'webpack --node-env production'
module.exports = {
mode: isDevelopment ? 'development' : 'production',
entry: {
app: ['./src/Main.fs.js', './content/tailwind-source.css']
},
// Add a hash to the output file name in production to prevent browser caching if code changes
output: {
filename: isDevelopment ? '[name].js' : '[name].[contenthash].js',
},
// Enable source maps in development mode
...(isDevelopment && { devtool: 'eval-source-map' }),
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: './content/index.html'
}),
!isDevelopment && new CopyWebpackPlugin({ patterns: [{ from: './content/assets' }] }),
!isDevelopment && new MiniCssExtractPlugin({ filename: 'style.[contenthash].css' }),
isDevelopment && new ReactRefreshWebpackPlugin(),
].filter(Boolean),
devServer: {
static: {
directory: path.resolve(__dirname, './content/assets'),
}
},
module: {
rules: [
{
test: /\.js$/,
enforce: "pre",
use: ["source-map-loader"],
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
// Use babel-preset-env to generate JS compatible with most-used browsers.
// More info at https://babeljs.io/docs/en/babel-preset-env.html
'@babel/preset-env',
{
// This adds polyfills when needed. Requires core-js dependency.
// See https://babeljs.io/docs/en/babel-preset-env#usebuiltins
// Note that you still need to add custom polyfills if necessary (e.g. whatwg-fetch)
useBuiltIns: 'usage',
corejs: '3.15',
}
],
'@babel/preset-react'
],
plugins: [
isDevelopment && require.resolve('react-refresh/babel'),
].filter(Boolean),
}
}
},
{
test: /\.css$/,
use: [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
].filter(Boolean),
},
]
}
};
================================================
FILE: src/CssClassesTypeProvider.fs
================================================
namespace Zanaptak.TypedCssClasses
open Zanaptak.TypedCssClasses.Internal.FSharpData.Helpers
open Zanaptak.TypedCssClasses.Internal.FSharpData.IO
open Zanaptak.TypedCssClasses.Internal.FSharpData.ProvidedTypes
open Zanaptak.TypedCssClasses.Internal.ProviderImplementation.ProvidedTypes
open FSharp.Core.CompilerServices
open System
open System.Reflection
open System.Runtime.InteropServices
open System.IO
module TypeProvider =
[< TypeProvider >]
type CssClassesTypeProvider ( config : TypeProviderConfig ) as this =
inherit DisposableTypeProviderForNamespaces( config )
let ctorLog = ResizeArray< string >()
let proc = System.Diagnostics.Process.GetCurrentProcess()
do
ctorLog.Add ( sprintf "Environment.CommandLine: %s" Environment.CommandLine )
ctorLog.Add ( sprintf "Environment.CurrentDirectory: %s" Environment.CurrentDirectory )
ctorLog.Add ( sprintf "TypeProviderConfig.RuntimeAssembly: %s" config.RuntimeAssembly )
ctorLog.Add ( sprintf "TypeProviderConfig.ResolutionFolder: %s" ( if isNull config.ResolutionFolder then "<null>" else config.ResolutionFolder ) )
ctorLog.Add ( sprintf "TypeProviderConfig.IsHostedExecution: %b" config.IsHostedExecution )
ctorLog.Add ( sprintf "TypeProviderConfig.IsInvalidationSupported: %b" config.IsInvalidationSupported )
ctorLog.Add ( sprintf "TypeProviderConfig.TemporaryFolder: %s" config.TemporaryFolder )
let ns = "Zanaptak.TypedCssClasses"
let asm = Assembly.GetExecutingAssembly()
let parentType = ProvidedTypeDefinition( asm , ns , "CssClasses" , Some ( typeof< obj > ) )
let buildTypes ( typeName : string ) ( args : obj[] ) =
let osDelimiters = args.[ 9 ] :?> string
let expandVariables = args.[ 10 ] :?> bool
let processStringParameter ( arg : obj ) =
arg :?> string
|> Utils.platformSpecific osDelimiters
|> fun s -> if expandVariables then Environment.ExpandEnvironmentVariables s else s
let resolutionFolder = args.[ 2 ] |> processStringParameter
let finalResolutionFolder =
[ resolutionFolder ; config.ResolutionFolder ; Environment.CurrentDirectory ]
|> List.find ( fun dir -> not ( String.IsNullOrWhiteSpace dir ) )
let logFile = args.[ 5 ] |> processStringParameter
if not ( String.IsNullOrWhiteSpace logFile ) then
enableLogType typeName ( Path.GetFullPath( Path.Combine( finalResolutionFolder , logFile ) ) )
logfType typeName this.Id "**** CssClassesTypeProvider[%i]: Host process <%s> [%i] requesting type: %s" this.Id proc.ProcessName proc.Id typeName
ctorLog |> Seq.iter ( logType typeName this.Id )
let commandParam = args.[ 6 ] |> processStringParameter
let commandConfig =
if String.IsNullOrWhiteSpace commandParam then None
else
Some {
commandFile = commandParam
argumentPrefix = args.[ 7 ] |> processStringParameter
argumentSuffix = args.[ 8 ] |> processStringParameter
}
logfType typeName this.Id "Configured resolution folder: %s" finalResolutionFolder
logfType typeName this.Id "Configured command: %s" commandParam
let source = args.[ 0 ] |> processStringParameter
let naming = args.[ 1 ] :?> Naming
let getProperties = args.[ 3 ] :?> bool
let nameCollisions = args.[ 4 ] :?> NameCollisions
let fableCssModule = args.[ 11 ] :?> bool
let tryParseTextClassNames value =
logType typeName this.Id "Parsing CSS"
Utils.tryParseCssClassNames value
let convertClassNamesToProperties value =
logType typeName this.Id "Creating properties"
Utils.getPropertiesFromClassNames naming nameCollisions value
let createType parseResult =
logType typeName this.Id "Creating type"
let cssType = ProvidedTypeDefinition( asm , ns , typeName , Some ( typeof< obj > ) )
Utils.addTypeMembersFromCss fableCssModule source getProperties parseResult cssType
cssType
generateType source commandConfig this config finalResolutionFolder typeName tryParseTextClassNames convertClassNamesToProperties createType
let parameters = [
ProvidedStaticParameter( "source" , typeof< string >, parameterDefaultValue = "" )
ProvidedStaticParameter( "naming" , typeof< Naming >, parameterDefaultValue = Naming.Verbatim )
ProvidedStaticParameter( "resolutionFolder" , typeof< string >, parameterDefaultValue = "" )
ProvidedStaticParameter( "getProperties" , typeof< bool >, parameterDefaultValue = false )
ProvidedStaticParameter( "nameCollisions" , typeof< NameCollisions >, parameterDefaultValue = NameCollisions.BasicSuffix )
ProvidedStaticParameter( "logFile" , typeof< string >, parameterDefaultValue = "" )
ProvidedStaticParameter( "commandFile" , typeof< string >, parameterDefaultValue = "" )
ProvidedStaticParameter( "argumentPrefix" , typeof< string >, parameterDefaultValue = "" )
ProvidedStaticParameter( "argumentSuffix" , typeof< string >, parameterDefaultValue = "" )
ProvidedStaticParameter( "osDelimiters" , typeof< string >, parameterDefaultValue = ",=" )
ProvidedStaticParameter( "expandVariables" , typeof< bool >, parameterDefaultValue = true )
ProvidedStaticParameter( "fableCssModule" , typeof< bool >, parameterDefaultValue = false )
]
let helpText = """
<summary>Typed CSS classes. Provides generated properties representing CSS classes from a stylesheet.</summary>
<param name='source'>Location of a CSS stylesheet (file path or web URL), or a string containing CSS text.</param>
<param name='naming'>Naming strategy for class name properties.
One of: Naming.Verbatim (default), Naming.Underscores, Naming.CamelCase, Naming.PascalCase.</param>
<param name='resolutionFolder'>A directory that is used when resolving relative file references.</param>
<param name='getProperties'>Adds a GetProperties() method that returns a seq of all generated property name/value pairs.</param>
<param name='nameCollisions'>Behavior of name collisions that arise from naming strategy.
One of: NameCollisions.BasicSuffix (default), NameCollisions.ExtendedSuffix, NameCollisions.Omit.</param>
<param name='logFile'>File path to write logging information to.</param>
<param name='commandFile'>Executable file to run, with source parameter as an argument, to produce the CSS output used to generate properties.</param>
<param name='argumentPrefix'>Argument string to include before the source parameter, separated by a space, when running command.</param>
<param name='argumentSuffix'>Argument string to include after the source parameter, separated by a space, when running command.</param>
<param name='osDelimiters'>Characters that separate platform-specific values in the provided parameters.
Default is ",="</param>
<param name='expandVariables'>Expand environment variables in the provided parameters.</param>
<param name='fableCssModule'>Resolve properties to Fable import expressions instead of raw class names to allow processing as a CSS Module.</param>
"""
do parentType.AddXmlDoc helpText
do parentType.DefineStaticParameters( parameters , buildTypes )
do this.AddNamespace( ns, [ parentType ] )
[< TypeProviderAssembly >]
do ()
#if INTERNALS_VISIBLE
open System.Runtime.CompilerServices
[< assembly : InternalsVisibleTo( "TypedCssClasses.Tests" ) >]
do ()
#endif
================================================
FILE: src/Fable.Core.fs
================================================
// Fable replaces methods from Fable.Core just by fullname so we can avoid
// reference issues by including the methods we need in this assembly
module Fable.Core.JsInterop
let import<'T> (selector: string) (path: string): 'T =
failwithf "Attempted to use Fable CSS Module import expression in non-Fable context (class:%s, source:%s) [Zanaptak.TypedCssClasses]" selector path
================================================
FILE: src/TypedCssClasses.fsproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DisableImplicitSystemValueTupleReference>true</DisableImplicitSystemValueTupleReference>
<Configurations>Debug;Release;ReleaseTest</Configurations>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
<PackageId>Zanaptak.TypedCssClasses</PackageId>
<Authors>zanaptak</Authors>
<Product>Zanaptak.TypedCssClasses</Product>
<PackageTags>f#;fsharp;css</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Description>A CSS class type provider for F# web development. Bring external stylesheet classes into your F# code as design-time discoverable compiler-verified properties.</Description>
<AssemblyName>Zanaptak.TypedCssClasses</AssemblyName>
<PackageProjectUrl>https://github.com/zanaptak/TypedCssClasses</PackageProjectUrl>
<PackageReleaseNotes>https://github.com/zanaptak/TypedCssClasses/blob/main/CHANGELOG.md#100-2021-07-30</PackageReleaseNotes>
<RepositoryUrl>https://github.com/zanaptak/TypedCssClasses.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;INTERNALS_VISIBLE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseTest|AnyCPU'">
<DefineConstants>TRACE;INTERNALS_VISIBLE</DefineConstants>
<Optimize>true</Optimize>
<Tailcalls>true</Tailcalls>
</PropertyGroup>
<ItemGroup>
<None Include="vendor\README.md" />
<None Include="vendor\FSharp.TypeProviders.SDK\LICENSE.md" />
<Compile Include="vendor\FSharp.TypeProviders.SDK\ProvidedTypes.fsi" />
<Compile Include="vendor\FSharp.TypeProviders.SDK\ProvidedTypes.fs" />
</ItemGroup>
<ItemGroup>
<None Include="vendor\FSharp.Data\LICENSE.md" />
<Compile Include="vendor\FSharp.Data\Http.fs" />
<Compile Include="vendor\FSharp.Data\IO.fs" />
<Compile Include="vendor\FSharp.Data\Caching.fs" />
<Compile Include="vendor\FSharp.Data\Helpers.fs" />
</ItemGroup>
<ItemGroup>
<Compile Include="Fable.Core.fs" />
<Compile Include="Types.fs" />
<Compile Include="Utils.fs" />
<Compile Include="CssClassesTypeProvider.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="FSharp.Core" Version="4.3.4" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
</Project>
================================================
FILE: src/TypedCssClasses.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29102.190
MinimumVisualStudioVersion = 10.0.40219.1
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "TypedCssClasses", "TypedCssClasses.fsproj", "{AA9D7239-6607-4388-AEB9-3236174560EE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CF019DE1-D98F-40B5-A1D4-FC4BE9AC74B6}"
ProjectSection(SolutionItems) = preProject
..\.editorconfig = ..\.editorconfig
..\.gitattributes = ..\.gitattributes
..\.gitignore = ..\.gitignore
..\CHANGELOG.md = ..\CHANGELOG.md
..\invoke.build.ps1 = ..\invoke.build.ps1
..\README.md = ..\README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{149E8767-E720-444B-BC61-C64C8510D37A}"
ProjectSection(SolutionItems) = preProject
..\doc\configuration.md = ..\doc\configuration.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
ReleaseTest|Any CPU = ReleaseTest|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AA9D7239-6607-4388-AEB9-3236174560EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA9D7239-6607-4388-AEB9-3236174560EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA9D7239-6607-4388-AEB9-3236174560EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA9D7239-6607-4388-AEB9-3236174560EE}.Release|Any CPU.Build.0 = Release|Any CPU
{AA9D7239-6607-4388-AEB9-3236174560EE}.ReleaseTest|Any CPU.ActiveCfg = ReleaseTest|Any CPU
{AA9D7239-6607-4388-AEB9-3236174560EE}.ReleaseTest|Any CPU.Build.0 = ReleaseTest|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{149E8767-E720-444B-BC61-C64C8510D37A} = {CF019DE1-D98F-40B5-A1D4-FC4BE9AC74B6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5CDB4F14-36E1-489C-B4F3-E9E8B8E027F4}
EndGlobalSection
EndGlobal
================================================
FILE: src/Types.fs
================================================
namespace Zanaptak.TypedCssClasses
type Naming =
| Verbatim = 0
| Underscores = 1
| CamelCase = 2
| PascalCase = 3
type NameCollisions =
| BasicSuffix = 0
| ExtendedSuffix = 1
| Omit = 2
================================================
FILE: src/Utils.fs
================================================
module internal Zanaptak.TypedCssClasses.Utils
open System
open System.Globalization
open System.Text.RegularExpressions
open System.Collections.Generic
open System.Runtime.InteropServices
open System.Web
open Zanaptak.TypedCssClasses.Internal.ProviderImplementation.ProvidedTypes
open Zanaptak.TypedCssClasses.Internal.FSharpData.ProvidedTypes
open Zanaptak.TypedCssClasses.Internal.FSharpData.Helpers
open FSharp.Quotations
/// Converts the string to an array of Int32 code-points (the actual Unicode Code Point number).
let toCodePoints (source : string) : seq<int> =
let mapper i c =
// Ignore the low-surrogate because it's already been converted
if c |> Char.IsLowSurrogate then None
else Char.ConvertToUtf32 (source, i) |> Some
source |> Seq.mapi mapper |> Seq.choose id
/// Converts the array of Int32 code-points (the actual Unicode Code Point number) to a string.
let ofCodePoints (source: seq<int>) : string =
source |> Seq.map Char.ConvertFromUtf32 |> String.concat String.Empty
let replacementChar = Char.ConvertFromUtf32 0xFFFD
let isUnicodeScalar codePoint =
( codePoint >= 0 && codePoint <= 0xD7FF ) || ( codePoint >= 0xE000 && codePoint <= 0x10FFFF )
(*
HTML5 -- https://www.w3.org/TR/html5/syntax.html#parsing-html-documents
Any occurrences of any characters in the ranges U+0001 to U+0008, U+000E to U+001F, U+007F to
U+009F, U+FDD0 to U+FDEF, and characters U+000B, U+FFFE, U+FFFF, U+1FFFE, U+1FFFF, U+2FFFE,
U+2FFFF, U+3FFFE, U+3FFFF, U+4FFFE, U+4FFFF, U+5FFFE, U+5FFFF, U+6FFFE, U+6FFFF, U+7FFFE, U+7FFFF,
U+8FFFE, U+8FFFF, U+9FFFE, U+9FFFF, U+AFFFE, U+AFFFF, U+BFFFE, U+BFFFF, U+CFFFE, U+CFFFF, U+DFFFE,
U+DFFFF, U+EFFFE, U+EFFFF, U+FFFFE, U+FFFFF, U+10FFFE, and U+10FFFF are parse errors. These are all
control characters or permanently undefined Unicode characters (noncharacters).
The handling of U+0000 NULL characters varies based on where the characters are found. In general,
they are ignored except where doing so could plausibly introduce an attack vector. This handling
is, by necessity, spread across both the tokenization stage and the tree construction stage.
*)
let isValidHtml codePoint =
not (
codePoint <= 0x8
|| ( codePoint >= 0xE && codePoint <= 0x1F )
|| ( codePoint >= 0x7F && codePoint <= 0x9F )
|| ( codePoint >= 0xFDD0 && codePoint <= 0xFDEF )
|| codePoint = 0xB
|| ( codePoint &&& 0xFFFE = 0xFFFE ) // also catches 0xFFFF bits
)
// Since output will be in HTML attribute, replace any invalid HTML characters.
// (Don't skip; prefer to expose problems than hide them.)
let replaceNonHtml ( text : string ) =
text
|> toCodePoints
|> Seq.map ( fun cp -> if isValidHtml cp then Char.ConvertFromUtf32 cp else replacementChar )
|> String.concat ""
// Escape for use in xmldoc
let escapeHtml ( text : string ) =
text
|> toCodePoints
|> Seq.map ( fun cp ->
match cp with
| 0x26 -> "&"
| 0x3C -> "<"
| 0x3E -> ">"
| 0x22 -> """
| 0x27 -> "'" // apostrophe
| 0x2F -> "/" // slash (additional XSS close-tag protection)
| cp when isValidHtml cp -> Char.ConvertFromUtf32 cp
| _ -> replacementChar
)
|> String.concat ""
let unescapeHexStr hexStr =
match Int32.TryParse( hexStr , NumberStyles.HexNumber , CultureInfo.InvariantCulture ) with
| true , codePoint when isUnicodeScalar codePoint -> Char.ConvertFromUtf32 codePoint
| _ -> replacementChar
// https://github.com/dotnet/fsharp/blob/master/src/fsharp/PrettyNaming.fs
/// The characters that are allowed to be the first character of an identifier.
let IsIdentifierFirstCharacter c =
if c = '_' then true
else
match Char.GetUnicodeCategory c with
// Letters
| UnicodeCategory.UppercaseLetter
| UnicodeCategory.LowercaseLetter
| UnicodeCategory.TitlecaseLetter
| UnicodeCategory.ModifierLetter
| UnicodeCategory.OtherLetter
| UnicodeCategory.LetterNumber -> true
| _ -> false
// https://github.com/dotnet/fsharp/blob/master/src/fsharp/PrettyNaming.fs
/// The characters that are allowed to be in an identifier.
let IsIdentifierPartCharacter c =
if c = '\'' then true // Tick
else
match Char.GetUnicodeCategory c with
// Letters
| UnicodeCategory.UppercaseLetter
| UnicodeCategory.LowercaseLetter
| UnicodeCategory.TitlecaseLetter
| UnicodeCategory.ModifierLetter
| UnicodeCategory.OtherLetter
| UnicodeCategory.LetterNumber
// Numbers
| UnicodeCategory.DecimalDigitNumber
// Connectors
| UnicodeCategory.ConnectorPunctuation // includes '_'
// Combiners
| UnicodeCategory.NonSpacingMark
| UnicodeCategory.SpacingCombiningMark -> true
| _ -> false
// https://github.com/dotnet/fsharp/blob/master/src/fsharp/lexhelp.fs
let keywords =
[
"abstract" ; "and" ; "as" ; "assert" ; "asr" ; "base" ; "begin" ; "class" ; "const" ; "default" ; "delegate" ; "do"
"done" ; "downcast" ; "downto" ; "elif" ; "else" ; "end" ; "exception" ; "extern" ; "false" ; "finally" ; "fixed"
"for" ; "fun" ; "function" ; "global" ; "if" ; "in" ; "inherit" ; "inline" ; "interface" ; "internal" ; "land"
"lazy" ; "let" ; "lor" ; "lsl" ; "lsr" ; "lxor" ; "match" ; "member" ; "mod" ; "module" ; "mutable" ; "namespace"
"new" ; "null" ; "of" ; "open" ; "or" ; "override" ; "private" ; "public" ; "rec" ; "return" ; "sig" ; "static"
"struct" ; "then" ; "to" ; "true" ; "try" ; "type" ; "upcast" ; "use" ; "val" ; "void" ; "when" ; "while" ; "with"
"yield" ; "_" ; "__token_OBLOCKSEP" ; "__token_OWITH" ; "__token_ODECLEND" ; "__token_OTHEN" ; "__token_OELSE"
"__token_OEND" ; "__token_ODO" ; "__token_OLET" ; "__token_constraint" ; "break" ; "checked" ; "component"
"constraint" ; "continue" ; "fori" ; "include" ; "mixin" ; "parallel" ; "params" ; "process" ; "protected"
"pure" ; "sealed" ; "trait" ; "tailcall" ; "virtual" ; "__SOURCE_DIRECTORY__" ; "__SOURCE_FILE__" ; "__LINE__"
]
|> Set.ofList
let isKeyword s = keywords |> Set.contains s
let symbolsToUnderscores s =
// Process as codepoints so that surrogate pairs are handled as 1 character
s
|> toCodePoints
|> Seq.mapi ( fun i cp ->
if cp > 0xFFFF then [| '_' |] // replace supplementary char
else
let ch = char cp
// Replace symbols even if valid identifier chars, for consistency (visual and editor selection/cursor behavior)
if ch = '\'' then [| '_' |] // replace despite valid identifier char
elif Char.GetUnicodeCategory ch = UnicodeCategory.ConnectorPunctuation then [| '_' |] // replace despite valid identifier char
elif IsIdentifierPartCharacter ch then
if i > 0 then [| ch |] // interior position, valid interior char in interior position
elif IsIdentifierFirstCharacter ch then [| ch |] // first position, valid first char
else [| '_' ; ch |] // fisrt position, valid interior char but not valid first char, prefix with _
else [| '_' |] // replace all other chars
)
|> Seq.toArray
|> Array.collect id
|> String
|> fun s -> if isKeyword s then s + "_" else s // suffix with _ if final identifier is a keyword
// Insert underscores at word boundaries inferred from case changes
let wordCaseBoundaries s =
s
|> fun s -> Regex.Replace ( s , @"(\p{Lu})(\p{Ll})" , "_$1$2" , RegexOptions.None , TimeSpan.FromSeconds 10. ) // upper then lower
|> fun s -> Regex.Replace ( s , @"([^_\p{Lu}])([\p{Lu}])" , "$1_$2" , RegexOptions.None , TimeSpan.FromSeconds 10. ) // upper after non upper
|> fun s -> Regex.Replace ( s , @"(\p{Nd})([\p{Lu}\p{Ll}])" , "$1_$2" , RegexOptions.None , TimeSpan.FromSeconds 10. ) // upper/lower after digit
let capitalize ( s : string ) = s.[ 0 ].ToString().ToUpperInvariant() + s.[ 1 .. ].ToLowerInvariant()
type Case = Pascal | Camel
// convert to mixed case, based on word boundaries identified by symbols and case changes
let toCase ( case : Case ) ( s : string ) =
s
|> symbolsToUnderscores
|> wordCaseBoundaries
|> fun s -> s.Split( [| '_' |] , StringSplitOptions.RemoveEmptyEntries )
|> Array.mapi ( fun i s -> if i > 0 || case = Pascal then capitalize s else s.ToLowerInvariant() )
|> String.concat ""
|> fun s -> if String.IsNullOrWhiteSpace s then "__" else s
|> fun s -> if IsIdentifierFirstCharacter s.[ 0 ] then s else "_" + s // first position not valid, prefix with _
|> fun s -> if isKeyword s then s + "_" else s // suffix with _ if final identifier is a keyword
let toPascalCase ( s : string ) = toCase Pascal s
let toCamelCase ( s : string ) = toCase Camel s
// http://fssnip.net/bj
let levenshtein ( word1 : string ) ( word2 : string ) =
let chars1, chars2 = word1.ToCharArray(), word2.ToCharArray()
let m, n = chars1.Length, chars2.Length
let table : int[,] = Array2D.zeroCreate (m + 1) (n + 1)
for i in 0..m do
for j in 0..n do
match i, j with
| i, 0 -> table.[i, j] <- i * 10000
| 0, j -> table.[i, j] <- j * 10000
| _, _ ->
let delete = table.[i-1, j] + 10000
let insert = table.[i, j-1] + 10000 // ins/del highest cost, changes length
let substitute =
if chars1.[i - 1] = chars2.[j - 1] then
table.[i-1, j-1] // same character no cost
elif Char.ToUpperInvariant( chars1.[i - 1] ) = Char.ToUpperInvariant( chars2.[j - 1] ) then
table.[i-1, j-1] + 1 // case change lowest cost
else
table.[i-1, j-1] + 100 // substitution cost less than ins/del, keeps same length
table.[i, j] <- List.min [delete; insert; substitute]
table.[m, n] //, table
// Capture selector text from first non-whitespace up to first {
// Then capture content inside the outer {} block (handles nested {} with .NET balancing groups regex feature)
let selectorsAndBlockCapture = @"
\s*
(?<selectors>
( \\ . | [^{\s] ) (?# start with any escaped char or non-open-brace non-space char )
( \\ . | [^{] )* (?# capture all escaped chars or non-open-brace chars )
)
( (?<!\\)\{ | (?<=(?<!\\)(\\\\)+)\{ ) (?# non-escaped `{`: 0 or even number of preceding backslashes )
(?<blockcontent>
(?>
( (?<!\\)\{ | (?<=(?<!\\)(\\\\)+)\{ ) (?<c>) (?# non-escaped `{`, increment brace counter )
|
( \\ . | [^{}] )+ (?# any escaped chars or non-brace chars )
|
( (?<!\\)\} | (?<=(?<!\\)(\\\\)+)\} ) (?<-c>) (?# non-escaped `}`, decrement brace counter )
)*
(?(c)(?!)) (?# require brace counter of 0 )
)
( (?<!\\)\} | (?<=(?<!\\)(\\\\)+)\} ) (?# non-escaped `}`: 0 or even number of preceding backslashes )
"
let classCapture = @"
( (?<!\\)\. | (?<=(?<!\\)(\\\\)+)\. ) (?# non-escaped period: 0 or even number of preceding backslashes )
(?<class>
( \\ . | [^\s\\[\](){}|:,.+>~] )+ (?# capture all escaped chars or non-delimeters )
)
"
let rec selectorPreludesFromCss text =
Regex.Matches(
text
, selectorsAndBlockCapture
, RegexOptions.IgnorePatternWhitespace ||| RegexOptions.ExplicitCapture
, TimeSpan.FromSeconds 10.
)
|> Seq.cast
|> Seq.map ( fun ( m : Match ) -> m.Groups.[ "selectors" ].Value.Trim() , m.Groups.[ "blockcontent" ].Value )
|> Seq.collect ( fun ( selectors , blockContent ) ->
if selectors.StartsWith( "@media" ) || selectors.StartsWith( "@supports" ) then
selectorPreludesFromCss blockContent
else
Seq.singleton selectors
)
let classNamesFromSelectorText text =
Regex.Matches(
text
, classCapture
, RegexOptions.IgnorePatternWhitespace ||| RegexOptions.ExplicitCapture
, TimeSpan.FromSeconds 10.
)
|> Seq.cast
|> Seq.map ( fun ( m : Match ) ->
m.Groups.[ "class" ].Value
|> fun t ->
Regex.Replace(
t
, @"\\([0-9a-fA-F]{1,6}|.)"
, MatchEvaluator( fun m ->
let escapeStr = m.Groups.[ 1 ].Value
if Regex.IsMatch( escapeStr , @"^[0-9a-fA-F]+$" ) then
unescapeHexStr escapeStr
else
escapeStr
)
, RegexOptions.None
, TimeSpan.FromSeconds 10.
)
|> replaceNonHtml
)
let classNamesFromCss text =
text
|> selectorPreludesFromCss
|> Seq.collect classNamesFromSelectorText
|> Seq.distinct
let basicSuffixGenerator () =
let nameSet = new HashSet<_>()
fun ( baseName : string ) ->
let mutable name = baseName
let mutable trySuffix = 1
while nameSet.Contains name do
trySuffix <- trySuffix + 1
name <- baseName + "_" + string trySuffix
nameSet.Add name |> ignore
name
type ExtendedSuffixGenerator () =
let nameSet = new HashSet<_>()
member this.Single ( baseName : string ) =
let mutable name = baseName
let mutable trySuffix = ""
while nameSet.Contains name do
// shouldn't happen, but just in case
if trySuffix = "" then
trySuffix <- "__1_of_1"
else
trySuffix <- "_" + trySuffix
name <- baseName + trySuffix
nameSet.Add name |> ignore
name
member this.Multiple ( baseName : string ) count =
let mutable leadingUnderscores = "__"
let digits = ( float count |> Math.Log10 |> int ) + 1
let numStr num = ( string num ).PadLeft( digits , '0' )
let nameArray underscores = Array.init count ( fun i -> sprintf "%s%s%s_of_%s" baseName underscores ( numStr ( i + 1 ) ) ( numStr count ) )
let mutable names = nameArray leadingUnderscores
while names |> Array.exists ( fun name -> nameSet.Contains name ) do
// If any in a group match an existing name, try again with additional underscore
leadingUnderscores <- leadingUnderscores + "_"
names <- nameArray leadingUnderscores
names |> Array.iter ( fun name -> nameSet.Add name |> ignore )
names
let getPropertiesFromClassNames naming nameCollisions classNames =
let transformer =
match naming with
| Naming.Underscores -> symbolsToUnderscores
| Naming.CamelCase -> toCamelCase
| Naming.PascalCase -> toPascalCase
| _ -> id
let initialProperties =
classNames
|> Array.map ( fun s -> { Name = transformer s ; Value = s } )
|> Array.filter ( fun p -> not ( p.Name.Contains( "``" ) ) ) // impossible to represent as verbatim property name, user will have to use string value
match nameCollisions with
| NameCollisions.Omit ->
initialProperties
|> Array.groupBy ( fun p -> p.Name )
|> Array.filter ( fun ( _ , props ) -> props.Length = 1 )
|> Array.collect snd
| NameCollisions.ExtendedSuffix ->
let nameGen = ExtendedSuffixGenerator()
initialProperties
|> Array.groupBy ( fun p -> p.Name )
|> Array.sortBy ( fun ( propName , props ) -> props.Length , propName )
|> Array.collect ( fun ( propName , props ) ->
if Array.length props = 1 then
[| { props.[ 0 ] with Name = nameGen.Single propName } |]
else
let sorted = props |> Array.sortBy ( fun p -> levenshtein propName p.Value , p.Value )
let names = nameGen.Multiple propName props.Length
Array.zip sorted names
|> Array.map ( fun ( p , name ) -> { p with Name = name } )
)
| _ ->
let nameGen = basicSuffixGenerator ()
// A name that exactly matches raw value is exempt from conflict resolution, reserve unique name immediately.
// register unique base names in case later suffixed name conflicts
// e.g. xyz_2 followed by group of xyz, xyz, xyz, they need to know xyz_2 is already reserved
let sameNames , differentNames = initialProperties |> Array.partition ( fun p -> p.Name = p.Value )
let sameNamesFinal = sameNames |> Array.map ( fun p -> { p with Name = nameGen p.Name } )
let differentNamesFinal =
differentNames
|> Array.groupBy ( fun p -> p.Name )
|> Array.collect ( fun ( propName , props ) ->
if Array.length props = 1 then
[| { props.[ 0 ] with Name = nameGen propName } |]
else
// entries with same property name, closest to underying text value gets first chance at unique base name, others get numbered suffix
props
|> Array.sortBy ( fun p -> levenshtein propName p.Value , p.Value )
|> Array.map ( fun p -> { p with Name = nameGen propName } )
)
Array.append sameNamesFinal differentNamesFinal
let tryParseCssClassNames text =
// Remove comments
let text = Regex.Replace( text , @"\s*/\*([^*]|\*(?!/))*\*/\s*" , "" , RegexOptions.None , TimeSpan.FromSeconds 10. )
// Check for existence of one declaration block to indicate valid css.
// Used when file open fails and we are subsequently checking if file-like string is actually inline CSS.
// If no declarations found, we can assume it was in fact an incorrectly-specified file and report meaningful error.
let cssContainsBlock =
Regex.IsMatch(
text
, selectorsAndBlockCapture
, RegexOptions.IgnorePatternWhitespace ||| RegexOptions.ExplicitCapture
, TimeSpan.FromSeconds 10.
)
if cssContainsBlock then
classNamesFromCss text
|> Seq.toArray
|> Some
else None
// Hard-coded since we know we are targeting netstandard2.0
// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.osplatform.create?view=netstandard-2.0
let private validPlatforms = set [ "FREEBSD" ; "LINUX" ; "OSX" ; "WINDOWS" ]
let platformSpecific ( delimiters : string ) ( text : string ) =
if String.IsNullOrWhiteSpace delimiters then text
else
let delimiters = delimiters.Trim()
let outerDelimiter = delimiters.[ 0 ]
let innerDelimiter = if delimiters.Length = 1 then '=' else delimiters.[ 1 ]
let segments = text.Split( outerDelimiter );
if Array.length segments = 1 then Array.head segments
else
let platformValueTuples =
segments
|> Array.map ( fun segment ->
match segment.Split( [| innerDelimiter |] , 2 ) with
| [| platform ; value |] ->
let platform = platform.Trim().ToUpperInvariant()
if Set.contains platform validPlatforms then
platform , value
else
"" , segment
| _ -> "" , segment
)
// First check for at least one valid platform string.
// Else assume accidental syntax match and return original whole string.
if Array.exists ( fun ( plat , _ ) -> plat <> "" ) platformValueTuples then
platformValueTuples
|> Array.tryFind ( fun ( plat , _ ) ->
// Find first for current platform
plat <> "" && RuntimeInformation.IsOSPlatform ( OSPlatform.Create( plat ) )
)
|> Option.orElseWith ( fun () ->
// Else find first with non specified platform
platformValueTuples |> Array.tryFind ( fun ( plat , _ ) -> plat = "" )
)
|> Option.orElseWith ( fun () ->
// Else use first regardless of platform specifier
Array.tryHead platformValueTuples
)
|> Option.map snd
|> Option.defaultValue ""
else
text
let importClassFromCssModule (source: string) (className: string) =
// By separating `default` and the class name with a period,
// Fable will automatically generate JS code like this:
// import style from "./style.module.css";
// let myClass = style.["my-class"];
let selector = Expr.Value("default." + className)
let path = Expr.Value(HttpUtility.JavaScriptStringEncode source)
fun (_args: Expr list) -> <@@ Fable.Core.JsInterop.import<string> %%selector %%path @@>
let addTypeMembersFromCss isCssModule source getProperties ( cssClasses : Property array ) ( cssType : ProvidedTypeDefinition ) =
cssClasses
|> Array.iter ( fun c ->
let propName , propValue = c.Name , c.Value
if isCssModule then
let prop = ProvidedProperty(propName, typeof<string>, isStatic = true, getterCode = (importClassFromCssModule source propValue))
cssType.AddMember prop
else
// Use literal field instead of property for better completion list performance in IDEs
let field = ProvidedField.Literal( propName , typeof< string > , propValue ) // public static literal
field.SetFieldAttributes( field.Attributes ||| Reflection.FieldAttributes.InitOnly ) // prevent assignment
cssType.AddMember field
)
if getProperties then
let rowType = ProvidedTypeDefinition("Property", Some(typeof<string[]>), hideObjectMethods = true)
let rowNameProp = ProvidedProperty("Name", typeof<string>, getterCode = fun (Singleton row) -> <@@ (%%row:string[]).[0] @@>)
rowNameProp.AddXmlDoc "Generated property name using specified naming strategy."
let rowValueProp = ProvidedProperty("Value", typeof<string>, getterCode = fun (Singleton row) -> <@@ (%%row:string[]).[1] @@>)
rowValueProp.AddXmlDoc "The underlying CSS class value."
rowType.AddMember rowNameProp
rowType.AddMember rowValueProp
cssType.AddMember rowType
let propsArray = cssClasses |> Array.map ( fun p -> [| p.Name ; p.Value |] )
let usedNames = cssClasses |> Array.map ( fun p -> p.Name ) |> Set.ofArray
let methodName =
Seq.init 99 ( fun i -> "GetProperties" + if i = 0 then "" else "_" + string ( i + 1 ) )
|> Seq.find ( fun s -> usedNames |> Set.contains s |> not )
let staticMethod =
ProvidedMethod(methodName, [], typedefof<seq<_>>.MakeGenericType(rowType), isStatic = true,
invokeCode = fun _-> <@@ propsArray @@>)
cssType.AddMember staticMethod
================================================
FILE: src/vendor/FSharp.Data/Caching.fs
================================================
//
// Adapted from FSharp.Data for Zanaptak.TypedCssClasses by zanaptak.
//
/// Implements caching using in-memory and local file system
module internal Zanaptak.TypedCssClasses.Internal.FSharpData.Caching
open System
open System.Collections.Concurrent
open System.Diagnostics
open System.IO
open System.Security.Cryptography
open System.Text
open Zanaptak.TypedCssClasses.Internal.FSharpData.IO
type ICache<'TValue> =
abstract Set : key:string * value:'TValue -> unit
abstract TryRetrieve : key:string * ?extendCacheExpiration:bool -> 'TValue option
abstract Remove : key:string -> unit
/// Creates a cache that uses in-memory collection
let createInMemoryCache (expiration:TimeSpan) =
let dict = ConcurrentDictionary<string,'TValue*DateTime>()
let rec invalidationFunction key =
async {
do! Async.Sleep (int expiration.TotalMilliseconds)
match dict.TryGetValue(key) with
| true, (_, timestamp) ->
if DateTime.UtcNow - timestamp >= expiration then
match dict.TryRemove(key) with
| true, _ -> log (sprintf "Cache expired: %O" key)
| _ -> ()
else
do! invalidationFunction key
| _ -> ()
}
{ new ICache<_> with
member __.Set(key, value) =
dict.[key] <- (value, DateTime.UtcNow)
invalidationFunction key |> Async.Start
member x.TryRetrieve(key, ?extendCacheExpiration) =
match dict.TryGetValue(key) with
| true, (value, timestamp) when DateTime.UtcNow - timestamp < expiration ->
if extendCacheExpiration = Some true then
dict.[key] <- (value, DateTime.UtcNow)
Some value
| _ -> None
member __.Remove(key) =
match dict.TryRemove(key) with
| true, _ -> log (sprintf "Explicitly removed from cache: %O" key)
| _ -> ()
}
/// Get hash code of a string - used to determine cache file
let private hashString (plainText:string) =
let plainTextBytes = Encoding.UTF8.GetBytes(plainText)
let hash = new SHA1Managed()
let hashBytes = hash.ComputeHash(plainTextBytes)
let s = Convert.ToBase64String(hashBytes)
s.Replace("ab","abab").Replace("\\","ab")
/// Creates a cache that stores data in a local file system
let createInternetFileCache prefix expiration =
// %UserProfile%\AppData\Local\Microsoft\Windows\INetCache
let cacheFolder =
if Environment.OSVersion.Platform = PlatformID.Unix
then Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/.cache"
else Environment.GetFolderPath(Environment.SpecialFolder.InternetCache)
let downloadCache = Path.Combine(cacheFolder, prefix)
// Get file name for a given string (using hash)
let cacheFile key =
let sha1 = hashString key
let encoded = Uri.EscapeDataString sha1
Path.Combine(downloadCache, encoded + ".txt")
// A simple check for now. This is to guard against a corrupted cache file.
let isWellFormedResult result = not (String.IsNullOrEmpty result)
try
// Try to create directory, if it does not exist
if not (Directory.Exists downloadCache) then
Directory.CreateDirectory downloadCache |> ignore
let cache =
{ new ICache<string> with
member __.Set(key, value) =
let cacheFile = cacheFile key
try File.WriteAllText(cacheFile, value)
with e ->
Debug.WriteLine("Caching: Failed to write file {0} with an exception: {1}", cacheFile, e.Message)
member __.TryRetrieve(key, ?extendCacheExpiration) =
if extendCacheExpiration = Some true then
failwith "Not implemented"
let cacheFile = cacheFile key
try
if File.Exists cacheFile && DateTime.UtcNow - File.GetLastWriteTimeUtc cacheFile < expiration then
let result = File.ReadAllText cacheFile
if isWellFormedResult result
then Some result
else None
else None
with e ->
Debug.WriteLine("Caching: Failed to read file {0} with an exception: {1}", cacheFile, e.Message)
None
member __.Remove(key) =
let cacheFile = cacheFile key
try
File.Delete(cacheFile)
with e ->
Debug.WriteLine("Caching: Failed to delete file {0} with an exception: {1}", cacheFile, e.Message)
}
// Ensure that we can access the file system by writing a sample value to the cache
cache.Set("$$$test$$$", "dummyValue")
match cache.TryRetrieve("$$$test$$$") with
| Some "dummyValue" ->
cache.Remove("$$$test$$$") |> ignore
cache
| _ ->
// fallback to an in memory cache
createInMemoryCache expiration
with e ->
Debug.WriteLine("Caching: Fall back to memory cache, because of an exception: {0}", e.Message)
// fallback to an in memory cache
createInMemoryCache expiration
================================================
FILE: src/vendor/FSharp.Data/Helpers.fs
================================================
//
// Adapted from FSharp.Data for Zanaptak.TypedCssClasses by zanaptak.
//
// Copyright 2011-2015, Tomas Petricek (http://tomasp.net), Gustavo Guerra (http://functionalflow.co.uk), and other contributors
// Licensed under the Apache License, Version 2.0, see LICENSE.md in this project
//
// Helpers for writing type providers
namespace Zanaptak.TypedCssClasses.Internal.FSharpData
open System
open System.Collections.Generic
open System.Diagnostics
open System.IO
open System.Text
open System.Text.RegularExpressions
open System.Web
open FSharp.Core.CompilerServices
open Zanaptak.TypedCssClasses.Internal.FSharpData.Caching
open Zanaptak.TypedCssClasses.Internal.FSharpData.IO
open Zanaptak.TypedCssClasses.Internal.ProviderImplementation.ProvidedTypes
module ProvidedTypes =
type Property = { Name : string ; Value : string }
type DisposableTypeProviderForNamespaces(config, ?assemblyReplacementMap) as x =
inherit TypeProviderForNamespaces(config, ?assemblyReplacementMap=assemblyReplacementMap)
let disposeActions = ResizeArray()
static let mutable idCount = 0
let id = idCount
let filesToWatch = Dictionary< string , string list >()
do idCount <- idCount + 1
let dispose typeNameOpt =
lock disposeActions <| fun () ->
for i = disposeActions.Count-1 downto 0 do
let disposeAction = disposeActions.[i]
let discard = disposeAction typeNameOpt
if discard then
disposeActions.RemoveAt(i)
do
log (sprintf "Creating TypeProviderForNamespaces %O [%d]" x id)
x.Disposing.Add <| fun _ ->
using (logTime "DisposingEvent" (sprintf "%O [%d]" x id)) <| fun _ ->
dispose None
member __.Id = id
member __.SetFileToWatch(fullTypeName, path) =
lock filesToWatch <| fun () ->
filesToWatch.[fullTypeName] <- path
member __.GetFileToWatch(fullTypeName) =
lock filesToWatch <| fun () ->
match filesToWatch.TryGetValue(fullTypeName) with
| true, path -> Some path
| _ -> None
member __.AddDisposeAction action =
lock disposeActions <| fun () -> disposeActions.Add action
member __.InvalidateOneType typeName =
dispose (Some typeName)
logfType typeName id "Calling invalidate for %O [%d]" x id
base.Invalidate()
override x.Finalize() =
log (sprintf "Finalize %O [%d]" x id)
module internal Helpers =
open ProvidedTypes
/// Helper active pattern that can be used when constructing InvokeCode
/// (to avoid writing pattern matching or incomplete matches):
///
/// p.InvokeCode <- fun (Singleton self) -> <@ 1 + 2 @>
///
let (|Singleton|) = function [l] -> l | _ -> failwith "Parameter mismatch"
let private cacheDuration = TimeSpan.FromDays 90.
let private webUrisCache = createInternetFileCache "Zanaptak.TypedCssClasses" cacheDuration
let private invalidPathChars = Set.ofArray ( Path.GetInvalidPathChars() )
let private invalidFileChars = Set.ofArray ( Path.GetInvalidFileNameChars() )
let private isValidFilenameSyntax str =
if
String.IsNullOrWhiteSpace str
|| Seq.exists invalidPathChars.Contains str
|| String.IsNullOrWhiteSpace( Path.GetFileName str )
|| Seq.exists invalidFileChars.Contains ( Path.GetFileName str )
then false
else true
let private tryGetUri fullTypeName tpInstance str =
try
match Uri.TryCreate(str, UriKind.RelativeOrAbsolute) with
| false, _ -> None
| true, uri ->
if isWeb uri then Some uri
else
let path = if uri.IsAbsoluteUri then pathFromFileUri uri else uri.OriginalString
if isValidFilenameSyntax path then Some uri else None
with
| ex ->
logfType fullTypeName tpInstance "tryGetUri error, assuming non file: %O" ex
None
/// If relative file URI, resolves to absolute.
/// Returns absolute URI and flag indicating whether it is a web URI.
let private resolveUri resolutionFolder ( uri : Uri ) =
if uri.IsAbsoluteUri then
uri, isWeb uri
else
Uri( Path.GetFullPath( Path.Combine( resolutionFolder , uri.OriginalString ) ) , UriKind.Absolute ) , false
/// Check if file exists and return absolute path if so
let private tryFilePathExists fullTypeName tpInstance resolutionFolder path =
match tryGetUri fullTypeName tpInstance path with
| None -> None
| Some uri ->
try
match resolveUri resolutionFolder uri with
| uri , false ->
// non-web uri, check if exists
let resolvedPath = pathFromFileUri uri
if File.Exists resolvedPath then Some resolvedPath else None
| _ -> None // web uri
with
| ex ->
logfType fullTypeName tpInstance "resolver error, assuming non file: %A" ex
None // in case of any IO error on resolve or existence check
type RunCommandConfig = {
commandFile : string
argumentPrefix : string
argumentSuffix : string
}
/// Reads a sample parameter for a type provider, detecting if it is a uri and fetching it if needed
/// Samples from the web are cached for 30 minutes
/// Samples from the filesystem are read using shared read, so it works when the file is locked by Excel or similar tools,
///
/// Parameters:
/// * valueToBeParsedOrItsUri - the text which can be a sample or an uri for a sample
/// * parseFunc - receives the file/url extension (or "" if not applicable) and the text value
/// * formatName - the description of what is being parsed (for the error message)
/// * tp - the type provider
/// * cfg - the type provider config
/// * resource - when specified, we first try to treat read the sample from an embedded resource
/// (the value specified assembly and resource name e.g. "MyCompany.MyAssembly, some_resource.json")
/// * resolutionFolder - if the type provider allows to override the resolutionFolder pass it here
let private parseTextAtDesignTime
valueToBeParsedOrItsUri
(tp:DisposableTypeProviderForNamespaces)
(cfg:TypeProviderConfig)
resolutionFolder
fullTypeName
tryParseText =
using (logTimeType fullTypeName tp.Id "ParseTextFromSource" fullTypeName) <| fun _ ->
let formatName = "CSS"
let parseDefaultEmpty x = tryParseText x |> Option.defaultValue [||]
match tryGetUri fullTypeName tp.Id valueToBeParsedOrItsUri with
| None -> parseDefaultEmpty valueToBeParsedOrItsUri , []
| Some uri ->
let resolver = {
ResolutionType = DesignTime
DefaultResolutionFolder = cfg.ResolutionFolder
ResolutionFolder = resolutionFolder
}
let readText() =
let reader, toWatch = asyncRead fullTypeName tp.Id resolver formatName "" uri
use reader = reader |> Async.RunSynchronously
reader.ReadToEnd() , toWatch |> Option.map ( fun s -> [ s ] ) |> Option.defaultValue []
try
let sample , fileWatchList =
if isWeb uri then
match webUrisCache.TryRetrieve(uri.OriginalString) with
| Some text ->
logfType fullTypeName tp.Id "Web URI retrieved from cache: %s" uri.OriginalString
text , []
| None ->
let text , fileWatchList = readText()
webUrisCache.Set(uri.OriginalString, text)
text , fileWatchList
else
readText()
parseDefaultEmpty sample , fileWatchList
with _ ->
// File read failed, try as text instead, if this also fails we'll reraise the original file exception
let attemptedTextParseResult =
if not uri.IsAbsoluteUri then
logfType fullTypeName tp.Id "File read failed, attempting processing as text"
try
// Returns None if text parse failed to match any CSS; in that case we'll assume it was a failed file uri
tryParseText valueToBeParsedOrItsUri
with _ -> None
else None
match attemptedTextParseResult with
| Some parseResult -> parseResult , []
| None -> reraise ()
/// Runs the command supplied to the TP and uses the output of the command as the source sample.
///
/// Parameters:
/// * source - file path to use as main argument for command
/// * commandConfig - configuration for command to run and its arguments
/// * parseFunc - receives the file/url extension (or "" if not applicable) and the text value
/// * tp - the type provider
/// * cfg - the type provider config
/// * resolutionFolder - if the type provider allows to override the resolutionFolder pass it here
let private runCommandAtDesignTime
source
( commandConfig : RunCommandConfig )
(tp:DisposableTypeProviderForNamespaces)
(cfg:TypeProviderConfig)
resolutionFolder
fullTypeName =
using (logTimeType fullTypeName tp.Id "RunCommand" fullTypeName) <| fun _ ->
let initialFileWatchList =
match tryFilePathExists fullTypeName tp.Id resolutionFolder source with
| None -> []
| Some path -> [ path ]
let stdoutSb = StringBuilder()
let stderrSb = StringBuilder()
let pinfo = ProcessStartInfo( commandConfig.commandFile )
pinfo.UseShellExecute <- false
pinfo.RedirectStandardInput <- false
pinfo.RedirectStandardOutput <- true
pinfo.RedirectStandardError <- true
pinfo.CreateNoWindow <- true
pinfo.Arguments <-
[ commandConfig.argumentPrefix ; source ; commandConfig.argumentSuffix ]
|> List.filter ( fun arg -> arg <> "" )
|> String.concat " "
pinfo.WorkingDirectory <- resolutionFolder
logfType fullTypeName tp.Id "Process filename: %s" pinfo.FileName
logfType fullTypeName tp.Id "Arguments: %s" pinfo.Arguments
logfType fullTypeName tp.Id "Working directory: %s" pinfo.WorkingDirectory
use p = new Process()
p.StartInfo <- pinfo
p.OutputDataReceived.Add( fun eventArgs ->
if not ( isNull eventArgs.Data ) then
if stdoutSb.Length = 0 then logType fullTypeName tp.Id "StandardOutput data started"
stdoutSb.AppendLine( eventArgs.Data ) |> ignore
)
p.ErrorDataReceived.Add( fun eventArgs ->
if not ( isNull eventArgs.Data ) then
if stderrSb.Length = 0 then logType fullTypeName tp.Id "StandardError data started"
stderrSb.AppendLine( eventArgs.Data ) |> ignore
)
p.Start() |> ignore
p.BeginOutputReadLine()
p.BeginErrorReadLine()
logType fullTypeName tp.Id "Process started"
if p.WaitForExit( 60000 ) then
logType fullTypeName tp.Id "Process completed"
p.WaitForExit() // flush remaining output to the data receive events
if p.ExitCode <> 0 then
let stderr = stderrSb.ToString()
failwithf "Command failed with exit code %i and stderr: %s" p.ExitCode stderr
use outReader = new StringReader( stdoutSb.ToString() )
// Check initial lines for local files to monitor for changes
let rec checkLine fileWatchList =
if outReader.Peek() < 0 then
( fileWatchList , "" )
else
let line = outReader.ReadLine()
if line.Length > 1000 then
( fileWatchList , line )
else
match tryFilePathExists fullTypeName tp.Id resolutionFolder line with
| None ->
( fileWatchList , line )
| Some path ->
logfType fullTypeName tp.Id "Add file from command output to watch list: %s" path
checkLine ( path :: fileWatchList )
let fileWatchList , firstNonFileLine = checkLine initialFileWatchList
let css =
if outReader.Peek() < 0 then firstNonFileLine
else firstNonFileLine + "\n" + outReader.ReadToEnd()
let maxLen = 50
if css.Length > maxLen then
logfType fullTypeName tp.Id "Output (%i of %i chars): %s" maxLen css.Length ( css.Substring( 0 , maxLen ) )
else
logfType fullTypeName tp.Id "Output: %s" css
css , fileWatchList
else
logType fullTypeName tp.Id "Timed out waiting for process to end"
try p.Kill() with ex -> logfType fullTypeName tp.Id "Error killing process: %O" ex
failwithf "Command timed out: %s %s" pinfo.FileName pinfo.Arguments
let private parseResultCache : ICache< string array * string list > = createInMemoryCache (TimeSpan.FromHours 2.)
let private providedTypesCache = createInMemoryCache (TimeSpan.FromSeconds 30.0)
let private activeDisposeActions = HashSet<_>()
// Cache generated types for a short time, since VS invokes the TP multiple tiems
// Also cache temporarily during partial invalidation since the invalidation of one TP always causes invalidation of all TPs
let internal getOrCreateProvidedType (cfg: TypeProviderConfig) (tp:DisposableTypeProviderForNamespaces) (fullTypeName:string) createTypeFn cacheKey =
let fullKey = (fullTypeName, cfg.RuntimeAssembly, cfg.ResolutionFolder, cfg.SystemRuntimeAssemblyVersion)
let tpInstance = tp.Id
let setupDisposeAction filesToWatch =
if activeDisposeActions.Add(fullTypeName, tp.Id) then
logfType fullTypeName tpInstance "Setting up dispose action"
let watcherDisposer =
match filesToWatch with
| Some files when not ( List.isEmpty files ) ->
let name = sprintf "%s [%d]" fullTypeName tp.Id
// Hold a weak reference to the type provider instance. If the TP instance is leaked
// and not held strongly by anyone else, then don't hold it strongly here.
let tpref = WeakReference<_>(tp)
let invalidateAction action path =
match tpref.TryGetTarget() with
| true, tp ->
logfType fullTypeName tpInstance "File change: %s - Invalidate type: %s" path fullTypeName
tp.InvalidateOneType(fullTypeName)
| _ ->
logfType fullTypeName tpInstance "File change: %s - No TP reference to invalidate" path
()
Some (watchForChanges fullTypeName tpInstance files (name, invalidateAction))
| _ -> None
// On disposal of one of the types, remove that type from the cache, and add all others to the cache
tp.AddDisposeAction <| fun typeNameBeingDisposedOpt ->
// All dispose actions for all types run when any type invalidated (and then new instance of main TP created).
// If multiple types invalidated, all actions run for each invalidation (unless action removes itself).
// Dispose action of invalidated type clears watchers and caches for that type and removes the dispose action so it doesn't rerun.
// Dispose action of non-invalidated type only clears watchers, and leaves action in place in case invalidation of that type is still in queue.
// Change from FSharp.Data: Don't cache types on invalidation. Next creation should be cheap due to the internal parse result cache.
using (logTimeType fullTypeName tpInstance "DisposeAction" fullTypeName) <| fun _ ->
logfType fullTypeName tpInstance "Invalidated type: %s" ( typeNameBeingDisposedOpt |> Option.defaultValue "" )
// might be called more than once for each watcher, but the Dispose action is a NOP the second time
watcherDisposer |> Option.iter (fun watcher -> watcher.Dispose())
match typeNameBeingDisposedOpt with
| Some typeNameBeingDisposed when fullTypeName = typeNameBeingDisposed ->
logfType fullTypeName tpInstance "Removing parse result from cache, key=%s" cacheKey
parseResultCache.Remove cacheKey
logfType fullTypeName tpInstance "Removing type from cache and dropping dispose action"
providedTypesCache.Remove(fullTypeName)
// for the case where a file used by two TPs, when the file changes
// there will be two invalidations: A and B
// when the dispose action is called with A, A is removed from the cache
// so we need to remove the dispose action so it will won't be added when disposed is called with B
true
| _ ->
logfType fullTypeName tpInstance "Keeping dispose action during invalidation of other type"
// for the case where a file used by two TPs, when the file changes
// there will be two invalidations: A and B
// when the dispose action is called with A, B is added to the cache
// so we need to keep the dispose action around so it will be called with B and the cache is removed
false
match providedTypesCache.TryRetrieve(fullTypeName, true) with
| Some (providedType, fullKey2, watchedFile) when fullKey = fullKey2 ->
logType fullTypeName tpInstance "Retrieve type from cache"
setupDisposeAction watchedFile
providedType
| _ ->
let providedType = createTypeFn()
let filesToWatch = tp.GetFileToWatch(fullTypeName)
logType fullTypeName tpInstance "Add type to cache"
providedTypesCache.Set(fullTypeName, (providedType, fullKey, filesToWatch))
setupDisposeAction filesToWatch
providedType
// Modified version of original generateType function to process CSS and create type
let generateType
source
( commandConfig : RunCommandConfig option )
(tp:DisposableTypeProviderForNamespaces)
(cfg:TypeProviderConfig)
resolutionFolder
fullTypeName
tryParseTextClassNames
convertClassNamesToProperties
( createType : Property array -> ProvidedTypeDefinition ) =
using ( logTimeType fullTypeName tp.Id "GenerateType" fullTypeName ) <| fun _ ->
let cacheKey =
match commandConfig with
| Some commandConfig ->
let keyParts =
[
commandConfig.commandFile
; ( [ commandConfig.argumentPrefix ; source ; commandConfig.argumentSuffix ] |> List.filter ( fun arg -> arg <> "" ) |> String.concat " " )
; resolutionFolder
]
|> List.map ( fun s -> HttpUtility.JavaScriptStringEncode( s , true ) )
sprintf "Process:%s,Arguments:%s,Directory:%s" keyParts.[ 0 ] keyParts.[ 1 ] keyParts.[ 2 ]
| None ->
match tryGetUri fullTypeName tp.Id source with
| Some uri ->
let uri , isWeb = resolveUri resolutionFolder uri
if isWeb then
"URI:" + HttpUtility.JavaScriptStringEncode( uri.OriginalString , true )
else
"File:" + HttpUtility.JavaScriptStringEncode( pathFromFileUri uri , true )
| None ->
"Text:" + HttpUtility.JavaScriptStringEncode( source , true )
let createProvidedTypeFromData () =
let parseResult , fileWatchList =
match parseResultCache.TryRetrieve( cacheKey , true ) with
| Some classNamesAndFiles ->
logfType fullTypeName tp.Id "Retrieve parse result from cache, key=%s" cacheKey
classNamesAndFiles
| None ->
let parsedClassNames , files =
match commandConfig with
| Some commandConfig ->
let text , files = runCommandAtDesignTime source commandConfig tp cfg resolutionFolder fullTypeName
tryParseTextClassNames text |> Option.defaultValue [||] , files
| None -> parseTextAtDesignTime source tp cfg resolutionFolder fullTypeName tryParseTextClassNames
logfType fullTypeName tp.Id "Add parse result to cache, key=%s" cacheKey
parseResultCache.Set( cacheKey , ( parsedClassNames , files ) )
parsedClassNames , files
logfType fullTypeName tp.Id "Parsed CSS class count: %i" parseResult.Length
if ( cfg.IsInvalidationSupported && not ( List.isEmpty fileWatchList ) ) then
tp.SetFileToWatch( fullTypeName , List.distinct fileWatchList )
let parsedProperties =
using
( logTimeType fullTypeName tp.Id "ConvertToProperties" fullTypeName )
( fun _ -> convertClassNamesToProperties parseResult )
createType parsedProperties
try
getOrCreateProvidedType cfg tp fullTypeName createProvidedTypeFromData cacheKey
with ex ->
logfType fullTypeName tp.Id "Error: %O" ex
reraise ()
================================================
FILE: src/vendor/FSharp.Data/Http.fs
================================================
//
// Adapted from FSharp.Data for Zanaptak.TypedCssClasses by zanaptak.
//
// --------------------------------------------------------------------------------------
// Utilities for working with network, downloading resources with specified headers etc.
// --------------------------------------------------------------------------------------
namespace Zanaptak.TypedCssClasses.Internal.FSharpData.Http
open System
open System.Globalization
open System.IO
open System.Net
open System.Text
open System.Text.RegularExpressions
open System.Threading
open System.Reflection
open System.Runtime.CompilerServices
open System.Runtime.InteropServices
/// The method to use in an HTTP request
module internal HttpMethod =
// RFC 2626 specifies 8 methods
/// Request information about the communication options available on the request/response chain identified by the URI
let Options = "OPTIONS"
/// Retrieve whatever information (in the form of an entity) is identified by the URI
let Get = "GET"
/// Identical to GET except that the server MUST NOT return a message-body in the response
let Head = "HEAD"
/// Requests that the server accepts the entity enclosed in the request as a
/// new subordinate of the resource identified by the Request-URI in the Request-Line
let Post = "POST"
/// Requests that the enclosed entity be stored under the supplied Request-URI
let Put = "PUT"
/// Requests that the origin server deletes the resource identified by the Request-URI
let Delete = "DELETE"
/// Used to invoke a remote, application-layer loop- back of the request message
let Trace = "TRACE"
/// Reserved for use with a proxy that can dynamically switch to being a tunnel
let Connect = "CONNECT"
// RFC 4918 (WebDAV) adds 7 methods
/// Retrieves properties defined on the resource identified by the request URI
let PropFind = "PROPFIND"
/// Processes instructions specified in the request body to set and/or remove properties defined on the resource identified by the request URI
let PropPatch = "PROPPATCH"
/// Creates a new collection resource at the location specified by the Request URI
let MkCol = "MKCOL"
/// Creates a duplicate of the source resource, identified by the Request-URI, in the destination resource, identified by the URI in the Destination header
let Copy = "COPY"
/// Logical equivalent of a copy, followed by consistency maintenance processing, followed by a delete of the source where all three actions are performed atomically
let Move = "MOVE"
/// Used to take out a lock of any access type on the resource identified by the request URI.
let Lock = "LOCK"
/// Removes the lock identified by the lock token from the request URI, and all other resources included in the lock
let Unlock = "UNLOCK"
// RFC 5789 adds one more
/// Requests that the origin server applies partial modifications contained in the entity enclosed in the request to the resource identified by the request URI
let Patch = "PATCH"
/// Headers that can be sent in an HTTP request
module internal HttpRequestHeaders =
/// Content-Types that are acceptable for the response
let Accept (contentType:string) = "Accept", contentType
/// Character sets that are acceptable
let AcceptCharset (characterSets:string) = "Accept-Charset", characterSets
/// Acceptable version in time
let AcceptDatetime (dateTime:DateTime) = "Accept-Datetime", dateTime.ToString("R", CultureInfo.InvariantCulture)
/// List of acceptable encodings. See HTTP compression.
let AcceptEncoding (encoding:string) = "Accept-Encoding", encoding
/// List of acceptable human languages for response
let AcceptLanguage (language:string) = "Accept-Language", language
/// The Allow header, which specifies the set of HTTP methods supported.
let Allow (methods:string) = "Allow", methods
/// Authentication credentials for HTTP authentication
let Authorization (credentials:string) = "Authorization", credentials
/// Authentication header using Basic Auth encoding
let BasicAuth (username:string) (password:string) =
let base64Encode (s:string) =
let bytes = Encoding.UTF8.GetBytes(s)
Convert.ToBase64String(bytes)
sprintf "%s:%s" username password |> base64Encode |> sprintf "Basic %s" |> Authorization
/// Used to specify directives that MUST be obeyed by all caching mechanisms along the request/response chain
let CacheControl (control:string) = "Cache-Control", control
/// What type of connection the user-agent would prefer
let Connection (connection:string) = "Connection", connection
/// Describes the placement of the content. Valid dispositions are: inline, attachment, form-data
let ContentDisposition (placement: string, name: string option, fileName: string option) =
let namePart = match name with Some n -> sprintf "; name=\"%s\"" n | None -> ""
let fileNamePart = match fileName with Some n -> sprintf "; filename=\"%s\"" n | None -> ""
"Content-Disposition", sprintf "%s%s%s" placement namePart fileNamePart
/// The type of encoding used on the data
let ContentEncoding (encoding:string) = "Content-Encoding", encoding
/// The language the content is in
let ContentLanguage (language:string) = "Content-Language", language
/// An alternate location for the returned data
let ContentLocation (location:string) = "Content-Location", location
/// A Base64-encoded binary MD5 sum of the content of the request body
let ContentMD5 (md5sum:string) = "Content-MD5", md5sum
/// Where in a full body message this partial message belongs
let ContentRange (range:string) = "Content-Range", range
/// The MIME type of the body of the request (used with POST and PUT requests)
let ContentType (contentType:string) = "Content-Type", contentType
/// The MIME type of the body of the request (used with POST and PUT requests) with an explicit encoding
let ContentTypeWithEncoding (contentType, charset:Encoding) = "Content-Type", sprintf "%s; charset=%s" contentType (charset.WebName)
/// The date and time that the message was sent
let Date (date:DateTime) = "Date", date.ToString("R", CultureInfo.InvariantCulture)
/// Indicates that particular server behaviors are required by the client
let Expect (behaviors:string) = "Expect", behaviors
/// Gives the date/time after which the response is considered stale
let Expires (dateTime:DateTime) = "Expires", dateTime.ToString("R", CultureInfo.InvariantCulture)
/// The email address of the user making the request
let From (email:string) = "From", email
/// The domain name of the server (for virtual hosting), and the TCP port number on which the server is listening.
/// The port number may be omitted if the port is the standard port for the service requested.
let Host (host:string) = "Host", host
/// Only perform the action if the client supplied entity matches the same entity on the server.
/// This is mainly for methods like PUT to only update a resource if it has not been modified since the user last updated it. If-Match: "737060cd8c284d8af7ad3082f209582d" Permanent
let IfMatch (entity:string) = "If-Match", entity
/// Allows a 304 Not Modified to be returned if content is unchanged
let IfModifiedSince (dateTime:DateTime) = "If-Modified-Since", dateTime.ToString("R", CultureInfo.InvariantCulture)
/// Allows a 304 Not Modified to be returned if content is unchanged
let IfNoneMatch (etag:string) = "If-None-Match", etag
/// If the entity is unchanged, send me the part(s) that I am missing; otherwise, send me the entire new entity
let IfRange (range:string) = "If-Range", range
/// Only send the response if the entity has not been modified since a specific time
let IfUnmodifiedSince (dateTime:DateTime) = "If-Unmodified-Since", dateTime.ToString("R", CultureInfo.InvariantCulture)
/// Specifies a parameter used into order to maintain a persistent connection
let KeepAlive (keepAlive:string) = "Keep-Alive", keepAlive
/// Specifies the date and time at which the accompanying body data was last modified
let LastModified (dateTime:DateTime) = "Last-Modified", dateTime.ToString("R", CultureInfo.InvariantCulture)
/// Limit the number of times the message can be forwarded through proxies or gateways
let MaxForwards (count:int) = "Max-Forwards", count.ToString()
/// Initiates a request for cross-origin resource sharing (asks server for an 'Access-Control-Allow-Origin' response header)
let Origin (origin:string) = "Origin", origin
/// Implementation-specific headers that may have various effects anywhere along the request-response chain.
let Pragma (pragma:string) = "Pragma", pragma
/// Optional instructions to the server to control request processing. See RFC https://tools.ietf.org/html/rfc7240 for more details
let Prefer (prefer:string) = "Prefer", prefer
/// Authorization credentials for connecting to a proxy.
let ProxyAuthorization (credentials:string) = "Proxy-Authorization", credentials
/// Request only part of an entity. Bytes are numbered from 0
let Range (start:int64, finish:int64) = "Range", sprintf "bytes=%d-%d" start finish
/// This is the address of the previous web page from which a link to the currently requested page was followed. (The word "referrer" is misspelled in the RFC as well as in most implementations.)
let Referer (referer:string) = "Referer", referer
/// The transfer encodings the user agent is willing to accept: the same values as for the response header
/// Transfer-Encoding can be used, plus the "trailers" value (related to the "chunked" transfer method) to
/// notify the server it expects to receive additional headers (the trailers) after the last, zero-sized, chunk.
let TE (te:string) = "TE", te
/// The Trailer general field value indicates that the given set of header fields is present in the trailer of a message encoded with chunked transfer-coding
let Trailer (trailer:string) = "Trailer", trailer
/// The TransferEncoding header indicates the form of encoding used to safely transfer the entity to the user. The valid directives are one of: chunked, compress, deflate, gzip, or identity.
let TransferEncoding (directive: string) = "Transfer-Encoding", directive
/// Microsoft extension to the HTTP specification used in conjunction with WebDAV functionality.
let Translate (translate:string) = "Translate", translate
/// Specifies additional communications protocols that the client supports.
let Upgrade (upgrade:string) = "Upgrade", upgrade
/// The user agent string of the user agent
let UserAgent (userAgent:string) = "User-Agent", userAgent
/// Informs the server of proxies through which the request was sent
let Via (server:string) = "Via", server
/// A general warning about possible problems with the entity body
let Warning (message:string) = "Warning", message
/// Override HTTP method.
let XHTTPMethodOverride (httpMethod:string) = "X-HTTP-Method-Override", httpMethod
/// Headers that can be received in an HTTP response
module internal HttpResponseHeaders =
/// Specifying which web sites can participate in cross-origin resource sharing
let [<Literal>] AccessControlAllowOrigin = "Access-Control-Allow-Origin"
/// What partial content range types this server supports
let [<Literal>] AcceptRanges = "Accept-Ranges"
/// The age the object has been in a proxy cache in seconds
let [<Literal>] Age = "Age"
/// Valid actions for a specified resource. To be used for a 405 Method not allowed
let [<Literal>] Allow = "Allow"
/// Tells all caching mechanisms from server to client whether they may cache this object. It is measured in seconds
let [<Literal>] CacheControl = "Cache-Control"
/// Options that are desired for the connection
let [<Literal>] Connection = "Connection"
/// The type of encoding used on the data. See HTTP compression.
let [<Literal>] ContentEncoding = "Content-Encoding"
/// The language the content is in
let [<Literal>] ContentLanguage = "Content-Language"
/// The length of the response body in octets (8-bit bytes)
let [<Literal>] ContentLength = "Content-Length"
/// An alternate location for the returned data
let [<Literal>] ContentLocation = "Content-Location"
/// A Base64-encoded binary MD5 sum of the content of the response
let [<Literal>] ContentMD5 = "Content-MD5"
/// An opportunity to raise a "File Download" dialogue box for a known MIME type with binary format or suggest a filename for dynamic content. Quotes are necessary with special characters.
let [<Literal>] ContentDisposition = "Content-Disposition"
/// Where in a full body message this partial message belongs
let [<Literal>] ContentRange = "Content-Range"
/// The MIME type of this content
let [<Literal>] ContentType = "Content-Type"
/// The date and time that the message was sent (in "HTTP-date" format as defined by RFC 2616)
let [<Literal>] Date = "Date"
/// An identifier for a specific version of a resource, often a message digest
let [<Literal>] ETag = "ETag"
/// Gives the date/time after which the response is considered stale
let [<Literal>] Expires = "Expires"
/// The last modified date for the requested object
let [<Literal>] LastModified = "Last-Modified"
/// Used to express a typed relationship with another resource, where the relation type is defined by RFC 5988
let [<Literal>] Link = "Link"
/// Used in redirection, or when a new resource has been created.
let [<Literal>] Location = "Location"
/// This header is supposed to set P3P policy
let [<Literal>] P3P = "P3P"
/// Implementation-specific headers that may have various effects anywhere along the request-response chain.
let [<Literal>] Pragma = "Pragma"
/// Request authentication to access the proxy.
let [<Literal>] ProxyAuthenticate = "Proxy-Authenticate"
/// Used in redirection, or when a new resource has been created. This refresh redirects after 5 seconds.
let [<Literal>] Refresh = "Refresh"
/// If an entity is temporarily unavailable, this instructs the client to try again later. Value could be a specified period of time (in seconds) or a HTTP-date.[28]
let [<Literal>] RetryAfter = "Retry-After"
/// A name for the server
let [<Literal>] Server = "Server"
/// An HTTP cookie
let [<Literal>] SetCookie = "Set-Cookie"
/// The HTTP status of the response
let [<Literal>] Status = "Status"
/// A HSTS Policy informing the HTTP client how long to cache the HTTPS only policy and whether this applies to subdomains.
let [<Literal>] StrictTransportSecurity = "Strict-Transport-Security"
/// The Trailer general field value indicates that the given set of header fields is present in the trailer of a message encoded with chunked transfer-coding.
let [<Literal>] Trailer = "Trailer"
/// The form of encoding used to safely transfer the entity to the user. Currently defined methods are: chunked, compress, deflate, gzip, identity.
let [<Literal>] TransferEncoding = "Transfer-Encoding"
/// Tells downstream proxies how to match future request headers to decide whether the cached response can be used rather than requesting a fresh one from the origin server.
let [<Literal>] Vary = "Vary"
/// Informs the client of proxies through which the response was sent.
let [<Literal>] Via = "Via"
/// A general warning about possible problems with the entity body.
let [<Literal>] Warning = "Warning"
/// Indicates the authentication scheme that should be used to access the requested entity.
let [<Literal>] WWWAuthenticate = "WWW-Authenticate"
/// Status codes that can be received in an HTTP response
module internal HttpStatusCodes =
/// The server has received the request headers and the client should proceed to send the request body.
let [<Literal>] Continue = 100
/// The requester has asked the server to switch protocols and the server has agreed to do so.
let [<Literal>] SwitchingProtocols = 101
/// This code indicates that the server has received and is processing the request, but no response is available yet.
let [<Literal>] Processing = 102
/// Used to return some response headers before final HTTP message.
let [<Literal>] EarlyHints = 103
/// Standard response for successful HTTP requests.
let [<Literal>] OK = 200
/// The request has been fulfilled, resulting in the creation of a new resource.
let [<Literal>] Created = 201
/// The request has been accepted for processing, but the processing has not been completed.
let [<Literal>] Accepted = 202
/// The server is a transforming proxy (e.g. a Web accelerator) that received a 200 OK from its origin, but is returning a modified version of the origin's response.
let [<Literal>] NonAuthoritativeInformation = 203
/// The server successfully processed the request and is not returning any content.
let [<Literal>] NoContent = 204
/// The server successfully processed the request, but is not returning any content.
let [<Literal>] ResetContent = 205
/// The server is delivering only part of the resource (byte serving) due to a range header sent by the client.
let [<Literal>] PartialContent = 206
/// The message body that follows is by default an XML message and can contain a number of separate response codes, depending on how many sub-requests were made.
let [<Literal>] MultiStatus = 207
/// The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response, and are not being included again.
let [<Literal>] AlreadyReported = 208
/// The server has fulfilled a request for the resource, and the response is a representation of the result of one or more instance-manipulations applied to the current instance.
let [<Literal>] IMUsed = 226
/// Indicates multiple options for the resource from which the client may choose (via agent-driven content negotiation).
let [<Literal>] MultipleChoices = 300
/// This and all future requests should be directed to the given URI.
let [<Literal>] MovedPermanently = 301
/// Tells the client to look at (browse to) another url. 302 has been superseded by 303 and 307.
let [<Literal>] Found = 302
/// The response to the request can be found under another URI using the GET method.
let [<Literal>] SeeOther = 303
/// Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.
let [<Literal>] NotModified = 304
/// The requested resource is available only through a proxy, the address for which is provided in the response.
let [<Literal>] UseProxy = 305
/// No longer used. Originally meant "Subsequent requests should use the specified proxy."
let [<Literal>] SwitchProxy = 306
/// In this case, the request should be repeated with another URI; however, future requests should still use the original URI.
let [<Literal>] TemporaryRedirect = 307
/// The request and all future requests should be repeated using another URI.
let [<Literal>] PermanentRedirect = 308
/// The server cannot or will not process the request due to an apparent client error.
let [<Literal>] BadRequest = 400
/// Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided.
let [<Literal>] Unauthorized = 401
/// Reserved for future use.
let [<Literal>] PaymentRequired = 402
/// The request was valid, but the server is refusing action. The user might not have the necessary permissions for a resource, or may need an account of some sort.
let [<Literal>] Forbidden = 403
/// The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible.
let [<Literal>] NotFound = 404
/// A request method is not supported for the requested resource.
let [<Literal>] MethodNotAllowed = 405
/// The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.
let [<Literal>] NotAcceptable = 406
/// The client must first authenticate itself with the proxy.
let [<Literal>] ProxyAuthenticationRequired = 407
/// The server timed out waiting for the request.
let [<Literal>] RequestTimeout = 408
/// Indicates that the request could not be processed because of conflict in the request, such as an edit conflict between multiple simultaneous updates.
let [<Literal>] Conflict = 409
/// Indicates that the resource requested is no longer available and will not be available again.
let [<Literal>] Gone = 410
/// The request did not specify the length of its content, which is required by the requested resource.
let [<Literal>] LengthRequired = 411
/// The server does not meet one of the preconditions that the requester put on the request.
let [<Literal>] PreconditionFailed = 412
/// The request is larger than the server is willing or able to process.
let [<Literal>] PayloadTooLarge = 413
/// The URI provided was too long for the server to process.
let [<Literal>] URITooLong = 414
/// The request entity has a media type which the server or resource does not support.
let [<Literal>] UnsupportedMediaType = 415
/// The client has asked for a portion of the file (byte serving), but the server cannot supply that portion.
let [<Literal>] RangeNotSatisfiable = 416
/// The server cannot meet the requirements of the Expect request-header field.
let [<Literal>] ExpectationFailed = 417
/// The request was directed at a server that is not able to produce a response.
let [<Literal>] MisdirectedRequest = 421
/// The request was well-formed but was unable to be followed due to semantic errors.
let [<Literal>] UnprocessableEntity = 422
/// The resource that is being accessed is locked.
let [<Literal>] Locked = 423
/// The request failed because it depended on another request and that request failed (e.g., a PROPPATCH).
let [<Literal>] FailedDependency = 424
/// The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field.
let [<Literal>] UpgradeRequired = 426
/// The origin server requires the request to be conditional.
let [<Literal>] PreconditionRequired = 428
/// The user has sent too many requests in a given amount of time.
let [<Literal>] TooManyRequests = 429
/// The server is unwilling to process the request because either an individual header field, or all the header fields collectively, are too large.
let [<Literal>] RequestHeaderFieldsTooLarge = 431
/// A server operator has received a legal demand to deny access to a resource or to a set of resources that includes the requested resource.
let [<Literal>] UnavailableForLegalReasons = 451
/// A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.
let [<Literal>] InternalServerError = 500
/// The server either does not recognize the request method, or it lacks the ability to fulfil the request.
let [<Literal>] NotImplemented = 501
/// The server was acting as a gateway or proxy and received an invalid response from the upstream server.
let [<Literal>] BadGateway = 502
/// The server is currently unavailable (because it is overloaded or down for maintenance).
let [<Literal>] ServiceUnavailable = 503
/// The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.
let [<Literal>] GatewayTimeout = 504
/// The server does not support the HTTP protocol version used in the request.
let [<Literal>] HTTPVersionNotSupported = 505
/// Transparent content negotiation for the request results in a circular reference.
let [<Literal>] VariantAlsoNegotiates = 506
/// The server is unable to store the representation needed to complete the request.
let [<Literal>] InsufficientStorage = 507
/// The server detected an infinite loop while processing the request.
let [<Literal>] LoopDetected = 508
/// Further extensions to the request are required for the server to fulfil it.
let [<Literal>] NotExtended = 510
/// The client needs to authenticate to gain network access.
let [<Literal>] NetworkAuthenticationRequired = 511
type internal MultipartItem = | MultipartItem of formField: string * filename: string * content: Stream
/// The body to send in an HTTP request
type internal HttpRequestBody =
| TextRequest of string
| BinaryUpload of byte[]
| FormValues of seq<string * string>
/// A sequence of formParamName * fileName * fileContent groups
| Multipart of boundary: string * parts: seq<MultipartItem>
/// The response body returned by an HTTP request
type internal HttpResponseBody =
| Text of string
| Binary of byte[]
/// The response returned by an HTTP request
type internal HttpResponse =
{ Body : HttpResponseBody
StatusCode: int
ResponseUrl : string
/// If the same header is present multiple times, the values will be concatenated with comma as the separator
Headers : Map<string,string>
Cookies : Map<string,string> }
/// The response returned by an HTTP request with direct access to the response stream
type internal HttpResponseWithStream =
{ ResponseStream : Stream
StatusCode: int
ResponseUrl : string
/// If the same header is present multiple times, the values will be concatenated with comma as the separator
Headers : Map<string,string>
Cookies : Map<string,string> }
/// Constants for common HTTP content types
module internal HttpContentTypes =
/// */*
let [<Literal>] Any = "*/*"
/// plain/text
let [<Literal>] Text = "text/plain"
/// application/octet-stream
let [<Literal>] Binary = "application/octet-stream"
/// application/octet-stream
let [<Literal>] Zip = "application/zip"
/// application/octet-stream
let [<Literal>] GZip = "application/gzip"
/// application/x-www-form-urlencoded
let [<Literal>] FormValues = "application/x-www-form-urlencoded"
/// application/json
let [<Literal>] Json = "application/json"
/// application/javascript
let [<Literal>] JavaScript = "application/javascript"
/// application/xml
let [<Literal>] Xml = "application/xml"
/// application/rss+xml
let [<Literal>] Rss = "application/rss+xml"
/// application/atom+xml
let [<Literal>] Atom = "application/atom+xml"
/// application/rdf+xml
let [<Literal>] Rdf = "application/rdf+xml"
/// text/html
let [<Literal>] Html = "text/html"
/// application/xhtml+xml
let [<Literal>] XHtml = "application/xhtml+xml"
/// application/soap+xml
let [<Literal>] Soap = "application/soap+xml"
/// text/csv
let [<Literal>] Csv = "text/csv"
/// application/json-rpc
let [<Literal>] JsonRpc = "application/json-rpc"
/// multipart/form-data
let Multipart boundary = sprintf "multipart/form-data; boundary=%s" boundary
/// text/css
let [<Literal>] Css = "text/css"
type private HeaderEnum = System.Net.HttpRequestHeader
module internal MimeTypes =
open System.Collections.Generic
let private pairs =
[|
(".323", "text/h323")
(".3g2", "video/3gpp2")
(".3gp", "video/3gpp")
(".3gp2", "video/3gpp2")
(".3gpp", "video/3gpp")
(".7z", "application/x-7z-compressed")
(".aa", "audio/audible")
(".AAC", "audio/aac")
(".aaf", "application/octet-stream")
(".aax", "audio/vnd.audible.aax")
(".ac3", "audio/ac3")
(".aca", "application/octet-stream")
(".accda", "application/msaccess.addin")
(".accdb", "application/msaccess")
(".accdc", "application/msaccess.cab")
(".accde", "application/msaccess")
(".accdr", "application/msaccess.runtime")
(".accdt", "application/msaccess")
(".accdw", "application/msaccess.webapplication")
(".accft", "application/msaccess.ftemplate")
(".acx", "application/internet-property-stream")
(".AddIn", "text/xml")
(".ade", "application/msaccess")
(".adobebridge", "application/x-bridge-url")
(".adp", "application/msaccess")
(".ADT", "audio/vnd.dlna.adts")
(".ADTS", "audio/aac")
(".afm", "application/octet-stream")
(".ai", "application/postscript")
(".aif", "audio/aiff")
(".aifc", "audio/aiff")
(".aiff", "audio/aiff")
(".air", "application/vnd.adobe.air-application-installer-package+zip")
(".amc", "application/mpeg")
(".anx", "application/annodex")
(".apk", "application/vnd.android.package-archive" )
(".application", "application/x-ms-application")
(".art", "image/x-jg")
(".asa", "application/xml")
(".asax", "application/xml")
(".ascx", "application/xml")
(".asd", "application/octet-stream")
(".asf", "video/x-ms-asf")
(".ashx", "application/xml")
(".asi", "application/octet-stream")
(".asm", "text/plain")
(".asmx", "application/xml")
(".aspx", "application/xml")
(".asr", "video/x-ms-asf")
(".asx", "video/x-ms-asf")
(".atom", "application/atom+xml")
(".au", "audio/basic")
(".avi", "video/x-msvideo")
(".axa", "audio/annodex")
(".axs", "application/olescript")
(".axv", "video/annodex")
(".bas", "text/plain")
(".bcpio", "application/x-bcpio")
(".bin", "application/octet-stream")
(".bmp", "image/bmp")
(".c", "text/plain")
(".cab", "application/octet-stream")
(".caf", "audio/x-caf")
(".calx", "application/vnd.ms-office.calx")
(".cat", "application/vnd.ms-pki.seccat")
(".cc", "text/plain")
(".cd", "text/plain")
(".cdda", "audio/aiff")
(".cdf", "application/x-cdf")
(".cer", "application/x-x509-ca-cert")
(".cfg", "text/plain")
(".chm", "application/octet-stream")
(".class", "application/x-java-applet")
(".clp", "application/x-msclip")
(".cmd", "text/plain")
(".cmx", "image/x-cmx")
(".cnf", "text/plain")
(".cod", "image/cis-cod")
(".config", "application/xml")
(".contact", "text/x-ms-contact")
(".coverage", "application/xml")
(".cpio", "application/x-cpio")
(".cpp", "text/plain")
(".crd", "application/x-mscardfile")
(".crl", "application/pkix-crl")
(".crt", "application/x-x509-ca-cert")
(".cs", "text/plain")
(".csdproj", "text/plain")
(".csh", "application/x-csh")
(".csproj", "text/plain")
(".css", "text/css")
(".csv", "text/csv")
(".cur", "application/octet-stream")
(".cxx", "text/plain")
(".dat", "application/octet-stream")
(".datasource", "application/xml")
(".dbproj", "text/plain")
(".dcr", "application/x-director")
(".def", "text/plain")
(".deploy", "application/octet-stream")
(".der", "application/x-x509-ca-cert")
(".dgml", "application/xml")
(".dib", "image/bmp")
(".dif", "video/x-dv")
(".dir", "application/x-director")
(".disco", "text/xml")
(".divx", "video/divx")
(".dll", "application/x-msdownload")
(".dll.config", "text/xml")
(".dlm", "text/dlm")
(".doc", "application/msword")
(".docm", "application/vnd.ms-word.document.macroEnabled.12")
(".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
(".dot", "application/msword")
(".dotm", "application/vnd.ms-word.template.macroEnabled.12")
(".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template")
(".dsp", "application/octet-stream")
(".dsw", "text/plain")
(".dtd", "text/xml")
(".dtsConfig", "text/xml")
(".dv", "video/x-dv")
(".dvi", "application/x-dvi")
(".dwf", "drawing/x-dwf")
(".dwp", "application/octet-stream")
(".dxr", "application/x-director")
(".eml", "message/rfc822")
(".emz", "application/octet-stream")
(".eot", "application/vnd.ms-fontobject")
(".eps", "application/postscript")
(".etl", "application/etl")
(".etx", "text/x-setext")
(".evy", "application/envoy")
(".exe", "application/octet-stream")
(".exe.config", "text/xml")
(".fdf", "application/vnd.fdf")
(".fif", "application/fractals")
(".filters", "application/xml")
(".fla", "application/octet-stream")
(".flac", "audio/flac")
(".flr", "x-world/x-vrml")
(".flv", "video/x-flv")
(".fsscript", "application/fsharp-script")
(".fsx", "application/fsharp-script")
(".generictest", "application/xml")
(".gif", "image/gif")
(".gpx", "application/gpx+xml")
(".group", "text/x-ms-group")
(".gsm", "audio/x-gsm")
(".gtar", "application/x-gtar")
(".gz", "application/x-gzip")
(".h", "text/plain")
(".hdf", "application/x-hdf")
(".hdml", "text/x-hdml")
(".hhc", "application/x-oleobject")
(".hhk", "application/octet-stream")
(".hhp", "application/octet-stream")
(".hlp", "application/winhlp")
(".hpp", "text/plain")
(".hqx", "application/mac-binhex40")
(".hta", "application/hta")
(".htc", "text/x-component")
(".htm", "text/html")
(".html", "text/html")
(".htt", "text/webviewhtml")
(".hxa", "application/xml")
(".hxc", "application/xml")
(".hxd", "application/octet-stream")
(".hxe", "application/xml")
(".hxf", "application/xml")
(".hxh", "application/octet-stream")
(".hxi", "application/octet-stream")
(".hxk", "application/xml")
(".hxq", "application/octet-stream")
(".hxr", "application/octet-stream")
(".hxs", "application/octet-stream")
(".hxt", "text/html")
(".hxv", "application/xml")
(".hxw", "application/octet-stream")
(".hxx", "text/plain")
(".i", "text/plain")
(".ico", "image/x-icon")
(".ics", "application/octet-stream")
(".idl", "text/plain")
(".ief", "image/ief")
(".iii", "application/x-iphone")
(".inc", "text/plain")
(".inf", "application/octet-stream")
(".ini", "text/plain")
(".inl", "text/plain")
(".ins", "application/x-internet-signup")
(".ipa", "application/x-itunes-ipa")
(".ipg", "application/x-itunes-ipg")
(".ipproj", "text/plain")
(".ipsw", "application/x-itunes-ipsw")
(".iqy", "text/x-ms-iqy")
(".isp", "application/x-internet-signup")
(".ite", "application/x-itunes-ite")
(".itlp", "application/x-itunes-itlp")
(".itms", "application/x-itunes-itms")
(".itpc", "application/x-itunes-itpc")
(".IVF", "video/x-ivf")
(".jar", "application/java-archive")
(".java", "application/octet-stream")
(".jck", "application/liquidmotion")
(".jcz", "application/liquidmotion")
(".jfif", "image/pjpeg")
(".jnlp", "application/x-java-jnlp-file")
(".jpb", "application/octet-stream")
(".jpe", "image/jpeg")
(".jpeg", "image/jpeg")
(".jpg", "image/jpeg")
(".js", "application/javascript")
(".json", "application/json")
(".jsx", "text/jscript")
(".jsxbin", "text/plain")
(".latex", "application/x-latex")
(".library-ms", "application/windows-library+xml")
(".lit", "application/x-ms-reader")
(".loadtest", "application/xml")
(".lpk", "application/octet-stream")
(".lsf", "video/x-la-asf")
(".lst", "text/plain")
(".lsx", "video/x-la-asf")
(".lzh", "application/octet-stream")
(".m13", "application/x-msmediaview")
(".m14", "application/x-msmediaview")
(".m1v", "video/mpeg")
(".m2t", "video/vnd.dlna.mpeg-tts")
(".m2ts", "video/vnd.dlna.mpeg-tts")
(".m2v", "video/mpeg")
(".m3u", "audio/x-mpegurl")
(".m3u8", "audio/x-mpegurl")
(".m4a", "audio/m4a")
(".m4b", "audio/m4b")
(".m4p", "audio/m4p")
(".m4r", "audio/x-m4r")
(".m4v", "video/x-m4v")
(".mac", "image/x-macpaint")
(".mak", "text/plain")
(".man", "application/x-troff-man")
(".manifest", "application/x-ms-manifest")
(".map", "text/plain")
(".master", "application/xml")
(".mda", "application/msaccess")
(".mdb", "application/x-msaccess")
(".mde", "application/msaccess")
(".mdp", "application/octet-stream")
(".me", "application/x-troff-me")
(".mfp", "application/x-shockwave-flash")
(".mht", "message/rfc822")
(".mhtml", "message/rfc822")
(".mid", "audio/mid")
(".midi", "audio/mid")
(".mix", "application/octet-stream")
(".mk", "text/plain")
(".mmf", "application/x-smaf")
(".mno", "text/xml")
(".mny", "application/x-msmoney")
(".mod", "video/mpeg")
(".mov", "video/quicktime")
(".movie", "video/x-sgi-movie")
(".mp2", "video/mpeg")
(".mp2v", "video/mpeg")
(".mp3", "audio/mpeg")
(".mp4", "video/mp4")
(".mp4v", "video/mp4")
(".mpa", "video/mpeg")
(".mpe", "video/mpeg")
(".mpeg", "video/mpeg")
(".mpf", "application/vnd.ms-mediapackage")
(".mpg", "video/mpeg")
(".mpp", "application/vnd.ms-project")
(".mpv2", "video/mpeg")
(".mqv", "video/quicktime")
(".ms", "application/x-troff-ms")
(".msi", "application/octet-stream")
(".mso", "application/octet-stream")
(".mts", "video/vnd.dlna.mpeg-tts")
(".mtx", "application/xml")
(".mvb", "application/x-msmediaview")
(".mvc", "application/x-miva-compiled")
(".mxp", "application/x-mmxp")
(".nc", "application/x-netcdf")
(".nsc", "video/x-ms-asf")
(".nws", "message/rfc822")
(".ocx", "application/octet-stream")
(".oda", "application/oda")
(".odb", "application/vnd.oasis.opendocument.database")
(".odc", "application/vnd.oasis.opendocument.chart")
(".odf", "application/vnd.oasis.opendocument.formula")
(".odg", "application/vnd.oasis.opendocument.graphics")
(".odh", "text/plain")
(".odi", "application/vnd.oasis.opendocument.image")
(".odl", "text/plain")
(".odm", "application/vnd.oasis.opendocument.text-master")
(".odp", "application/vnd.oasis.opendocument.presentation")
(".ods", "application/vnd.oasis.opendocument.spreadsheet")
(".odt", "application/vnd.oasis.opendocument.text")
(".oga", "audio/ogg")
(".ogg", "audio/ogg")
(".ogv", "video/ogg")
(".ogx", "application/ogg")
(".one", "application/onenote")
(".onea", "application/onenote")
(".onepkg", "application/onenote")
(".onetmp", "application/onenote")
(".onetoc", "application/onenote")
(".onetoc2", "application/onenote")
(".opus", "audio/ogg")
(".orderedtest", "application/xml")
(".osdx", "application/opensearchdescription+xml")
(".otf", "application/font-sfnt")
(".otg", "application/vnd.oasis.opendocument.graphics-template")
(".oth", "application/vnd.oasis.opendocument.text-web")
(".otp", "application/vnd.oasis.opendocument.presentation-template")
(".ots", "application/vnd.oasis.opendocument.spreadsheet-template")
(".ott", "application/vnd.oasis.opendocument.text-template")
(".oxt", "application/vnd.openofficeorg.extension")
(".p10", "application/pkcs10")
(".p12", "application/x-pkcs12")
(".p7b", "application/x-pkcs7-certificates")
(".p7c", "application/pkcs7-mime")
(".p7m", "application/pkcs7-mime")
(".p7r", "application/x-pkcs7-certreqresp")
(".p7s", "application/pkcs7-signature")
(".pbm", "image/x-portable-bitmap")
(".pcast", "application/x-podcast")
(".pct", "image/pict")
(".pcx", "application/octet-stream")
(".pcz", "application/octet-stream")
(".pdf", "application/pdf")
(".pfb", "application/octet-stream")
(".pfm", "application/octet-stream")
(".pfx", "application/x-pkcs12")
(".pgm", "image/x-portable-graymap")
(".pic", "image/pict")
(".pict", "image/pict")
(".pkgdef", "text/plain")
(".pkgundef", "text/plain")
(".pko", "application/vnd.ms-pki.pko")
(".pls", "audio/scpls")
(".pma", "application/x-perfmon")
(".pmc", "application/x-perfmon")
(".pml", "application/x-perfmon")
(".pmr", "application/x-perfmon")
(".pmw", "application/x-perfmon")
(".png", "image/png")
(".pnm", "image/x-portable-anymap")
(".pnt", "image/x-macpaint")
(".pntg", "image/x-macpaint")
(".pnz", "image/png")
(".pot", "application/vnd.ms-powerpoint")
(".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12")
(".potx", "application/vnd.openxmlformats-officedocument.presentationml.template")
(".ppa", "application/vnd.ms-powerpoint")
(".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12")
(".ppm", "image/x-portable-pixmap")
(".pps", "application/vnd.ms-powerpoint")
(".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12")
(".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow")
(".ppt", "application/vnd.ms-powerpoint")
(".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12")
(".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation")
(".prf", "application/pics-rules")
(".prm", "application/octet-stream")
(".prx", "application/octet-stream")
(".ps", "application/postscript")
(".psc1", "application/PowerShell")
(".psd", "application/octet-stream")
(".psess", "application/xml")
(".psm", "application/octet-stream")
(".psp", "application/octet-stream")
(".pub", "application/x-mspublisher")
(".pwz", "application/vnd.ms-powerpoint")
(".qht", "text/x-html-insertion")
(".qhtm", "text/x-html-insertion")
(".qt", "video/quicktime")
(".qti", "image/x-quicktime")
(".qtif", "image/x-quicktime")
(".qtl", "application/x-quicktimeplayer")
(".qxd", "application/octet-stream")
(".ra", "audio/x-pn-realaudio")
(".ram", "audio/x-pn-realaudio")
(".rar", "application/x-rar-compressed")
(".ras", "image/x-cmu-raster")
(".rat", "application/rat-file")
(".rc", "text/plain")
(".rc2", "text/plain")
(".rct", "text/plain")
(".rdlc", "application/xml")
(".reg", "text/plain")
(".resx", "application/xml")
(".rf", "image/vnd.rn-realflash")
(".rgb", "image/x-rgb")
(".rgs", "text/plain")
(".rm", "application/vnd.rn-realmedia")
(".rmi", "audio/mid")
(".rmp", "application/vnd.rn-rn_music_package")
(".roff", "application/x-troff")
(".rpm", "audio/x-pn-realaudio-plugin")
(".rqy", "text/x-ms-rqy")
(".rtf", "application/rtf")
(".rtx", "text/richtext")
(".ruleset", "application/xml")
(".s", "text/plain")
(".safariextz", "application/x-safari-safariextz")
(".scd", "application/x-msschedule")
(".scr", "text/plain")
(".sct", "text/scriptlet")
(".sd2", "audio/x-sd2")
(".sdp", "application/sdp")
(".sea", "application/octet-stream")
(".searchConnector-ms", "application/windows-search-connector+xml")
(".setpay", "application/set-payment-initiation")
(".setreg", "application/set-registration-initiation")
(".settings", "application/xml")
(".sgimb", "application/x-sgimb")
(".sgml", "text/sgml")
(".sh", "application/x-sh")
(".shar", "application/x-shar")
(".shtml", "text/html")
(".sit", "application/x-stuffit")
(".sitemap", "application/xml")
(".skin", "application/xml")
(".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12")
(".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide")
(".slk", "application/vnd.ms-excel")
(".sln", "text/plain")
(".slupkg-ms", "application/x-ms-license")
(".smd", "audio/x-smd")
(".smi", "application/octet-stream")
(".smx", "audio/x-smd")
(".smz", "audio/x-smd")
(".snd", "audio/basic")
(".snippet", "application/xml")
(".snp", "application/octet-stream")
(".sol", "text/plain")
(".sor", "text/plain")
(".spc", "application/x-pkcs7-certificates")
(".spl", "application/futuresplash")
(".spx", "audio/ogg")
(".src", "application/x-wais-source")
(".srf", "text/plain")
(".SSISDeploymentManifest", "text/xml")
(".ssm", "application/streamingmedia")
(".sst", "application/vnd.ms-pki.certstore")
(".stl", "application/vnd.ms-pki.stl")
(".sv4cpio", "application/x-sv4cpio")
(".sv4crc", "application/x-sv4crc")
(".svc", "application/xml")
(".svg", "image/svg+xml")
(".swf", "application/x-shockwave-flash")
(".step", "application/step")
(".stp", "application/step")
(".t", "application/x-troff")
(".tar", "application/x-tar")
(".tcl", "application/x-tcl")
(".testrunconfig", "application/xml")
(".testsettings", "application/xml")
(".tex", "application/x-tex")
(".texi", "application/x-texinfo")
(".texinfo", "application/x-texinfo")
(".tgz", "application/x-compressed")
(".thmx", "application/vnd.ms-officetheme")
(".thn", "application/octet-stream")
(".tif", "image/tiff")
(".tiff", "image/tiff")
(".tlh", "text/plain")
(".tli", "text/plain")
(".toc", "application/octet-stream")
(".tr", "application/x-troff")
(".trm", "application/x-msterminal")
(".trx", "application/xml")
(".ts", "video/vnd.dlna.mpeg-tts")
(".tsv", "text/tab-separated-values")
(".ttf", "application/font-sfnt")
(".tts", "video/vnd.dlna.mpeg-tts")
(".txt", "text/plain")
(".u32", "application/octet-stream")
(".uls", "text/iuls")
(".user", "text/plain")
(".ustar", "application/x-ustar")
(".vb", "text/plain")
(".vbdproj", "text/plain")
(".vbk", "video/mpeg")
(".vbproj", "text/plain")
(".vbs", "text/vbscript")
(".vcf", "text/x-vcard")
(".vcproj", "application/xml")
(".vcs", "text/plain")
(".vcxproj", "application/xml")
(".vddproj", "text/plain")
(".vdp", "text/plain")
(".vdproj", "text/plain")
(".vdx", "application/vnd.ms-visio.viewer")
(".vml", "text/xml")
(".vscontent", "application/xml")
(".vsct", "text/xml")
(".vsd", "application/vnd.visio")
(".vsi", "application/ms-vsi")
(".vsix", "application/vsix")
(".vsixlangpack", "text/xml")
(".vsixmanifest", "text/xml")
(".vsmdi", "application/xml")
(".vspscc", "text/plain")
(".vss", "application/vnd.visio")
(".vsscc", "text/plain")
(".vssettings", "text/xml")
(".vssscc", "text/plain")
(".vst", "application/vnd.visio")
(".vstemplate", "text/xml")
gitextract_1rqpensr/
├── .editorconfig
├── .gitattributes
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── doc/
│ └── configuration.md
├── invoke.build.ps1
├── sample/
│ ├── FableSass/
│ │ ├── .config/
│ │ │ └── dotnet-tools.json
│ │ ├── .editorconfig
│ │ ├── .gitignore
│ │ ├── FableSass.fsproj
│ │ ├── FableSass.sln
│ │ ├── README.md
│ │ ├── content/
│ │ │ ├── _base.sass
│ │ │ ├── index.html
│ │ │ └── styles.sass
│ │ ├── package.json
│ │ ├── sass-process.js
│ │ ├── src/
│ │ │ ├── App.fs
│ │ │ └── Main.fs
│ │ └── webpack.config.js
│ └── FableTailwind/
│ ├── .config/
│ │ └── dotnet-tools.json
│ ├── .editorconfig
│ ├── .gitignore
│ ├── FableTailwind.fsproj
│ ├── FableTailwind.sln
│ ├── README.md
│ ├── content/
│ │ ├── index.html
│ │ └── tailwind-source.css
│ ├── package.json
│ ├── postcss.config.js
│ ├── src/
│ │ ├── App.fs
│ │ └── Main.fs
│ ├── tailwind-process.js
│ ├── tailwind.config.js
│ └── webpack.config.js
├── src/
│ ├── CssClassesTypeProvider.fs
│ ├── Fable.Core.fs
│ ├── TypedCssClasses.fsproj
│ ├── TypedCssClasses.sln
│ ├── Types.fs
│ ├── Utils.fs
│ └── vendor/
│ ├── FSharp.Data/
│ │ ├── Caching.fs
│ │ ├── Helpers.fs
│ │ ├── Http.fs
│ │ ├── IO.fs
│ │ └── LICENSE.md
│ ├── FSharp.TypeProviders.SDK/
│ │ ├── LICENSE.md
│ │ ├── ProvidedTypes.fs
│ │ └── ProvidedTypes.fsi
│ └── README.md
└── test/
├── TestWithFable/
│ ├── .gitignore
│ ├── README.md
│ ├── TestWithFable.sln
│ ├── package.json
│ ├── public/
│ │ └── index.html
│ ├── src/
│ │ ├── TestWithFable.fsproj
│ │ ├── folder/
│ │ │ ├── folder.fs
│ │ │ ├── folder1.css
│ │ │ └── folder2.css
│ │ ├── import/
│ │ │ ├── import.fs
│ │ │ ├── import.js
│ │ │ ├── import1.css
│ │ │ └── import2.css
│ │ ├── main.css
│ │ ├── main.fs
│ │ └── sass/
│ │ ├── sass.fs
│ │ └── sass.sass
│ └── webpack.config.js
└── TypedCssClasses.Tests/
├── Tests.fs
├── TypedCssClasses.Tests.fsproj
├── TypedCssClasses.Tests.sln
└── testdata/
├── bootstrap-431-classes-reference.txt
├── bootstrap-431-min-css.txt
├── tailwind-10-classes-reference.txt
└── tailwind-10-min-css.txt
Condensed preview — 77 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,711K chars).
[
{
"path": ".editorconfig",
"chars": 198,
"preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 4\ntrim_trailing_whitespace = true\ninsert_final_newli"
},
{
"path": ".gitattributes",
"chars": 2518,
"preview": "###############################################################################\n# Set default behavior to automatically "
},
{
"path": ".gitignore",
"chars": 6049,
"preview": "*.log.*\n\n\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-on"
},
{
"path": "CHANGELOG.md",
"chars": 3201,
"preview": "# Changelog - Zanaptak.TypedCssClasses\n\n[](https://githu"
},
{
"path": "LICENSE",
"chars": 1065,
"preview": "MIT License\n\nCopyright (c) 2019 zanaptak\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "README.md",
"chars": 4968,
"preview": "# Zanaptak.TypedCssClasses\n\n[](https://github.com/zanapt"
},
{
"path": "doc/configuration.md",
"chars": 10189,
"preview": "# Configuration - Zanaptak.TypedCssClasses\n\n[](https://g"
},
{
"path": "invoke.build.ps1",
"chars": 7521,
"preview": "# Install PowerShell Core:\n# https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell\n# Ins"
},
{
"path": "sample/FableSass/.config/dotnet-tools.json",
"chars": 145,
"preview": "{\n \"version\": 1,\n \"isRoot\": true,\n \"tools\": {\n \"fable\": {\n \"version\": \"3.2.9\",\n \"commands\": [\n \"f"
},
{
"path": "sample/FableSass/.editorconfig",
"chars": 198,
"preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 4\ntrim_trailing_whitespace = true\ninsert_final_newli"
},
{
"path": "sample/FableSass/.gitignore",
"chars": 6086,
"preview": "dist/\n*.fs.js\n*.fs.js.map\nTypedCssClasses.log\n\n## Ignore Visual Studio temporary files, build results, and\n## files gene"
},
{
"path": "sample/FableSass/FableSass.fsproj",
"chars": 601,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <TargetFramework>netstandard2.0</TargetFramework>\n </PropertyG"
},
{
"path": "sample/FableSass/FableSass.sln",
"chars": 1486,
"preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.2912"
},
{
"path": "sample/FableSass/README.md",
"chars": 1999,
"preview": "# TypedCssClasses Fable Sass Sample\n\n\n\nThis sample illustrates the following concepts:\n\n- Using [Ty"
},
{
"path": "sample/FableSass/content/_base.sass",
"chars": 303,
"preview": "$font-stack: monospace\n$primary-color: blue\n$secondary-color: white\n\ndiv\n .base\n font: 1.25rem $font-stack\n "
},
{
"path": "sample/FableSass/content/index.html",
"chars": 299,
"preview": "<!doctype html>\n<html>\n<head>\n <title>FableSass</title>\n <meta http-equiv='Content-Type' content='text/html; charset=u"
},
{
"path": "sample/FableSass/content/styles.sass",
"chars": 113,
"preview": "@use 'base'\n\ndiv\n .inverse\n background-color: base.$primary-color\n color: base.$secondary-color\n"
},
{
"path": "sample/FableSass/package.json",
"chars": 1140,
"preview": "{\n \"private\": true,\n \"scripts\": {\n \"start\": \"dotnet fable watch FableSass.fsproj --sourceMaps --run webpack"
},
{
"path": "sample/FableSass/sass-process.js",
"chars": 463,
"preview": "const sass = require('sass')\nconst fs = require('fs')\nconst inputFile = fs.realpathSync(process.argv[2])\n\n// run the pre"
},
{
"path": "sample/FableSass/src/App.fs",
"chars": 1334,
"preview": "module App\n\nopen Zanaptak.TypedCssClasses\n\n// Configure type provider to use result of sass transformation.\ntype Css =\n "
},
{
"path": "sample/FableSass/src/Main.fs",
"chars": 95,
"preview": "module Main\n\nopen Feliz\n\nReactDOM.render(App.App(), Browser.Dom.document.getElementById \"app\")\n"
},
{
"path": "sample/FableSass/webpack.config.js",
"chars": 3469,
"preview": "const path = require('path');\n\nconst HtmlWebpackPlugin = require('html-webpack-plugin'); // Inject <script> or <link> ta"
},
{
"path": "sample/FableTailwind/.config/dotnet-tools.json",
"chars": 145,
"preview": "{\n \"version\": 1,\n \"isRoot\": true,\n \"tools\": {\n \"fable\": {\n \"version\": \"3.2.9\",\n \"commands\": [\n \"f"
},
{
"path": "sample/FableTailwind/.editorconfig",
"chars": 198,
"preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 4\ntrim_trailing_whitespace = true\ninsert_final_newli"
},
{
"path": "sample/FableTailwind/.gitignore",
"chars": 6109,
"preview": "dist/\n*.fs.js\n*.fs.js.map\ntailwind-generated.css\nTypedCssClasses.log\n\n## Ignore Visual Studio temporary files, build res"
},
{
"path": "sample/FableTailwind/FableTailwind.fsproj",
"chars": 564,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <TargetFramework>netstandard2.0</TargetFramework>\n </PropertyG"
},
{
"path": "sample/FableTailwind/FableTailwind.sln",
"chars": 1584,
"preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.2912"
},
{
"path": "sample/FableTailwind/README.md",
"chars": 3589,
"preview": "# TypedCssClasses Fable Tailwind Sample\n\n\n\nThis sample illustrates the following concepts:\n\n- Using [Ty"
},
{
"path": "sample/FableTailwind/content/index.html",
"chars": 303,
"preview": "<!doctype html>\n<html>\n<head>\n <title>FableTailwind</title>\n <meta http-equiv='Content-Type' content='text/html; chars"
},
{
"path": "sample/FableTailwind/content/tailwind-source.css",
"chars": 491,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer components {\n\n /* Custom component classes: https:"
},
{
"path": "sample/FableTailwind/package.json",
"chars": 1347,
"preview": "{\n \"private\": true,\n \"scripts\": {\n \"start\": \"dotnet fable watch FableTailwind.fsproj --sourceMaps --run web"
},
{
"path": "sample/FableTailwind/postcss.config.js",
"chars": 340,
"preview": "const isDevelopment = process.env.WEBPACK_SERVE === 'true';\n\nconst cssnano = require('cssnano')({\n preset: ['default'"
},
{
"path": "sample/FableTailwind/src/App.fs",
"chars": 2871,
"preview": "module App\n\nopen Zanaptak.TypedCssClasses\n\n// Configure type provider to use generated Tailwind classes.\n// Naming.Verba"
},
{
"path": "sample/FableTailwind/src/Main.fs",
"chars": 95,
"preview": "module Main\n\nopen Feliz\n\nReactDOM.render(App.App(), Browser.Dom.document.getElementById \"app\")\n"
},
{
"path": "sample/FableTailwind/tailwind-process.js",
"chars": 821,
"preview": "const fs = require('fs')\n\nconst tailwindConfigFile = fs.realpathSync(process.argv[2])\nconst inputFile = fs.realpathSync("
},
{
"path": "sample/FableTailwind/tailwind.config.js",
"chars": 2745,
"preview": "const colors = require('tailwindcss/colors')\n\nmodule.exports = {\n purge: [\n './content/**/*.html',\n './"
},
{
"path": "sample/FableTailwind/webpack.config.js",
"chars": 3653,
"preview": "const path = require('path');\n\nconst HtmlWebpackPlugin = require('html-webpack-plugin'); // Inject <script> or <link> ta"
},
{
"path": "src/CssClassesTypeProvider.fs",
"chars": 8135,
"preview": "namespace Zanaptak.TypedCssClasses\n\nopen Zanaptak.TypedCssClasses.Internal.FSharpData.Helpers\nopen Zanaptak.TypedCssClas"
},
{
"path": "src/Fable.Core.fs",
"chars": 382,
"preview": "// Fable replaces methods from Fable.Core just by fullname so we can avoid\n// reference issues by including the methods "
},
{
"path": "src/TypedCssClasses.fsproj",
"chars": 2707,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <TargetFramework>netstandard2.0</TargetFramework>\n <Disable"
},
{
"path": "src/TypedCssClasses.sln",
"chars": 2137,
"preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.2910"
},
{
"path": "src/Types.fs",
"chars": 217,
"preview": "namespace Zanaptak.TypedCssClasses\n\ntype Naming =\n | Verbatim = 0\n | Underscores = 1\n | CamelCase = 2\n | Pas"
},
{
"path": "src/Utils.fs",
"chars": 23082,
"preview": "module internal Zanaptak.TypedCssClasses.Utils\n\nopen System\nopen System.Globalization\nopen System.Text.RegularExpression"
},
{
"path": "src/vendor/FSharp.Data/Caching.fs",
"chars": 5095,
"preview": "//\n// Adapted from FSharp.Data for Zanaptak.TypedCssClasses by zanaptak.\n//\n\n/// Implements caching using in-memory and "
},
{
"path": "src/vendor/FSharp.Data/Helpers.fs",
"chars": 22798,
"preview": "//\n// Adapted from FSharp.Data for Zanaptak.TypedCssClasses by zanaptak.\n//\n\n// Copyright 2011-2015, Tomas Petricek (htt"
},
{
"path": "src/vendor/FSharp.Data/Http.fs",
"chars": 90948,
"preview": "//\n// Adapted from FSharp.Data for Zanaptak.TypedCssClasses by zanaptak.\n//\n\n// ----------------------------------------"
},
{
"path": "src/vendor/FSharp.Data/IO.fs",
"chars": 10017,
"preview": "//\n// Adapted from FSharp.Data for Zanaptak.TypedCssClasses by zanaptak.\n//\n\n/// Helper functions called from the genera"
},
{
"path": "src/vendor/FSharp.Data/LICENSE.md",
"chars": 10061,
"preview": "Copyright 2011-2017, Tomas Petricek (http://tomasp.net), Gustavo Guerra (http://functionalflow.co.uk), and other contrib"
},
{
"path": "src/vendor/FSharp.TypeProviders.SDK/LICENSE.md",
"chars": 1106,
"preview": "The MIT License (MIT)\n\nCopyright (c) Microsoft Corporation.\nAll rights reserved.\n\nPermission is hereby granted, free of "
},
{
"path": "src/vendor/FSharp.TypeProviders.SDK/ProvidedTypes.fs",
"chars": 821114,
"preview": "//\n// Modified for Zanaptak.TypedCssClasses by zanaptak.\n//\n\n// Copyright (c) Microsoft Corporation, Tomas Petricek, Gus"
},
{
"path": "src/vendor/FSharp.TypeProviders.SDK/ProvidedTypes.fsi",
"chars": 29010,
"preview": "//\n// Modified for Zanaptak.TypedCssClasses by zanaptak.\n//\n\n// Copyright (c) Microsoft Corporation 2005-2014 and other "
},
{
"path": "src/vendor/README.md",
"chars": 512,
"preview": "Code is copied from the following sources. \n\n* FSharp.TypeProviders.SDK\n * included [license](./FSharp.TypeProviders.SD"
},
{
"path": "test/TestWithFable/.gitignore",
"chars": 19,
"preview": "*.log.*\nbundle.js*\n"
},
{
"path": "test/TestWithFable/README.md",
"chars": 834,
"preview": "Build the `Debug` version of the type provider.\n```\ndotnet build ../../src/TypedCssClasses.fsproj -c Debug\n```\n\nThe `src"
},
{
"path": "test/TestWithFable/TestWithFable.sln",
"chars": 1444,
"preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.2912"
},
{
"path": "test/TestWithFable/package.json",
"chars": 532,
"preview": "{\n \"private\": true,\n \"scripts\": {\n \"start\": \"webpack serve\",\n \"build\": \"webpack\"\n },\n \"dependencies\": {\n \"@"
},
{
"path": "test/TestWithFable/public/index.html",
"chars": 361,
"preview": "<!doctype html>\n<html>\n<head>\n <title>Fable</title>\n <meta http-equiv='Content-Type' content='text/html; charset=utf-8"
},
{
"path": "test/TestWithFable/src/TestWithFable.fsproj",
"chars": 1119,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <TargetFramework>netstandard2.0</TargetFramework>\n <LangVers"
},
{
"path": "test/TestWithFable/src/folder/folder.fs",
"chars": 485,
"preview": "module Folder\nopen Fable.React\nopen Fable.React.Props\nopen Zanaptak.TypedCssClasses\n\ntype css = CssClasses<\"folder/folde"
},
{
"path": "test/TestWithFable/src/folder/folder1.css",
"chars": 45,
"preview": ".folder1class { background-color: skyblue; }\n"
},
{
"path": "test/TestWithFable/src/folder/folder2.css",
"chars": 45,
"preview": ".folder2class { background-color: skyblue; }\n"
},
{
"path": "test/TestWithFable/src/import/import.fs",
"chars": 454,
"preview": "module Import\nopen Fable.React\nopen Fable.React.Props\nopen Zanaptak.TypedCssClasses\n\ntype css = CssClasses<\"import1.css\""
},
{
"path": "test/TestWithFable/src/import/import.js",
"chars": 744,
"preview": "const postcss = require('postcss')\nconst postcssImport = require('postcss-import')\nconst fs = require('fs')\nconst inputF"
},
{
"path": "test/TestWithFable/src/import/import1.css",
"chars": 69,
"preview": "@import \"import2.css\";\n\n.import1class { background-color: skyblue; }\n"
},
{
"path": "test/TestWithFable/src/import/import2.css",
"chars": 45,
"preview": ".import2class { background-color: skyblue; }\n"
},
{
"path": "test/TestWithFable/src/main.css",
"chars": 42,
"preview": ".mainclass { background-color: skyblue; }\n"
},
{
"path": "test/TestWithFable/src/main.fs",
"chars": 760,
"preview": "module Main\nopen Fable.React\nopen Fable.React.Props\nopen Zanaptak.TypedCssClasses\n\ntype css = CssClasses<\"main.css\", log"
},
{
"path": "test/TestWithFable/src/sass/sass.fs",
"chars": 319,
"preview": "module Sass\nopen Fable.React\nopen Fable.React.Props\nopen Zanaptak.TypedCssClasses\n\ntype css = CssClasses<\"sass.sass\", lo"
},
{
"path": "test/TestWithFable/src/sass/sass.sass",
"chars": 41,
"preview": ".sassclass\n background-color: skyblue\n"
},
{
"path": "test/TestWithFable/webpack.config.js",
"chars": 730,
"preview": "var path = require(\"path\");\n\nmodule.exports = {\n mode: \"development\",\n entry: \"./src/TestWithFable.fsproj\",\n ou"
},
{
"path": "test/TypedCssClasses.Tests/Tests.fs",
"chars": 12331,
"preview": "module TypedCssClasses.Tests\n\nopen NUnit.Framework\nopen Zanaptak.TypedCssClasses\nopen Zanaptak.TypedCssClasses.Utils\nope"
},
{
"path": "test/TypedCssClasses.Tests/TypedCssClasses.Tests.fsproj",
"chars": 1672,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <TargetFramework>netcoreapp3.1</TargetFramework>\n <IsPackab"
},
{
"path": "test/TypedCssClasses.Tests/TypedCssClasses.Tests.sln",
"chars": 1347,
"preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.2912"
},
{
"path": "test/TypedCssClasses.Tests/testdata/bootstrap-431-classes-reference.txt",
"chars": 17257,
"preview": "accordion\nactive\nalert\nalert-danger\nalert-dark\nalert-dismissible\nalert-heading\nalert-info\nalert-light\nalert-link\nalert-p"
},
{
"path": "test/TypedCssClasses.Tests/testdata/bootstrap-431-min-css.txt",
"chars": 155758,
"preview": "/*!\n * Bootstrap v4.3.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 The Bootstrap Authors\n * Copyright 2011-2019 "
},
{
"path": "test/TypedCssClasses.Tests/testdata/tailwind-10-min-css.txt",
"chars": 361112,
"preview": "/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adju"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the zanaptak/TypedCssClasses GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 77 files (1.6 MB), approximately 451.2k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.