servel 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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