benry-config 0.1.0 → 0.2.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.
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