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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 68aa3fcde45ef2b5d719a46cdbf40a626791d71f
4
- data.tar.gz: 682e23bda6b0a4488f27fcf8f9c93606c6aa1665
3
+ metadata.gz: 368f3a82244101749c5bd21dda7e82d229d093c4
4
+ data.tar.gz: 66955c76c3cc09c6d38ea396da93ff3df65036b6
5
5
  SHA512:
6
- metadata.gz: 19a8a5e7b912be06b0cc9aa2dbce64272b717656b4fff91f4fe964efcf73dbbe4db48c5edb323e70278e044f7084a0ff7bf1af3fcdb27e8ecef89b29a9212cd8
7
- data.tar.gz: 3564b088e3b9153d50e7efd73448da11141e0b55049475542900c3ab051c13a2288a1e3792654e19ed7f9453a7288b4621be1a27f90ef195d18ee6d8a9f112ad
6
+ metadata.gz: 5d278f493f55dcff11d89d97c958e01564bb8bda9a80b82d4b13fc055726a53c771179803b32dcc913c7eb9c3a750a9d7b5cad5059b4f97c0e7cb1d45b6ad5f8
7
+ data.tar.gz: 8865989fd4ed41d1629753967e92bc4ef7a4b124aa456eaa129aa804c92a2900454d434ed6e28bd3d92135dc311c56f6a1700e2398c880e1415f82ebe9ffda15
data/bin/servel CHANGED
@@ -1,6 +1,5 @@
1
- #!/usr/bin/env ruby
2
- puts "ARGV: #{ARGV.inspect}"
3
-
4
- require "servel"
5
-
1
+ #!/usr/bin/env ruby
2
+
3
+ require "servel"
4
+
6
5
  Servel::CLI.new(ARGV).start
@@ -1,8 +1,9 @@
1
1
  require 'rack'
2
2
  require 'rack/handler/puma'
3
- require 'haml'
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"
@@ -18,7 +18,8 @@ class Servel::App
18
18
 
19
19
  return [404, {}, []] unless fs_path.exist?
20
20
 
21
- Servel::Index.new(url_root: url_root, url_path: url_path, fs_path: fs_path).render
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)
@@ -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
@@ -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
- @path = Pathname.new(path)
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: @path.basename,
52
- name: @path.basename,
66
+ href: @path_basename,
67
+ name: @path_basename,
53
68
  size: size,
54
- mtime: @path.mtime
69
+ mtime: @path_mtime
55
70
  )
56
71
  end
57
72
 
58
73
  def type
59
- if @path.directory?
74
+ if @path_directory
60
75
  "Dir"
61
- elsif @path.file?
62
- @path.extname.sub(/^\./, "")
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 @path.file? && @path.extname
84
+ return nil unless @path_file && @path_extname
70
85
 
71
- case @path.extname.downcase
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 @path.file?
88
- klasses << "directory" if @path.directory?
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 @path.directory?
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
- @path.directory? ? nil : @path.size
124
+ @path_directory ? nil : @path_size
110
125
  end
111
126
  end
@@ -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] = Haml::Engine.new(include(path), ENGINE_OPTIONS.merge(filename: 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
@@ -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
@@ -1,43 +1,91 @@
1
- class Servel::Index
2
- def initialize(url_root:, url_path:, fs_path:)
3
- @url_root = url_root
4
- @url_path = url_path
5
- @fs_path = fs_path
6
- end
7
-
8
- def render
9
- Servel::HamlContext.render('index.haml', locals)
10
- end
11
-
12
- def locals
13
- {
14
- url_root: @url_root,
15
- url_path: @url_path,
16
- directories: directories,
17
- files: files
18
- }
19
- end
20
-
21
- def directories
22
- list = @fs_path.children.select { |child| child.directory? }
23
- list = sort_paths(list).map { |path| Servel::EntryFactory.for(path) }
24
-
25
- unless @url_path == "/"
26
- list.unshift(Servel::EntryFactory.parent("../"))
27
- list.unshift(Servel::EntryFactory.top(@url_root == "" ? "/" : @url_root))
28
- end
29
-
30
- list.unshift(Servel::EntryFactory.home("/")) if @url_root != ""
31
-
32
- list
33
- end
34
-
35
- def files
36
- list = @fs_path.children.select { |child| child.file? }
37
- sort_paths(list).map { |path| Servel::EntryFactory.for(path) }
38
- end
39
-
40
- def sort_paths(paths)
41
- Naturalsorter::Sorter.sort(paths.map(&:to_s), true).map { |path| Pathname.new(path) }
42
- end
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 Name
6
+ %th{sort_attrs(sort, "name")}
7
+ %a{href: "#"} Name
7
8
  %th.new-tab
8
- %th.type Type
9
- %th.size Size
10
- %th.modified Modified
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