Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
vnotepad
Exa
提交
d5aa3208
E
Exa
项目概览
vnotepad
/
Exa
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
E
Exa
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
d5aa3208
编写于
2月 04, 2015
作者:
B
Ben S
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'tree'
上级
7acc1b09
e8546099
变更
6
隐藏空白更改
内联
并排
Showing
6 changed file
with
143 addition
and
75 deletion
+143
-75
src/column.rs
src/column.rs
+0
-2
src/dir.rs
src/dir.rs
+5
-2
src/file.rs
src/file.rs
+30
-27
src/main.rs
src/main.rs
+8
-6
src/options.rs
src/options.rs
+32
-17
src/output.rs
src/output.rs
+68
-21
未找到文件。
src/column.rs
浏览文件 @
d5aa3208
...
...
@@ -5,7 +5,6 @@ use ansi_term::Style;
#[derive(PartialEq,
Debug,
Copy)]
pub
enum
Column
{
Permissions
,
FileName
,
FileSize
(
SizeFormat
),
Blocks
,
User
,
...
...
@@ -49,7 +48,6 @@ impl Column {
pub
fn
header
(
&
self
)
->
&
'static
str
{
match
*
self
{
Column
::
Permissions
=>
"Permissions"
,
Column
::
FileName
=>
"Name"
,
Column
::
FileSize
(
_
)
=>
"Size"
,
Column
::
Blocks
=>
"Blocks"
,
Column
::
User
=>
"User"
,
...
...
src/dir.rs
浏览文件 @
d5aa3208
...
...
@@ -31,11 +31,14 @@ impl Dir {
/// Produce a vector of File objects from an initialised directory,
/// printing out an error if any of the Files fail to be created.
pub
fn
files
(
&
self
)
->
Vec
<
File
>
{
///
/// Passing in `recurse` means that any directories will be scanned for
/// their contents, as well.
pub
fn
files
(
&
self
,
recurse
:
bool
)
->
Vec
<
File
>
{
let
mut
files
=
vec!
[];
for
path
in
self
.contents
.iter
()
{
match
File
::
from_path
(
path
,
Some
(
self
))
{
match
File
::
from_path
(
path
,
Some
(
self
)
,
recurse
)
{
Ok
(
file
)
=>
files
.push
(
file
),
Err
(
e
)
=>
println!
(
"{}: {}"
,
path
.display
(),
e
),
}
...
...
src/file.rs
浏览文件 @
d5aa3208
...
...
@@ -32,6 +32,7 @@ pub struct File<'a> {
pub
ext
:
Option
<
String
>
,
pub
path
:
Path
,
pub
stat
:
io
::
FileStat
,
pub
this
:
Option
<
Dir
>
,
}
impl
<
'a
>
File
<
'a
>
{
...
...
@@ -39,12 +40,12 @@ impl<'a> File<'a> {
/// appropriate. Paths specified directly on the command-line have no Dirs.
///
/// This uses lstat instead of stat, which doesn't follow symbolic links.
pub
fn
from_path
(
path
:
&
Path
,
parent
:
Option
<&
'a
Dir
>
)
->
IoResult
<
File
<
'a
>>
{
fs
::
lstat
(
path
)
.map
(|
stat
|
File
::
with_stat
(
stat
,
path
,
parent
))
pub
fn
from_path
(
path
:
&
Path
,
parent
:
Option
<&
'a
Dir
>
,
recurse
:
bool
)
->
IoResult
<
File
<
'a
>>
{
fs
::
lstat
(
path
)
.map
(|
stat
|
File
::
with_stat
(
stat
,
path
,
parent
,
recurse
))
}
/// Create a new File object from the given Stat result, and other data.
pub
fn
with_stat
(
stat
:
io
::
FileStat
,
path
:
&
Path
,
parent
:
Option
<&
'a
Dir
>
)
->
File
<
'a
>
{
pub
fn
with_stat
(
stat
:
io
::
FileStat
,
path
:
&
Path
,
parent
:
Option
<&
'a
Dir
>
,
recurse
:
bool
)
->
File
<
'a
>
{
// The filename to display is the last component of the path. However,
// the path has no components for `.`, `..`, and `/`, so in these
...
...
@@ -58,12 +59,23 @@ impl<'a> File<'a> {
// replacement characters.
let
filename
=
String
::
from_utf8_lossy
(
bytes
);
// If we are recursing, then the `this` field contains a Dir object
// that represents the current File as a directory, if it is a
// directory. This is used for the --tree option.
let
this
=
if
recurse
&&
stat
.kind
==
io
::
FileType
::
Directory
{
Dir
::
readdir
(
path
)
.ok
()
}
else
{
None
};
File
{
path
:
path
.clone
(),
dir
:
parent
,
stat
:
stat
,
name
:
filename
.to_string
(),
ext
:
ext
(
filename
.as_slice
()),
this
:
this
,
}
}
...
...
@@ -82,7 +94,6 @@ impl<'a> File<'a> {
pub
fn
display
<
U
:
Users
>
(
&
self
,
column
:
&
Column
,
users_cache
:
&
mut
U
)
->
Cell
{
match
*
column
{
Permissions
=>
self
.permissions_string
(),
FileName
=>
self
.file_name_view
(),
FileSize
(
f
)
=>
self
.file_size
(
f
),
HardLinks
=>
self
.hard_links
(),
Inode
=>
self
.inode
(),
...
...
@@ -98,15 +109,12 @@ impl<'a> File<'a> {
///
/// It consists of the file name coloured in the appropriate style,
/// with special formatting for a symlink.
pub
fn
file_name_view
(
&
self
)
->
Cell
{
pub
fn
file_name_view
(
&
self
)
->
String
{
if
self
.stat.kind
==
io
::
FileType
::
Symlink
{
self
.symlink_file_name_view
()
}
else
{
Cell
{
length
:
0
,
// This length is ignored (rightmost column)
text
:
self
.file_colour
()
.paint
(
&*
self
.name
)
.to_string
(),
}
self
.file_colour
()
.paint
(
&*
self
.name
)
.to_string
()
}
}
...
...
@@ -118,7 +126,7 @@ impl<'a> File<'a> {
/// an error, highlight the target and arrow in red. The error would
/// be shown out of context, and it's almost always because the
/// target doesn't exist.
fn
symlink_file_name_view
(
&
self
)
->
Cell
{
fn
symlink_file_name_view
(
&
self
)
->
String
{
let
name
=
&*
self
.name
;
let
style
=
self
.file_colour
();
...
...
@@ -129,26 +137,20 @@ impl<'a> File<'a> {
};
match
self
.target_file
(
&
target_path
)
{
Ok
(
file
)
=>
Cell
{
length
:
0
,
// These lengths are never actually used...
text
:
format!
(
"{} {} {}{}{}"
,
style
.paint
(
name
),
GREY
.paint
(
"=>"
),
Cyan
.paint
(
target_path
.dirname_str
()
.unwrap
()),
Cyan
.paint
(
"/"
),
file
.file_colour
()
.paint
(
file
.name
.as_slice
())),
},
Err
(
filename
)
=>
Cell
{
length
:
0
,
// ...because the rightmost column lengths are ignored!
text
:
format!
(
"{} {} {}"
,
style
.paint
(
name
),
Red
.paint
(
"=>"
),
Red
.underline
()
.paint
(
filename
.as_slice
())),
},
Ok
(
file
)
=>
format!
(
"{} {} {}{}{}"
,
style
.paint
(
name
),
GREY
.paint
(
"=>"
),
Cyan
.paint
(
target_path
.dirname_str
()
.unwrap
()),
Cyan
.paint
(
"/"
),
file
.file_colour
()
.paint
(
file
.name
.as_slice
())),
Err
(
filename
)
=>
format!
(
"{} {} {}"
,
style
.paint
(
name
),
Red
.paint
(
"=>"
),
Red
.underline
()
.paint
(
filename
.as_slice
())),
}
}
else
{
Cell
::
paint
(
style
,
name
)
style
.paint
(
name
)
.to_string
(
)
}
}
...
...
@@ -184,6 +186,7 @@ impl<'a> File<'a> {
stat
:
stat
,
name
:
filename
.to_string
(),
ext
:
ext
(
filename
.as_slice
()),
this
:
None
,
})
}
else
{
...
...
src/main.rs
浏览文件 @
d5aa3208
...
...
@@ -39,11 +39,14 @@ fn exa(options: &Options) {
let
path
=
Path
::
new
(
file
);
match
fs
::
stat
(
&
path
)
{
Ok
(
stat
)
=>
{
if
stat
.kind
==
FileType
::
Directory
&&
options
.dir_action
!=
DirAction
::
AsFile
{
if
stat
.kind
==
FileType
::
Directory
&&
options
.dir_action
==
DirAction
::
Tree
{
files
.push
(
File
::
with_stat
(
stat
,
&
path
,
None
,
true
));
}
else
if
stat
.kind
==
FileType
::
Directory
&&
options
.dir_action
!=
DirAction
::
AsFile
{
dirs
.push
(
path
);
}
else
{
files
.push
(
File
::
with_stat
(
stat
,
&
path
,
None
));
files
.push
(
File
::
with_stat
(
stat
,
&
path
,
None
,
false
));
}
}
Err
(
e
)
=>
println!
(
"{}: {}"
,
file
,
e
),
...
...
@@ -55,7 +58,7 @@ fn exa(options: &Options) {
let
mut
first
=
files
.is_empty
();
if
!
files
.is_empty
()
{
options
.view
(
None
,
&
files
[]);
options
.view
(
None
,
&
files
[]
,
options
.filter
);
}
// Directories are put on a stack rather than just being iterated through,
...
...
@@ -77,8 +80,7 @@ fn exa(options: &Options) {
match
Dir
::
readdir
(
&
dir_path
)
{
Ok
(
ref
dir
)
=>
{
let
unsorted_files
=
dir
.files
();
let
files
:
Vec
<
File
>
=
options
.transform_files
(
unsorted_files
);
let
files
=
options
.transform_files
(
dir
.files
(
false
));
// When recursing, add any directories to the dirs stack
// backwards: the *last* element of the stack is used each
...
...
@@ -95,7 +97,7 @@ fn exa(options: &Options) {
}
count
+=
1
;
options
.view
(
Some
(
dir
),
&
files
[]);
options
.view
(
Some
(
dir
),
&
files
[]
,
options
.filter
);
}
Err
(
e
)
=>
{
println!
(
"{}: {}"
,
dir_path
.display
(),
e
);
...
...
src/options.rs
浏览文件 @
d5aa3208
...
...
@@ -20,10 +20,15 @@ use self::Misfire::*;
pub
struct
Options
{
pub
dir_action
:
DirAction
,
pub
path_strs
:
Vec
<
String
>
,
pub
filter
:
FileFilter
,
view
:
View
,
}
#[derive(PartialEq,
Debug,
Copy)]
pub
struct
FileFilter
{
reverse
:
bool
,
show_invisibles
:
bool
,
sort_field
:
SortField
,
view
:
View
,
}
impl
Options
{
...
...
@@ -45,6 +50,7 @@ impl Options {
getopts
::
optflag
(
"R"
,
"recurse"
,
"recurse into directories"
),
getopts
::
optopt
(
"s"
,
"sort"
,
"field to sort by"
,
"WORD"
),
getopts
::
optflag
(
"S"
,
"blocks"
,
"show number of file system blocks"
),
getopts
::
optflag
(
"T"
,
"tree"
,
"recurse into subdirectories in a tree view"
),
getopts
::
optflag
(
"x"
,
"across"
,
"sort multi-column view entries across"
),
getopts
::
optflag
(
"?"
,
"help"
,
"show list of command-line options"
),
];
...
...
@@ -64,20 +70,28 @@ impl Options {
};
Ok
(
Options
{
dir_action
:
try!
(
dir_action
(
&
matches
)),
path_strs
:
if
matches
.free
.is_empty
()
{
vec!
[
"."
.to_string
()
]
}
else
{
matches
.free
.clone
()
},
reverse
:
matches
.opt_present
(
"reverse"
),
show_invisibles
:
matches
.opt_present
(
"all"
),
sort_field
:
sort_field
,
view
:
try!
(
view
(
&
matches
)),
dir_action
:
try!
(
dir_action
(
&
matches
)),
path_strs
:
if
matches
.free
.is_empty
()
{
vec!
[
"."
.to_string
()
]
}
else
{
matches
.free
.clone
()
},
view
:
try!
(
view
(
&
matches
)),
filter
:
FileFilter
{
reverse
:
matches
.opt_present
(
"reverse"
),
show_invisibles
:
matches
.opt_present
(
"all"
),
sort_field
:
sort_field
,
},
})
}
pub
fn
transform_files
<
'a
>
(
&
self
,
files
:
Vec
<
File
<
'a
>>
)
->
Vec
<
File
<
'a
>>
{
self
.filter
.transform_files
(
files
)
}
/// Display the files using this Option's View.
pub
fn
view
(
&
self
,
dir
:
Option
<&
Dir
>
,
files
:
&
[
File
])
{
self
.view
.view
(
dir
,
files
)
pub
fn
view
(
&
self
,
dir
:
Option
<&
Dir
>
,
files
:
&
[
File
]
,
filter
:
FileFilter
)
{
self
.view
.view
(
dir
,
files
,
filter
)
}
}
impl
FileFilter
{
/// Transform the files (sorting, reversing, filtering) before listing them.
pub
fn
transform_files
<
'a
>
(
&
self
,
mut
files
:
Vec
<
File
<
'a
>>
)
->
Vec
<
File
<
'a
>>
{
...
...
@@ -111,7 +125,7 @@ impl Options {
/// What to do when encountering a directory?
#[derive(PartialEq,
Debug,
Copy)]
pub
enum
DirAction
{
AsFile
,
List
,
Recurse
AsFile
,
List
,
Recurse
,
Tree
}
/// User-supplied field to sort by.
...
...
@@ -189,7 +203,7 @@ fn view(matches: &getopts::Matches) -> Result<View, Misfire> {
Err
(
Misfire
::
Useless
(
"oneline"
,
true
,
"long"
))
}
else
{
Ok
(
View
::
Details
(
try!
(
Columns
::
new
(
matches
)),
matches
.opt_present
(
"header"
)))
Ok
(
View
::
Details
(
try!
(
Columns
::
new
(
matches
)),
matches
.opt_present
(
"header"
)
,
matches
.opt_present
(
"tree"
)
))
}
}
else
if
matches
.opt_present
(
"binary"
)
{
...
...
@@ -242,12 +256,14 @@ fn file_size(matches: &getopts::Matches) -> Result<SizeFormat, Misfire> {
fn
dir_action
(
matches
:
&
getopts
::
Matches
)
->
Result
<
DirAction
,
Misfire
>
{
let
recurse
=
matches
.opt_present
(
"recurse"
);
let
list
=
matches
.opt_present
(
"list-dirs"
);
let
tree
=
matches
.opt_present
(
"tree"
);
match
(
recurse
,
list
)
{
(
true
,
true
)
=>
Err
(
Misfire
::
Conflict
(
"recurse"
,
"list-dirs"
)),
(
true
,
false
)
=>
Ok
(
DirAction
::
Recurse
),
(
false
,
true
)
=>
Ok
(
DirAction
::
AsFile
),
(
false
,
false
)
=>
Ok
(
DirAction
::
List
),
match
(
recurse
,
list
,
tree
)
{
(
true
,
true
,
_
)
=>
Err
(
Misfire
::
Conflict
(
"recurse"
,
"list-dirs"
)),
(
true
,
false
,
false
)
=>
Ok
(
DirAction
::
Recurse
),
(
true
,
false
,
true
)
=>
Ok
(
DirAction
::
Tree
),
(
false
,
true
,
_
)
=>
Ok
(
DirAction
::
AsFile
),
(
false
,
false
,
_
)
=>
Ok
(
DirAction
::
List
),
}
}
...
...
@@ -304,7 +320,6 @@ impl Columns {
}
}
columns
.push
(
FileName
);
columns
}
}
...
...
src/output.rs
浏览文件 @
d5aa3208
...
...
@@ -4,24 +4,24 @@ use std::iter::{AdditiveIterator, repeat};
use
column
::{
Column
,
Cell
};
use
column
::
Alignment
::
Left
;
use
dir
::
Dir
;
use
file
::
File
;
use
options
::
Columns
;
use
file
::
{
File
,
GREY
}
;
use
options
::
{
Columns
,
FileFilter
}
;
use
users
::
OSUsers
;
use
ansi_term
::
Style
::
Plain
;
#[derive(PartialEq,
Copy,
Debug)]
pub
enum
View
{
Details
(
Columns
,
bool
),
Details
(
Columns
,
bool
,
bool
),
Lines
,
Grid
(
bool
,
usize
),
}
impl
View
{
pub
fn
view
(
&
self
,
dir
:
Option
<&
Dir
>
,
files
:
&
[
File
])
{
pub
fn
view
(
&
self
,
dir
:
Option
<&
Dir
>
,
files
:
&
[
File
]
,
filter
:
FileFilter
)
{
match
*
self
{
View
::
Grid
(
across
,
width
)
=>
grid_view
(
across
,
width
,
files
),
View
::
Details
(
ref
cols
,
header
)
=>
details_view
(
&*
cols
.for_dir
(
dir
),
files
,
head
er
),
View
::
Details
(
ref
cols
,
header
,
tree
)
=>
details_view
(
&*
cols
.for_dir
(
dir
),
files
,
header
,
tree
,
filt
er
),
View
::
Lines
=>
lines_view
(
files
),
}
}
...
...
@@ -30,7 +30,7 @@ impl View {
/// The lines view literally just displays each file, line-by-line.
fn
lines_view
(
files
:
&
[
File
])
{
for
file
in
files
.iter
()
{
println!
(
"{}"
,
file
.file_name_view
()
.text
);
println!
(
"{}"
,
file
.file_name_view
());
}
}
...
...
@@ -122,7 +122,7 @@ fn grid_view(across: bool, console_width: usize, files: &[File]) {
}
}
fn
details_view
(
columns
:
&
[
Column
],
files
:
&
[
File
],
header
:
bool
)
{
fn
details_view
(
columns
:
&
[
Column
],
files
:
&
[
File
],
header
:
bool
,
tree
:
bool
,
filter
:
FileFilter
)
{
// The output gets formatted into columns, which looks nicer. To
// do this, we have to write the results into a table, instead of
// displaying each file immediately, then calculating the maximum
...
...
@@ -131,33 +131,80 @@ fn details_view(columns: &[Column], files: &[File], header: bool) {
let
mut
cache
=
OSUsers
::
empty_cache
();
let
mut
table
:
Vec
<
Vec
<
Cell
>>
=
files
.iter
()
.map
(|
f
|
columns
.iter
()
.map
(|
c
|
f
.display
(
c
,
&
mut
cache
))
.collect
())
.collect
();
let
mut
table
=
Vec
::
new
();
get_files
(
columns
,
&
mut
cache
,
tree
,
&
mut
table
,
files
,
0
,
filter
);
if
header
{
table
.insert
(
0
,
columns
.iter
()
.map
(|
c
|
Cell
::
paint
(
Plain
.underline
(),
c
.header
()))
.collect
());
let
row
=
Row
{
depth
:
0
,
cells
:
columns
.iter
()
.map
(|
c
|
Cell
::
paint
(
Plain
.underline
(),
c
.header
()))
.collect
(),
name
:
Plain
.underline
()
.paint
(
"Name"
)
.to_string
(),
last
:
false
,
children
:
false
,
};
table
.insert
(
0
,
row
);
}
let
column_widths
:
Vec
<
usize
>
=
range
(
0
,
columns
.len
())
.map
(|
n
|
table
.iter
()
.map
(|
row
|
row
[
n
]
.length
)
.max
()
.unwrap_or
(
0
))
.map
(|
n
|
table
.iter
()
.map
(|
row
|
row
.cells
[
n
]
.length
)
.max
()
.unwrap_or
(
0
))
.collect
();
let
mut
stack
=
Vec
::
new
();
for
row
in
table
.iter
()
{
for
(
num
,
column
)
in
columns
.iter
()
.enumerate
()
{
if
num
!=
0
{
print!
(
" "
);
// Separator
let
padding
=
column_widths
[
num
]
-
row
.cells
[
num
]
.length
;
print!
(
"{} "
,
column
.alignment
()
.pad_string
(
&
row
.cells
[
num
]
.text
,
padding
));
}
if
tree
{
stack
.resize
(
row
.depth
+
1
,
"├──"
);
stack
[
row
.depth
]
=
if
row
.last
{
"└──"
}
else
{
"├──"
};
for
i
in
range
(
1
,
row
.depth
+
1
)
{
print!
(
"{}"
,
GREY
.paint
(
stack
[
i
]));
}
if
num
==
columns
.len
()
-
1
{
// The final column doesn't need to have trailing spaces
print!
(
"{}"
,
row
[
num
]
.text
);
if
row
.children
{
stack
[
row
.depth
]
=
if
row
.last
{
" "
}
else
{
"│ "
};
}
else
{
let
padding
=
column_widths
[
num
]
-
row
[
num
]
.length
;
print!
(
"{}"
,
column
.alignment
()
.pad_string
(
&
row
[
num
]
.text
,
padding
));
if
row
.depth
!=
0
{
print!
(
" "
);
}
}
print!
(
"{}
\n
"
,
row
.name
);
}
}
fn
get_files
(
columns
:
&
[
Column
],
cache
:
&
mut
OSUsers
,
recurse
:
bool
,
dest
:
&
mut
Vec
<
Row
>
,
src
:
&
[
File
],
depth
:
usize
,
filter
:
FileFilter
)
{
for
(
index
,
file
)
in
src
.iter
()
.enumerate
()
{
let
row
=
Row
{
depth
:
depth
,
cells
:
columns
.iter
()
.map
(|
c
|
file
.display
(
c
,
cache
))
.collect
(),
name
:
file
.file_name_view
(),
last
:
index
==
src
.len
()
-
1
,
children
:
file
.this
.is_some
(),
};
dest
.push
(
row
);
if
recurse
{
if
let
Some
(
ref
dir
)
=
file
.this
{
let
files
=
filter
.transform_files
(
dir
.files
(
true
));
get_files
(
columns
,
cache
,
recurse
,
dest
,
files
.as_slice
(),
depth
+
1
,
filter
);
}
}
print!
(
"
\n
"
);
}
}
struct
Row
{
pub
depth
:
usize
,
pub
cells
:
Vec
<
Cell
>
,
pub
name
:
String
,
pub
last
:
bool
,
pub
children
:
bool
,
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录