particle-calendar 0.1.0 → 0.1.1
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 +4 -4
- data/README.md +11 -2
- data/config/availability.example.yml +3 -0
- data/lib/availability/application.rb +2 -1
- data/lib/availability/cli.rb +2 -0
- data/lib/availability/config.rb +20 -6
- data/lib/availability/renderer.rb +16 -7
- data/lib/availability/setup.rb +4 -2
- data/lib/availability/version.rb +1 -1
- data/templates/nginx.conf.erb +9 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e9dccada98e81f51f44f33a7b403e3a1c1822784af127acd3ebb882942cbb3ee
|
|
4
|
+
data.tar.gz: cc94bd0ffe53aec20a7569524c4741d4a2b6a2a9a9d8a2dbdc294b622757c16b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 63208f59ada5e3f59be029d064588dfea533061a18d34139a0063fb26cc5725baf5af301b49da4a14e7b1b9e68206234352814f68f47750ab3ccce9519f12e96
|
|
7
|
+
data.tar.gz: bb7143dcf7636bfe0eaa802b61c43b4787ab9ada41c2db7d5ea8c2dcb6fc010b52fe28479b6f7df07fa1be1e4275c0cf9f94c65b884aa57ff581857446e3a483
|
data/README.md
CHANGED
|
@@ -53,7 +53,7 @@ Create a starter configuration, an Nginx server-block sample, and the static out
|
|
|
53
53
|
particle setup
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
By default this writes `particle.yml` with mode `0600`, `particle.nginx.conf`, and `
|
|
56
|
+
By default this writes `particle.yml` with mode `0600`, `particle.nginx.conf`, `public/`, and `log/` in the current directory. It generates a long random URL path for the Nginx sample and refuses to replace either setup file if it already exists.
|
|
57
57
|
|
|
58
58
|
Customize every destination when needed:
|
|
59
59
|
|
|
@@ -78,6 +78,7 @@ calendar_urls:
|
|
|
78
78
|
|
|
79
79
|
days_to_show: 28
|
|
80
80
|
minimum_slot_minutes: 60
|
|
81
|
+
first_day_of_week: monday
|
|
81
82
|
|
|
82
83
|
event_buffer:
|
|
83
84
|
before_minutes: 30
|
|
@@ -151,6 +152,14 @@ availability:
|
|
|
151
152
|
unavailable: true
|
|
152
153
|
```
|
|
153
154
|
|
|
155
|
+
### Calendar week start
|
|
156
|
+
|
|
157
|
+
`first_day_of_week` controls the first column of the rendered calendar and its week labels. It defaults to `monday`; use any lowercase weekday name, for example:
|
|
158
|
+
|
|
159
|
+
```yaml
|
|
160
|
+
first_day_of_week: sunday
|
|
161
|
+
```
|
|
162
|
+
|
|
154
163
|
### Minimum slots and event buffers
|
|
155
164
|
|
|
156
165
|
`minimum_slot_minutes` removes shorter free fragments after subtraction. The default is `0`.
|
|
@@ -235,7 +244,6 @@ Create a dedicated account or deploy as an unprivileged service user, install Ru
|
|
|
235
244
|
```bash
|
|
236
245
|
gem install particle-calendar
|
|
237
246
|
cd /opt/particle
|
|
238
|
-
mkdir -p log
|
|
239
247
|
particle setup --server-name calendar.example.com
|
|
240
248
|
# Edit particle.yml and replace the example calendar placeholders.
|
|
241
249
|
particle generate --config /opt/particle/particle.yml --output /opt/particle/public
|
|
@@ -297,6 +305,7 @@ The repository includes a [`deploy/availability.logrotate`](deploy/availability.
|
|
|
297
305
|
- **Calendar exceeded a safe limit:** reduce an unusually large displayed range or inspect the feed privately for excessive event counts or dense recurrence rules. The generator intentionally fails before publishing rather than risking resource exhaustion.
|
|
298
306
|
- **Old update timestamp:** inspect cron logs. A stale page usually means a later run failed safely.
|
|
299
307
|
- **Permission denied while publishing:** the generator needs write permission on `public/`; Nginx needs read permission only.
|
|
308
|
+
- **Nginx reports that `index.htmlindex.html` is not a directory:** replace the installed page locations with the current `particle.nginx.conf` example, then validate and reload Nginx. Older examples pointed a trailing-slash location directly at the file, causing Nginx's index module to append `index.html` twice.
|
|
300
309
|
|
|
301
310
|
## Known limits
|
|
302
311
|
|
data/lib/availability/cli.rb
CHANGED
|
@@ -113,6 +113,7 @@ module Availability
|
|
|
113
113
|
config_path: File.join(@working_directory, 'particle.yml'),
|
|
114
114
|
nginx_path: File.join(@working_directory, 'particle.nginx.conf'),
|
|
115
115
|
output_dir: File.join(@working_directory, 'public'),
|
|
116
|
+
log_dir: File.join(@working_directory, 'log'),
|
|
116
117
|
server_name: 'example.com',
|
|
117
118
|
url_path: "/#{@random_path.call}/"
|
|
118
119
|
}
|
|
@@ -150,6 +151,7 @@ module Availability
|
|
|
150
151
|
@output.puts("Created #{setup.config_path} (mode 0600)")
|
|
151
152
|
@output.puts("Created #{setup.nginx_path}")
|
|
152
153
|
@output.puts("Created output directory #{setup.output_dir}")
|
|
154
|
+
@output.puts("Created log directory #{setup.log_dir}")
|
|
153
155
|
@output.puts('Next: edit the config, run particle generate, then review and install the Nginx file.')
|
|
154
156
|
end
|
|
155
157
|
|
data/lib/availability/config.rb
CHANGED
|
@@ -14,7 +14,7 @@ module Availability
|
|
|
14
14
|
include ConfigValueParser
|
|
15
15
|
|
|
16
16
|
TOP_LEVEL_KEYS = %w[
|
|
17
|
-
enabled timezone calendar_urls days_to_show minimum_slot_minutes event_buffer availability
|
|
17
|
+
enabled timezone calendar_urls days_to_show minimum_slot_minutes event_buffer availability first_day_of_week
|
|
18
18
|
].freeze
|
|
19
19
|
EVENT_BUFFER_KEYS = %w[before_minutes after_minutes].freeze
|
|
20
20
|
WEEKDAYS = %w[sunday monday tuesday wednesday thursday friday saturday].freeze
|
|
@@ -24,7 +24,7 @@ module Availability
|
|
|
24
24
|
MAX_CALENDAR_URLS = 20
|
|
25
25
|
|
|
26
26
|
attr_reader :enabled, :timezone, :calendar_urls, :days_to_show,
|
|
27
|
-
:minimum_slot_minutes, :buffer_before_minutes, :buffer_after_minutes
|
|
27
|
+
:minimum_slot_minutes, :buffer_before_minutes, :buffer_after_minutes, :first_day_of_week
|
|
28
28
|
|
|
29
29
|
def self.load(path, env: ENV)
|
|
30
30
|
raise ConfigError, "configuration file not found: #{path}" unless File.file?(path)
|
|
@@ -41,10 +41,7 @@ module Availability
|
|
|
41
41
|
@raw = stringify_keys(raw)
|
|
42
42
|
@env = env
|
|
43
43
|
validate_known_keys(@raw, TOP_LEVEL_KEYS, 'configuration')
|
|
44
|
-
|
|
45
|
-
@timezone = parse_timezone
|
|
46
|
-
@days_to_show = bounded_positive_integer('days_to_show', default: 28, maximum: MAX_DAYS_TO_SHOW)
|
|
47
|
-
@minimum_slot_minutes = nonnegative_integer('minimum_slot_minutes', default: 0)
|
|
44
|
+
parse_general_settings
|
|
48
45
|
parse_buffers
|
|
49
46
|
@availability = parse_availability
|
|
50
47
|
@calendar_urls = parse_calendar_urls
|
|
@@ -56,6 +53,14 @@ module Availability
|
|
|
56
53
|
|
|
57
54
|
private
|
|
58
55
|
|
|
56
|
+
def parse_general_settings
|
|
57
|
+
@enabled = boolean('enabled', default: true)
|
|
58
|
+
@timezone = parse_timezone
|
|
59
|
+
@days_to_show = bounded_positive_integer('days_to_show', default: 28, maximum: MAX_DAYS_TO_SHOW)
|
|
60
|
+
@minimum_slot_minutes = nonnegative_integer('minimum_slot_minutes', default: 0)
|
|
61
|
+
@first_day_of_week = parse_first_day_of_week
|
|
62
|
+
end
|
|
63
|
+
|
|
59
64
|
def stringify_keys(value)
|
|
60
65
|
if value.is_a?(Hash)
|
|
61
66
|
return value.each_with_object({}) do |(key, item), hash|
|
|
@@ -76,6 +81,15 @@ module Availability
|
|
|
76
81
|
raise ConfigError, "timezone is unknown: #{name}"
|
|
77
82
|
end
|
|
78
83
|
|
|
84
|
+
def parse_first_day_of_week
|
|
85
|
+
day = @raw.fetch('first_day_of_week', 'monday')
|
|
86
|
+
unless day.is_a?(String) && WEEKDAYS.include?(day)
|
|
87
|
+
raise ConfigError, "first_day_of_week must be one of: #{WEEKDAYS.join(', ')}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
day
|
|
91
|
+
end
|
|
92
|
+
|
|
79
93
|
def parse_buffers
|
|
80
94
|
buffer = @raw.fetch('event_buffer', {})
|
|
81
95
|
raise ConfigError, 'event_buffer must be a mapping' unless buffer.is_a?(Hash)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'cgi'
|
|
4
|
+
require 'date'
|
|
4
5
|
require 'erb'
|
|
5
6
|
|
|
6
7
|
module Availability
|
|
@@ -12,14 +13,15 @@ module Availability
|
|
|
12
13
|
@template = ERB.new(File.read(template_path), trim_mode: '-')
|
|
13
14
|
end
|
|
14
15
|
|
|
15
|
-
def render(days:, generated_at:, timezone:, enabled:, today:, days_to_show:)
|
|
16
|
+
def render(days:, generated_at:, timezone:, enabled:, today:, days_to_show:, first_day_of_week:)
|
|
16
17
|
view = View.new(
|
|
17
18
|
days: days,
|
|
18
19
|
generated_at: generated_at,
|
|
19
20
|
timezone: timezone,
|
|
20
21
|
enabled: enabled,
|
|
21
22
|
today: today,
|
|
22
|
-
days_to_show: days_to_show
|
|
23
|
+
days_to_show: days_to_show,
|
|
24
|
+
first_day_of_week: first_day_of_week
|
|
23
25
|
)
|
|
24
26
|
@template.result(view.template_binding)
|
|
25
27
|
end
|
|
@@ -30,13 +32,14 @@ module Availability
|
|
|
30
32
|
|
|
31
33
|
attr_reader :enabled, :today
|
|
32
34
|
|
|
33
|
-
def initialize(days:, generated_at:, timezone:, enabled:, today:, days_to_show:)
|
|
35
|
+
def initialize(days:, generated_at:, timezone:, enabled:, today:, days_to_show:, first_day_of_week:)
|
|
34
36
|
@days = days
|
|
35
37
|
@generated_at = generated_at
|
|
36
38
|
@timezone = timezone
|
|
37
39
|
@enabled = enabled
|
|
38
40
|
@today = today
|
|
39
41
|
@days_to_show = days_to_show
|
|
42
|
+
@first_day_of_week = first_day_of_week
|
|
40
43
|
end
|
|
41
44
|
|
|
42
45
|
def template_binding
|
|
@@ -57,8 +60,8 @@ module Availability
|
|
|
57
60
|
|
|
58
61
|
def format_week_label(week)
|
|
59
62
|
first_date = week.compact.first.date
|
|
60
|
-
|
|
61
|
-
"Week of #{
|
|
63
|
+
week_start = first_date - weekday_offset(first_date)
|
|
64
|
+
"Week of #{week_start.day} #{week_start.strftime('%B %Y')}"
|
|
62
65
|
end
|
|
63
66
|
|
|
64
67
|
def format_time(time)
|
|
@@ -68,13 +71,13 @@ module Availability
|
|
|
68
71
|
def weeks
|
|
69
72
|
return [] if @days.empty?
|
|
70
73
|
|
|
71
|
-
padded_days = Array.new(@days.first.date
|
|
74
|
+
padded_days = Array.new(weekday_offset(@days.first.date)) + @days
|
|
72
75
|
padded_days.concat(Array.new((7 - padded_days.length) % 7))
|
|
73
76
|
padded_days.each_slice(7).to_a
|
|
74
77
|
end
|
|
75
78
|
|
|
76
79
|
def weekdays
|
|
77
|
-
WEEKDAYS
|
|
80
|
+
WEEKDAYS.rotate(WEEKDAYS.index(@first_day_of_week))
|
|
78
81
|
end
|
|
79
82
|
|
|
80
83
|
def updated_at
|
|
@@ -97,6 +100,12 @@ module Availability
|
|
|
97
100
|
|
|
98
101
|
"#{@today.strftime('%-d %b')}–#{final_with_year}"
|
|
99
102
|
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def weekday_offset(date)
|
|
107
|
+
(date.wday - Date::DAYNAMES.index(@first_day_of_week.capitalize)) % 7
|
|
108
|
+
end
|
|
100
109
|
end
|
|
101
110
|
end
|
|
102
111
|
end
|
data/lib/availability/setup.rb
CHANGED
|
@@ -9,21 +9,23 @@ module Availability
|
|
|
9
9
|
SERVER_NAME_PATTERN = /\A[a-zA-Z0-9.-]+\z/
|
|
10
10
|
URL_PATH_PATTERN = %r{\A/[a-zA-Z0-9_-]+/\z}
|
|
11
11
|
|
|
12
|
-
def initialize(config_path:, nginx_path:, output_dir:, server_name:, url_path:)
|
|
12
|
+
def initialize(config_path:, nginx_path:, output_dir:, log_dir:, server_name:, url_path:)
|
|
13
13
|
@config_path = File.expand_path(config_path)
|
|
14
14
|
@nginx_path = File.expand_path(nginx_path)
|
|
15
15
|
@output_dir = File.expand_path(output_dir)
|
|
16
|
+
@log_dir = File.expand_path(log_dir)
|
|
16
17
|
@server_name = server_name
|
|
17
18
|
@url_path = url_path
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
attr_reader :config_path, :nginx_path, :output_dir
|
|
21
|
+
attr_reader :config_path, :nginx_path, :output_dir, :log_dir
|
|
21
22
|
|
|
22
23
|
def run
|
|
23
24
|
validate!
|
|
24
25
|
ensure_targets_are_available!
|
|
25
26
|
create_files
|
|
26
27
|
FileUtils.mkdir_p(output_dir)
|
|
28
|
+
FileUtils.mkdir_p(log_dir)
|
|
27
29
|
true
|
|
28
30
|
rescue SystemCallError => e
|
|
29
31
|
cleanup_created_files
|
data/lib/availability/version.rb
CHANGED
data/templates/nginx.conf.erb
CHANGED
|
@@ -19,7 +19,8 @@ server {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
location = <%= @url_path %> {
|
|
22
|
-
alias "<%= escaped_output_dir %>/
|
|
22
|
+
alias "<%= escaped_output_dir %>/";
|
|
23
|
+
index index.html;
|
|
23
24
|
default_type text/html;
|
|
24
25
|
|
|
25
26
|
# For enforced privacy, create this file with `htpasswd` and uncomment:
|
|
@@ -27,6 +28,13 @@ server {
|
|
|
27
28
|
# auth_basic_user_file /etc/nginx/particle.htpasswd;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
# The index module redirects internally here after verifying that the file exists.
|
|
32
|
+
location = <%= @url_path %>index.html {
|
|
33
|
+
internal;
|
|
34
|
+
alias "<%= escaped_output_dir %>/index.html";
|
|
35
|
+
default_type text/html;
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
location = <%= @url_path %>favicon.svg {
|
|
31
39
|
alias "<%= escaped_output_dir %>/favicon.svg";
|
|
32
40
|
default_type image/svg+xml;
|