meetup-generator 1.2.84
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.travis.yml +17 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +23 -0
- data/README.md +20 -0
- data/Rakefile +26 -0
- data/bin/meetup-generator.rb +31 -0
- data/config.ru +2 -0
- data/lib/all_the_things.yaml +817 -0
- data/lib/build_helpers.rb +9 -0
- data/lib/meetup-generator.rb +63 -0
- data/lib/version.rb +4 -0
- data/meetup-generator.gemspec +35 -0
- data/public/css/main.css +61 -0
- data/public/favicon.ico +0 -0
- data/public/zxspectr.woff +0 -0
- data/spec/acceptance_spec.rb +87 -0
- data/spec/unit_spec.rb +68 -0
- data/views/default.slim +48 -0
- data/website.xml +100 -0
- metadata +240 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
# Everything needed for a meetup generator
|
5
|
+
#
|
6
|
+
class MeetupGenerator
|
7
|
+
attr_reader :words, :lib, :unused_templates
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
grep, dict = find_unix_stuff
|
11
|
+
@words = `#{grep} "^[a-z]*$" #{dict}`.split("\n")
|
12
|
+
@lib = YAML.load_file(ROOT + 'lib' + 'all_the_things.yaml')
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_unix_stuff
|
16
|
+
case RUBY_PLATFORM
|
17
|
+
when /solaris/
|
18
|
+
%w[/bin/grep /usr/share/lib/dict/words]
|
19
|
+
when /linux/
|
20
|
+
%w[/bin/grep /usr/share/dict/words]
|
21
|
+
when /darwin/
|
22
|
+
%w[/usr/bin/grep /usr/share/dict/words]
|
23
|
+
else
|
24
|
+
abort "unsupported platform: #{RUBY_PLATFORM}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def title
|
29
|
+
@unused_templates ||= lib[:template].dup
|
30
|
+
t = unused_templates.sample
|
31
|
+
unused_templates.delete(t)
|
32
|
+
t.scan(/%\w+%/).each { |k| t = t.sub(k, lib[k[1..-2].to_sym].sample) }
|
33
|
+
t.scan(/RAND\d+/).each do |i|
|
34
|
+
t = t.sub(i, rand(2..(i.sub(/RAND/, '').to_i)).to_s)
|
35
|
+
end
|
36
|
+
t
|
37
|
+
end
|
38
|
+
|
39
|
+
def talks(count = 5)
|
40
|
+
@unused_templates = lib[:template].dup
|
41
|
+
count.times.with_object([]) { |_i, a| a.<< title }
|
42
|
+
end
|
43
|
+
|
44
|
+
def talker
|
45
|
+
lib[:first_name].sample + ' ' + lib[:last_name].sample
|
46
|
+
end
|
47
|
+
|
48
|
+
def role
|
49
|
+
lib[:job_role].sample + ' ' + lib[:job_title].sample
|
50
|
+
end
|
51
|
+
|
52
|
+
def company
|
53
|
+
words.sample.sub(/([^aeiou])er$/, '\\1r').downcase + '.io'
|
54
|
+
end
|
55
|
+
|
56
|
+
def refreshment
|
57
|
+
lib[:food_style].sample + ' ' + lib[:food].sample
|
58
|
+
end
|
59
|
+
|
60
|
+
def talk
|
61
|
+
{ talk: talks(1)[0], talker: talker, role: role, company: company }
|
62
|
+
end
|
63
|
+
end
|
data/lib/version.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'date'
|
3
|
+
require 'English'
|
4
|
+
require_relative 'lib/build_helpers'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'meetup-generator'
|
8
|
+
gem.version = gem_version
|
9
|
+
gem.date = Date.today.to_s
|
10
|
+
|
11
|
+
gem.summary = 'Stupid fatuous random string generatpr'
|
12
|
+
gem.description = 'Generates a website advertising a fictional ' \
|
13
|
+
'DevOps meetup, using all the latest buzzwords, ' \
|
14
|
+
'new-shiny, and clichés'
|
15
|
+
gem.authors = ['Robert Fisher']
|
16
|
+
gem.email = 'slackboy@gmail.com'
|
17
|
+
gem.homepage = 'https://github.com/snltd/meetup-generator'
|
18
|
+
gem.license = 'BSD-2-Clause'
|
19
|
+
|
20
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
21
|
+
gem.test_files = gem.files.grep(%r{^spec/})
|
22
|
+
|
23
|
+
gem.add_runtime_dependency 'sinatra', '~>2.0', '>= 2.0.1'
|
24
|
+
gem.add_runtime_dependency 'slim', '~> 3.0', '>= 3.0.0'
|
25
|
+
gem.add_runtime_dependency 'puma', '~> 3.11', '>= 3.11.0'
|
26
|
+
|
27
|
+
gem.add_development_dependency 'bundler', '~> 1.3'
|
28
|
+
gem.add_development_dependency 'minitest', '~> 5.11', '>= 5.11.0'
|
29
|
+
gem.add_development_dependency 'nokogiri', '~> 1.8', '>= 1.8.0'
|
30
|
+
gem.add_development_dependency 'rack-test', '~> 0.8', '>= 0.8.0'
|
31
|
+
gem.add_development_dependency 'rake', '~> 12.3', '>= 12.3.0'
|
32
|
+
gem.add_development_dependency 'rubocop', '~> 0.52', '>= 0.52.0'
|
33
|
+
|
34
|
+
gem.required_ruby_version = Gem::Requirement.new('>= 2.2.0')
|
35
|
+
end
|
data/public/css/main.css
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
@font-face {
|
2
|
+
font-family: 'ZX Spectrum';
|
3
|
+
src: url('/zxspectr.woff') format('woff');
|
4
|
+
}
|
5
|
+
|
6
|
+
body {
|
7
|
+
font-family: 'ZX Spectrum';
|
8
|
+
font-size: 0.9em;
|
9
|
+
background: #000;
|
10
|
+
color: #0f0;
|
11
|
+
line-height: 120%;
|
12
|
+
}
|
13
|
+
|
14
|
+
.tperson {
|
15
|
+
font-size: small;
|
16
|
+
margin-left: 12em;
|
17
|
+
}
|
18
|
+
|
19
|
+
ul {
|
20
|
+
list-style: none;
|
21
|
+
}
|
22
|
+
|
23
|
+
li {
|
24
|
+
padding-top: 1em;
|
25
|
+
text-indent: -9em;
|
26
|
+
margin-left: 9em;
|
27
|
+
}
|
28
|
+
|
29
|
+
.ttitle {
|
30
|
+
/*font-style: italic;*/
|
31
|
+
font-weight: bold;
|
32
|
+
background-color: #0ff;
|
33
|
+
color: #000;
|
34
|
+
}
|
35
|
+
|
36
|
+
h1 {
|
37
|
+
text-align: center;
|
38
|
+
padding-bottom: 0.5em;
|
39
|
+
margin-bottom: 1em;
|
40
|
+
color: #ff0;
|
41
|
+
line-height:100%;
|
42
|
+
border-bottom: 4px dotted #f0f;
|
43
|
+
}
|
44
|
+
|
45
|
+
#container {
|
46
|
+
padding-top: 3em;
|
47
|
+
width: 900px;
|
48
|
+
margin-left: auto;
|
49
|
+
margin-right: auto;
|
50
|
+
}
|
51
|
+
|
52
|
+
#footer {
|
53
|
+
margin-top: 20em;
|
54
|
+
font-size: x-small;
|
55
|
+
text-align: right;
|
56
|
+
}
|
57
|
+
|
58
|
+
a {
|
59
|
+
text-decoration: none;
|
60
|
+
color: #f0f;
|
61
|
+
}
|
data/public/favicon.ico
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Acceptance tests for the meetup generator. Yeah, I know.
|
4
|
+
#
|
5
|
+
%w[minitest minitest/unit minitest/autorun rack/test pathname json
|
6
|
+
yaml cgi nokogiri].each { |f| require f }
|
7
|
+
|
8
|
+
MGROOT = Pathname.new(__FILE__).realpath.dirname.parent
|
9
|
+
|
10
|
+
OUTER_APP = Rack::Builder.parse_file((MGROOT + 'config.ru').to_s).first
|
11
|
+
|
12
|
+
class TestApp < MiniTest::Test
|
13
|
+
attr_reader :things
|
14
|
+
include Rack::Test::Methods
|
15
|
+
|
16
|
+
def initialize(args)
|
17
|
+
super(args)
|
18
|
+
@things = YAML.load_file(MGROOT + 'lib' + 'all_the_things.yaml')
|
19
|
+
end
|
20
|
+
|
21
|
+
def app
|
22
|
+
OUTER_APP
|
23
|
+
end
|
24
|
+
|
25
|
+
# Load the page 1000 times and make sure no talk titles occur
|
26
|
+
# twice
|
27
|
+
#
|
28
|
+
def test_no_repeats
|
29
|
+
1000.times do
|
30
|
+
html = Nokogiri::HTML(get('/').body)
|
31
|
+
titles = html.css('span[class="ttitle"]').map(&:text)
|
32
|
+
assert_equal(titles, titles.uniq)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_default
|
37
|
+
#
|
38
|
+
# Get all of the templates used to generate talk titles, and re-run
|
39
|
+
# the test until we've seen them all. Then we know no template
|
40
|
+
# causes a crash. That's good enough.
|
41
|
+
#
|
42
|
+
templates = things[:template].map do |t|
|
43
|
+
escaped = Regexp.escape(CGI.escapeHTML(t))
|
44
|
+
matcher = escaped.gsub(/%\w+%/, '\w+').gsub(/RAND\d+/, '\d+')
|
45
|
+
Regexp.new('^.*ttitle">' + matcher + '</span>.*$')
|
46
|
+
end
|
47
|
+
|
48
|
+
until templates.empty?
|
49
|
+
get '/'
|
50
|
+
assert last_response.ok?
|
51
|
+
assert last_response.header['Content-Type'] == 'text/html;charset=utf-8'
|
52
|
+
x = last_response.body
|
53
|
+
assert_match(/The code./, x)
|
54
|
+
assert_match(/DevOps Meetup/, x)
|
55
|
+
templates.each { |t| templates.delete(t) if x.match(t) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_api_talk
|
60
|
+
get '/api/talk'
|
61
|
+
assert last_response.ok?
|
62
|
+
assert last_response.header['Content-Type'] == 'application/json'
|
63
|
+
resp = last_response.body
|
64
|
+
refute_empty resp
|
65
|
+
j = JSON.parse(resp)
|
66
|
+
fields = %w[talk talker role company]
|
67
|
+
assert_equal(j.keys, fields)
|
68
|
+
fields.each { |f| refute_empty j[f] }
|
69
|
+
name = j['talker'].split
|
70
|
+
assert_includes(things[:first_name], name.first)
|
71
|
+
assert_includes(things[:last_name], name.last)
|
72
|
+
assert j['company'].end_with?('.io')
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_api_misc
|
76
|
+
%w[title talker company role refreshment].each do |word|
|
77
|
+
get format('/api/%s', word)
|
78
|
+
assert last_response.ok?
|
79
|
+
assert last_response.header['Content-Type'] == 'application/json'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_api_404
|
84
|
+
get '/api/no_such_thing'
|
85
|
+
assert_equal(last_response.status, 404)
|
86
|
+
end
|
87
|
+
end
|
data/spec/unit_spec.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require_relative '../lib/meetup-generator'
|
5
|
+
|
6
|
+
THINGS = { food_style: %w[artisan],
|
7
|
+
food: %w[flatbread],
|
8
|
+
first_name: %w[John],
|
9
|
+
last_name: %w[Smith],
|
10
|
+
job_role: %w[Neckbeard],
|
11
|
+
job_title: ['Without Portfolio'],
|
12
|
+
tech: %w[Ruby],
|
13
|
+
template: ['RAND20 %tech% things'] }.freeze
|
14
|
+
|
15
|
+
class Giblets < MeetupGenerator
|
16
|
+
def initialize; end
|
17
|
+
end
|
18
|
+
|
19
|
+
class GibletsTest < MiniTest::Test
|
20
|
+
attr_reader :m
|
21
|
+
|
22
|
+
def setup
|
23
|
+
@m = Giblets.new
|
24
|
+
m.instance_variable_set(:@lib, THINGS)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_title
|
28
|
+
x = m.title
|
29
|
+
assert_instance_of(String, x)
|
30
|
+
assert !x.empty?
|
31
|
+
refute_match(/%\w+%/, x)
|
32
|
+
refute_match(/RAND/, x)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_talk
|
36
|
+
x = m.talks(1)
|
37
|
+
assert_instance_of(Array, x)
|
38
|
+
assert_instance_of(String, x.first)
|
39
|
+
toks = x.first.split
|
40
|
+
number = toks.first.to_i
|
41
|
+
assert number > 0
|
42
|
+
assert number <= 20
|
43
|
+
assert_equal('Ruby', toks[1])
|
44
|
+
assert_equal('things', toks[2])
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_talker
|
48
|
+
assert_equal(m.talker, 'John Smith')
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_role
|
52
|
+
assert_equal(m.role, 'Neckbeard Without Portfolio')
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_company_no_e
|
56
|
+
m.instance_variable_set(:@words, %w[leadswinger])
|
57
|
+
assert_equal(m.company, 'leadswingr.io')
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_company
|
61
|
+
m.instance_variable_set(:@words, %w[Cabbage])
|
62
|
+
assert_equal(m.company, 'cabbage.io')
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_refreshment
|
66
|
+
assert_equal(m.refreshment, 'artisan flatbread')
|
67
|
+
end
|
68
|
+
end
|
data/views/default.slim
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
doctype html
|
2
|
+
html
|
3
|
+
head
|
4
|
+
title #Devops Meetup
|
5
|
+
meta charset='utf-8'
|
6
|
+
link href='/css/main.css' rel='stylesheet'
|
7
|
+
|
8
|
+
body
|
9
|
+
#container
|
10
|
+
h1 #DevOps Meetup // Shoreditch, probably // #{(Date.today + 1).strftime("%d/%m/%Y")}
|
11
|
+
|
12
|
+
ul
|
13
|
+
li 18:00 // Introduction
|
14
|
+
|
15
|
+
li
|
16
|
+
' 18:10 // Lightning talk //
|
17
|
+
span.ttitle=@talks.pop
|
18
|
+
.tperson=@jobs.pop
|
19
|
+
|
20
|
+
li
|
21
|
+
' 18:20 //
|
22
|
+
span.ttitle=@talks.pop
|
23
|
+
.tperson=@jobs.pop
|
24
|
+
|
25
|
+
li 18:50 // break
|
26
|
+
.tperson=@food
|
27
|
+
|
28
|
+
li
|
29
|
+
' 19:20 //
|
30
|
+
span.ttitle=@talks.pop
|
31
|
+
.tperson=@jobs.pop
|
32
|
+
|
33
|
+
li
|
34
|
+
' 19:40 // Ignite //
|
35
|
+
span.ttitle=@talks.pop
|
36
|
+
.tperson=@jobs.pop
|
37
|
+
|
38
|
+
li
|
39
|
+
' 20:00 //
|
40
|
+
span.ttitle=@talks.pop
|
41
|
+
.tperson=@jobs.pop
|
42
|
+
|
43
|
+
li
|
44
|
+
' 20:30 // Close
|
45
|
+
.tperson Everyone is hiring, but no one's paying
|
46
|
+
|
47
|
+
#footer
|
48
|
+
a href='https://github.com/snltd/meetup-generator' The code.
|
data/website.xml
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
<?xml version='1.0'?>
|
2
|
+
|
3
|
+
<!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
|
4
|
+
|
5
|
+
<service_bundle type='manifest' name='snltd:sinatra:meetup-generator'>
|
6
|
+
|
7
|
+
<service
|
8
|
+
name='application/sinatra/meetup-generator'
|
9
|
+
type='service'
|
10
|
+
version='1'>
|
11
|
+
|
12
|
+
<single_instance />
|
13
|
+
|
14
|
+
<property_group name='startd' type='framework'>
|
15
|
+
|
16
|
+
<stability value='Unstable' />
|
17
|
+
|
18
|
+
<propval
|
19
|
+
name='duration'
|
20
|
+
type='astring'
|
21
|
+
value='transient' />
|
22
|
+
|
23
|
+
</property_group>
|
24
|
+
|
25
|
+
<property_group name='options' type='applications'>
|
26
|
+
|
27
|
+
<propval
|
28
|
+
name='rackup'
|
29
|
+
type='astring'
|
30
|
+
value='/opt/local/bin/rackup' />
|
31
|
+
|
32
|
+
<propval
|
33
|
+
name='user'
|
34
|
+
type='astring'
|
35
|
+
value='sinatra' />
|
36
|
+
|
37
|
+
<propval
|
38
|
+
name='dir'
|
39
|
+
type='astring'
|
40
|
+
value='/www/meetup-generator' />
|
41
|
+
|
42
|
+
<propval
|
43
|
+
name='port'
|
44
|
+
type='astring'
|
45
|
+
value='8002' />
|
46
|
+
|
47
|
+
</property_group>
|
48
|
+
|
49
|
+
<instance name='default' enabled='true'>
|
50
|
+
|
51
|
+
<!-- Run in multi-user mode -->
|
52
|
+
|
53
|
+
<dependency
|
54
|
+
name='multi-user'
|
55
|
+
grouping='require_all'
|
56
|
+
restart_on='none'
|
57
|
+
type='service'>
|
58
|
+
|
59
|
+
<service_fmri value='svc:/milestone/multi-user-server:default' />
|
60
|
+
|
61
|
+
</dependency>
|
62
|
+
|
63
|
+
<exec_method
|
64
|
+
name='start'
|
65
|
+
type='method'
|
66
|
+
timeout_seconds='0'
|
67
|
+
exec='%{options/rackup} -D -p %{options/port} %{options/dir}/config.ru'>
|
68
|
+
<method_context>
|
69
|
+
<method_credential
|
70
|
+
user='%{options/user}'
|
71
|
+
group='nogroup'
|
72
|
+
privileges='basic,!proc_session,!proc_info,!file_link_any'/>
|
73
|
+
</method_context>
|
74
|
+
</exec_method>
|
75
|
+
|
76
|
+
<exec_method
|
77
|
+
name='stop'
|
78
|
+
type='method'
|
79
|
+
timeout_seconds='10'
|
80
|
+
exec='pkill -2 -f "%{options/rackup}.*-p %{options/port}"' />
|
81
|
+
|
82
|
+
</instance>
|
83
|
+
|
84
|
+
<template>
|
85
|
+
|
86
|
+
<common_name>
|
87
|
+
<loctext xml:lang='C'>
|
88
|
+
meetup-generator website
|
89
|
+
</loctext>
|
90
|
+
</common_name>
|
91
|
+
|
92
|
+
<documentation>
|
93
|
+
<doc_link name='meetup.rfisher.co.uk' uri='http://meetup.rdfisher.co.uk' />
|
94
|
+
</documentation>
|
95
|
+
|
96
|
+
</template>
|
97
|
+
|
98
|
+
</service>
|
99
|
+
|
100
|
+
</service_bundle>
|