Compare commits

..

No commits in common. 'master' and 'v0.1.8' have entirely different histories.

@ -1,73 +0,0 @@
on:
push:
branches: [master]
pull_request:
branches: ["*"]
name: build
jobs:
test_macos:
name: Run Tests on macOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v1
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v1
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Setup toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: clippy
override: true
- name: check
run: cargo check
- name: clippy
run: cargo clippy
- name: test
run: cargo test
- name: build
run: cargo build --release
test_ubuntu:
name: Run Tests on Ubuntu (x86_64)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Cache cargo registry
uses: actions/cache@v3
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v3
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v3
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: check
run: cargo check
- name: clippy
run: cargo clippy
- name: test
run: cargo test
- name: build
run: cargo build --release

@ -1,162 +0,0 @@
name: Create Release
on:
push:
tags:
- "v*.*.*"
jobs:
build_armv7:
name: Build for armv7
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Cache cargo registry
uses: actions/cache@v3
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v3
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v3
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Setup Toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: armv7-unknown-linux-gnueabihf
override: true
- name: Build release
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --release --locked --target armv7-unknown-linux-gnueabihf
- name: Get current version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/}
- name: Package Binary
run: cp doc/phd.1 target/armv7-unknown-linux-gnueabihf/release && cd target/armv7-unknown-linux-gnueabihf/release && tar zcvf phd-${{ steps.get_version.outputs.VERSION }}-linux-armv7.tgz phd phd.1
- name: Upload Artifact
uses: actions/upload-artifact@v1
with:
name: phd-linux-armv7
path: target/armv7-unknown-linux-gnueabihf/release/phd-${{ steps.get_version.outputs.VERSION }}-linux-armv7.tgz
build_linux:
name: Build for Linux x86_64
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Cache cargo registry
uses: actions/cache@v3
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v3
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v3
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Build release
run: cargo build --locked --release
- name: Get current version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/}
- name: Package Binary
run: cp doc/phd.1 target/release && cd target/release && tar zcvf phd-${{ steps.get_version.outputs.VERSION }}-linux-x86_64.tgz phd phd.1
- name: Upload Artifact
uses: actions/upload-artifact@v1
with:
name: phd-linux-x86_64
path: target/release/phd-${{ steps.get_version.outputs.VERSION }}-linux-x86_64.tgz
build_macos:
name: Build for macOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v1
- name: Cache cargo registry
uses: actions/cache@v3
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v3
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v3
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Setup Toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Build release
uses: actions-rs/cargo@v1
with:
command: build
args: --locked --release
- name: Get current version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/}
- name: Package Binary
run: cp doc/phd.1 target/release && cd target/release && zip -r phd-${{ steps.get_version.outputs.VERSION }}-macos.zip phd phd.1
- name: Upload Artifact
uses: actions/upload-artifact@v1
with:
name: phd-macos
path: target/release/phd-${{ steps.get_version.outputs.VERSION }}-macos.zip
create:
name: Create Release
needs: [build_armv7, build_linux, build_macos]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Get current version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/}
- name: Download macOS artifact
uses: actions/download-artifact@v1
with:
name: phd-macos
- name: Download Linux (x86_64) artifact
uses: actions/download-artifact@v1
with:
name: phd-linux-x86_64
- name: Download Linux (armv7) artifact
uses: actions/download-artifact@v1
with:
name: phd-linux-armv7
- name: Create Release
uses: softprops/action-gh-release@v1
with:
draft: true
prerelease: true
files: |
phd-macos/phd-${{ steps.get_version.outputs.VERSION }}-macos.zip
phd-linux-x86_64/phd-${{ steps.get_version.outputs.VERSION }}-linux-x86_64.tgz
phd-linux-armv7/phd-${{ steps.get_version.outputs.VERSION }}-linux-armv7.tgz
body_path: CHANGELOG.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

@ -1,62 +0,0 @@
## v0.1.15
- Update `alphanumeric-sort` dependency so `cargo install phd` works again.
- Recommend `cargo install phd --locked` in the README
## v0.1.14
- If the `NO_COLOR` env variable is set, colors won't be printed to
the log. Same as starting with `--no-color`.
See https://no-color.org/.
## v0.1.13
- Added `--no-color` command line option to not display color when
logging.
- Slight change to binding behavior: if `-p` is passed without `-b`,
we'll try to bind to that port. To this easier: `phd -p 7777`
- Accept `?` as query string indicator, not just `TAB`. See #3.
## v0.1.12
`phd` now uses `-b` and `--bind` to set the host and port to
bind to. `-p` and `-h` are now strictly for URL generation.
This should hopefully make it easier to run `phd` behind a
proxy and still generate proper links.
Thanks to @bradfier for the patch!
## v0.1.11
`phd` now ships with a basic manual!
It will be installed via homebrew and (eventually) AUR.
For now you can view it by cloning the repository and running:
man ./doc/phd.1
Enjoy!
## v0.1.10
`phd` can now render a single page to stdout, instead of starting
as a server. Those of us in the biz refer to this as "serverless".
For example, if your Gopher site lives in `/srv/gopher` and you want
to render the main page, just run:
phd -r / /srv/gopher
This will print the raw Gopher menu to stdout!
To view the "/about" page, pass that selector:
phd -r / /srv/gopher
Edge computing is now Gopher-scale! Enjoy!
## v0.1.9
Switch to using GitHub Actions for release automation.

49
Cargo.lock generated

@ -1,74 +1,81 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "alphanumeric-sort"
version = "1.4.4"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77e9c9abb82613923ec78d7a461595d52491ba7240f3c64c0bbe0e6d98e0fce0"
[[package]]
name = "content_inspector"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
dependencies = [
"memchr",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "gophermap"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "hermit-abi"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f629dc602392d3ec14bfc8a09b5e644d7ffd725102b48b81e59f90f2633621d7"
dependencies = [
"libc",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libc"
version = "0.2.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
[[package]]
name = "memchr"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
[[package]]
name = "num_cpus"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
dependencies = [
"hermit-abi",
"libc",
"hermit-abi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "phd"
version = "0.1.15"
version = "0.1.8"
dependencies = [
"alphanumeric-sort",
"content_inspector",
"shell-escape",
"threadpool",
"alphanumeric-sort 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"content_inspector 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"gophermap 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"shell-escape 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "shell-escape"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "170a13e64f2a51b77a45702ba77287f5c6829375b04a69cf2222acd17d0cfab9"
[[package]]
name = "threadpool"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
dependencies = [
"num_cpus",
"num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum alphanumeric-sort 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f37ce94154d73f6961f87571a3ab7814e1608f373bd55a933e3e771b6dd59fc4"
"checksum content_inspector 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
"checksum gophermap 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6ec2186bfad5a5dcbc9307dbc2d2444062300a836ae91b00dd80c3b71c34af3b"
"checksum hermit-abi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f629dc602392d3ec14bfc8a09b5e644d7ffd725102b48b81e59f90f2633621d7"
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
"checksum shell-escape 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "170a13e64f2a51b77a45702ba77287f5c6829375b04a69cf2222acd17d0cfab9"
"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"

@ -1,6 +1,6 @@
[package]
name = "phd"
version = "0.1.15"
version = "0.1.8"
authors = ["chris west <c@xvxx.io>"]
license = "MIT"
edition = "2018"
@ -22,11 +22,12 @@ opt-level = 'z' # Optimize for size.
pre-release-replacements = [
{file="README.md", search="phd-v\\d+\\.\\d+\\.\\d+-", replace="{{crate_name}}-v{{version}}-"},
{file="README.md", search="/v\\d+\\.\\d+\\.\\d+/", replace="/v{{version}}/"},
{file="CHANGELOG.md", search="\\d+\\.\\d+\\.\\d+-dev", replace="{{version}}"},
]
dev-version-ext = "dev"
[dependencies]
content_inspector = "0.2.4"
threadpool = "1.7.1"
alphanumeric-sort = "1.4"
gophermap = "0.1.2"
alphanumeric-sort = "1.0.11"
shell-escape = "0.1.4"

@ -34,15 +34,3 @@ $(RELEASE): $(SOURCES)
# Build the debug version
$(DEBUG): $(SOURCES)
cargo build
# Build manual
.PHONY: manual
manual: doc/phd.1
doc/phd.1: doc/phd.1.md scdoc
scdoc < doc/phd.1.md > doc/phd.1
# Must have scdoc installed to build manual.
scdoc:
@which scdoc || (echo "scdoc(1) not found."; \
echo "please install to build the manual: https://repology.org/project/scdoc"; exit 1)

@ -4,50 +4,39 @@
| )| )| )
|__/ | / |__/
|
-->
<p align="center">
<img src="./img/logo.png"> <br>
--> <p align="center"> <img src="./img/logo.png"> <br>
<a href="https://github.com/xvxx/phd/releases">
<img src="https://img.shields.io/github/v/release/xvxx/phd?include_prereleases">
</a>
<a href="https://crates.io/crates/phd">
<img src="https://img.shields.io/crates/v/phd">
</a>
<a href="https://github.com/xvxx/phd/actions?query=workflow%3Abuild">
<img src="https://github.com/xvxx/phd/workflows/build/badge.svg">
</a>
</p>
---
`phd` is a small, easy-to-use gopher server.
Point it at a directory and it'll serve up all the text files,
Point it at a directory and it'll serve up all its text files,
sub-directories, and binary files over Gopher. Any `.gph` files will
be served up as [gophermaps][map] and executable `.gph` files will be
be served up as [gopermaps][map] and executable `.gph` files will be
run as a program with their output served to the client, like the
glorious cgi-bin days of yore!
### ~ special files ~
### special files:
- **`header.gph`**: If it exists in a directory, its content will be
- **header.gph**: If it exists in a directory, its content will be
shown above the directory's content. Put ASCII art in it.
- **`footer.gph`**: Same, but will be shown below a directory's content.
- **`index.gph`**: Completely replaces a directory's content with what's
- **footer.gph**: Same, but will be shown below a directory's content.
- **index.gph**: Completely replaces a directory's content with what's
in this file.
- **`??.gph`**: Visiting `gopher://yoursite/1/dog/` will try to render
`dog.gph` from disk. Visiting `/1/dog.gph` will render the raw
content of the .gph file.
- **`.reverse`**: If this exists, the directory contents will be listed
- **??.gph**: Visiting gopher://yoursite/1/dog/ will try to render
`dog.gph` from disk. Visiting /1/dog.gph will render the raw content
of the .gph file.
- **.reverse**: If this exists, the directory contents will be listed
in reverse alphanumeric order. Useful for phloggin', if you date
your posts.
Any line in a `.gph` file that doesn't contain tabs (`\t`) will get an
`i` automatically prefixed, turning it into a Gopher information item.
Any line in a `.gph` file that doesn't contain tabs (`\t`) and doesn't
start with an `i` will get an `i` automatically prefixed, turning it
into a gopher information item.
For your convenience, phd supports **[geomyidae][gmi]** syntax for
creating links:
Alternatively, phd supports [geomyidae][gmi] syntax:
This is an info line.
[1|This is a link|/help|server|port]
@ -56,10 +45,7 @@ creating links:
`server` and `port` will get translated into the server and port of
the actively running server, eg `localhost` and `7070`.
Any line containing a tab character (`\t`) will be sent as-is to the
client, meaning you can write and serve up raw Gophermap files too.
### ~ dynamic content ~
### dynamic content:
Any `.gph` file that is marked **executable** with be run as if it
were a standalone program and its output will be sent to the client.
@ -95,7 +81,7 @@ then:
[INFO] |_| |_|_| \__, |\___/| .__/|_| |_|\___|_|
[INFO] |___/ |_|
### ~ ruby on rails ~
### ruby on rails:
`sh` is fun, but for serious work you need a serious scripting
language like Ruby or PHP or Node.JS:
@ -115,7 +101,7 @@ language like Ruby or PHP or Node.JS:
end
Now you can finally share the file sizes of a directory with the world
of Gopher!
of Gopher!
$ phetch -r 0.0.0.0:7070/1/sizes
i~ file sizes ~ (null) 127.0.0.1 7070
@ -130,19 +116,14 @@ of Gopher!
isizes.gph 276B (null) 127.0.0.1 7070
isrc 224B (null) 127.0.0.1 7070
## ~ usage ~
Usage:
## usage
phd [options] <root directory>
Options:
-r, --render SELECTOR Render and print SELECTOR to stdout only.
-h, --host HOST Hostname for links. [Default: {host}]
-p, --port PORT Port for links. [Default: {port}]
-b, --bind ADDRESS Socket address to bind to. [Default: {bind}]
--no-color Don't show colors in log messages.
-p, --port Port to bind to.
-h, --host Hostname to use when generating links.
Other flags:
@ -154,52 +135,45 @@ of Gopher!
phd ./path/to/site # Serve directory over port 7070.
phd -p 70 docs # Serve 'docs' directory on port 70
phd -h gopher.com # Serve current directory over port 7070
# using hostname 'gopher.com'
phd -r / ./site # Render local gopher site to stdout.
# using hostname "gopher.com"
## ~ installation ~
## installation
On macOS you can install with [Homebrew](https://brew.sh/):
brew install xvxx/code/phd
Binaries for Linux, Mac, and Raspberry Pi are available at
Binaries for Linux, Mac, and Raspberry Pi are available at
gopher://phkt.io/1/releases/phd and https://github.com/xvxx/phd/releases:
- [phd-v0.1.15-linux-x86_64.tar.gz][0]
- [phd-v0.1.15-linux-armv7.tar.gz (Raspberry Pi)][1]
- [phd-v0.1.15-macos.zip][2]
Just unzip/untar the `phd` program into your `$PATH` and get going!
If you have **[cargo][rustup]**, you can install the crate directly:
- [phd-v0.1.8-linux-x86_64.tar.gz][0]
- [phd-v0.1.8-linux-armv7.tar.gz (Raspberry Pi)][1]
- [phd-v0.1.8-macos.zip][2]
cargo install phd --locked
Just unzip/untar the `phd` program into your $PATH and get going!
## ~ development ~
## development
cargo run -- ./path/to/gopher/site
## ~ resources ~
## resources
- gopher://bitreich.org/1/scm/geomyidae/files.gph
- https://github.com/gophernicus/gophernicus/blob/master/README.Gophermap
- https://gopher.zone/posts/how-to-gophermap/
- [rfc 1436](https://tools.ietf.org/html/rfc1436)
## ~ todo ~
## todo
- [ ] script/serverless mode
- [ ] systemd config, or something
- [ ] TLS support
- [ ] man page
- [ ] ipv6
- [ ] user input sanitization tests
## ~ status ~
phd is no longer under active development, but the latest version works great.
[0]: https://github.com/xvxx/phd/releases/download/v0.1.15/phd-v0.1.15-linux-x86_64.tar.gz
[1]: https://github.com/xvxx/phd/releases/download/v0.1.15/phd-v0.1.15-linux-armv7.tar.gz
[2]: https://github.com/xvxx/phd/releases/download/v0.1.15/phd-v0.1.15-macos.zip
[0]: https://github.com/xvxx/phd/releases/download/v0.1.8/phd-v0.1.8-linux-x86_64.tar.gz
[1]: https://github.com/xvxx/phd/releases/download/v0.1.8/phd-v0.1.8-linux-armv7.tar.gz
[2]: https://github.com/xvxx/phd/releases/download/v0.1.8/phd-v0.1.8-macos.zip
[map]: https://en.wikipedia.org/wiki/Gopher_(protocol)#Source_code_of_a_menu
[gmi]: http://r-36.net/scm/geomyidae/
[rustup]: https://rustup.rs
[gmi]: gopher://bitreich.org/1/scm/geomyidae

@ -1,206 +0,0 @@
.\" Generated by scdoc 1.11.0
.\" Complete documentation for this program is not available as a GNU info page
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.nh
.ad l
.\" Begin generated content:
.TH "PHD" "1" "2020-06-27"
.P
.SH NAME
.P
phd - an estoeric gopher server
.P
.SH SYNOPSIS
.P
\fBphd\fR [\fIOPTIONS\fR] [\fISITE ROOT\fR]
.P
.SH DESCRIPTION
.P
\fBphd\fR is a small, easy-to-use gopher server.
.P
Point it at a directory and it'll serve up all the text files,
sub-directories, and binary files over Gopher. Any \fB.gph\fR files will
be served up as Gophermaps and executable \fB.gph\fR files will be
run as a program with their output served to the client, like the
glorious cgi-bin days of yore!
.P
Usually \fBphd\fR is started with a path to your Gopher site:
.P
.RS 4
phd /srv/gopher
.P
.RE
If no path is given, \fBphd\fR will use the current directory as the root
of your Gopher site.
.P
.SH OPTIONS
.P
\fB-r\fR \fISELECTOR\fR, \fB--render\fR \fISELECTOR\fR
.P
.RS 4
Rather than start as a server, render the \fISELECTOR\fR of the site using the options provided and print the raw response to \fBSTDOUT\fR.
.P
.RE
\fB-b\fR \fIADDRESS\fR, \fB--bind\fR \fIADDRESS\fR
.RS 4
Set the socket address to bind to, e.g. \fB127.0.0.1:7070\fR
.P
.RE
\fB-p\fR \fIPORT\fR, \fB--port\fR \fIPORT\fR
.RS 4
Set the \fIPORT\fR to use when generating Gopher links.
.P
.RE
\fB-h\fR \fIHOST\fR, \fB--host\fR \fIHOST\fR
.RS 4
Set the \fIHOST\fR to use when generating Gopher links.
.P
.RE
\fB-h\fR, \fB--help\fR
.RS 4
Print a help summary and exit.
.P
.RE
\fB-v\fR, \fB--version\fR
.RS 4
Print version information and exit.
.P
.RE
.SH SPECIAL FILES
.P
The following files have special behavior when present in a directory
that \fBphd\fR is tasked with serving:
.P
\fBheader.gph\fR
.RS 4
If it exists in a directory, its content will be shown above the directory's content. Put ASCII art in it.
.P
.RE
\fBfooter.gph\fR
.RS 4
Same, but will be shown below a directory's content.
.P
.RE
\fBindex.gph\fR
.RS 4
Completely replaces a directory's content with what's in this file.
.P
.RE
\fB??.gph\fR
.RS 4
Visiting \fBgopher://yoursite/1/dog/\fR will try to render \fBdog.gph\fR from disk. Visiting \fB/1/dog.gph\fR will render the raw content of the .gph file.
.P
.RE
\fB.reverse\fR
.RS 4
If this exists, the directory contents will be listed in reverse alphanumeric order. Useful for phloggin', if you date your posts.
.P
.RE
.SH GOPHERMAP SYNTAX
.P
Any line in a \fB.gph\fR file that doesn't contain tabs (\fBt\fR) will get an
\fBi\fR automatically prefixed, turning it into a Gopher information item.
.P
For your convenience, phd supports \fBgeomyidae\fR syntax for
creating links:
.P
.nf
.RS 4
This is an info line\&.
[1|This is a link|/help|server|port]
[h|URL Link|URL:https://noogle\&.com]
.fi
.RE
.P
\fBserver\fR and \fBport\fR will get translated into the server and port of
the actively running server, eg \fBlocalhost\fR and \fB7070\fR.
.P
Any line containing a tab character (\fBt\fR) will be sent as-is to the
client, meaning you can write and serve up raw Gophermap files too.
.P
.SH DYNAMIC CONTENT
.P
Any \fB.gph\fR file that is marked \fBexecutable\fR with be run as if it
were a standalone program and its output will be sent to the client.
It will be passed three arguments: the query string (if any), the
server's hostname, and the current port. Do with them what you will.
.P
For example:
.P
.nf
.RS 4
$ cat echo\&.gph
#!/bin/sh
echo "Hi, world! You said:" $1
echo "1Visit Gopherpedia / gopherpedia\&.com 70"
.fi
.RE
.P
Then:
.P
.nf
.RS 4
$ gopher-client gopher://localhost/1/echo?something
[INFO] Hi, world! You said: something
[LINK] Visit Gopherpedia
.fi
.RE
.P
Or more seriously:
.P
.nf
.RS 4
$ cat figlet\&.gph
#!/bin/sh
figlet $1
.fi
.RE
.P
then:
.P
.nf
.RS 4
$ gopher-client gopher://localhost/1/figlet?hi gopher
[INFO] _ _ _
[INFO] | |__ (_) __ _ ___ _ __ | |__ ___ _ __
[INFO] | '_ | | / _` |/ _ | '_ | '_ / _ '__|
[INFO] | | | | | | (_| | (_) | |_) | | | | __/ |
[INFO] |_| |_|_| __, |___/| \&.__/|_| |_|___|_|
[INFO] |___/ |_|
.fi
.RE
.P
.SS RESOURCES
.P
geomyidae source code
.RS 4
gopher://bitreich.org/1/scm/geomyidae/files.gph
.P
.RE
Example Gophermap
.RS 4
https://github.com/gophernicus/gophernicus/blob/master/README.Gophermap
.P
.RE
Gophermaps
.RS 4
https://gopher.zone/posts/how-to-gophermap/
.P
.RE
RFC 1436:
.RS 4
https://tools.ietf.org/html/rfc1436
.P
.RE
.SH ABOUT
.P
\fBphd\fR is maintained by chris west and released under the MIT license.
.P
phd's Gopher hole:
.RS 4
\fIgopher://phkt.io/1/phd\fR
.RE
phd's webpage:
.RS 4
\fIhttps://github.com/xvxx/phd\fR

@ -1,154 +0,0 @@
PHD(1)
# NAME
phd - an estoeric gopher server
# SYNOPSIS
*phd* [_OPTIONS_] [_SITE ROOT_]
# DESCRIPTION
*phd* is a small, easy-to-use gopher server.
Point it at a directory and it'll serve up all the text files,
sub-directories, and binary files over Gopher. Any *.gph* files will
be served up as Gophermaps and executable *.gph* files will be
run as a program with their output served to the client, like the
glorious cgi-bin days of yore!
Usually *phd* is started with a path to your Gopher site:
phd /srv/gopher
If no path is given, *phd* will use the current directory as the root
of your Gopher site.
# OPTIONS
*-r* _SELECTOR_, *--render* _SELECTOR_
Rather than start as a server, render the _SELECTOR_ of the site using the options provided and print the raw response to *STDOUT*.
*-b* _ADDRESS_, *--bind* _ADDRESS_
Set the socket address to bind to, e.g. *127.0.0.1:7070*
*-p* _PORT_, *--port* _PORT_
Set the _PORT_ to use when generating Gopher links.
*-h* _HOST_, *--host* _HOST_
Set the _HOST_ to use when generating Gopher links.
*-h*, *--help*
Print a help summary and exit.
*-v*, *--version*
Print version information and exit.
# SPECIAL FILES
The following files have special behavior when present in a directory
that *phd* is tasked with serving:
*header.gph*
If it exists in a directory, its content will be shown above the directory's content. Put ASCII art in it.
*footer.gph*
Same, but will be shown below a directory's content.
*index.gph*
Completely replaces a directory's content with what's in this file.
*??.gph*
Visiting *gopher://yoursite/1/dog/* will try to render *dog.gph* from disk. Visiting */1/dog.gph* will render the raw content of the .gph file.
*.reverse*
If this exists, the directory contents will be listed in reverse alphanumeric order. Useful for phloggin', if you date your posts.
# GOPHERMAP SYNTAX
Any line in a *.gph* file that doesn't contain tabs (*\t*) will get an
*i* automatically prefixed, turning it into a Gopher information item.
For your convenience, phd supports *geomyidae* syntax for
creating links:
```
This is an info line.
[1|This is a link|/help|server|port]
[h|URL Link|URL:https://noogle.com]
```
*server* and *port* will get translated into the server and port of
the actively running server, eg *localhost* and *7070*.
Any line containing a tab character (*\t*) will be sent as-is to the
client, meaning you can write and serve up raw Gophermap files too.
# DYNAMIC CONTENT
Any *.gph* file that is marked *executable* with be run as if it
were a standalone program and its output will be sent to the client.
It will be passed three arguments: the query string (if any), the
server's hostname, and the current port. Do with them what you will.
For example:
```
$ cat echo.gph
#!/bin/sh
echo "Hi, world! You said:" $1
echo "1Visit Gopherpedia / gopherpedia.com 70"
```
Then:
```
$ gopher-client gopher://localhost/1/echo?something
[INFO] Hi, world! You said: something
[LINK] Visit Gopherpedia
```
Or more seriously:
```
$ cat figlet.gph
#!/bin/sh
figlet $1
```
then:
```
$ gopher-client gopher://localhost/1/figlet?hi gopher
[INFO] _ _ _
[INFO] | |__ (_) __ _ ___ _ __ | |__ ___ _ __
[INFO] | '_ \| | / _` |/ _ \| '_ \| '_ \ / _ \ '__|
[INFO] | | | | | | (_| | (_) | |_) | | | | __/ |
[INFO] |_| |_|_| \__, |\___/| .__/|_| |_|\___|_|
[INFO] |___/ |_|
```
## RESOURCES
geomyidae source code
gopher://bitreich.org/1/scm/geomyidae/files.gph
Example Gophermap
https://github.com/gophernicus/gophernicus/blob/master/README.Gophermap
Gophermaps
https://gopher.zone/posts/how-to-gophermap/
RFC 1436:
https://tools.ietf.org/html/rfc1436
# ABOUT
*phd* is maintained by chris west and released under the MIT license.
phd's Gopher hole:
_gopher://phkt.io/1/phd_
phd's webpage:
_https://github.com/xvxx/phd_

@ -1,9 +1,9 @@
/ |
___ (___ ___|
| )| )| )
|__/ | / |__/
|
/ |
___ (___ ___|
| )| )| )
|__/ | / |__/
|
~ browse source ~
(updated nightly)

@ -5,24 +5,7 @@
//! println!("{}Error: {}{}", color::Red, "Something broke.", color::Reset);
//! ```
use std::{
fmt,
sync::atomic::{AtomicBool, Ordering as AtomicOrdering},
};
/// Whether to show colors or not.
/// Defaults to true.
static SHOW_COLORS: AtomicBool = AtomicBool::new(true);
/// Hide colors.
pub fn hide_colors() {
SHOW_COLORS.swap(false, AtomicOrdering::Relaxed);
}
/// Are we showing colors are not?
pub fn showing_colors() -> bool {
SHOW_COLORS.load(AtomicOrdering::Relaxed)
}
use std::fmt;
macro_rules! color {
($t:ident, $code:expr) => {
@ -30,11 +13,7 @@ macro_rules! color {
pub struct $t;
impl fmt::Display for $t {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if showing_colors() {
write!(f, "\x1b[{}m", $code)
} else {
write!(f, "")
}
write!(f, "\x1b[{}m", $code)
}
}
};

@ -1,127 +0,0 @@
//! Gopher type "borrowed" from phetch.
use std::fmt;
/// Gopher types are defined according to RFC 1436.
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Type {
Text, // 0
Menu, // 1
CSOEntity, // 2
Error, // 3
Binhex, // 4
DOSFile, // 5
UUEncoded, // 6
Search, // 7
Telnet, // 8
Binary, // 9
Mirror, // +
GIF, // g
Telnet3270, // T
HTML, // h
Image, // I
PNG, // p
Info, // i
Sound, // s
Document, // d
}
impl Type {
/// Is this an info line?
pub fn is_info(self) -> bool {
self == Type::Info
}
/// Text document?
pub fn is_text(self) -> bool {
self == Type::Text
}
/// HTML link?
pub fn is_html(self) -> bool {
self == Type::HTML
}
/// Telnet link?
pub fn is_telnet(self) -> bool {
self == Type::Telnet
}
/// Is this a link, ie something we can navigate to or open?
pub fn is_link(self) -> bool {
!self.is_info()
}
/// Is this something we can download?
pub fn is_download(self) -> bool {
match self {
Type::Binhex
| Type::DOSFile
| Type::UUEncoded
| Type::Binary
| Type::GIF
| Type::Image
| Type::PNG
| Type::Sound
| Type::Document => true,
_ => false,
}
}
/// Gopher Item Type to RFC char.
pub fn to_char(self) -> char {
match self {
Type::Text => '0',
Type::Menu => '1',
Type::CSOEntity => '2',
Type::Error => '3',
Type::Binhex => '4',
Type::DOSFile => '5',
Type::UUEncoded => '6',
Type::Search => '7',
Type::Telnet => '8',
Type::Binary => '9',
Type::Mirror => '+',
Type::GIF => 'g',
Type::Telnet3270 => 'T',
Type::HTML => 'h',
Type::Image => 'I',
Type::PNG => 'p',
Type::Info => 'i',
Type::Sound => 's',
Type::Document => 'd',
}
}
/// Create a Gopher Item Type from its RFC char code.
pub fn from(c: char) -> Option<Type> {
Some(match c {
'0' => Type::Text,
'1' => Type::Menu,
'2' => Type::CSOEntity,
'3' => Type::Error,
'4' => Type::Binhex,
'5' => Type::DOSFile,
'6' => Type::UUEncoded,
'7' => Type::Search,
'8' => Type::Telnet,
'9' => Type::Binary,
'+' => Type::Mirror,
'g' => Type::GIF,
'T' => Type::Telnet3270,
'h' => Type::HTML,
'I' => Type::Image,
'p' => Type::PNG,
'i' => Type::Info,
's' => Type::Sound,
'd' => Type::Document,
_ => return None,
})
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.to_char())
}
}

@ -11,7 +11,6 @@
#![allow(clippy::while_let_on_iterator)]
pub mod color;
pub mod gopher;
pub mod request;
pub mod server;

@ -1,38 +1,21 @@
use phd;
use std::process;
const DEFAULT_BIND: &str = "[::]:7070";
const DEFAULT_HOST: &str = "127.0.0.1";
const DEFAULT_PORT: u16 = 7070;
fn main() {
let args = std::env::args().skip(1).collect::<Vec<_>>();
let mut args = args.iter();
let args: Vec<String> = std::env::args().skip(1).collect();
let mut root = ".";
let mut addr = DEFAULT_BIND;
let mut iter = args.iter();
let mut host = DEFAULT_HOST;
let mut port = DEFAULT_PORT;
let mut render = "";
while let Some(arg) = args.next() {
while let Some(arg) = iter.next() {
match arg.as_ref() {
"--version" | "-v" | "-version" => return print_version(),
"--help" | "-help" => return print_help(),
"--no-color" | "-no-color" => phd::color::hide_colors(),
"--render" | "-render" | "-r" => {
if let Some(path) = args.next() {
render = path;
} else {
render = "/";
}
}
"--bind" | "-b" | "-bind" => {
if let Some(a) = args.next() {
addr = a
}
}
"--port" | "-p" | "-port" => {
if let Some(p) = args.next() {
if let Some(p) = iter.next() {
port = p
.parse()
.map_err(|_| {
@ -43,15 +26,15 @@ fn main() {
}
}
"-h" => {
if let Some(h) = args.next() {
host = &h;
if let Some(h) = iter.next() {
host = h;
} else {
return print_help();
}
}
"--host" | "-host" => {
if let Some(h) = args.next() {
host = &h;
if let Some(h) = iter.next() {
host = h;
}
}
_ => {
@ -59,32 +42,13 @@ fn main() {
eprintln!("unknown flag: {}", arg);
process::exit(1);
} else {
root = &arg;
root = arg;
}
}
}
}
// https://no-color.org/
if std::env::var("NO_COLOR").is_ok() {
phd::color::hide_colors()
}
// If port was given and socket wasn't, bind to that port.
let bind = if port != DEFAULT_PORT && addr == DEFAULT_BIND {
format!("[::]:{}", port).parse().unwrap()
} else {
addr.parse().unwrap()
};
if !render.is_empty() {
return match phd::server::render(host, port, root, &render) {
Ok(out) => print!("{}", out),
Err(e) => eprintln!("{}", e),
};
}
if let Err(e) = phd::server::start(bind, host, port, root) {
if let Err(e) = phd::server::start(host, port, root) {
eprintln!("{}", e);
}
}
@ -97,28 +61,15 @@ fn print_help() {
Options:
-r, --render SELECTOR Render and print SELECTOR to stdout only.
-h, --host HOST Hostname for links. [Default: {host}]
-p, --port PORT Port for links. [Default: {port}]
-b, --bind ADDRESS Socket address to bind to. [Default: {bind}]
--no-color Don't show colors in log messages.
-p, --port Port to bind to. [Default: {port}]
-h, --host Hostname when generating links. [Default: {host}]
Other flags:
-h, --help Print this screen.
-v, --version Print phd version.
Examples:
phd ./path/to/site # Serve directory over port 7070.
phd -p 70 docs # Serve 'docs' directory on port 70
phd -h gopher.com # Serve current directory over port 7070
# using hostname 'gopher.com'
phd -r / ./site # Render local gopher site to stdout.
",
-v, --version Print phd version.",
host = DEFAULT_HOST,
port = DEFAULT_PORT,
bind = DEFAULT_BIND,
);
}

@ -35,11 +35,12 @@ impl Request {
/// Path to the target file on disk requested by this request.
pub fn file_path(&self) -> String {
format!(
"{}/{}",
self.root.to_string().trim_end_matches('/'),
self.selector.replace("..", ".").trim_start_matches('/')
)
let mut path = self.root.to_string();
if !path.ends_with('/') {
path.push('/');
}
path.push_str(self.selector.replace("..", ".").trim_start_matches('/'));
path
}
/// Path to the target file relative to the server root.
@ -51,11 +52,7 @@ impl Request {
pub fn parse_request(&mut self, line: &str) {
self.query.clear();
self.selector.clear();
if let Some((i, _)) = line
.chars()
.enumerate()
.find(|&(_, c)| c == '\t' || c == '?')
{
if let Some(i) = line.find('\t') {
if line.len() > i {
self.query.push_str(&line[i + 1..]);
self.selector.push_str(&line[..i]);

@ -1,16 +1,16 @@
//! A simple multi-threaded Gopher server.
use crate::{color, gopher, Request, Result};
use crate::{color, Request, Result};
use gophermap::{GopherMenu, ItemType};
use std::{
cmp::Ordering,
fs::{self, DirEntry},
io::{self, prelude::*, BufReader, Read, Write},
net::{SocketAddr, TcpListener, TcpStream},
net::{TcpListener, TcpStream},
os::unix::fs::PermissionsExt,
path::Path,
process::Command,
str,
sync::atomic::{AtomicBool, Ordering as AtomicOrdering},
};
use threadpool::ThreadPool;
@ -24,42 +24,19 @@ const MAX_PEEK_SIZE: usize = 1024;
/// Files not displayed in directory listings.
const IGNORED_FILES: [&str; 3] = ["header.gph", "footer.gph", ".reverse"];
/// Whether to print info!() messages to stdout.
/// Defaults to true.
static SHOW_INFO: AtomicBool = AtomicBool::new(true);
/// Hide info! messages.
fn hide_info() {
SHOW_INFO.swap(false, AtomicOrdering::Relaxed);
}
/// Print status message to the server's stdout.
macro_rules! info {
($e:expr) => {
if SHOW_INFO.load(AtomicOrdering::Relaxed) {
println!("{}", $e);
}
};
($fmt:expr, $($args:expr),*) => {
info!(format!($fmt, $($args),*));
};
($fmt:expr, $($args:expr,)*) => {
info!(format!($fmt, $($args,)*));
};
}
/// Starts a Gopher server at the specified host, port, and root directory.
pub fn start(bind: SocketAddr, host: &str, port: u16, root: &str) -> Result<()> {
let listener = TcpListener::bind(&bind)?;
pub fn start(host: &str, port: u16, root: &str) -> Result<()> {
let addr = format!("{}:{}", "0.0.0.0", port);
let listener = TcpListener::bind(&addr)?;
let full_root_path = fs::canonicalize(&root)?.to_string_lossy().to_string();
let pool = ThreadPool::new(MAX_WORKERS);
info!(
"{}» Listening {}on {}{}{} at {}{}{}",
println!(
"{}┬ Listening {}on {}{}{} at {}{}{}",
color::Yellow,
color::Reset,
color::Yellow,
bind,
addr,
color::Reset,
color::Blue,
full_root_path,
@ -67,7 +44,7 @@ pub fn start(bind: SocketAddr, host: &str, port: u16, root: &str) -> Result<()>
);
for stream in listener.incoming() {
let stream = stream?;
info!(
println!(
"{}┌ Connection{} from {}{}",
color::Green,
color::Reset,
@ -77,7 +54,7 @@ pub fn start(bind: SocketAddr, host: &str, port: u16, root: &str) -> Result<()>
let req = Request::from(host, port, root)?;
pool.execute(move || {
if let Err(e) = accept(stream, req) {
info!("{}└ {}{}", color::Red, e, color::Reset);
eprintln!("{}└ {}{}", color::Red, e, color::Reset);
}
});
}
@ -85,11 +62,11 @@ pub fn start(bind: SocketAddr, host: &str, port: u16, root: &str) -> Result<()>
}
/// Reads from the client and responds.
fn accept(mut stream: TcpStream, mut req: Request) -> Result<()> {
fn accept(stream: TcpStream, mut req: Request) -> Result<()> {
let reader = BufReader::new(&stream);
let mut lines = reader.lines();
if let Some(Ok(line)) = lines.next() {
info!(
println!(
"{}│{} Client sent:\t{}{:?}{}",
color::Green,
color::Reset,
@ -98,25 +75,15 @@ fn accept(mut stream: TcpStream, mut req: Request) -> Result<()> {
color::Reset
);
req.parse_request(&line);
write_response(&mut stream, req)?;
write_response(&stream, req)?;
}
Ok(())
}
/// Render a response to a String.
pub fn render(host: &str, port: u16, root: &str, selector: &str) -> Result<String> {
hide_info();
let mut req = Request::from(host, port, root)?;
req.parse_request(&selector);
let mut out = vec![];
write_response(&mut out, req)?;
Ok(String::from_utf8_lossy(&out).into())
}
/// Writes a response to a client based on a Request.
fn write_response<W>(w: &mut W, mut req: Request) -> Result<()>
fn write_response<'a, W>(w: &'a W, mut req: Request) -> Result<()>
where
W: Write,
&'a W: Write,
{
let path = req.file_path();
@ -154,9 +121,9 @@ where
}
/// Send a directory listing (menu) to the client based on a Request.
fn write_dir<W>(w: &mut W, req: Request) -> Result<()>
fn write_dir<'a, W>(w: &'a W, req: Request) -> Result<()>
where
W: Write,
&'a W: Write,
{
let path = req.file_path();
if !fs_exists(&path) {
@ -177,10 +144,12 @@ where
)?;
}
let mut menu = GopherMenu::with_write(w);
let rel_path = req.relative_file_path();
// show directory entries
let reverse = format!("{}/.reverse", path);
let mut reverse = path.to_string();
reverse.push_str("/.reverse");
let paths = sort_paths(&path, fs_exists(&reverse))?;
for entry in paths {
let file_name = entry.file_name();
@ -188,15 +157,11 @@ where
if f.chars().nth(0) == Some('.') || IGNORED_FILES.contains(&f.as_ref()) {
continue;
}
let path = format!(
"{}/{}",
rel_path.trim_end_matches('/'),
file_name.to_string_lossy()
);
write!(
w,
"{}{}\t{}\t{}\t{}\r\n",
file_type(&entry).to_char(),
let mut path = rel_path.clone();
path.push('/');
path.push_str(&file_name.to_string_lossy());
menu.write_entry(
file_type(&entry),
&file_name.to_string_lossy(),
&path,
&req.host,
@ -204,9 +169,11 @@ where
)?;
}
let footer = format!("{}/footer.gph", path.trim_end_matches('/'));
let mut footer = path;
footer.push_str("/footer.gph");
if fs_exists(&footer) {
let sel = format!("{}/footer.gph", req.selector);
let mut sel = req.selector.clone();
sel.push_str("/footer.gph");
write_gophermap(
w,
Request {
@ -216,9 +183,8 @@ where
)?;
}
write!(w, ".\r\n");
info!(
menu.end()?;
println!(
"{}│{} Server reply:\t{}DIR {}{}{}",
color::Green,
color::Reset,
@ -231,14 +197,14 @@ where
}
/// Send a file to the client based on a Request.
fn write_file<W>(w: &mut W, req: Request) -> Result<()>
fn write_file<'a, W>(mut w: &'a W, req: Request) -> Result<()>
where
W: Write,
&'a W: Write,
{
let path = req.file_path();
let mut f = fs::File::open(&path)?;
io::copy(&mut f, w)?;
info!(
io::copy(&mut f, &mut w)?;
println!(
"{}│{} Server reply:\t{}FILE {}{}{}",
color::Green,
color::Reset,
@ -251,9 +217,9 @@ where
}
/// Send a gophermap (menu) to the client based on a Request.
fn write_gophermap<W>(w: &mut W, req: Request) -> Result<()>
fn write_gophermap<'a, W>(mut w: &'a W, req: Request) -> Result<()>
where
W: Write,
&'a W: Write,
{
let path = req.file_path();
@ -265,9 +231,9 @@ where
};
for line in reader.lines() {
write!(w, "{}", gph_line_to_gopher(line, &req))?;
w.write_all(gph_line_to_gopher(line, &req).as_bytes())?;
}
info!(
println!(
"{}│{} Server reply:\t{}MAP {}{}{}",
color::Green,
color::Reset,
@ -319,8 +285,10 @@ fn gph_line_to_gopher(line: &str, req: &Request) -> String {
} else {
match line.matches('\t').count() {
0 => {
// Always insert `i` prefix to any lines without tabs.
line.insert(0, 'i');
// Insert `i` prefix to any prefix-less lines without tabs.
if line.chars().nth(0) != Some('i') {
line.insert(0, 'i');
}
line.push_str(&format!("\t(null)\t{}\t{}", req.host, req.port))
}
// Auto-add host and port to lines with just a selector.
@ -333,26 +301,26 @@ fn gph_line_to_gopher(line: &str, req: &Request) -> String {
line
}
fn write_not_found<W>(w: &mut W, req: Request) -> Result<()>
fn write_not_found<'a, W>(mut w: &'a W, req: Request) -> Result<()>
where
W: Write,
&'a W: Write,
{
let line = format!("3Not Found: {}\t/\tnone\t70\r\n", req.selector);
info!(
println!(
"{}│ Not found: {}{}{}",
color::Red,
color::Cyan,
req.relative_file_path(),
color::Reset,
);
write!(w, "{}", line)?;
w.write_all(line.as_bytes())?;
Ok(())
}
/// Determine the gopher type for a DirEntry on disk.
fn file_type(dir: &fs::DirEntry) -> gopher::Type {
fn file_type(dir: &fs::DirEntry) -> ItemType {
let metadata = match dir.metadata() {
Err(_) => return gopher::Type::Error,
Err(_) => return ItemType::Error,
Ok(md) => md,
};
@ -361,17 +329,17 @@ fn file_type(dir: &fs::DirEntry) -> gopher::Type {
let mut buffer: Vec<u8> = vec![];
let _ = file.take(MAX_PEEK_SIZE as u64).read_to_end(&mut buffer);
if content_inspector::inspect(&buffer).is_binary() {
gopher::Type::Binary
ItemType::Binary
} else {
gopher::Type::Text
ItemType::File
}
} else {
gopher::Type::Error
ItemType::Error
}
} else if metadata.is_dir() {
gopher::Type::Menu
ItemType::Directory
} else {
gopher::Type::Error
ItemType::Error
}
}
@ -410,10 +378,7 @@ fn sort_paths(dir_path: &str, reverse: bool) -> Result<Vec<DirEntry>> {
let a_is_dir = is_dir(a);
let b_is_dir = is_dir(b);
if a_is_dir && b_is_dir || !a_is_dir && !b_is_dir {
let ord = alphanumeric_sort::compare_os_str::<&Path, &Path>(
a.path().as_ref(),
b.path().as_ref(),
);
let ord = alphanumeric_sort::compare_os_str(a.path().as_ref(), b.path().as_ref());
if reverse {
ord.reverse()
} else {

Loading…
Cancel
Save