meetup-generator 0.0.20210128

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ def locate_config_ru
6
+ Pathname.new(__FILE__).dirname.parent + 'config.ru'
7
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'date'
5
+ require 'zlib'
6
+
7
+ LIB = Pathname.new(__dir__)
8
+
9
+ # Everything needed for a meetup generator
10
+ #
11
+ class MeetupGenerator
12
+ attr_reader :words, :lib
13
+
14
+ def initialize
15
+ @words = Zlib::GzipReader.open(LIB + 'words.gz').readlines.map(&:strip)
16
+ @lib = YAML.safe_load(IO.read(LIB + 'all_the_things.yaml'),
17
+ symbolize_names: true)
18
+ end
19
+
20
+ # @param num [Integer] how many talks you want
21
+ # @return [Hash] full meetup agenda
22
+ #
23
+ def agenda(num = 5)
24
+ { talks: lib[:template].sample(num).map { |t| talk(t) },
25
+ refreshment: refreshment,
26
+ location: location,
27
+ date: date }
28
+ end
29
+
30
+ # @param templates [Array[String]] array of templates
31
+ #
32
+ def talk(template = random_template)
33
+ { title: title(template),
34
+ talker: talker,
35
+ role: role,
36
+ company: company }
37
+ end
38
+
39
+ def location
40
+ 'Shoreditch, probably'
41
+ end
42
+
43
+ def date
44
+ (Date.today + 1).strftime('%d/%m/%Y')
45
+ end
46
+
47
+ def title(template = random_template)
48
+ replace_word(replace_number(replace_ops(replace_things(template))))
49
+ end
50
+
51
+ def random_template(number = 1)
52
+ lib[:template].sample(number).first
53
+ end
54
+
55
+ def talker
56
+ pair(:first_name, :last_name)
57
+ end
58
+
59
+ def role
60
+ pair(:job_role, :job_title)
61
+ end
62
+
63
+ def refreshment
64
+ pair(:food_style, :food)
65
+ end
66
+
67
+ def company
68
+ format('%<word>s.io', word: words.sample.sub(/([^aeiou])er$/, '\\1r'))
69
+ end
70
+
71
+ def something_ops
72
+ (lib[:something_ops] * 4).sample(rand(2..4)).join + 'Ops'
73
+ end
74
+
75
+ def pair(list1, list2)
76
+ [lib[list1].sample, lib[list2].sample].join(' ')
77
+ end
78
+
79
+ def replace_things(template)
80
+ return template unless template =~ /%[a-z_]+%/
81
+
82
+ replace_things(template.sub(/%([a-z_]+)%/) do
83
+ lib[Regexp.last_match(1).to_sym].sample
84
+ end)
85
+ end
86
+
87
+ def replace_word(template)
88
+ return template unless template.include?('%WORD%')
89
+
90
+ replace_word(template.sub('%WORD%', words.sample.capitalize))
91
+ end
92
+
93
+ def replace_ops(template)
94
+ return template unless template.include?('%FNOPS%')
95
+
96
+ replace_ops(template.sub('%FNOPS%', something_ops))
97
+ end
98
+
99
+ def replace_number(template)
100
+ return template unless template =~ /%RAND\d+%/
101
+
102
+ replace_number(template.sub(/%RAND(\d+)%/) do
103
+ rand(2..Regexp.last_match(1).to_i).to_s
104
+ end)
105
+ end
106
+ end
Binary file
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ # Github actions sets the RELEASE_VERSION environment variable
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = 'meetup-generator'
9
+ gem.version = ENV['RELEASE_VERSION'] ||
10
+ "0.0.#{Time.now.strftime('%Y%m%d')}"
11
+ gem.date = Date.today.to_s
12
+
13
+ gem.summary = 'Stupid fatuous random string generatpr'
14
+ gem.description = 'Generates a website advertising a fictional ' \
15
+ 'DevOps meetup, using all the latest buzzwords, ' \
16
+ 'new-shiny, and clichés'
17
+ gem.authors = ['Robert Fisher']
18
+ gem.email = 'rob@sysdef.xyz'
19
+ gem.homepage = 'https://github.com/snltd/meetup-generator'
20
+ gem.license = 'BSD-2-Clause'
21
+
22
+ gem.bindir = 'bin'
23
+ gem.executables = ['meetup-generator.rb', 'locate_meetup-generator']
24
+ gem.files = IO.readlines('package/runtime_files', chomp: true)
25
+ gem.test_files = gem.files.grep(%r{^spec/})
26
+ gem.require_paths = %w[lib]
27
+
28
+ gem.add_runtime_dependency 'puma', '~> 5.1'
29
+ gem.add_runtime_dependency 'sinatra', '~>2.1'
30
+ gem.add_runtime_dependency 'slim', '~> 4.1'
31
+
32
+ gem.add_development_dependency 'bundler', '~> 2.0'
33
+ gem.add_development_dependency 'minitest', '~> 5.14'
34
+ gem.add_development_dependency 'nokogiri', '~> 1.10'
35
+ gem.add_development_dependency 'rack-test', '~> 1.1'
36
+ gem.add_development_dependency 'rake', '~> 13.0'
37
+ gem.add_development_dependency 'rubocop', '~> 1.9'
38
+ gem.add_development_dependency 'rubocop-minitest', '~> 0.10'
39
+ gem.add_development_dependency 'rubocop-performance', '~> 1.3'
40
+ gem.add_development_dependency 'rubocop-rake', '~> 0.5'
41
+
42
+ gem.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
43
+ end
@@ -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='sysdef/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.sysdef.xyz' uri='http://meetup.sysdef.xyz' />
94
+ </documentation>
95
+
96
+ </template>
97
+
98
+ </service>
99
+
100
+ </service_bundle>
@@ -0,0 +1,64 @@
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
+ .indent {
15
+ font-size: small;
16
+ margin-left: 12em;
17
+ }
18
+
19
+ .indent a {
20
+ color: #0f0;
21
+ }
22
+
23
+ ul {
24
+ list-style: none;
25
+ }
26
+
27
+ li {
28
+ padding-top: 1em;
29
+ text-indent: -9em;
30
+ margin-left: 9em;
31
+ }
32
+
33
+ .ttitle {
34
+ font-weight: bold;
35
+ background-color: #0ff;
36
+ color: #000;
37
+ }
38
+
39
+ h1 {
40
+ text-align: center;
41
+ padding-bottom: 0.5em;
42
+ margin-bottom: 1em;
43
+ color: #ff0;
44
+ line-height:100%;
45
+ border-bottom: 4px dotted #f0f;
46
+ }
47
+
48
+ #container {
49
+ padding-top: 3em;
50
+ width: 900px;
51
+ margin-left: auto;
52
+ margin-right: auto;
53
+ }
54
+
55
+ #footer {
56
+ margin-top: 20em;
57
+ font-size: x-small;
58
+ text-align: right;
59
+ }
60
+
61
+ a {
62
+ text-decoration: none;
63
+ color: #f0f;
64
+ }
Binary file
Binary file
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ %w[minitest minitest/unit minitest/autorun rack/test pathname json
5
+ yaml cgi nokogiri].each { |f| require f }
6
+
7
+ MGROOT = Pathname.new(__FILE__).realpath.dirname.parent
8
+
9
+ OUTER_APP = Rack::Builder.parse_file((MGROOT + 'config.ru').to_s).first
10
+
11
+ # Acceptance tests for the meetup generator. Yeah, I know.
12
+ #
13
+ class TestApp < MiniTest::Test
14
+ attr_reader :things
15
+
16
+ include Rack::Test::Methods
17
+
18
+ def initialize(args)
19
+ super(args)
20
+ @things = YAML.safe_load(IO.read(MGROOT + 'lib' + 'all_the_things.yaml'),
21
+ symbolize_names: true)
22
+ end
23
+
24
+ def app
25
+ OUTER_APP
26
+ end
27
+
28
+ # Load the page a thousand times and make sure no talk titles
29
+ # occur twice
30
+ #
31
+ def test_no_repeats
32
+ 1000.times do
33
+ html = Nokogiri::HTML(get('/').body)
34
+ titles = html.css('span[class="ttitle"]').map(&:text)
35
+ assert_equal(titles, titles.uniq)
36
+ end
37
+ end
38
+
39
+ # Get all of the templates used to generate talk titles, and
40
+ # re-run the test until we've seen them all. Then we know no
41
+ # template causes a crash. That's good enough.
42
+ #
43
+ def test_default
44
+ templates = things[:template].map do |t|
45
+ escaped = Regexp.escape(CGI.escapeHTML(t))
46
+ matcher = escaped.gsub(/%\w+%/, '[\w\-]+').gsub(/RAND\d+/, '\d+')
47
+ .gsub(/FNOPS/, '\w+')
48
+ Regexp.new('^.*ttitle">' + matcher + '</span>.*$')
49
+ end
50
+
51
+ until templates.empty?
52
+ get '/'
53
+ assert_equal('text/html;charset=utf-8',
54
+ last_response.header['Content-Type'])
55
+ last_response.ok?
56
+ resp = last_response.body
57
+ assert_match(/The code./, resp)
58
+ assert_match(/DevOps Meetup/, resp)
59
+ templates.each { |t| templates.delete(t) if resp.match?(t) }
60
+ end
61
+ end
62
+
63
+ def test_api_talker
64
+ get format('/api/talker')
65
+ assert last_response.ok?
66
+ assert_instance_of(String, last_response.body)
67
+ assert_match(/^"\w+ \w+"$/, last_response.body)
68
+ assert last_response.header['Content-Type'] == 'application/json'
69
+ end
70
+
71
+ def test_api_company
72
+ get format('/api/company')
73
+ assert last_response.ok?
74
+ assert_instance_of(String, last_response.body)
75
+ assert_match(/^"\w+.io"$/, last_response.body)
76
+ assert last_response.header['Content-Type'] == 'application/json'
77
+ end
78
+
79
+ def test_api_misc
80
+ %w[title role refreshment location date].each do |word|
81
+ get format('/api/%<word>s', word: word)
82
+ assert last_response.ok?
83
+ assert_instance_of(String, last_response.body)
84
+ assert last_response.header['Content-Type'] == 'application/json'
85
+ end
86
+ end
87
+
88
+ def test_api_talk
89
+ get format('/api/talk')
90
+ assert last_response.ok?
91
+ assert_instance_of(String, last_response.body)
92
+ as_obj = JSON.parse(last_response.body, symbolize_names: true)
93
+ assert_equal(%i[title talker role company], as_obj.keys)
94
+ end
95
+
96
+ def test_api_agenda
97
+ get format('/api/agenda')
98
+ assert last_response.ok?
99
+ assert_instance_of(String, last_response.body)
100
+ agenda = JSON.parse(last_response.body, symbolize_names: true)
101
+ assert_equal(%i[date location refreshment talks], agenda.keys.sort)
102
+
103
+ agenda[:talks].each do |t|
104
+ assert_equal(%i[title talker role company], t.keys)
105
+ end
106
+
107
+ assert_instance_of(String, agenda[:refreshment])
108
+ assert_instance_of(String, agenda[:date])
109
+ assert_instance_of(String, agenda[:location])
110
+ end
111
+
112
+ def test_api_not_found
113
+ get '/api/no_such_thing'
114
+ assert_equal(last_response.status, 404)
115
+ end
116
+ end