hyaslide 0.5.1 → 0.6.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: 1b1309b029e63871fa5502347a9f0affede6510d
4
- data.tar.gz: 7ad330af25e54778f296b72f1d499234c3dd1194
3
+ metadata.gz: d773246c1bf2e1e2ab74c42db9ed6dde47d41bae
4
+ data.tar.gz: 84554225d647e2cad5f9ad5b16705465cc4ad216
5
5
  SHA512:
6
- metadata.gz: fc289bd9a4ff9e9ff7e98003885505c533c5d487f4ba07b29645fea3a9dc25a4708f6b374e6c34c9dda2f2a817de11eeff2264ac0c664b827da5503512080ad3
7
- data.tar.gz: 52296be7cd8e7d71c1c710cebf8a0f8d21ac48227a8c04d57af40768a7c79af315d805f68e1697591b1a81aca37be9c813d13a049ebe5564b8f6aafebe827620
6
+ metadata.gz: e5bee3218504024156e104a044b653e9fb01af1d82b878dc8a5c100e10c368ce5967c908e5ac0e62510d435cf01c7ee4f5f5528610d23131cdb6c5d8b99a803f
7
+ data.tar.gz: 4eccfe9359b02fdd5309d57a04161a07a92b1007c0bc3c38c43ea3d71f02f26dc2734db1b6b5cf9efa39522c3e0e91913df482775fc902c48dc9593dec3c82ff
@@ -18,201 +18,216 @@ class String
18
18
  end
19
19
  end
20
20
 
21
- module Redcarpet
22
- module Render
23
- class Hyaslide < Base
24
- def initialize
25
- super
26
- @enter_title_page = false
27
- @enter_page = false
28
- @page_count = 0
29
- clear_list_state
30
- end
21
+ module Hyaslide
22
+ class Renderer < Redcarpet::Render::Base
23
+ def self.create(name)
24
+ Class.new(Renderer) do
25
+ def self.page_name
26
+ @page_name
27
+ end
31
28
 
32
- def clear_list_state
33
- @enter_list = false
34
- @list_depth = 0
35
- end
29
+ def self.page_name=(name)
30
+ @page_name = name
31
+ end
36
32
 
37
- [
38
- # block-level calls
39
- :block_quote,
33
+ def page_name
34
+ self.class.page_name
35
+ end
36
+ end.tap{|c| c.page_name = name }
37
+ end
40
38
 
41
- # span-level calls
42
- :autolink,
43
- :underline, :raw_html,
44
- :triple_emphasis, :strikethrough,
45
- :superscript, :highlight,
39
+ def initialize
40
+ super
41
+ @enter_title_page = false
42
+ @enter_page = false
43
+ @page_count = 0
44
+ clear_list_state
45
+ end
46
46
 
47
- # footnotes
48
- :footnotes, :footnote_def, :footnote_ref,
47
+ def clear_list_state
48
+ @enter_list = false
49
+ @list_depth = 0
50
+ end
49
51
 
50
- # low level rendering
51
- #:entity, :normal_text
52
- ].each do |method|
53
- define_method method do |*args|
54
- "#{method} #{args.first}"
55
- end
52
+ [
53
+ # block-level calls
54
+ :block_quote,
55
+
56
+ # span-level calls
57
+ :autolink,
58
+ :underline, :raw_html,
59
+ :triple_emphasis, :strikethrough,
60
+ :superscript, :highlight,
61
+
62
+ # footnotes
63
+ :footnotes, :footnote_def, :footnote_ref,
64
+
65
+ # low level rendering
66
+ #:entity, :normal_text
67
+ ].each do |method|
68
+ define_method method do |*args|
69
+ "#{method} #{args.first}"
56
70
  end
71
+ end
57
72
 
58
- def hrule
59
- clear_list_state
60
- "".tap do |result|
61
- if @enter_title_page || @enter_page
62
- result <<
73
+ def hrule
74
+ clear_list_state
75
+ "".tap do |result|
76
+ if @enter_title_page || @enter_page
77
+ result <<
63
78
  <<EOD
64
- end
65
79
  end
66
80
  end
81
+ end
67
82
 
68
83
  EOD
69
- @enter_title_page = @enter_page = false
70
- end
84
+ @enter_title_page = @enter_page = false
85
+ end
71
86
 
72
- result << <<EOD
87
+ result << <<EOD
73
88
  class Hyaslide::Page#{@page_count} < Hyaslide::PageBase
74
- def content
75
- [].tap do |children|
89
+ def content
90
+ [].tap do |children|
76
91
  EOD
77
92
 
78
- @enter_page = true
79
- @page_count += 1
80
- end
93
+ @enter_page = true
94
+ @page_count += 1
81
95
  end
96
+ end
82
97
 
83
- def header(text, header_level)
84
- clear_list_state
98
+ def header(text, header_level)
99
+ clear_list_state
85
100
 
86
- if header_level > 3
87
- return " children << h#{header_level}(nil, #{text.escape})\n"
88
- end
101
+ if header_level > 3
102
+ return " children << h#{header_level}(nil, #{text.escape})\n"
103
+ end
89
104
 
90
- "".tap do |result|
91
- if @enter_title_page || @enter_page
92
- result <<
105
+ "".tap do |result|
106
+ if @enter_title_page || @enter_page
107
+ result <<
93
108
  <<EOD
94
- end
95
109
  end
96
110
  end
111
+ end
97
112
 
98
113
  EOD
99
- @enter_title_page = @enter_page = false
100
- end
114
+ @enter_title_page = @enter_page = false
115
+ end
101
116
 
102
- if header_level == 1
103
- @enter_title_page = true
104
- @title = text.escape
105
- else
106
- @enter_page = true
107
- end
117
+ if header_level == 1
118
+ @enter_title_page = true
119
+ @title = text.escape
120
+ else
121
+ @enter_page = true
122
+ end
108
123
 
109
124
 
110
- result <<
125
+ result <<
111
126
  <<EOD
112
127
  class Hyaslide::Page#{@page_count} < Hyaslide::PageBase
113
- def header
114
- h#{header_level}(nil, #{text.escape})
115
- end
128
+ def header
129
+ h#{header_level}(nil, #{text.escape})
130
+ end
116
131
 
117
- def content
118
- [].tap do |children|
132
+ def content
133
+ [].tap do |children|
119
134
  EOD
120
135
 
121
- @page_count += 1
122
- end
136
+ @page_count += 1
123
137
  end
138
+ end
124
139
 
125
- def doc_footer(*args)
140
+ def doc_footer(*args)
126
141
  <<EOD
127
- end
128
142
  end
129
143
  end
144
+ end
130
145
 
131
146
  Hyaslide.page_count = #{@page_count}
132
147
  Hyaslide.title = #{@title}
133
148
  EOD
134
- end
135
-
136
- def list_item(item, orderd)
137
- "".tap do |result|
138
- if item =~ /.*\n +children << /
139
- result << item.sub(/(.*)\n +children << /) { " li(nil, #{$1.strip.escape}),\n " }.rstrip
140
- result << ",\n"
141
- @enter_list = false
142
- else
143
- result << " li(nil, #{item.strip.escape}),\n"
144
- end
145
- end
146
- end
149
+ end
147
150
 
148
- def list(text, ordered)
149
- "".tap do |result|
150
- result << " children << ul(nil,\n"
151
- result << text.sub(/,\n\z/, "\n")
152
- result << " )\n"
151
+ def list_item(item, orderd)
152
+ "".tap do |result|
153
+ if item =~ /.*\n +children << /
154
+ result << item.sub(/(.*)\n +children << /) { " li(nil, #{$1.strip.escape}),\n " }.rstrip
155
+ result << ",\n"
153
156
  @enter_list = false
157
+ else
158
+ result << " li(nil, #{item.strip.escape}),\n"
154
159
  end
155
160
  end
161
+ end
156
162
 
157
- def link(link, title, content)
158
- "<a>{href: #{link.escape}, target: \"_blank\"}, #{content.escape}</a>"
163
+ def list(text, ordered)
164
+ "".tap do |result|
165
+ result << " children << ul(nil,\n"
166
+ result << text.sub(/,\n\z/, "\n")
167
+ result << " )\n"
168
+ @enter_list = false
159
169
  end
170
+ end
160
171
 
161
- def image(link, title, alt_text)
162
- "<img>{className: \"#{alt_text}\", src: #{link.escape}}</img>"
163
- end
172
+ def link(link, title, content)
173
+ "<a>{href: #{link.escape}, target: \"_blank\"}, #{content.escape}</a>"
174
+ end
164
175
 
165
- def block_code(code, language)
166
- clear_list_state
167
-
168
- formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', line_numbers: true)
169
- lexer = case language
170
- when 'ruby'
171
- Rouge::Lexers::Ruby.new
172
- when 'javascript'
173
- Rouge::Lexers::Javascript.new
174
- else
175
- Rouge::Lexers::PlainText.new
176
- end
177
- highlight = formatter.format(lexer.lex(code))
178
- " children << code({ dangerouslySetInnerHTML: { __html: %q{#{highlight}} } })\n"
179
- end
176
+ def image(link, title, alt_text)
177
+ href = "assets/#{page_name}/images/#{link}"
178
+ "<img>{className: \"#{alt_text}\", src: #{href.escape}}</img>"
179
+ end
180
180
 
181
- def block_html(html)
182
- clear_list_state
183
- " children << code({ dangerouslySetInnerHTML: { __html: %q{#{html}} } })\n"
184
- end
181
+ def block_code(code, language)
182
+ clear_list_state
183
+
184
+ formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', line_numbers: true)
185
+ lexer = case language
186
+ when 'ruby'
187
+ Rouge::Lexers::Ruby.new
188
+ when 'javascript'
189
+ Rouge::Lexers::Javascript.new
190
+ else
191
+ Rouge::Lexers::PlainText.new
192
+ end
193
+ highlight = formatter.format(lexer.lex(code))
194
+ " children << code({ dangerouslySetInnerHTML: { __html: %q{#{highlight}} } })\n"
195
+ end
185
196
 
186
- def emphasis(*args)
187
- "# #{args}"
188
- end
197
+ def block_html(html)
198
+ clear_list_state
199
+ " children << code({ dangerouslySetInnerHTML: { __html: %q{#{html}} } })\n"
200
+ end
189
201
 
190
- def double_emphasis(*args)
191
- "<strong>#{args.first}</strong>"
192
- end
202
+ def emphasis(*args)
203
+ "# #{args}"
204
+ end
193
205
 
194
- def paragraph(text)
195
- lines = text.split(/ $/)
196
- if text[0] == '%'
197
- class_name = text[1...text.index(':')]
198
- lines[0] = lines.first[(lines.first.index(':') + 1)...lines.first.length].lstrip
199
- " children << p({className:\"#{class_name}\"}, #{lines.map{|l| l.escape}.join(',Hyalite.create_element(\'br\'),')})\n"
200
- else
201
- " children << p(nil, #{lines.map{|l| l.escape}.join(',Hyalite.create_element(\'br\'),')})\n"
202
- end
203
- end
206
+ def double_emphasis(*args)
207
+ "<strong>#{args.first}</strong>"
208
+ end
204
209
 
205
- def table(header, body)
206
- "#{header}#{body}"
210
+ def paragraph(text)
211
+ lines = text.split(/ $/)
212
+ if text[0] == '%'
213
+ class_name = text[1...text.index(':')]
214
+ lines[0] = lines.first[(lines.first.index(':') + 1)...lines.first.length].lstrip
215
+ " children << p({className:\"#{class_name}\"}, #{lines.map{|l| l.escape}.join(',Hyalite.create_element(\'br\'),')})\n"
216
+ else
217
+ " children << p(nil, #{lines.map{|l| l.escape}.join(',Hyalite.create_element(\'br\'),')})\n"
207
218
  end
219
+ end
208
220
 
209
- def table_row(content)
210
- content + "\n"
211
- end
221
+ def table(header, body)
222
+ "#{header}#{body}"
223
+ end
212
224
 
213
- def table_cell(content, alignment)
214
- content + "\t"
215
- end
225
+ def table_row(content)
226
+ content + "\n"
227
+ end
228
+
229
+ def table_cell(content, alignment)
230
+ content + "\t"
216
231
  end
217
232
  end
218
233
  end
@@ -7,7 +7,6 @@ require_relative './render_hyaslide'
7
7
 
8
8
  module Hyaslide
9
9
  class SlideLoader
10
- MARKDOWN = Redcarpet::Markdown.new(Redcarpet::Render::Hyaslide, fenced_code_blocks: true)
11
10
 
12
11
  attr_reader :slides
13
12
 
@@ -15,9 +14,11 @@ module Hyaslide
15
14
  @slides = []
16
15
  end
17
16
 
18
- def load_slide(name)
17
+ def init_slide(name)
19
18
  Dir.mkdir('app/slides') unless Dir.exist?('app/slides')
20
- Dir.mkdir(src_path(name)) unless Dir.exist?(src_path(name))
19
+ Dir.mkdir('app/scripts') unless Dir.exist?('app/scripts')
20
+ Dir.mkdir(SlideLoader.src_path(name)) unless Dir.exist?(SlideLoader.src_path(name))
21
+ Dir.mkdir(SlideLoader.script_path(name)) unless Dir.exist?(SlideLoader.script_path(name))
21
22
 
22
23
  File.foreach("data/#{name}/slide.md") do |line|
23
24
  if line =~ /\A# (.+)/
@@ -26,26 +27,47 @@ module Hyaslide
26
27
  end
27
28
  end
28
29
 
30
+ SlideLoader.load_slide(name)
31
+ end
32
+
33
+ def self.load_slide(name)
29
34
  File.open("#{src_path(name)}/pages.rb", "w+") do |f|
30
35
  data = File.read("data/#{name}/slide.md")
31
- f.write MARKDOWN.render(data)
36
+ markdown = Redcarpet::Markdown.new(Hyaslide::Renderer.create(name), fenced_code_blocks: true)
37
+ f.write markdown.render(data)
38
+ end
39
+
40
+ if File.exist?("data/#{name}/script.md")
41
+ File.open("#{script_path(name)}/script.html", "w+") do |f|
42
+ script = File.read("data/#{name}/script.md")
43
+ markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(hard_wrap: true), autolink: true, fenced_code_blocks: true)
44
+ f.write markdown.render(script)
45
+ end
32
46
  end
33
47
 
34
48
  File.open("#{src_path(name)}/app.rb", "w+") do |f|
35
49
  f.write Tilt::StringTemplate.new(File.expand_path('../../../template/app.rb', __FILE__)).render(Object.new, name: name)
36
50
  end
51
+
52
+ File.open("#{src_path(name)}/script.rb", "w+") do |f|
53
+ f.write Tilt::StringTemplate.new(File.expand_path('../../../template/script.rb', __FILE__)).render(Object.new, name: name)
54
+ end
37
55
  end
38
56
 
39
- def src_path(name)
57
+ def self.src_path(name)
40
58
  "app/slides/#{name}"
41
59
  end
42
60
 
61
+ def self.script_path(name)
62
+ "app/scripts/#{name}"
63
+ end
64
+
43
65
  def add_slide(name)
44
- load_slide(name)
66
+ init_slide(name)
45
67
 
46
68
  EM.defer do
47
- FSSM.monitor("data/#{@name}", "slide.md") do
48
- update {|base, relative| SlideLoader.load_slide(@name) }
69
+ FSSM.monitor("data/#{name}", %w(slide.md script.md)) do
70
+ update {|base, relative| Hyaslide::SlideLoader.load_slide(name) }
49
71
  delete {|base, relative|}
50
72
  create {|base, relative|}
51
73
  end
@@ -1,3 +1,3 @@
1
1
  module Hyaslide
2
- VERSION = "0.5.1"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -1,19 +1,23 @@
1
1
  require 'slide'
2
2
  require 'page_base'
3
3
  require 'slides/#{name}/pages'
4
+ require 'browser/socket'
5
+ require 'browser/location'
4
6
 
5
7
  Hyaslide.slide_name = '#{name}'
6
8
 
7
9
  module App
8
- def self.render
9
- Hyalite.render(Hyalite.create_element(Hyaslide::Slide, nil), $document['.hyaslide'])
10
+ def self.render(ws)
11
+ Hyalite.render(Hyalite.create_element(Hyaslide::Slide, {ws:ws}), $document['.hyaslide'])
10
12
  end
11
13
  end
12
14
 
13
15
  $document.ready do
16
+ ws = Browser::Socket.new("ws://\#{$window.location.host}/push_notification/start/slide/#{name}")
17
+
14
18
  $window.on(:resize) do
15
- App.render
19
+ App.render(ws)
16
20
  end
17
21
 
18
- App.render
22
+ App.render(ws)
19
23
  end
@@ -51,7 +51,7 @@ module Hyaslide
51
51
  end
52
52
 
53
53
  def initial_state
54
- page_num = $window.location.uri.sub(/.*\/#\/([0-9]+)/, '\1').to_i
54
+ page_num = $window.location.uri.sub(/.*#([0-9]+)/, '\1').to_i
55
55
 
56
56
  {
57
57
  page_number: page_num,
@@ -63,7 +63,15 @@ module Hyaslide
63
63
 
64
64
  def component_did_mount
65
65
  $window.on(:keydown) do |evt|
66
- handle_key_down(evt)
66
+ handle_key_down(evt.code)
67
+ end
68
+
69
+ @props[:ws].on(:message) do |msg|
70
+ (event, value) = msg.data.split(':')
71
+ case event
72
+ when 'keydown'
73
+ handle_key_down(value.to_i)
74
+ end
67
75
  end
68
76
 
69
77
  router = Router.new
@@ -75,8 +83,8 @@ module Hyaslide
75
83
  $window.location.assign("/#{Hyaslide.slide_name}##{num}")
76
84
  end
77
85
 
78
- def handle_key_down(evt)
79
- case evt.code
86
+ def handle_key_down(keycode)
87
+ case keycode
80
88
  when 39
81
89
  page_to(@state[:page_number] + 1) if @state[:page_number] < Hyaslide.page_count
82
90
  when 37
@@ -96,7 +104,7 @@ module Hyaslide
96
104
  when 70
97
105
  set_state(footer_visible: !@state[:footer_visible])
98
106
  else
99
- puts "keycode = #{evt.code}"
107
+ puts "keycode = #{keycode}"
100
108
  end
101
109
  end
102
110
 
@@ -119,7 +127,7 @@ module Hyaslide
119
127
  div({
120
128
  className: 'slide',
121
129
  style: {zoom: zoom, top: "#{top}px", left: "#{left}px"},
122
- onKeyDown: -> (evt) { handle_key_down(evt) }
130
+ onKeyDown: -> (evt) { handle_key_down(evt.code) }
123
131
  },
124
132
  pages(SLIDE_HEIGHT * zoom)
125
133
  ),
@@ -0,0 +1,7 @@
1
+ # Sample
2
+
3
+ Write script here.
4
+
5
+ This page accessible from psth: '/script/sample'
6
+
7
+ You can controll the slide from this page. Press arrow keys.
@@ -1,4 +1,5 @@
1
1
  require 'sinatra'
2
+ require 'sinatra-websocket'
2
3
  require 'opal'
3
4
  require 'opal/sprockets'
4
5
 
@@ -28,6 +29,8 @@ class Server < Sinatra::Base
28
29
  end
29
30
 
30
31
  set :opal, opal
32
+ set :sockets, []
33
+ set :slide_sockets, {}
31
34
  enable :sessions
32
35
  end
33
36
 
@@ -36,13 +39,38 @@ class Server < Sinatra::Base
36
39
  haml :index
37
40
  end
38
41
 
42
+ get "/favicon.ico" do
43
+ end
44
+
39
45
  get '/:slide_name' do
40
46
  @slide_name = params['slide_name']
41
47
  haml :slide
42
48
  end
43
49
 
44
- get "/favicon.ico" do
50
+ get '/script/:slide_name' do
51
+ @slide_name = params['slide_name']
52
+ @script_html = "<h1>Test</h1>"
53
+ haml :script
45
54
  end
46
- end
47
-
48
55
 
56
+ get '/push_notification/start/:page/:slide_name' do
57
+ request.websocket do |ws|
58
+ ws.onopen do
59
+ if params['page'] == 'slide'
60
+ slide_name = params['slide_name']
61
+ (settings.slide_sockets[slide_name] ||= []) << ws
62
+ end
63
+ settings.sockets << ws
64
+ end
65
+ ws.onmessage do |msg|
66
+ (slide_name, evt, value) = msg.split(':')
67
+ EM.next_tick do
68
+ settings.slide_sockets[slide_name].each{|s| s.send("#{evt}:#{value}") }
69
+ end
70
+ end
71
+ ws.onclose do
72
+ settings.sockets.delete(ws)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -10,4 +10,4 @@
10
10
  %ul
11
11
  - @slides.each do |slide|
12
12
  %li
13
- %a{href: slide[:name]}= slide[:title]
13
+ %a{href: slide[:name]}= "[#{slide[:name]}] \"#{slide[:title]}\""
@@ -0,0 +1,10 @@
1
+ !!!
2
+ %html(lang="en" data-framework="hyalite")
3
+ %head
4
+ %meta{charset:"utf-8"}
5
+ %title= "Script of #{@slide_name}"
6
+
7
+ %body
8
+ %section.script
9
+
10
+ = ::Opal::Sprockets.javascript_include_tag("slides/#{@slide_name}/script", sprockets: settings.opal.sprockets, prefix: '/assets', debug: true)
@@ -0,0 +1,24 @@
1
+ require 'hyalite'
2
+ require 'browser/http'
3
+ require 'browser/socket'
4
+ require 'browser/location'
5
+
6
+ class Script
7
+ include Hyalite::Component
8
+ include Hyalite::Component::ShortHand
9
+
10
+ def render
11
+ div({dangerouslySetInnerHTML: { __html: @props[:script_html]}})
12
+ end
13
+ end
14
+
15
+ $document.ready do
16
+ ws = Browser::Socket.new("ws://\#{$window.location.host}/push_notification/start/script/#{name}")
17
+ $window.on('keydown') do |evt|
18
+ ws.write("#{name}:keydown:\#{evt.code}")
19
+ end
20
+
21
+ response = Browser::HTTP.get!('/assets/scripts/#{name}/script.html')
22
+ Hyalite.render(Hyalite.create_element(Script, script_html: response.text), $document['.script'])
23
+ end
24
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hyaslide
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - youchan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-11 00:00:00.000000000 Z
11
+ date: 2016-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sinatra-websocket
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: thin
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -249,10 +263,13 @@ files:
249
263
  - template/project/assets/images/turtle.png
250
264
  - template/project/config.ru
251
265
  - template/project/data/sample/css/custom.css
266
+ - template/project/data/sample/script.md
252
267
  - template/project/data/sample/slide.md
253
268
  - template/project/server.rb
254
269
  - template/project/views/index.haml
270
+ - template/project/views/script.haml
255
271
  - template/project/views/slide.haml
272
+ - template/script.rb
256
273
  homepage: https://github.com/youchan/hyalide
257
274
  licenses:
258
275
  - MIT