meetup-generator 0.0.20210128

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.
@@ -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