mirror of
https://github.com/svenstaro/miniserve
synced 2024-07-08 20:05:56 +00:00
Implemented sorting
This commit is contained in:
parent
ba9b92a415
commit
4585d5456c
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -126,6 +126,11 @@ dependencies = [
|
|||
"memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
@ -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"
|
||||
|
|
|
@ -27,4 +27,5 @@ base64 = "0.10"
|
|||
percent-encoding = "1.0.1"
|
||||
htmlescape = "0.3.1"
|
||||
bytesize = "1.0.0"
|
||||
nanoid = "0.2.0"
|
||||
nanoid = "0.2.0"
|
||||
alphanumeric-sort = "1.0.6"
|
19
README.md
19
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
|
||||
|
|
141
src/main.rs
141
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};
|
||||
|
@ -29,6 +31,13 @@ struct BasicAuthParams {
|
|||
password: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum SortingMethods {
|
||||
Natural,
|
||||
Alpha,
|
||||
DirsFirst,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MiniserveConfig {
|
||||
verbose: bool,
|
||||
|
@ -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.
|
||||
|
@ -140,6 +202,14 @@ pub fn parse_args() -> MiniserveConfig {
|
|||
.long("random-route")
|
||||
.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")
|
||||
|
@ -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>\
|
||||
|
|
Loading…
Reference in New Issue
Block a user