servel 0.11.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/servel +4 -5
- data/lib/servel.rb +3 -1
- data/lib/servel/app.rb +2 -1
- data/lib/servel/entry.rb +11 -2
- data/lib/servel/entry_factory.rb +28 -13
- data/lib/servel/haml_context.rb +20 -2
- data/lib/servel/home_app.rb +11 -11
- data/lib/servel/index.rb +90 -42
- data/lib/servel/instrumentation.rb +26 -0
- data/lib/servel/templates/_gallery.haml +10 -10
- data/lib/servel/templates/_listing.haml +8 -4
- data/lib/servel/templates/css/common.css +24 -24
- data/lib/servel/templates/css/gallery-text.css +39 -39
- data/lib/servel/templates/css/gallery.css +8 -29
- data/lib/servel/templates/css/home.css +15 -15
- data/lib/servel/templates/css/index.css +14 -14
- data/lib/servel/templates/css/listing.css +71 -55
- data/lib/servel/templates/css/normalize.css +447 -447
- data/lib/servel/templates/home.haml +18 -18
- data/lib/servel/templates/index.haml +23 -21
- data/lib/servel/templates/js/gallery.js +21 -2
- data/lib/servel/templates/js/listing.js +26 -0
- data/lib/servel/templates/js/ume.js +63 -63
- data/lib/servel/templates/js/url-search-params.js +2 -0
- data/lib/servel/version.rb +1 -1
- data/servel.gemspec +2 -1
- metadata +24 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 368f3a82244101749c5bd21dda7e82d229d093c4
|
4
|
+
data.tar.gz: 66955c76c3cc09c6d38ea396da93ff3df65036b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d278f493f55dcff11d89d97c958e01564bb8bda9a80b82d4b13fc055726a53c771179803b32dcc913c7eb9c3a750a9d7b5cad5059b4f97c0e7cb1d45b6ad5f8
|
7
|
+
data.tar.gz: 8865989fd4ed41d1629753967e92bc4ef7a4b124aa456eaa129aa804c92a2900454d434ed6e28bd3d92135dc311c56f6a1700e2398c880e1415f82ebe9ffda15
|
data/bin/servel
CHANGED
data/lib/servel.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'rack'
|
2
2
|
require 'rack/handler/puma'
|
3
|
-
require '
|
3
|
+
require 'hamlit'
|
4
4
|
require 'naturalsorter'
|
5
5
|
require 'active_support/all'
|
6
|
+
require 'lru_redux'
|
6
7
|
|
7
8
|
require 'thread'
|
8
9
|
require 'pathname'
|
@@ -17,6 +18,7 @@ module Servel
|
|
17
18
|
end
|
18
19
|
|
19
20
|
require "servel/version"
|
21
|
+
require "servel/instrumentation"
|
20
22
|
require "servel/entry"
|
21
23
|
require "servel/entry_factory"
|
22
24
|
require "servel/haml_context"
|
data/lib/servel/app.rb
CHANGED
@@ -18,7 +18,8 @@ class Servel::App
|
|
18
18
|
|
19
19
|
return [404, {}, []] unless fs_path.exist?
|
20
20
|
|
21
|
-
|
21
|
+
request = Rack::Request.new(env)
|
22
|
+
Servel::Index.new(url_root: url_root, url_path: url_path, fs_path: fs_path, params: request.params).render
|
22
23
|
end
|
23
24
|
|
24
25
|
def redirect(location)
|
data/lib/servel/entry.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
class Servel::Entry
|
2
|
-
attr_reader :type, :media_type, :listing_classes, :icon, :href, :name, :size, :mtime
|
2
|
+
attr_reader :ftype, :type, :media_type, :listing_classes, :icon, :href, :name, :size, :mtime
|
3
3
|
|
4
|
-
def initialize(type:, media_type: nil, listing_classes:, icon:, href:, name:, size: nil, mtime: nil)
|
4
|
+
def initialize(ftype:, type:, media_type: nil, listing_classes:, icon:, href:, name:, size: nil, mtime: nil)
|
5
|
+
@ftype = ftype
|
5
6
|
@type = type
|
6
7
|
@media_type = media_type
|
7
8
|
@listing_classes = listing_classes
|
@@ -12,6 +13,14 @@ class Servel::Entry
|
|
12
13
|
@mtime = mtime
|
13
14
|
end
|
14
15
|
|
16
|
+
def directory?
|
17
|
+
@ftype == :directory
|
18
|
+
end
|
19
|
+
|
20
|
+
def file?
|
21
|
+
@ftype == :file
|
22
|
+
end
|
23
|
+
|
15
24
|
def media?
|
16
25
|
!@media_type.nil?
|
17
26
|
end
|
data/lib/servel/entry_factory.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
class Servel::EntryFactory
|
2
|
+
extend Servel::Instrumentation
|
3
|
+
|
2
4
|
IMAGE_EXTS = %w(.jpg .jpeg .png .gif)
|
3
5
|
VIDEO_EXTS = %w(.webm .mp4 .mkv)
|
4
6
|
AUDIO_EXTS = %w(.mp3 .m4a .wav)
|
@@ -6,6 +8,7 @@ class Servel::EntryFactory
|
|
6
8
|
|
7
9
|
def self.home(href)
|
8
10
|
Servel::Entry.new(
|
11
|
+
ftype: :directory,
|
9
12
|
type: "Dir",
|
10
13
|
listing_classes: "home directory",
|
11
14
|
icon: "🏠",
|
@@ -16,6 +19,7 @@ class Servel::EntryFactory
|
|
16
19
|
|
17
20
|
def self.top(href)
|
18
21
|
Servel::Entry.new(
|
22
|
+
ftype: :directory,
|
19
23
|
type: "Dir",
|
20
24
|
listing_classes: "top directory",
|
21
25
|
icon: "🔝",
|
@@ -26,6 +30,7 @@ class Servel::EntryFactory
|
|
26
30
|
|
27
31
|
def self.parent(href)
|
28
32
|
Servel::Entry.new(
|
33
|
+
ftype: :directory,
|
29
34
|
type: "Dir",
|
30
35
|
listing_classes: "parent directory",
|
31
36
|
icon: "⬆️",
|
@@ -39,36 +44,46 @@ class Servel::EntryFactory
|
|
39
44
|
end
|
40
45
|
|
41
46
|
def initialize(path)
|
42
|
-
@
|
47
|
+
@path_basename = path.basename.to_s
|
48
|
+
@path_extname = path.extname.to_s
|
49
|
+
stat = path.stat
|
50
|
+
@path_mtime = stat.mtime
|
51
|
+
@path_size = stat.size
|
52
|
+
@path_ftype = stat.ftype.intern
|
53
|
+
@path_directory = @path_ftype == :directory
|
54
|
+
@path_file = @path_ftype == :file
|
43
55
|
end
|
44
56
|
|
57
|
+
instrument :initialize
|
58
|
+
|
45
59
|
def entry
|
46
60
|
Servel::Entry.new(
|
61
|
+
ftype: @path_ftype,
|
47
62
|
type: type,
|
48
63
|
media_type: media_type,
|
49
64
|
listing_classes: listing_classes,
|
50
65
|
icon: icon,
|
51
|
-
href: @
|
52
|
-
name: @
|
66
|
+
href: @path_basename,
|
67
|
+
name: @path_basename,
|
53
68
|
size: size,
|
54
|
-
mtime: @
|
69
|
+
mtime: @path_mtime
|
55
70
|
)
|
56
71
|
end
|
57
72
|
|
58
73
|
def type
|
59
|
-
if @
|
74
|
+
if @path_directory
|
60
75
|
"Dir"
|
61
|
-
elsif @
|
62
|
-
@
|
76
|
+
elsif @path_file
|
77
|
+
@path_extname.sub(/^\./, "")
|
63
78
|
else
|
64
79
|
""
|
65
80
|
end
|
66
81
|
end
|
67
82
|
|
68
83
|
def media_type
|
69
|
-
return nil unless @
|
84
|
+
return nil unless @path_file && @path_extname
|
70
85
|
|
71
|
-
case @
|
86
|
+
case @path_extname.downcase
|
72
87
|
when *IMAGE_EXTS
|
73
88
|
:image
|
74
89
|
when *VIDEO_EXTS
|
@@ -84,15 +99,15 @@ class Servel::EntryFactory
|
|
84
99
|
|
85
100
|
def listing_classes
|
86
101
|
klasses = []
|
87
|
-
klasses << "file" if @
|
88
|
-
klasses << "directory" if @
|
102
|
+
klasses << "file" if @path_file
|
103
|
+
klasses << "directory" if @path_directory
|
89
104
|
klasses << "media" if media_type
|
90
105
|
klasses << media_type if media_type
|
91
106
|
klasses.join(" ")
|
92
107
|
end
|
93
108
|
|
94
109
|
def icon
|
95
|
-
return "📁" if @
|
110
|
+
return "📁" if @path_directory
|
96
111
|
case media_type
|
97
112
|
when :video
|
98
113
|
"🎞️"
|
@@ -106,6 +121,6 @@ class Servel::EntryFactory
|
|
106
121
|
end
|
107
122
|
|
108
123
|
def size
|
109
|
-
@
|
124
|
+
@path_directory ? nil : @path_size
|
110
125
|
end
|
111
126
|
end
|
data/lib/servel/haml_context.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class Servel::HamlContext
|
2
|
+
extend Servel::Instrumentation
|
2
3
|
include ActiveSupport::NumberHelper
|
3
4
|
|
4
|
-
ENGINE_OPTIONS = { remove_whitespace: true, escape_html: true, ugly: true }
|
5
5
|
LOCK = Mutex.new
|
6
6
|
|
7
7
|
def self.render(template, locals)
|
@@ -24,13 +24,31 @@ class Servel::HamlContext
|
|
24
24
|
(@build_path + path).read
|
25
25
|
end
|
26
26
|
|
27
|
+
def sort_attrs(sort, current_method)
|
28
|
+
data = { sort_method: current_method, sort_direction: "asc" }
|
29
|
+
classes = ["sortable"]
|
30
|
+
if sort[:method] == current_method
|
31
|
+
data[:sort_active] = true
|
32
|
+
data[:sort_direction] = sort[:direction]
|
33
|
+
classes << "sort-active"
|
34
|
+
classes << "sort-#{sort[:direction]}"
|
35
|
+
end
|
36
|
+
|
37
|
+
{
|
38
|
+
data: data,
|
39
|
+
class: classes
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
27
43
|
def haml_engine(path)
|
28
44
|
LOCK.synchronize do
|
29
45
|
@@haml_engine_cache ||= {}
|
30
46
|
unless @@haml_engine_cache.key?(path)
|
31
|
-
@@haml_engine_cache[path] =
|
47
|
+
@@haml_engine_cache[path] = Hamlit::Template.new(filename: path) { include(path) }
|
32
48
|
end
|
33
49
|
@@haml_engine_cache[path]
|
34
50
|
end
|
35
51
|
end
|
52
|
+
|
53
|
+
instrument :render, :partial, :include
|
36
54
|
end
|
data/lib/servel/home_app.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
class Servel::HomeApp
|
2
|
-
FAVICON_PATH = "/favicon.ico"
|
3
|
-
|
4
|
-
def initialize(roots)
|
5
|
-
@roots = roots
|
6
|
-
end
|
7
|
-
|
8
|
-
def call(env)
|
9
|
-
return [404, {}, []] if env["PATH_INFO"] == FAVICON_PATH
|
10
|
-
Servel::HamlContext.render('home.haml', { roots: @roots })
|
11
|
-
end
|
1
|
+
class Servel::HomeApp
|
2
|
+
FAVICON_PATH = "/favicon.ico"
|
3
|
+
|
4
|
+
def initialize(roots)
|
5
|
+
@roots = roots
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
return [404, {}, []] if env["PATH_INFO"] == FAVICON_PATH
|
10
|
+
Servel::HamlContext.render('home.haml', { roots: @roots })
|
11
|
+
end
|
12
12
|
end
|
data/lib/servel/index.rb
CHANGED
@@ -1,43 +1,91 @@
|
|
1
|
-
class Servel::Index
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
1
|
+
class Servel::Index
|
2
|
+
extend Servel::Instrumentation
|
3
|
+
LOCALS_CACHE = LruRedux::ThreadSafeCache.new(100)
|
4
|
+
SORT_METHODS = ["name", "mtime", "size", "type"]
|
5
|
+
SORT_DIRECTIONS = ["asc", "desc"]
|
6
|
+
|
7
|
+
def initialize(url_root:, url_path:, fs_path:, params:)
|
8
|
+
@url_root = url_root
|
9
|
+
@url_path = url_path
|
10
|
+
@fs_path = fs_path
|
11
|
+
@params = params
|
12
|
+
end
|
13
|
+
|
14
|
+
def render
|
15
|
+
Servel::HamlContext.render('index.haml', locals)
|
16
|
+
end
|
17
|
+
|
18
|
+
def sort_method
|
19
|
+
param = @params["_servel_sort_method"]
|
20
|
+
param = "name" unless SORT_METHODS.include?(param)
|
21
|
+
param
|
22
|
+
end
|
23
|
+
|
24
|
+
def sort_direction
|
25
|
+
param = @params["_servel_sort_direction"]
|
26
|
+
param = "asc" unless SORT_DIRECTIONS.include?(param)
|
27
|
+
param
|
28
|
+
end
|
29
|
+
|
30
|
+
def locals_cache_key
|
31
|
+
@locals_cache_key ||= [@fs_path.to_s, @fs_path.mtime.to_i, sort_method, sort_direction].join("-")
|
32
|
+
end
|
33
|
+
|
34
|
+
def locals
|
35
|
+
LOCALS_CACHE.getset(locals_cache_key) { build_locals }
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_locals
|
39
|
+
entries = @fs_path.children.map { |path| Servel::EntryFactory.for(path) }
|
40
|
+
|
41
|
+
{
|
42
|
+
url_root: @url_root,
|
43
|
+
url_path: @url_path,
|
44
|
+
directories: directories(entries),
|
45
|
+
files: files(entries),
|
46
|
+
sort: {
|
47
|
+
method: sort_method,
|
48
|
+
direction: sort_direction
|
49
|
+
}
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def directories(entries)
|
54
|
+
list = apply_sort(entries.select { |entry| entry.directory? })
|
55
|
+
|
56
|
+
unless @url_path == "/"
|
57
|
+
list.unshift(Servel::EntryFactory.parent("../"))
|
58
|
+
list.unshift(Servel::EntryFactory.top(@url_root == "" ? "/" : @url_root))
|
59
|
+
end
|
60
|
+
|
61
|
+
list.unshift(Servel::EntryFactory.home("/")) if @url_root != ""
|
62
|
+
|
63
|
+
list
|
64
|
+
end
|
65
|
+
|
66
|
+
def files(entries)
|
67
|
+
apply_sort(entries.select { |entry| entry.file? })
|
68
|
+
end
|
69
|
+
|
70
|
+
def apply_sort(entries)
|
71
|
+
entries = case sort_method
|
72
|
+
when "name"
|
73
|
+
Naturalsorter::Sorter.sort_by_method(entries, :name, true)
|
74
|
+
when "mtime"
|
75
|
+
entries.sort_by { |entry| entry.mtime }
|
76
|
+
when "size"
|
77
|
+
entries.sort_by { |entry| entry.size || 0 }
|
78
|
+
when "type"
|
79
|
+
entries.sort_by { |entry| entry.type }
|
80
|
+
end
|
81
|
+
|
82
|
+
entries.reverse! if sort_direction == "desc"
|
83
|
+
entries
|
84
|
+
end
|
85
|
+
|
86
|
+
def sort_entries(entries)
|
87
|
+
Naturalsorter::Sorter.sort_by_method(entries, :name, true)
|
88
|
+
end
|
89
|
+
|
90
|
+
instrument :locals, :directories, :files, :sort_entries
|
43
91
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Servel::Instrumentation
|
2
|
+
def instrument(*method_names)
|
3
|
+
return unless ENV["DEBUG"] == "1"
|
4
|
+
|
5
|
+
Array(method_names).each do |method_name|
|
6
|
+
original_method_name = "#{method_name}_without_instrumentation"
|
7
|
+
|
8
|
+
alias_method original_method_name, method_name
|
9
|
+
|
10
|
+
cumulative_time_spent = 0
|
11
|
+
|
12
|
+
define_method(method_name) do |*args|
|
13
|
+
start = Time.now
|
14
|
+
|
15
|
+
return_value = __send__(original_method_name, *args)
|
16
|
+
|
17
|
+
finish = Time.now
|
18
|
+
time_spent = ((finish - start) * 1000.0)
|
19
|
+
cumulative_time_spent += time_spent
|
20
|
+
puts "Running #{self.class.name}<#{object_id}>##{method_name}: #{time_spent.round(3)}ms (cumulative: #{cumulative_time_spent.round(3)}ms)"
|
21
|
+
|
22
|
+
return_value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
%img#image
|
2
|
-
%video#video{autoplay: true, controls: true}
|
3
|
-
%audio#audio{autoplay: true, controls: true}
|
4
|
-
#text
|
5
|
-
%a#text-anchor{href: '#'}
|
6
|
-
#text-content
|
7
|
-
#page-back.paginator ◀
|
8
|
-
#page-next.paginator ▶
|
9
|
-
#page-back-10.paginator ◀◀
|
10
|
-
#page-next-10.paginator ▶▶
|
1
|
+
%img#image
|
2
|
+
%video#video{autoplay: true, controls: true}
|
3
|
+
%audio#audio{autoplay: true, controls: true}
|
4
|
+
#text
|
5
|
+
%a#text-anchor{href: '#'}
|
6
|
+
#text-content
|
7
|
+
#page-back.paginator ◀
|
8
|
+
#page-next.paginator ▶
|
9
|
+
#page-back-10.paginator ◀◀
|
10
|
+
#page-next-10.paginator ▶▶
|
11
11
|
#page-toggle-size.paginator ⤡
|
@@ -3,11 +3,15 @@
|
|
3
3
|
%table
|
4
4
|
%thead
|
5
5
|
%tr
|
6
|
-
%th
|
6
|
+
%th{sort_attrs(sort, "name")}
|
7
|
+
%a{href: "#"} Name
|
7
8
|
%th.new-tab
|
8
|
-
%th.type
|
9
|
-
|
10
|
-
%th.
|
9
|
+
%th.type{sort_attrs(sort, "type")}
|
10
|
+
%a{href: "#"} Type
|
11
|
+
%th.size{sort_attrs(sort, "size")}
|
12
|
+
%a{href: "#"} Size
|
13
|
+
%th.modified{sort_attrs(sort, "mtime")}
|
14
|
+
%a{href: "#"} Modified
|
11
15
|
%tbody
|
12
16
|
- (directories + files).each do |file|
|
13
17
|
%tr
|