singer 0.2.0 → 0.4.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
  SHA256:
3
- metadata.gz: 52a8bd4bab0f3283223aa2db41a7bbffb58e84c8033f123892a5526942e35102
4
- data.tar.gz: 0031afac6095b8c2d40b6a699f698c5c170b2a115b4aeca0c5c29149cf22498b
3
+ metadata.gz: 23ff6172f3189c234f2b95c1aaee2a67e5b91ffd10c88375c54e37cb4417abb5
4
+ data.tar.gz: 037ff1bd0e545efd4156ad9ff84cb2934070f0e704e05156acbff1fd7d787cfb
5
5
  SHA512:
6
- metadata.gz: db604b8c31e53948e5a367b4282173ecc098b09ab1cd6c4ff28e941bfd5598818b03551354924e65671de348e09e5bb89d7695565d38340475a49051f9db0a9f
7
- data.tar.gz: 90505991f184b2925937143403c70b43287e6bce20088300fe79c603f52cde53d8b87645792b6c07564554c80b4f219689339ab09d96db78682dc4671f89f5c4
6
+ metadata.gz: 494f03f90b0584d1c0ba3ae4b72454151e8379591082e51fa61cbaae3ad65c50b91463ef52ba56208a7ef6f63d6c8b75f4c4f3c99430a9b367284e337f77cf59
7
+ data.tar.gz: 55646990590d66996705547ed95ff21b21a6a19609db53231c6f416d91fc37d84f0d1fc1c12189b932ade10a479ebef2f619cd5e1a0c8d3b07eb8a1f58f89d4a
data/exe/singer CHANGED
@@ -2,4 +2,5 @@
2
2
 
3
3
  require 'singer'
4
4
 
5
+ Singer.configure(ARGV)
5
6
  Singer.generate
@@ -0,0 +1,4 @@
1
+ module Singer
2
+ # holds settings
3
+ CONFIGURATION = Struct.new(:template_name, :camelcase_name, :snakecase_name).new
4
+ end
data/lib/singer/paths.rb CHANGED
@@ -4,5 +4,17 @@ module Singer
4
4
  def self.templates_from_gem
5
5
  @templates_from_gem ||= File.expand_path('../../templates', __dir__)
6
6
  end
7
+
8
+ def self.output_path(...)
9
+ File.join(...).gsub(/__([[:alpha:]][[:word:]]+)__/) do |match|
10
+ potential_config = $1.downcase
11
+
12
+ if CONFIGURATION.respond_to?(potential_config)
13
+ CONFIGURATION.send(potential_config)
14
+ else
15
+ match
16
+ end
17
+ end
18
+ end
7
19
  end
8
20
  end
@@ -18,18 +18,17 @@ module Singer
18
18
  def initialize(path)
19
19
  @path = path
20
20
  Dir.chdir(path) do # we could use only Dir[base: path], but all the code uses relative paths
21
- entries = Dir['**/*']
21
+ entries = Dir.glob('**/*', flags: File::FNM_DOTMATCH)
22
22
  @files = entries.select{ File.file?(_1) }
23
23
  end
24
24
  end
25
25
 
26
26
  def generate(output_path)
27
- FileUtils.mkdir_p(output_path)
28
27
  files.each do |file|
29
- input_file = File.join(path, file)
30
- output_file = File.join(output_path, file)
31
- FileUtils.mkdir_p(File.dirname(output_file))
32
- FileUtils.cp(input_file, output_file)
28
+ source_file = File.join(path, file)
29
+ target_file = Paths.output_path(output_path, file)
30
+ FileUtils.mkdir_p(File.dirname(target_file))
31
+ TemplatingMethods.send(:erb, source_file, target_file)
33
32
  end
34
33
  end
35
34
  end
@@ -0,0 +1,14 @@
1
+ require 'erb'
2
+
3
+ module Singer
4
+ # different ways to create output file from source template file
5
+ class TemplatingMethods
6
+ def self.copy(source_file, target_file)
7
+ FileUtils.cp(source_file, target_file)
8
+ end
9
+
10
+ def self.erb(source_file, target_file)
11
+ File.write(target_file, ERB.new(File.read(source_file)).result)
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module Singer
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.4.0'.freeze
3
3
  end
data/lib/singer.rb CHANGED
@@ -1,12 +1,34 @@
1
1
  require_relative 'singer/version'
2
+ require_relative 'singer/configuration'
2
3
  require_relative 'singer/paths'
3
4
  require_relative 'singer/template'
5
+ require_relative 'singer/templating_methods'
4
6
 
5
7
  # Singer, which generates Sinatra apps etc. from templates
6
8
  module Singer
7
9
  class Error < StandardError; end
10
+ class NameMissingError < StandardError; end
11
+ class TemplateUnknown < StandardError; end
12
+
13
+ DEFAULT_TEMPLATE_NAME = 'tdd'.freeze
14
+
15
+ def self.configure(argvies)
16
+ raise NameMissingError, 'Missing mandatory argument NAME' if argvies.empty?
17
+
18
+ encase_name(argvies.pop)
19
+ CONFIGURATION.template_name = argvies.shift || DEFAULT_TEMPLATE_NAME
20
+ end
21
+
22
+ def self.encase_name(name)
23
+ CONFIGURATION.snakecase_name = name.gsub(/(\w)([A-Z])/){ "#{$1}_#{$2}" }.gsub(/_+/, '_').downcase
24
+ CONFIGURATION.camelcase_name = CONFIGURATION.snakecase_name.split('_').map(&:capitalize).join
25
+ end
8
26
 
9
27
  def self.generate
10
- Template.all['tdd'].generate('.')
28
+ unless Template.all.key?(CONFIGURATION.template_name)
29
+ raise TemplateUnknown, "Template #{CONFIGURATION.template_name.inspect} not found"
30
+ end
31
+
32
+ Template.all[CONFIGURATION.template_name].generate('.')
11
33
  end
12
34
  end
@@ -0,0 +1,12 @@
1
+ # Ruby stuff
2
+ /coverage/
3
+ /doc/
4
+ /.bundle/
5
+ /vendor/
6
+
7
+ # prototyping and temporary stuff
8
+ /tmp/
9
+ /todo/
10
+
11
+ # database
12
+ /db/*.sqlite3
@@ -0,0 +1,28 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'colorize'
4
+ gem 'haml'
5
+ gem 'puma'
6
+ gem 'rackup'
7
+ gem 'sassc'
8
+ gem 'sinatra'
9
+ gem 'zeitwerk'
10
+
11
+ group :test do # for humans
12
+ gem 'fivemat'
13
+ gem 'minitest-rg'
14
+ end
15
+
16
+ group :test, :ci do
17
+ gem 'minitest', '~> 5.23.0' # fivemat double-outputs with newer than this
18
+ gem 'mocha'
19
+ gem 'rack-test'
20
+ gem 'rake'
21
+ gem 'rubocop'
22
+ gem 'simplecov'
23
+ end
24
+
25
+ group :ci do # for CI automation
26
+ gem 'minitest-junit'
27
+ gem 'simplecov-cobertura'
28
+ end
@@ -0,0 +1,3 @@
1
+ require_relative '../lib/zeitwerk_setup'
2
+
3
+ run <%= Singer::CONFIGURATION.camelcase_name %>::Server
@@ -0,0 +1,6 @@
1
+ port 80, '0.0.0.0'
2
+
3
+ app_domain = ENV.fetch('APP_DOMAIN', nil)
4
+ cert = "/etc/letsencrypt/live/#{app_domain}/fullchain.pem"
5
+ key = "/etc/letsencrypt/live/#{app_domain}/privkey.pem"
6
+ ssl_bind '0.0.0.0', 443, {cert:, key:} if File.exist?(cert) && File.exist?(key)
@@ -0,0 +1,3 @@
1
+ require 'zeitwerk_setup'
2
+
3
+ <%= Singer::CONFIGURATION.camelcase_name %>::Server.run!
@@ -0,0 +1,8 @@
1
+ module <%= Singer::CONFIGURATION.camelcase_name %>
2
+ # demo class - TODO: remove
3
+ class Hello
4
+ def self.greet(target)
5
+ "Hello, #{target}."
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,38 @@
1
+ require 'sinatra/base'
2
+ require 'haml'
3
+ require 'sassc'
4
+ require 'colorize'
5
+
6
+ module <%= Singer::CONFIGURATION.camelcase_name %>
7
+ # Sinatra app with routes
8
+ class Server < Sinatra::Base
9
+ set :root, '.' # to make directory structure with views/ work
10
+ enable :logging
11
+ configure :development do
12
+ set :bind, '0.0.0.0'
13
+ set :port, 80
14
+ end
15
+
16
+ def dump_errors!(boom, out = @env['rack.errors'])
17
+ filtered_backtrace = boom.backtrace.reject{ _1.include?('/vendor/') }
18
+ header = "#{Time.now.strftime('%F %T')} - #{boom.class} - #{boom.message.white.on_red}:"
19
+ error_message = [header, *filtered_backtrace].join("\n\t")
20
+ out.puts(error_message)
21
+ end
22
+
23
+ def self.compiled_style
24
+ SassC::Engine.new(File.read('views/style.sass'), syntax: :sass).render
25
+ end
26
+ COMPILED_STYLE = compiled_style
27
+
28
+ get '/' do
29
+ redirect '/hello/world'
30
+ end
31
+
32
+ get '/hello/:target' do # TODO: remove
33
+ @salutation = Hello.greet params[:target]
34
+
35
+ haml :index
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,6 @@
1
+ require 'zeitwerk'
2
+
3
+ loader = Zeitwerk::Loader.new
4
+ loader.push_dir 'lib'
5
+ loader.ignore('lib/zeitwerk_setup.rb')
6
+ loader.setup
@@ -0,0 +1,18 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/rg'
3
+
4
+ require 'mocha/minitest'
5
+ Mocha.configure do |c|
6
+ c.display_matching_invocations_on_failure = true
7
+ c.strict_keyword_argument_matching = true
8
+ c.stubbing_method_on_non_mock_object = :allow
9
+ c.stubbing_method_unnecessarily = :prevent
10
+ c.stubbing_non_existent_method = :prevent
11
+ c.stubbing_non_public_method = :allow
12
+ end
13
+
14
+ require 'rack/test'
15
+
16
+ require 'zeitwerk_setup'
17
+
18
+ ENV['APP_ENV'] = 'test' # so that Sinatra and Sequel know we're in test mode
@@ -0,0 +1,32 @@
1
+ require 'minitest_helper'
2
+
3
+ class TestServer < Minitest::Test
4
+ include Rack::Test::Methods
5
+
6
+ def app
7
+ <%= Singer::CONFIGURATION.camelcase_name %>::Server
8
+ end
9
+
10
+ def test_top_level_redirects
11
+ get '/'
12
+
13
+ assert last_response.redirect?
14
+ assert_match %r{/hello/world$}, last_response.headers['location']
15
+ end
16
+
17
+ # TODO: remove
18
+ def test_hello_with_param_slug_includes_a_greeting
19
+ get '/hello/everyone'
20
+
21
+ refute_predicate last_response, :redirect?
22
+ assert_predicate last_response, :ok?
23
+ assert_includes last_response.body, 'Hello, everyone.'
24
+ end
25
+
26
+ def test_hello_without_param_is_a_404_error
27
+ get '/hello'
28
+
29
+ refute_predicate last_response, :ok?
30
+ assert_equal 404, last_response.status
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ require 'minitest_helper'
2
+
3
+ class TestServerErrorDumping < Minitest::Test
4
+ def test_dump_errors_backtrace_is_filtered_against_vendor
5
+ exc = generate_exception_with_realistic_backtrace
6
+ assert exc.backtrace.any?{ _1.include?('/vendor/') } # now you see me
7
+
8
+ log = mock()
9
+ log.expects(:puts).with do |message|
10
+ refute message.lines.any?{ _1.include?('/vendor/') } # now you don't
11
+ end
12
+
13
+ app = <%= Singer::CONFIGURATION.camelcase_name %>::Server.new! # .new! returns real instance without Sinatra::Wrapper
14
+ app.dump_errors!(exc, log)
15
+ end
16
+
17
+ def generate_exception_with_realistic_backtrace
18
+ raise 'ciastka'
19
+ rescue RuntimeError => exc
20
+ exc
21
+ end
22
+
23
+ def test_generate_exception_with_realistic_backtrace
24
+ assert_kind_of RuntimeError, generate_exception_with_realistic_backtrace
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ require 'minitest_helper'
2
+
3
+ class TestAutoloading < Minitest::Test
4
+ def test_zeitwerk_has_one_loader
5
+ refute_empty Zeitwerk::Registry.loaders
6
+ assert_equal 1, Zeitwerk::Registry.loaders.size
7
+ end
8
+
9
+ # this test is kinda slow, but not as slow as Rack::Test
10
+ def test_zeitwerk_eager_autoloading_works
11
+ loader = Zeitwerk::Registry.loaders.first
12
+ # loader.log!
13
+ loader.eager_load(force: true)
14
+ rescue Zeitwerk::NameError => exc
15
+ flunk exc.message
16
+ else
17
+ pass
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ require 'minitest_helper'
2
+
3
+ # TODO: remove
4
+ class TestHello < Minitest::Test
5
+ def test_greet_with_a_param_generates_a_greeting_string
6
+ assert_equal 'Hello, stranger.', <%= Singer::CONFIGURATION.camelcase_name %>::Hello.greet(:stranger)
7
+ end
8
+ end
@@ -0,0 +1 @@
1
+ #salutation= @salutation
@@ -0,0 +1,7 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %title <%= Singer::CONFIGURATION.camelcase_name %>
5
+ %meta{charset: 'UTF-8'}
6
+ %style!= COMPILED_STYLE
7
+ %body!= yield
@@ -0,0 +1,17 @@
1
+ html
2
+ --primary-font: Departure Mono, Joystix Monospace, ui-monospace, monospace, sans-serif
3
+ font-family: var(--primary-font)
4
+ font-size: 64px
5
+ body
6
+ --color-text: Black
7
+ --color-background: White
8
+ color: var(--color-text)
9
+ background-color: var(--color-background)
10
+ margin: 0px
11
+ body > *
12
+ padding: 1em
13
+
14
+ @media (prefers-color-scheme: dark)
15
+ body
16
+ --color-text: White
17
+ --color-background: #222
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # gem 'awesome_print'
4
+ gem 'colorize'
5
+ gem 'minitest'
6
+ gem 'minitest-rg'
7
+ # gem 'rake'
8
+ # gem 'rubocop'
@@ -1,5 +1,5 @@
1
1
  require 'colorize'
2
- require 'awesome_print'
2
+ # require 'awesome_print'
3
3
 
4
4
  class <%= Singer::CONFIGURATION.camelcase_name %>
5
5
  def hello(who:)
@@ -1,7 +1,7 @@
1
1
  require 'minitest/rg'
2
2
  require 'minitest/autorun' if $PROGRAM_NAME == __FILE__
3
3
 
4
- require_relative '<%= Singer::CONFIGURATION.snakecase_name %>'
4
+ require_relative '../lib/<%= Singer::CONFIGURATION.snakecase_name %>'
5
5
 
6
6
  class Test<%= Singer::CONFIGURATION.camelcase_name %> < Minitest::Test
7
7
  # TEST_INPUT = File.read('input_mini.txt').lines(chomp: true)
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: singer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emil Chludziński
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-01-19 00:00:00.000000000 Z
11
- dependencies: []
10
+ date: 2025-01-26 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: erb
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  description: |
13
27
  Singer is a highly configurable templating system intended for generating Ruby boilerplate.
14
28
  It aims to make creating and extending Sinatra apps easier.
@@ -23,11 +37,30 @@ files:
23
37
  - README.md
24
38
  - exe/singer
25
39
  - lib/singer.rb
40
+ - lib/singer/configuration.rb
26
41
  - lib/singer/paths.rb
27
42
  - lib/singer/template.rb
43
+ - lib/singer/templating_methods.rb
28
44
  - lib/singer/version.rb
29
- - templates/tdd/lib/ruby.rb
30
- - templates/tdd/test/test_ruby.rb
45
+ - templates/sinatra_app/.gitignore
46
+ - templates/sinatra_app/Gemfile
47
+ - templates/sinatra_app/config/config.ru
48
+ - templates/sinatra_app/config/puma.rb
49
+ - templates/sinatra_app/exe/server.rb
50
+ - templates/sinatra_app/lib/__SNAKECASE_NAME__/hello.rb
51
+ - templates/sinatra_app/lib/__SNAKECASE_NAME__/server.rb
52
+ - templates/sinatra_app/lib/zeitwerk_setup.rb
53
+ - templates/sinatra_app/test/minitest_helper.rb
54
+ - templates/sinatra_app/test/server/test_server.rb
55
+ - templates/sinatra_app/test/server/test_server_error_dumping.rb
56
+ - templates/sinatra_app/test/test_autoloading.rb
57
+ - templates/sinatra_app/test/test_hello.rb
58
+ - templates/sinatra_app/views/index.haml
59
+ - templates/sinatra_app/views/layout.haml
60
+ - templates/sinatra_app/views/style.sass
61
+ - templates/tdd/Gemfile
62
+ - templates/tdd/lib/__SNAKECASE_NAME__.rb
63
+ - templates/tdd/test/test___SNAKECASE_NAME__.rb
31
64
  homepage: https://gitlab.com/tanstaafl/singer/
32
65
  licenses:
33
66
  - MIT