aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorboasting-squirrel <boasting.squirrel@gmail.com>2019-02-04 19:28:06 +0000
committerboasting-squirrel <boasting.squirrel@gmail.com>2019-02-04 19:28:06 +0000
commit4585d5456c8ad088b96ca51039b4e8274f7ccf38 (patch)
treed27a93cd52f91136891c3cd6fcc3d98e047c11a8
parentSome linting fixes (diff)
downloadminiserve-4585d5456c8ad088b96ca51039b4e8274f7ccf38.tar.gz
miniserve-4585d5456c8ad088b96ca51039b4e8274f7ccf38.zip
Implemented sorting
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml3
-rw-r--r--README.md19
-rw-r--r--src/main.rs141
4 files changed, 156 insertions, 14 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 1f3ba48..fd315cd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -127,6 +127,11 @@ dependencies = [
]
[[package]]
+name = "alphanumeric-sort"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -688,6 +693,7 @@ version = "0.3.0"
dependencies = [
"actix 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)",
"actix-web 0.7.18 (registry+https://github.com/rust-lang/crates.io-index)",
+ "alphanumeric-sort 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bytesize 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1720,6 +1726,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum actix_derive 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4300e9431455322ae393d43a2ba1ef96b8080573c0fc23b196219efedfb6ba69"
"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c"
"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
+"checksum alphanumeric-sort 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7cd2580c95c654d681db0194a310af67a293f5e1c8bafa5b35b63269c4665a39"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum arc-swap 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1025aeae2b664ca0ea726a89d574fe8f4e77dd712d443236ad1de00379450cf6"
"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
diff --git a/Cargo.toml b/Cargo.toml
index 4e89e56..50f52e1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,4 +27,5 @@ base64 = "0.10"
percent-encoding = "1.0.1"
htmlescape = "0.3.1"
bytesize = "1.0.0"
-nanoid = "0.2.0" \ No newline at end of file
+nanoid = "0.2.0"
+alphanumeric-sort = "1.0.6" \ No newline at end of file
diff --git a/README.md b/README.md
index cabf6ac..4176e51 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,25 @@ Sometimes this is just a more practical and quick way than doing things properly
miniserve -i 192.168.0.1 -i 10.13.37.10 -i ::1 -- /tmp/myshare
+### Sort files for easier navigation
+ miniserve --sort=natural /tmp/myshare # (default behaviour)
+ # 1/
+ # 2/
+ # 3
+ # 11
+
+ miniserve --sort=alpha /tmp/myshare
+ # 1/
+ # 11
+ # 2/
+ # 3
+
+ miniserve --sort=dirsfirst /tmp/myshare
+ # 1/
+ # 2/
+ # 11
+ # 3
+
## Features
- Easy to use
diff --git a/src/main.rs b/src/main.rs
index b7f9be9..e7392bd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,10 +6,12 @@ use clap::{crate_authors, crate_description, crate_name, crate_version};
use htmlescape::encode_minimal as escape_html_entity;
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use simplelog::{Config, LevelFilter, TermLogger};
+use std::cmp::Ordering;
use std::fmt::Write as FmtWrite;
use std::io::{self, Write};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs};
use std::path::{Path, PathBuf};
+use std::str::FromStr;
use std::thread;
use std::time::Duration;
use yansi::{Color, Paint};
@@ -30,6 +32,13 @@ struct BasicAuthParams {
}
#[derive(Clone, Debug)]
+enum SortingMethods {
+ Natural,
+ Alpha,
+ DirsFirst,
+}
+
+#[derive(Clone, Debug)]
pub struct MiniserveConfig {
verbose: bool,
path: std::path::PathBuf,
@@ -39,6 +48,59 @@ pub struct MiniserveConfig {
path_explicitly_chosen: bool,
no_symlinks: bool,
random_route: Option<String>,
+ sort_method: SortingMethods,
+}
+
+#[derive(PartialEq)]
+enum EntryType {
+ Directory,
+ File,
+}
+
+impl PartialOrd for EntryType {
+ fn partial_cmp(&self, other: &EntryType) -> Option<Ordering> {
+ match (self, other) {
+ (EntryType::Directory, EntryType::File) => Some(Ordering::Less),
+ (EntryType::File, EntryType::Directory) => Some(Ordering::Greater),
+ _ => Some(Ordering::Equal),
+ }
+ }
+}
+
+struct Entry {
+ name: String,
+ entry_type: EntryType,
+ link: String,
+ size: Option<bytesize::ByteSize>,
+}
+
+impl Entry {
+ fn new(
+ name: String,
+ entry_type: EntryType,
+ link: String,
+ size: Option<bytesize::ByteSize>,
+ ) -> Self {
+ Entry {
+ name,
+ entry_type,
+ link,
+ size,
+ }
+ }
+}
+
+impl FromStr for SortingMethods {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<SortingMethods, ()> {
+ match s {
+ "natural" => Ok(SortingMethods::Natural),
+ "alpha" => Ok(SortingMethods::Alpha),
+ "dirsfirst" => Ok(SortingMethods::DirsFirst),
+ _ => Err(()),
+ }
+ }
}
/// Decode a HTTP basic auth string into a tuple of username and password.
@@ -141,6 +203,14 @@ pub fn parse_args() -> MiniserveConfig {
.help("Generate a random 6-hexdigit route"),
)
.arg(
+ Arg::with_name("sort")
+ .short("s")
+ .long("sort")
+ .possible_values(&["natural", "alpha", "dirsfirst"])
+ .default_value("natural")
+ .help("Sort results"),
+ )
+ .arg(
Arg::with_name("no-symlinks")
.short("P")
.long("no-symlinks")
@@ -180,6 +250,12 @@ pub fn parse_args() -> MiniserveConfig {
None
};
+ let sort_method = matches
+ .value_of("sort")
+ .unwrap()
+ .parse::<SortingMethods>()
+ .unwrap();
+
MiniserveConfig {
verbose,
path: PathBuf::from(path.unwrap_or(".")),
@@ -189,6 +265,7 @@ pub fn parse_args() -> MiniserveConfig {
path_explicitly_chosen: path.is_some(),
no_symlinks,
random_route,
+ sort_method,
}
}
@@ -202,6 +279,7 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> {
let path = &app.state().path;
let no_symlinks = app.state().no_symlinks;
let random_route = app.state().random_route.clone();
+ let sort_method = app.state().sort_method.clone();
if path.is_file() {
None
} else {
@@ -210,7 +288,13 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> {
.expect("Couldn't create path")
.show_files_listing()
.files_listing_renderer(move |dir, req| {
- directory_listing(dir, req, no_symlinks, random_route.clone())
+ directory_listing(
+ dir,
+ req,
+ no_symlinks,
+ random_route.clone(),
+ sort_method.clone(),
+ )
}),
)
}
@@ -399,6 +483,7 @@ fn directory_listing<S>(
req: &HttpRequest<S>,
skip_symlinks: bool,
random_route: Option<String>,
+ sort_method: SortingMethods,
) -> Result<HttpResponse, io::Error> {
let index_of = format!("Index of {}", req.path());
let mut body = String::new();
@@ -415,6 +500,8 @@ fn directory_listing<S>(
}
}
+ let mut entries: Vec<Entry> = Vec::new();
+
for entry in dir.path.read_dir()? {
if dir.is_visible(&entry) {
let entry = entry.unwrap();
@@ -433,21 +520,15 @@ fn directory_listing<S>(
if skip_symlinks && metadata.file_type().is_symlink() {
continue;
}
-
if metadata.is_dir() {
- let _ = write!(
- body,
- "<tr><td><a class=\"directory\" href=\"{}\">{}/</a></td><td></td></tr>",
- file_url, file_name
- );
+ entries.push(Entry::new(file_name, EntryType::Directory, file_url, None));
} else {
- let _ = write!(
- body,
- "<tr><td><a class=\"file\" href=\"{}\">{}</a></td><td>{}</td></tr>",
- file_url,
+ entries.push(Entry::new(
file_name,
- ByteSize::b(metadata.len())
- );
+ EntryType::File,
+ file_url,
+ Some(ByteSize::b(metadata.len())),
+ ));
}
} else {
continue;
@@ -455,6 +536,40 @@ fn directory_listing<S>(
}
}
+ match sort_method {
+ SortingMethods::Natural => entries
+ .sort_by(|e1, e2| alphanumeric_sort::compare_str(e1.name.clone(), e2.name.clone())),
+ SortingMethods::Alpha => {
+ entries.sort_by(|e1, e2| e1.entry_type.partial_cmp(&e2.entry_type).unwrap());
+ entries.sort_by_key(|e| e.name.clone())
+ }
+ SortingMethods::DirsFirst => {
+ entries.sort_by_key(|e| e.name.clone());
+ entries.sort_by(|e1, e2| e1.entry_type.partial_cmp(&e2.entry_type).unwrap());
+ }
+ };
+
+ for entry in entries {
+ match entry.entry_type {
+ EntryType::Directory => {
+ let _ = write!(
+ body,
+ "<tr><td><a class=\"directory\" href=\"{}\">{}/</a></td><td></td></tr>",
+ entry.link, entry.name
+ );
+ }
+ EntryType::File => {
+ let _ = write!(
+ body,
+ "<tr><td><a class=\"file\" href=\"{}\">{}</a></td><td>{}</td></tr>",
+ entry.link,
+ entry.name,
+ entry.size.unwrap()
+ );
+ }
+ }
+ }
+
let html = format!(
"<html>\
<head>\