├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASE_NOTES.md ├── Tennis.sln ├── appveyor.yml ├── img ├── icon.png └── tennis.gif └── src ├── Layout.fs ├── Program.fs └── Tennis.fsproj /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | nupkg -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Tennis 2 | 3 | Please take a moment to review this document in order to make the contribution 4 | process easy and effective for everyone involved! 5 | 6 | ## Using the issue tracker 7 | 8 | Use the issues tracker for: 9 | 10 | * [bug reports](#bug-reports) 11 | * [feature requests](#feature-requests) 12 | * [submitting pull requests](#pull-requests) 13 | 14 | Please **do not** use the issue tracker for personal or commercial support requests. 15 | 16 | Personal support request should be discussed on Gitter. 17 | 18 | Comercial support is provided by [Lambda Factory](http://lambdafactory.io/) and other companies specializing in F# consulting. 19 | 20 | ## Bug reports 21 | 22 | A bug is either a _demonstrable problem_ that is caused by the code in the repository, 23 | or indicate missing, unclear, or misleading documentation. Good bug reports are extremely 24 | helpful - thank you! 25 | 26 | Guidelines for bug reports: 27 | 28 | 1. **Use the GitHub issue search** — check if the issue has already been 29 | reported. 30 | 31 | 2. **Check if the issue has been fixed** — try to reproduce it using the 32 | `master` branch in the repository. 33 | 34 | 3. **Isolate and report the problem** — ideally create a reduced test 35 | case. 36 | 37 | Please try to be as detailed as possible in your report. Include information about 38 | your Operating System, as well as your `dotnet` (or `mono` \ .Net Framework), F# and Tennis versions. Please provide steps to 39 | reproduce the issue as well as the outcome you were expecting! All these details 40 | will help developers to fix any potential bugs. 41 | 42 | Example: 43 | 44 | > Short and descriptive example bug report title 45 | > 46 | > A summary of the issue and the environment in which it occurs. If suitable, 47 | > include the steps required to reproduce the bug. 48 | > 49 | > 1. This is the first step 50 | > 2. This is the second step 51 | > 3. Further steps, etc. 52 | > 53 | > `` - a link to the reduced test case (e.g. a GitHub Gist) 54 | > 55 | > Any other information you want to share that is relevant to the issue being 56 | > reported. This might include the lines of code that you have identified as 57 | > causing the bug, and potential solutions (and your opinions on their 58 | > merits). 59 | 60 | ## Feature requests 61 | 62 | Feature requests are welcome and should be discussed on issue tracker. But take a moment to find 63 | out whether your idea fits with the scope and aims of the project. It's up to *you* 64 | to make a strong case to convince the community of the merits of this feature. 65 | Please provide as much detail and context as possible. 66 | 67 | 68 | ## Pull requests 69 | 70 | Good pull requests - patches, improvements, new features - are a fantastic 71 | help. They should remain focused in scope and avoid containing unrelated 72 | commits. 73 | 74 | **IMPORTANT**: By submitting a patch, you agree that your work will be 75 | licensed under the license used by the project. 76 | 77 | If you have any large pull request in mind (e.g. implementing features, 78 | refactoring code, etc), **please ask first** otherwise you risk spending 79 | a lot of time working on something that the project's developers might 80 | not want to merge into the project. 81 | 82 | Please adhere to the coding conventions in the project (indentation, 83 | accurate comments, etc.) and don't forget to add your own tests and 84 | documentation. When working with git, we recommend the following process 85 | in order to craft an excellent pull request: 86 | 87 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone your fork, 88 | and configure the remotes: 89 | 90 | ```bash 91 | # Clone your fork of the repo into the current directory 92 | git clone https://github.com//Tennis 93 | # Navigate to the newly cloned directory 94 | cd Tennis 95 | # Assign the original repo to a remote called "upstream" 96 | git remote add upstream https://github.com/LambdaFactory/Tennis 97 | ``` 98 | 99 | 2. If you cloned a while ago, get the latest changes from upstream, and update your fork: 100 | 101 | ```bash 102 | git checkout master 103 | git pull upstream master 104 | git push 105 | ``` 106 | 107 | 3. Create a new topic branch (off of `master`) to contain your feature, change, 108 | or fix. 109 | 110 | **IMPORTANT**: Making changes in `master` is discouraged. You should always 111 | keep your local `master` in sync with upstream `master` and make your 112 | changes in topic branches. 113 | 114 | ```bash 115 | git checkout -b 116 | ``` 117 | 118 | 4. Commit your changes in logical chunks. Keep your commit messages organized, 119 | with a short description in the first line and more detailed information on 120 | the following lines. Feel free to use Git's 121 | [interactive rebase](https://help.github.com/articles/about-git-rebase/) 122 | feature to tidy up your commits before making them public. 123 | 124 | 5. Make sure all the tests are still passing. 125 | 126 | ```bash 127 | ./build.sh 128 | ``` 129 | 130 | 6. Push your topic branch up to your fork: 131 | 132 | ```bash 133 | git push origin 134 | ``` 135 | 136 | 7. [Open a Pull Request](https://help.github.com/articles/about-pull-requests/) 137 | with a clear title and description. 138 | 139 | 8. If you haven't updated your pull request for a while, you should consider 140 | rebasing on master and resolving any conflicts. 141 | 142 | **IMPORTANT**: _Never ever_ merge upstream `master` into your branches. You 143 | should always `git rebase` on `master` to bring your changes up to date when 144 | necessary. 145 | 146 | ```bash 147 | git checkout master 148 | git pull upstream master 149 | git checkout 150 | git rebase master 151 | ``` 152 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Lambda Factory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tennis 2 | 3 | ![AppVeyor](https://ci.appveyor.com/api/projects/status/github/LambdaFactory/Tennis) ![NuGet](https://img.shields.io/nuget/v/Tennis.svg?style=flat-square) ![GitHub release](https://img.shields.io/github/release/LambdaFactory/Tennis.svg?style=flat-square) 4 | 5 | Simple, standalone, static content server for developers. Its aim is to provide simple utility for developers testing their web applications locally or working on sites having just static content (like HTML, CSS and JS). 6 | 7 | ![](https://raw.githubusercontent.com/LambdaFactory/Tennis/master/img/tennis.gif) 8 | 9 | ## How to use 10 | 11 | 0. Install .Net Core 2.1 12 | 1. `dotnet tool install -g tennis` 13 | 2. `tennis` 14 | 15 | ## Options 16 | 17 | * `--port` | `-p` - Specifies port used for hosting server. Default value: 8080 18 | * `--dir` | `-d` - Specifies root directory. Default is the current directory 19 | * `--help` - Displays list of options 20 | 21 | 22 | ## How to contribute 23 | 24 | *Imposter syndrome disclaimer*: I want your help. No really, I do. 25 | 26 | There might be a little voice inside that tells you you're not ready; that you need to do one more tutorial, or learn another framework, or write a few more blog posts before you can help me with this project. 27 | 28 | I assure you, that's not the case. 29 | 30 | This project has some clear Contribution Guidelines and expectations that you can [read here](https://github.com/LambdaFactory/Tennis/blob/master/CONTRIBUTING.md). 31 | 32 | The contribution guidelines outline the process that you'll need to follow to get a patch merged. By making expectations and process explicit, I hope it will make it easier for you to contribute. 33 | 34 | And you don't just have to write code. You can help out by writing documentation, tests, or even by giving feedback about this work. (And yes, that includes giving feedback about the contribution guidelines.) 35 | 36 | Thank you for contributing! 37 | 38 | 39 | ## Contributing and copyright 40 | 41 | The project is hosted on [GitHub](https://github.com/LambdaFactory/Tennis) where you can [report issues](https://github.com/LambdaFactory/Tennis/issues), fork 42 | the project and submit pull requests. 43 | 44 | The library is available under [MIT license](https://github.com/LambdaFactory/Tennis/blob/master/LICENSE.md), which allows modification and redistribution for both commercial and non-commercial purposes. 45 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | ### 1.0.2 - 08.07.2018 2 | * Fix link generation when running Tennis in root (for example `C:\`) 3 | 4 | ### 1.0.1 - 08.07.2018 5 | * Fix browsing on *nix 6 | 7 | ### 1.0.0 - 07.07.2018 8 | * Initial version 9 | * Hosting static files 10 | * Browsing directories 11 | * Option to set port -------------------------------------------------------------------------------- /Tennis.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Tennis", "src\Tennis.fsproj", "{96C40571-4692-40B0-8074-AA45AFCA6FC7}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {96C40571-4692-40B0-8074-AA45AFCA6FC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {96C40571-4692-40B0-8074-AA45AFCA6FC7}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {96C40571-4692-40B0-8074-AA45AFCA6FC7}.Debug|x64.ActiveCfg = Debug|Any CPU 24 | {96C40571-4692-40B0-8074-AA45AFCA6FC7}.Debug|x64.Build.0 = Debug|Any CPU 25 | {96C40571-4692-40B0-8074-AA45AFCA6FC7}.Debug|x86.ActiveCfg = Debug|Any CPU 26 | {96C40571-4692-40B0-8074-AA45AFCA6FC7}.Debug|x86.Build.0 = Debug|Any CPU 27 | {96C40571-4692-40B0-8074-AA45AFCA6FC7}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {96C40571-4692-40B0-8074-AA45AFCA6FC7}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {96C40571-4692-40B0-8074-AA45AFCA6FC7}.Release|x64.ActiveCfg = Release|Any CPU 30 | {96C40571-4692-40B0-8074-AA45AFCA6FC7}.Release|x64.Build.0 = Release|Any CPU 31 | {96C40571-4692-40B0-8074-AA45AFCA6FC7}.Release|x86.ActiveCfg = Release|Any CPU 32 | {96C40571-4692-40B0-8074-AA45AFCA6FC7}.Release|x86.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: 2 | - Visual Studio 2017 3 | - Ubuntu 4 | init: 5 | - git config --global core.autocrlf input 6 | 7 | build_script: 8 | - cmd: dotnet pack -c Release -o ./nupkg 9 | - sh: dotnet pack -c Release -o ./nupkg 10 | test: off 11 | version: 1.0.2.{build} 12 | artifacts: 13 | - path: 'src\nupkg\*.nupkg' 14 | name: NuGet 15 | -------------------------------------------------------------------------------- /img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LambdaFactory/Tennis/89aad5d63a900233ec432692be7a770d7bd664e1/img/icon.png -------------------------------------------------------------------------------- /img/tennis.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LambdaFactory/Tennis/89aad5d63a900233ec432692be7a770d7bd664e1/img/tennis.gif -------------------------------------------------------------------------------- /src/Layout.fs: -------------------------------------------------------------------------------- 1 | module Layout 2 | 3 | open Giraffe.GiraffeViewEngine 4 | open System.IO 5 | 6 | let layout dir (content: XmlNode list) = 7 | html [_class "has-navbar-fixed-top"] [ 8 | head [] [ 9 | meta [_charset "utf-8"] 10 | meta [_name "viewport"; _content "width=device-width, initial-scale=1" ] 11 | title [] [encodedText ("Tennis | " + dir)] 12 | link [_rel "stylesheet"; _href "https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" ] 13 | link [_rel "stylesheet"; _href "https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.1/css/bulma.min.css" ] 14 | 15 | ] 16 | body [] [ 17 | yield nav [ _class "navbar is-fixed-top has-shadow" ] [ 18 | div [_class "navbar-brand"] [ 19 | a [_class "navbar-item"; _href "/"] [ 20 | img [_src "https://raw.githubusercontent.com/LambdaFactory/Tennis/master/img/icon.png"; _width "28"; _height "28"] 21 | ] 22 | ] 23 | div [_class "navbar-menu"; _id "navMenu"] [ 24 | div [_class "navbar-start"] [ 25 | span [_class "navbar-item";] [rawText ("Current path - " + dir)] 26 | ] 27 | ] 28 | ] 29 | yield! content 30 | ] 31 | ] 32 | 33 | 34 | let fileList (runningDir: string) (currentDir: string) (dirs: string list) (files: string list) = 35 | let s = 36 | section [_class "section"] [ 37 | div [_class "content"] [ 38 | ul [] [ 39 | let parent = Directory.GetParent(currentDir) 40 | let relativeDir = if parent = null then currentDir else parent.FullName 41 | yield! dirs |> List.map (fun n -> 42 | let p = Path.GetRelativePath(relativeDir, n) 43 | let name = Path.GetRelativePath(currentDir, n) 44 | li [] [ a [_href p] [rawText name ]]) 45 | yield! files |> List.map (fun n -> 46 | let p = Path.GetRelativePath(relativeDir, n) 47 | let name = Path.GetRelativePath(currentDir, n) 48 | li [] [a [_href p] [rawText name ]] ) 49 | ] 50 | ] 51 | ] 52 | layout currentDir [s] 53 | 54 | let fileNotFound (runningDir: string) (path: string) = 55 | let p = Path.GetDirectoryName (path) 56 | let p = Path.GetRelativePath(runningDir,p) 57 | let s = 58 | section [_class "section"] [ 59 | div [_class "content"] [ 60 | 61 | h2 [] [rawText "File Not Found"] 62 | a [_href p] [rawText "Go Back"] 63 | ] 64 | ] 65 | layout path [s] -------------------------------------------------------------------------------- /src/Program.fs: -------------------------------------------------------------------------------- 1 | open Saturn 2 | open System.IO 3 | open Giraffe 4 | open Layout 5 | open Argu 6 | open System.Runtime.InteropServices 7 | 8 | type Arguments = 9 | | [] Port of port:int 10 | | [] Dir of dir:string 11 | with 12 | interface IArgParserTemplate with 13 | member s.Usage = 14 | match s with 15 | | Port _ -> "Specify a port." 16 | | Dir _ -> "Specify a root directory." 17 | 18 | 19 | let handler dir = scope { 20 | getf "%s" (fun s -> 21 | let isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) 22 | let s = if s = null then "/" else s 23 | let s = if isWindows then s.Replace("/", "\\") else s 24 | let p = dir + s 25 | if Directory.Exists p then 26 | let dirs = Directory.GetDirectories p |> Seq.toList 27 | let files = Directory.GetFiles(p, "*.*", SearchOption.TopDirectoryOnly) |> Seq.toList 28 | htmlView (fileList dir p dirs files) 29 | elif File.Exists p then 30 | streamFile true p None None 31 | else 32 | htmlView (fileNotFound dir p) 33 | ) 34 | } 35 | 36 | let app port dir = application { 37 | url (sprintf "http://0.0.0.0:%d/" port) 38 | router (handler dir) 39 | } 40 | 41 | [] 42 | let main argv = 43 | 44 | let parser = ArgumentParser.Create(programName = "tennis") 45 | let results = parser.Parse(argv, raiseOnUsage = false, ignoreUnrecognized = true) 46 | if not results.IsUsageRequested then 47 | let port = results.GetResult (Port, defaultValue = 8080) 48 | let dir = results.GetResult (Dir, defaultValue = Directory.GetCurrentDirectory()) 49 | app port dir 50 | |> run 51 | else 52 | parser.PrintUsage() |> printfn "%s" 53 | 0 -------------------------------------------------------------------------------- /src/Tennis.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.2 4 | Simple, static content server for developers 5 | Krzysztof Cieslak 6 | Copyright 2018 Lambda Factory 7 | https://github.com/LambdaFactory/Tennis/blob/master/LICENSE 8 | https://github.com/LambdaFactory/Tennis 9 | https://raw.githubusercontent.com/LambdaFactory/Tennis/master/img/icon.png 10 | https://github.com/LambdaFactory/Tennis 11 | Exe 12 | netcoreapp2.1 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | --------------------------------------------------------------------------------