benry-config 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/benry/config.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  ###
4
- ### $Release: 0.1.0 $
5
- ### $Copyright: copyright(c) 2016 kuwata-lab.com all rights reserved $
4
+ ### $Release: 0.2.0 $
5
+ ### $Copyright: copyright(c) 2016 kwatch@gmail.com $
6
6
  ### $License: MIT License $
7
7
  ###
8
8
 
@@ -14,69 +14,62 @@ module Benry
14
14
  end
15
15
 
16
16
 
17
- ##
18
- ## Configuration class.
19
- ##
20
- ## ex:
21
- ##
22
- ## #----- config/common.rb -----
23
- ## require 'benry/config'
24
- ## class CommonConfig < Benry::BaseConfig
25
- ## add :db_user , "user1"
26
- ## add :db_pass , ABSTRACT
27
- ## add :session_cooie , "SESS"
28
- ## add :session_secret , SECRET
29
- ## end
30
- ##
31
- ## #----- config/development.rb -----
32
- ## require 'config/common'
33
- ## class Config < TestCommonConfig
34
- ## set :db_pass , "pass1"
35
- ## end
36
- ##
37
- ## #----- config/development.private -----
38
- ## class Config
39
- ## set :session_secret , "abc123"
40
- ## end
41
- ##
42
- ## #----- main.rb -----
43
- ## rack_env = ENV['RACK_ENV'] or raise "$RACK_ENV required."
44
- ## require "./config/#{rack_env}.rb"
45
- ## load "./config/#{rack_env}.private"
46
- ## #
47
- ## $config = Config.new.freeze
48
- ## p $config.db_user #=> "user1"
49
- ## p $config.db_pass #=> "pass1"
50
- ## p $config.session_cookie #=> "SESS"
51
- ## p $config.session_secret #=> "abc123"
52
- ## #
53
- ## p $config.get_all(:db_) #=> {:user=>"user1", :pass=>"pass1"}
54
- ## p $config.get_all(:session_) #=> {:cookie=>"SESS", :secret=>"abc123"}
55
- ##
56
- class BaseConfig
17
+ class Config
57
18
 
58
19
  class AbstractValue
20
+
21
+ def initialize(envvar=nil)
22
+ #; [!6hcf9] accepts environment variable name.
23
+ @envvar = envvar
24
+ end
25
+
26
+ attr_reader :envvar
27
+
28
+ def [](envvar)
29
+ #; [!p0acp] returns new object with environment variable name.
30
+ return self.class.new(envvar)
31
+ end
32
+
33
+ end
34
+
35
+ class SecretValue < AbstractValue
59
36
  end
60
37
 
61
38
  ABSTRACT = AbstractValue.new # represents 'should be set in subclass'
62
- SECRET = AbstractValue.new # represents 'should be set in private config file'
39
+ SECRET = SecretValue.new # represents 'should be set in private config file'
63
40
 
64
41
  def initialize
65
42
  #; [!7rdq4] traverses parent class and gathers config values.
66
- pr = proc {|cls|
67
- pr.call(cls.superclass) if cls.superclass
68
- d = cls.instance_variable_get('@__dict')
69
- d.each {|k, v| instance_variable_set("@#{k}", v) } if d
70
- }
71
- pr.call(self.class)
72
- #; [!z9mno] raises ConfigError when ABSTRACT or SECRET is not overriden.
43
+ _traverse(self.class) {|k, v| instance_variable_set("@#{k}", v) }
73
44
  instance_variables().each do |ivar|
74
45
  val = instance_variable_get(ivar)
75
- ! val.is_a?(AbstractValue) or
46
+ next unless val.is_a?(AbstractValue)
47
+ #; [!v9f3k] when envvar name not specified...
48
+ if val.envvar == nil
49
+ #; [!z9mno] raises ConfigError if ABSTRACT or SECRET is not overriden.
76
50
  raise ConfigError.new("config ':#{ivar.to_s[1..-1]}' should be set, but not.")
51
+ #; [!ida3r] when envvar name specified...
52
+ else
53
+ #; [!txl88] raises ConfigError when envvar not set.
54
+ envvar = val.envvar
55
+ begin
56
+ val = ENV.fetch(envvar.to_s)
57
+ rescue KeyError
58
+ raise ConfigError.new("environment variable '$#{envvar}' should be set for config item ':#{ivar.to_s[1..-1]}'.")
59
+ end
60
+ #; [!y47ul] sets envvar value as config value if envvar provided.
61
+ instance_variable_set(ivar, val)
62
+ end
77
63
  end
78
64
  end
79
65
 
66
+ def _traverse(cls, &b)
67
+ _traverse(cls.superclass, &b) if cls.superclass && cls.superclass < BaseConfig
68
+ dict = cls.instance_variable_get(:@__dict)
69
+ dict.each(&b) if dict
70
+ end
71
+ private :_traverse
72
+
80
73
  ## Add new config. Raises ConfigError when already defined.
81
74
  def self.add(key, value, desc=nil)
82
75
  #; [!m7w96] raises ConfigError when already added.
@@ -134,7 +127,56 @@ module Benry
134
127
  return d
135
128
  end
136
129
 
130
+ def defined?(key)
131
+ #; [!y1fsh] returns true if config key defined.
132
+ #; [!k1b5q] returns false if config key not defined.
133
+ return instance_variable_defined?("@#{key}")
134
+ end
135
+
136
+ def each(sort=false, &b)
137
+ #; [!f4ljv] returns Enumerator object if block not given.
138
+ return to_enum(:each, sort) unless block_given?()
139
+ #; [!4wqpu] yields each key and val with hiding secret values.
140
+ #; [!a9glw] sorts keys if 'true' specified as the first argument.
141
+ _each(sort, true, &b)
142
+ #; [!wggik] returns self if block given.
143
+ return self
144
+ end
145
+
146
+ def each!(sort=false, &b)
147
+ #; [!zd9lk] returns Enumerator object if block not given.
148
+ return to_enum(:each!, sort) unless block_given?()
149
+ #; [!7i5p2] yields each key and val without hiding secret values.
150
+ #; [!aib7c] sorts keys if 'true' specified as the first argument.
151
+ _each(sort, false, &b)
152
+ #; [!2abgb] returns self if block given.
153
+ return self
154
+ end
155
+
156
+ private
157
+
158
+ def _each(sort, hide_secret, &b)
159
+ keys_d = {}
160
+ secrets_d = {}
161
+ _traverse(self.class) do |key, val|
162
+ keys_d[key] = true
163
+ secrets_d[key] = val if val.is_a?(SecretValue)
164
+ end
165
+ #; [!6yvgd] sorts keys if 'sort' is true.
166
+ keys = keys_d.keys()
167
+ keys.sort!() if sort
168
+ keys.each do |key|
169
+ #; [!5ledb] hides value if 'hide_secret' is true and value is Secretvalue object.
170
+ hide_p = hide_secret && secrets_d.key?(key)
171
+ val = hide_p ? "(secret)" : instance_variable_get("@#{key}".intern)
172
+ yield key, val
173
+ end
174
+ end
175
+
137
176
  end
138
177
 
139
178
 
179
+ BaseConfig = Config # for backward compatibility
180
+
181
+
140
182
  end
@@ -0,0 +1,144 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+
4
+ defined? PROJECT or abort "PROJECT required."
5
+ defined? RELEASE or abort "RELEASE required."
6
+ defined? COPYRIGHT or abort "COPYRIGHT required."
7
+ defined? LICENSE or abort "LICENSE required."
8
+
9
+ RELEASE =~ /\A\d+\.\d+\.\d+/ or abort "RELEASE=#{RELEASE}: invalid release number."
10
+
11
+
12
+ require 'rake/clean'
13
+ CLEAN << "build"
14
+ CLEAN.concat Dir.glob("#{PROJECT}-*.gem").collect {|x| x.sub(/\.gem$/, '') }
15
+ CLOBBER.concat Dir.glob("#{PROJECT}-*.gem")
16
+
17
+
18
+ task :default do
19
+ sh "rake -T", verbose: false
20
+ end unless Rake::Task.task_defined?(:default)
21
+
22
+
23
+ desc "show release guide"
24
+ task :guide do
25
+ do_guide()
26
+ end
27
+
28
+ def do_guide()
29
+ RELEASE != '0.0.0' or abort "** ERROR: 'RELEASE=X.X.X' required."
30
+ puts guide_message(PROJECT, RELEASE)
31
+ end
32
+
33
+ def guide_message(project, release)
34
+ target = "#{project}-#{release}"
35
+ tag = "#{project}-#{release}"
36
+ puts <<END
37
+ How to release:
38
+
39
+ $ git diff .
40
+ $ git status .
41
+ $ which ruby
42
+ $ rake test
43
+ $ rake test:all
44
+ $ rake doc
45
+ $ rake doc:export RELEASE=#{release}
46
+ $ rake readme:execute # optional
47
+ $ rake readme:toc # optional
48
+ $ rake package RELEASE=#{release}
49
+ $ rake package:extract # confirm files in gem file
50
+ $ (cd #{target}/data; find . -type f)
51
+ $ gem install #{target}.gem # confirm gem package
52
+ $ gem uninstall #{project}
53
+ $ gem push #{target}.gem # publish gem to rubygems.org
54
+ $ git tag #{tag} # or: git tag ruby-#{tag}
55
+ $ git push --tags
56
+ $ rake clean
57
+ $ mv #{target}.gem archive/
58
+ $ cd ../docs/
59
+ $ git add #{target}.html
60
+ $ git commit -m "[main] docs: update '#{target}.html'"
61
+ $ git push
62
+ END
63
+ end
64
+
65
+
66
+ desc "create 'README.md' and 'doc/*.html'"
67
+ task :doc do
68
+ x = PROJECT
69
+ cd "doc" do
70
+ sh "../../docs/md2 --md #{x}.mdx > ../README.md"
71
+ sh "../../docs/md2 #{x}.mdx > #{x}.html"
72
+ end
73
+ end
74
+
75
+ desc "copy 'doc/*.html' to '../docs/'"
76
+ task 'doc:export' do
77
+ RELEASE != '0.0.0' or abort "** ERROR: 'RELEASE=X.X.X' required."
78
+ x = PROJECT
79
+ cp "doc/#{x}.html", "../docs/"
80
+ edit_file!("../docs/#{x}.html")
81
+ end
82
+
83
+
84
+ desc "edit metadata in files"
85
+ task :edit do
86
+ do_edit()
87
+ end
88
+
89
+ def do_edit()
90
+ target_files().each do |fname|
91
+ edit_file!(fname)
92
+ end
93
+ end
94
+
95
+ def target_files()
96
+ $_target_files ||= begin
97
+ spec_src = File.read("#{PROJECT}.gemspec", encoding: 'utf-8')
98
+ spec = eval spec_src
99
+ spec.name == PROJECT or
100
+ abort "** ERROR: '#{PROJECT}' != '#{spec.name}' (project name in gemspec file)"
101
+ spec.files + Dir.glob("doc/*.mdx")
102
+ end
103
+ return $_target_files
104
+ end
105
+
106
+ def edit_file!(filename, verbose: true)
107
+ changed = edit_file(filename) do |s|
108
+ s = s.gsub(/\$Release[:].*?\$/, "$"+"Release: #{RELEASE} $")
109
+ s = s.gsub(/\$Copyright[:].*?\$/, "$"+"Copyright: #{COPYRIGHT} $")
110
+ s = s.gsub(/\$License[:].*?\$/, "$"+"License: #{LICENSE} $")
111
+ s
112
+ end
113
+ if verbose
114
+ puts "[C] #{filename}" if changed
115
+ puts "[U] #{filename}" unless changed
116
+ end
117
+ return changed
118
+ end
119
+
120
+ def edit_file(filename)
121
+ File.open(filename, 'rb+') do |f|
122
+ s1 = f.read()
123
+ s2 = yield s1
124
+ if s1 != s2
125
+ f.rewind()
126
+ f.truncate(0)
127
+ f.write(s2)
128
+ true
129
+ else
130
+ false
131
+ end
132
+ end
133
+ end
134
+
135
+
136
+ desc nil
137
+ task :'relink' do
138
+ Dir.glob("task/*.rb").each do |x|
139
+ src = "../" + x
140
+ next if File.identical?(src, x)
141
+ rm x
142
+ ln src, x
143
+ end
144
+ end
@@ -0,0 +1,72 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+
4
+ desc "create package (*.gem)"
5
+ task :package do
6
+ do_package()
7
+ end
8
+
9
+ def do_package()
10
+ RELEASE != '0.0.0' or abort "** ERROR: 'RELEASE=X.X.X' required."
11
+ ## copy
12
+ dir = "build"
13
+ rm_rf dir if File.exist?(dir)
14
+ mkdir dir
15
+ target_files().each do |file|
16
+ dest = File.join(dir, File.dirname(file))
17
+ mkdir_p dest, :verbose=>false unless File.exist?(dest)
18
+ cp file, "#{dir}/#{file}"
19
+ end
20
+ ## edit
21
+ Dir.glob("#{dir}/**/*").each do |file|
22
+ next unless File.file?(file)
23
+ edit_file!(file, verbose: false)
24
+ end
25
+ ## build
26
+ chdir dir do
27
+ sh "gem build #{PROJECT}.gemspec"
28
+ end
29
+ mv "#{dir}/#{PROJECT}-#{RELEASE}.gem", "."
30
+ rm_rf dir
31
+ end
32
+
33
+
34
+ desc "extract latest gem file"
35
+ task :'package:extract' do
36
+ do_package_extract()
37
+ end
38
+
39
+ def do_package_extract()
40
+ gemfile = Dir.glob("#{PROJECT}-*.gem").sort_by {|x| File.mtime(x) }.last
41
+ dir = gemfile.sub(/\.gem$/, '')
42
+ rm_rf dir if File.exist?(dir)
43
+ mkdir dir
44
+ mkdir "#{dir}/data"
45
+ cd dir do
46
+ sh "tar xvf ../#{gemfile}"
47
+ sh "gunzip *.gz"
48
+ cd "data" do
49
+ sh "tar xvf ../data.tar"
50
+ end
51
+ end
52
+ end
53
+
54
+
55
+ desc "upload gem file to rubygems.org"
56
+ task :publish do
57
+ do_publish()
58
+ end
59
+
60
+ def do_publish()
61
+ RELEASE != '0.0.0' or abort "** ERROR: 'RELEASE=X.X.X' required."
62
+ gemfile = "#{PROJECT}-#{RELEASE}.gem"
63
+ print "** Are you sure to publish #{gemfile}? [y/N]: "
64
+ answer = $stdin.gets().strip()
65
+ if answer.downcase == "y"
66
+ sh "gem push #{gemfile}"
67
+ #sh "git tag ruby-#{PROJECT}-#{RELEASE}"
68
+ sh "git tag #{PROJECT}-#{RELEASE}"
69
+ sh "#git push"
70
+ sh "#git push --tags"
71
+ end
72
+ end
@@ -0,0 +1,125 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ README_FILE = "README.md" unless defined? README_FILE
4
+ README_EXTRACT = /^[Ff]ile: +(\S+)/ unless defined? README_EXTRACT
5
+ README_CODESTART = /^```\w+$/ unless defined? README_CODESTART
6
+ README_CODEEND = /^```$/ unless defined? README_CODEEND
7
+ README_DESTDIR = "tmp/readme" unless defined? README_DESTDIR
8
+
9
+ require 'rake/clean'
10
+ CLEAN << "README.html"
11
+
12
+
13
+ def readme_extract_callback(filename, str)
14
+ return str
15
+ end
16
+
17
+
18
+ namespace :readme do
19
+
20
+
21
+ desc "retrieve scripts from #{README_FILE}"
22
+ task :retrieve do
23
+ do_readme_retrieve()
24
+ end
25
+
26
+ def do_readme_retrieve()
27
+ dir = README_DESTDIR
28
+ rm_rf dir if File.exist?(dir)
29
+ mkdir_p dir
30
+ s = File.read(README_FILE, encoding: 'utf-8')
31
+ filename = nil
32
+ buf = nil
33
+ s.each_line do |line|
34
+ case line
35
+ when README_EXTRACT
36
+ filename = $1
37
+ next
38
+ when README_CODESTART
39
+ if filename
40
+ buf = []
41
+ end
42
+ next
43
+ when README_CODEEND
44
+ if filename && buf
45
+ newfile = "#{dir}/#{filename}"
46
+ unless File.exist?(File.dirname(newfile))
47
+ mkdir_p File.dirname(newfile)
48
+ end
49
+ str = readme_extract_callback(filename, buf.join())
50
+ File.write(newfile, str, encoding: 'utf-8')
51
+ puts "[retrieve] #{newfile}"
52
+ end
53
+ filename = nil
54
+ buf = nil
55
+ next
56
+ end
57
+ #
58
+ if buf
59
+ buf << line
60
+ end
61
+ end
62
+ end
63
+
64
+
65
+ desc "execute code in readme file"
66
+ task :execute => :retrieve do
67
+ do_readme_execute()
68
+ end
69
+
70
+ def do_readme_execute()
71
+ Dir.glob(README_DESTDIR+'/**/*.rb').sort.each do |fpath|
72
+ puts "========================================"
73
+ sh "ruby -I lib #{fpath}" do end
74
+ end
75
+ end
76
+
77
+
78
+ desc "builds table of contents"
79
+ task :toc do
80
+ do_readme_toc()
81
+ end
82
+
83
+ def do_readme_toc()
84
+ url = ENV['README_URL'] or abort "$README_URL required."
85
+ mkdir "tmp" unless Dir.exist?("tmp")
86
+ htmlfile = "tmp/README.html"
87
+ sh "curl -s -o #{htmlfile} #{url}"
88
+ #rexp = /<h(\d) dir="auto"><a id="(.*?)" class="anchor".*><\/a>(.*)<\/h\1>/
89
+ rexp = /<h(\d) id="user-content-.*?" dir="auto"><a class="heading-link" href="#(.*?)">(.*)<svg/
90
+ html_str = File.read(htmlfile, encoding: 'utf-8')
91
+ buf = []
92
+ html_str.scan(rexp) do
93
+ level = $1.to_i
94
+ id = $2
95
+ title = $3
96
+ next if title =~ /Table of Contents/
97
+ title = title.gsub(/<\/?code>/, '`')
98
+ anchor = id.sub(/^user-content-/, '')
99
+ indent = " " * (level - 1)
100
+ buf << "#{indent}* <a href=\"##{anchor}\">#{title}</a>\n"
101
+ end
102
+ buf.shift() if buf[0] && buf[0] =~ /^\* /
103
+ toc_str = buf.join()
104
+ #
105
+ mdfile = README_FILE
106
+ changed = File.open(mdfile, "r+", encoding: 'utf-8') do |f|
107
+ s1 = f.read()
108
+ s2 = s1.sub(/(<!-- TOC -->\n).*(<!-- \/TOC -->\n)/m) {
109
+ [$1, toc_str, $2].join("\n")
110
+ }
111
+ if s1 != s2
112
+ f.rewind()
113
+ f.truncate(0)
114
+ f.write(s2)
115
+ true
116
+ else
117
+ false
118
+ end
119
+ end
120
+ puts "[changed] #{mdfile}" if changed
121
+ puts "[not changed] #{mdfile}" unless changed
122
+ end
123
+
124
+
125
+ end
data/task/test-task.rb ADDED
@@ -0,0 +1,81 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+
4
+ unless defined?(RUBY_VERSIONS)
5
+ RUBY_VERSIONS = (
6
+ if ENV['RUBY_VERSIONS']
7
+ ENV['RUBY_VERSIONS'].split()
8
+ else
9
+ ["3.2", "3.1", "3.0", "2.7", "2.6", "2.5", "2.4", "2.3"]
10
+ end
11
+ )
12
+ end
13
+
14
+
15
+ desc "run test"
16
+ task :test do
17
+ do_test()
18
+ end
19
+
20
+ def do_test()
21
+ run_test()
22
+ end
23
+
24
+ def run_test(ruby=nil, &b)
25
+ run_oktest(ruby, &b)
26
+ end
27
+
28
+ def run_minitest(ruby=nil, &b)
29
+ files = File.exist?("test/run_all.rb") \
30
+ ? ["test/run_all.rb"] \
31
+ : Dir.glob("test/**/*_test.rb")
32
+ if ruby
33
+ sh(ruby, *files, &b)
34
+ else
35
+ ruby(*files, &b)
36
+ end
37
+ end
38
+
39
+ def run_oktest(ruby=nil, &b)
40
+ argstr = "-r oktest -e Oktest.main -- test -sp"
41
+ if ruby
42
+ sh("#{ruby} #{argstr}", &b)
43
+ else
44
+ ruby(argstr, &b)
45
+ end
46
+ end
47
+
48
+
49
+ desc "run test in different ruby versions"
50
+ task :'test:all' do
51
+ do_test_all()
52
+ end
53
+
54
+ def do_test_all()
55
+ ENV['VS_HOME'] or
56
+ abort "[ERROR] rake test:all: '$VS_HOME' environment var required."
57
+ vs_home = ENV['VS_HOME'].split(/[:;]/).first
58
+ ruby_versions = RUBY_VERSIONS
59
+ test_all(vs_home, ruby_versions)
60
+ end
61
+
62
+ def test_all(vs_home, ruby_versions)
63
+ header = proc {|s| "\033[0;36m=============== #{s} ===============\033[0m" }
64
+ error = proc {|s| "\033[0;31m** #{s}\033[0m" }
65
+ comp = proc {|x, y| x.to_s.split('.').map(&:to_i) <=> y.to_s.split('.').map(&:to_i) }
66
+ ruby_versions.each do |ver|
67
+ dir = Dir.glob("#{vs_home}/ruby/#{ver}.*/").sort_by(&comp).last
68
+ puts ""
69
+ if dir
70
+ puts header.("#{ver} (#{dir})")
71
+ run_test("#{dir}/bin/ruby") do |ok, res|
72
+ $stderr.puts error.("test failed") unless ok
73
+ end
74
+ sleep 0.2
75
+ else
76
+ puts header.(ver)
77
+ $stderr.puts error.("ruby #{ver} not found")
78
+ sleep 1.0
79
+ end
80
+ end
81
+ end