free_zipcode_data 1.0.1
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 +7 -0
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +88 -0
- data/CHANGELOG +42 -0
- data/CODE_OF_CONDUCT.md +42 -0
- data/CONTRIBUTING.md +25 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +82 -0
- data/ISSUE_TEMPLATE.md +9 -0
- data/LICENSE.md +21 -0
- data/PULL_REQUEST_TEMPLATE.md +5 -0
- data/README.md +115 -0
- data/Rakefile +10 -0
- data/SUPPORT.md +6 -0
- data/all_us_counties.csv +3143 -0
- data/all_us_states.csv +52 -0
- data/all_us_zipcodes.csv +42367 -0
- data/bin/free_zipcode_data +9 -0
- data/counties_states_zipcodes.sql +46899 -0
- data/country_lookup_table.yml +989 -0
- data/free_zipcode_data.gemspec +43 -0
- data/lib/etl/common.rb +24 -0
- data/lib/etl/csv_source.rb +26 -0
- data/lib/etl/free_zipcode_data_job.rb +39 -0
- data/lib/free_zipcode_data/country_table.rb +46 -0
- data/lib/free_zipcode_data/county_table.rb +50 -0
- data/lib/free_zipcode_data/data_source.rb +88 -0
- data/lib/free_zipcode_data/db_table.rb +58 -0
- data/lib/free_zipcode_data/logger.rb +57 -0
- data/lib/free_zipcode_data/options.rb +21 -0
- data/lib/free_zipcode_data/runner.rb +172 -0
- data/lib/free_zipcode_data/sqlite_ram.rb +39 -0
- data/lib/free_zipcode_data/state_table.rb +51 -0
- data/lib/free_zipcode_data/version.rb +5 -0
- data/lib/free_zipcode_data/zipcode_table.rb +57 -0
- data/lib/free_zipcode_data.rb +43 -0
- data/lib/tasks/version.rake +181 -0
- data/spec/spec_helper.rb +35 -0
- metadata +270 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'colored'
|
|
4
|
+
require 'trollop'
|
|
5
|
+
require 'kiba'
|
|
6
|
+
|
|
7
|
+
require_relative '../etl/free_zipcode_data_job'
|
|
8
|
+
|
|
9
|
+
require 'pry' if ENV.fetch('APP_ENV', '') == 'development'
|
|
10
|
+
|
|
11
|
+
module FreeZipcodeData
|
|
12
|
+
# rubocop:disable Metrics/ClassLength
|
|
13
|
+
class Runner
|
|
14
|
+
attr_accessor :logger, :options
|
|
15
|
+
|
|
16
|
+
# Make a singleton but allow the class to be instantiated for easier testing
|
|
17
|
+
def self.instance
|
|
18
|
+
@instance || new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
@logger = Logger.instance
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def start
|
|
26
|
+
start_time = Time.now
|
|
27
|
+
opt = FreeZipcodeData::Options.instance
|
|
28
|
+
opt.initialize_hash(collect_args)
|
|
29
|
+
@options = opt.hash
|
|
30
|
+
|
|
31
|
+
logger.info("Starting FreeZipcodeData v#{VERSION}...".green)
|
|
32
|
+
|
|
33
|
+
datasource = DataSource.new(options.country)
|
|
34
|
+
datasource.download
|
|
35
|
+
|
|
36
|
+
db_file = File.join(options.work_dir, 'free_zipcode_data.sqlite3')
|
|
37
|
+
database = SqliteRam.new(db_file)
|
|
38
|
+
configure_meta(database.conn, datasource.datafile)
|
|
39
|
+
|
|
40
|
+
%i[country state county zipcode].each { |t| initialize_table(t, database) }
|
|
41
|
+
|
|
42
|
+
extract_transform_load(datasource, database)
|
|
43
|
+
|
|
44
|
+
logger.info("Saving database to disk '#{db_file}'...")
|
|
45
|
+
database.save_to_disk
|
|
46
|
+
|
|
47
|
+
if options.generate_files
|
|
48
|
+
logger.info('Generating .csv files...')
|
|
49
|
+
database.dump_tables(options.work_dir)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
elapsed = Time.at(Time.now - start_time).utc.strftime('%H:%M:%S')
|
|
53
|
+
logger.info("Processed #{datasource_line_count} zipcodes in [#{elapsed}].".yellow)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def initialize_table(table_sym, database)
|
|
59
|
+
tablename = options["#{table_sym}_tablename".to_sym]
|
|
60
|
+
logger.verbose("Initializing #{table_sym} table: '#{tablename}'...")
|
|
61
|
+
klass = instance_eval("#{titleize(table_sym)}Table", __FILE__, __LINE__)
|
|
62
|
+
table = klass.new(
|
|
63
|
+
database: database.conn,
|
|
64
|
+
tablename: tablename
|
|
65
|
+
)
|
|
66
|
+
table.build
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def datasource_line_count(filename)
|
|
70
|
+
@datasource_line_count ||= begin
|
|
71
|
+
count = File.foreach(filename).inject(0) { |c, _line| c + 1 }
|
|
72
|
+
logger.verbose("Processing #{count} zipcodes in '#{filename}'...")
|
|
73
|
+
count
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def configure_meta(database, datasource)
|
|
78
|
+
schema = <<-SQL
|
|
79
|
+
create table meta (
|
|
80
|
+
id integer not null primary key,
|
|
81
|
+
name varchar(255),
|
|
82
|
+
value varchar(255)
|
|
83
|
+
)
|
|
84
|
+
SQL
|
|
85
|
+
database.execute_batch(schema)
|
|
86
|
+
|
|
87
|
+
sql = <<-SQL
|
|
88
|
+
INSERT INTO meta (name, value)
|
|
89
|
+
VALUES ('line_count', #{datasource_line_count(datasource)})
|
|
90
|
+
SQL
|
|
91
|
+
database.execute(sql)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def extract_transform_load(datasource, database)
|
|
95
|
+
job = ETL::FreeZipcodeDataJob.setup(
|
|
96
|
+
datasource.datafile,
|
|
97
|
+
database.conn,
|
|
98
|
+
logger,
|
|
99
|
+
options
|
|
100
|
+
)
|
|
101
|
+
Kiba.run(job)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# rubocop:disable Metrics/BlockLength
|
|
105
|
+
# rubocop:disable Metrics/MethodLength
|
|
106
|
+
def collect_args
|
|
107
|
+
Trollop.options do
|
|
108
|
+
opt(
|
|
109
|
+
:work_dir,
|
|
110
|
+
'REQUIRED: Specify your work/build directory, where the SQLite and .csv files will be built',
|
|
111
|
+
type: :string, required: true, short: '-w'
|
|
112
|
+
)
|
|
113
|
+
opt(
|
|
114
|
+
:country,
|
|
115
|
+
'Specify the country code for processing, or all countries if not specified',
|
|
116
|
+
type: :string, required: false, short: '-f'
|
|
117
|
+
)
|
|
118
|
+
opt(
|
|
119
|
+
:generate_files,
|
|
120
|
+
'Generate CSV files: [counties.csv, states.csv, countries.csv, zipcodes.csv]',
|
|
121
|
+
type: :boolean, required: false, short: '-g', default: false
|
|
122
|
+
)
|
|
123
|
+
opt(
|
|
124
|
+
:country_tablename,
|
|
125
|
+
'Specify the name for the `countries` table',
|
|
126
|
+
type: :string, required: false, default: 'countries'
|
|
127
|
+
)
|
|
128
|
+
opt(
|
|
129
|
+
:state_tablename,
|
|
130
|
+
'Specify the name for the `states` table',
|
|
131
|
+
type: :string, required: false, default: 'states'
|
|
132
|
+
)
|
|
133
|
+
opt(
|
|
134
|
+
:county_tablename,
|
|
135
|
+
'Specify the name for the `counties` table',
|
|
136
|
+
type: :string, required: false, default: 'counties'
|
|
137
|
+
)
|
|
138
|
+
opt(
|
|
139
|
+
:zipcode_tablename,
|
|
140
|
+
'Specify the name for the `zipcodes` table',
|
|
141
|
+
type: :string, required: false, default: 'zipcodes'
|
|
142
|
+
)
|
|
143
|
+
opt(
|
|
144
|
+
:clobber,
|
|
145
|
+
'Overwrite existing files',
|
|
146
|
+
type: :boolean, required: false, short: '-c', default: false
|
|
147
|
+
)
|
|
148
|
+
opt(
|
|
149
|
+
:dry_run,
|
|
150
|
+
'Do not actually move or copy files',
|
|
151
|
+
type: :boolean, required: false, short: '-d',
|
|
152
|
+
default: false
|
|
153
|
+
)
|
|
154
|
+
opt(
|
|
155
|
+
:verbose,
|
|
156
|
+
'Be verbose with output',
|
|
157
|
+
type: :boolean, required: false, short: '-v',
|
|
158
|
+
default: false
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
# rubocop:enable Metrics/MethodLength
|
|
163
|
+
# rubocop:enable Metrics/BlockLength
|
|
164
|
+
|
|
165
|
+
def titleize(string)
|
|
166
|
+
ret = string.to_s.dup
|
|
167
|
+
ret[0] = ret[0].capitalize
|
|
168
|
+
ret
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
# rubocop:enable Metrics/ClassLength
|
|
172
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'sqlite3'
|
|
4
|
+
require 'csv'
|
|
5
|
+
|
|
6
|
+
# Open a SQlite DB, work with it in-memory and save back to disk
|
|
7
|
+
class SqliteRam
|
|
8
|
+
attr_reader :filename, :conn
|
|
9
|
+
|
|
10
|
+
def initialize(sqlite_filename)
|
|
11
|
+
@filename = sqlite_filename
|
|
12
|
+
@ram_db = SQLite3::Database.new(':memory:')
|
|
13
|
+
@file_db = SQLite3::Database.new(sqlite_filename)
|
|
14
|
+
@conn = @ram_db
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def save_to_disk
|
|
18
|
+
backup = SQLite3::Backup.new(@file_db, 'main', @ram_db, 'main')
|
|
19
|
+
backup.step(-1)
|
|
20
|
+
backup.finish
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def dump_tables(path)
|
|
24
|
+
tables = conn.execute('select name from sqlite_master where type = "table"')
|
|
25
|
+
sql = nil
|
|
26
|
+
tables.each do |table_array|
|
|
27
|
+
table = table_array.first
|
|
28
|
+
headers_sql = "pragma table_info('#{table}')"
|
|
29
|
+
header = conn.execute(headers_sql).map { |e| e[1] }
|
|
30
|
+
CSV.open(File.join(path, "#{table}.csv"), 'w') do |csv|
|
|
31
|
+
csv << header
|
|
32
|
+
sql = "select * from #{table}"
|
|
33
|
+
conn.execute(sql).each do |row_array|
|
|
34
|
+
csv << row_array
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'db_table'
|
|
4
|
+
|
|
5
|
+
module FreeZipcodeData
|
|
6
|
+
class StateTable < DbTable
|
|
7
|
+
def build
|
|
8
|
+
schema = <<-SQL
|
|
9
|
+
create table #{tablename} (
|
|
10
|
+
id integer not null primary key,
|
|
11
|
+
country_id integer not null,
|
|
12
|
+
abbr varchar(2) not null,
|
|
13
|
+
name varchar(255)
|
|
14
|
+
)
|
|
15
|
+
SQL
|
|
16
|
+
database.execute_batch(schema)
|
|
17
|
+
|
|
18
|
+
ndx = <<-SQL
|
|
19
|
+
CREATE UNIQUE INDEX "main"."unique_state"
|
|
20
|
+
ON #{tablename} (abbr, country_id COLLATE NOCASE ASC);
|
|
21
|
+
SQL
|
|
22
|
+
database.execute_batch(ndx)
|
|
23
|
+
|
|
24
|
+
ndx = <<-SQL
|
|
25
|
+
CREATE UNIQUE INDEX "main"."state_name"
|
|
26
|
+
ON #{tablename} (name COLLATE NOCASE ASC);
|
|
27
|
+
SQL
|
|
28
|
+
database.execute_batch(ndx)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def write(row)
|
|
32
|
+
return nil unless row[:short_state]
|
|
33
|
+
row[:state] = 'Marshall Islands' if row[:short_state] == 'MH' && row[:state].nil?
|
|
34
|
+
country_id = get_country_id(row[:country])
|
|
35
|
+
sql = <<-SQL
|
|
36
|
+
INSERT INTO states (abbr, name, country_id)
|
|
37
|
+
VALUES ('#{row[:short_state]}',
|
|
38
|
+
'#{escape_single_quotes(row[:state])}',
|
|
39
|
+
#{country_id}
|
|
40
|
+
)
|
|
41
|
+
SQL
|
|
42
|
+
begin
|
|
43
|
+
database.execute(sql)
|
|
44
|
+
rescue SQLite3::ConstraintException
|
|
45
|
+
# Swallow duplicates
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
update_progress
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'db_table'
|
|
4
|
+
|
|
5
|
+
module FreeZipcodeData
|
|
6
|
+
class ZipcodeTable < DbTable
|
|
7
|
+
def build
|
|
8
|
+
schema = <<-SQL
|
|
9
|
+
create table #{tablename} (
|
|
10
|
+
id integer not null primary key,
|
|
11
|
+
code varchar(10) not null,
|
|
12
|
+
state_id integer,
|
|
13
|
+
city varchar(255),
|
|
14
|
+
area_code varchar(3),
|
|
15
|
+
lat float,
|
|
16
|
+
lon float,
|
|
17
|
+
accuracy varchar(8)
|
|
18
|
+
)
|
|
19
|
+
SQL
|
|
20
|
+
database.execute_batch(schema)
|
|
21
|
+
|
|
22
|
+
ndx = <<-SQL
|
|
23
|
+
CREATE UNIQUE INDEX "main"."unique_zipcode"
|
|
24
|
+
ON #{tablename} (state_id, code, city COLLATE NOCASE ASC);
|
|
25
|
+
SQL
|
|
26
|
+
database.execute_batch(ndx)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def write(row)
|
|
30
|
+
return nil unless row[:postal_code]
|
|
31
|
+
|
|
32
|
+
state_id = get_state_id(row[:short_state], row[:state])
|
|
33
|
+
city_name = escape_single_quotes(row[:city])
|
|
34
|
+
|
|
35
|
+
sql = <<-SQL
|
|
36
|
+
INSERT INTO zipcodes (code, state_id, city, lat, lon, accuracy)
|
|
37
|
+
VALUES ('#{row[:postal_code]}',
|
|
38
|
+
'#{state_id}',
|
|
39
|
+
'#{city_name}',
|
|
40
|
+
'#{row[:latitude]}',
|
|
41
|
+
'#{row[:longitude]}',
|
|
42
|
+
'#{row[:accuracy]}'
|
|
43
|
+
)
|
|
44
|
+
SQL
|
|
45
|
+
|
|
46
|
+
begin
|
|
47
|
+
database.execute(sql)
|
|
48
|
+
rescue SQLite3::ConstraintException => _err
|
|
49
|
+
# there are some duplicates - swallow them
|
|
50
|
+
rescue StandardError => err
|
|
51
|
+
raise "Please file an issue at #{ISSUE_URL}: [#{err}] -> SQL: [#{sql}]"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
update_progress
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'readline'
|
|
4
|
+
|
|
5
|
+
require 'free_zipcode_data/version'
|
|
6
|
+
|
|
7
|
+
module FreeZipcodeData
|
|
8
|
+
def self.root
|
|
9
|
+
Pathname.new(File.dirname(__FILE__)).parent
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.current_environment
|
|
13
|
+
ENV.fetch('APP_ENV', 'development')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
#:nocov:
|
|
17
|
+
def self.config_file(filename = '.free_zipcode_data.yml')
|
|
18
|
+
return root.join('spec', 'fixtures', filename) if current_environment == 'test'
|
|
19
|
+
home = ENV.fetch('HOME')
|
|
20
|
+
file = ENV.fetch('FZD_CONFIG_FILE', File.join(home, '.free_zipcode_data.yml'))
|
|
21
|
+
FileUtils.touch(file)
|
|
22
|
+
file
|
|
23
|
+
end
|
|
24
|
+
#:nocov:
|
|
25
|
+
|
|
26
|
+
def self.os
|
|
27
|
+
if RUBY_PLATFORM.match?(/cygwin|mswin|mingw|bccwin|wince|emx/)
|
|
28
|
+
:retarded
|
|
29
|
+
else
|
|
30
|
+
:normal
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
autoload :CountryTable, 'free_zipcode_data/country_table'
|
|
35
|
+
autoload :StateTable, 'free_zipcode_data/state_table'
|
|
36
|
+
autoload :CountyTable, 'free_zipcode_data/county_table'
|
|
37
|
+
autoload :ZipcodeTable, 'free_zipcode_data/zipcode_table'
|
|
38
|
+
autoload :DataSource, 'free_zipcode_data/data_source'
|
|
39
|
+
autoload :Logger, 'free_zipcode_data/logger'
|
|
40
|
+
autoload :Options, 'free_zipcode_data/options'
|
|
41
|
+
autoload :Settings, 'free_zipcode_data/settings'
|
|
42
|
+
autoload :SqliteRam, 'free_zipcode_data/sqlite_ram'
|
|
43
|
+
end
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rake'
|
|
4
|
+
require 'readline'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
|
|
7
|
+
# rubocop:disable Metrics/BlockLength
|
|
8
|
+
namespace :version do
|
|
9
|
+
PROJECT_ROOT = File.expand_path(FileUtils.pwd).freeze
|
|
10
|
+
PROJECT_NAME = ENV['PROJECT_NAME'] || File.basename(PROJECT_ROOT)
|
|
11
|
+
|
|
12
|
+
desc 'Write changes to the CHANGELOG'
|
|
13
|
+
task :changes do
|
|
14
|
+
text = ask('CHANGELOG Entry:')
|
|
15
|
+
text.insert(
|
|
16
|
+
0,
|
|
17
|
+
"*#{read_version.join('.')}* (#{Time.now.strftime('%B %d, %Y')})\n\n"
|
|
18
|
+
)
|
|
19
|
+
text << "\n"
|
|
20
|
+
prepend_changelog(text)
|
|
21
|
+
launch_editor(changelog)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc 'Increment the patch version and write changes to the changelog'
|
|
25
|
+
task :bump_patch do
|
|
26
|
+
exit unless check_branch_and_warn
|
|
27
|
+
major, minor, patch = read_version
|
|
28
|
+
patch = patch.to_i + 1
|
|
29
|
+
write_version_file([major, minor, patch])
|
|
30
|
+
update_readme_version_strings
|
|
31
|
+
Rake::Task['version:changes'].invoke
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
desc 'Alias for :bump_patch'
|
|
35
|
+
task bump: :bump_patch
|
|
36
|
+
|
|
37
|
+
desc 'Increment the minor version and write changes to the changelog'
|
|
38
|
+
task :bump_minor do
|
|
39
|
+
exit unless check_branch_and_warn
|
|
40
|
+
major, minor, _patch = read_version
|
|
41
|
+
minor = minor.to_i + 1
|
|
42
|
+
patch = 0
|
|
43
|
+
write_version_file([major, minor, patch])
|
|
44
|
+
update_readme_version_strings
|
|
45
|
+
Rake::Task['version:changes'].invoke
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
desc 'Increment the major version and write changes to the changelog'
|
|
49
|
+
task :bump_major do
|
|
50
|
+
exit unless check_branch_and_warn
|
|
51
|
+
major, _minor, _patch = read_version
|
|
52
|
+
major = major.to_i + 1
|
|
53
|
+
minor = 0
|
|
54
|
+
patch = 0
|
|
55
|
+
write_version_file([major, minor, patch])
|
|
56
|
+
update_readme_version_strings
|
|
57
|
+
Rake::Task['version:changes'].invoke
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def version_file_path
|
|
63
|
+
split = PROJECT_NAME.split('-')
|
|
64
|
+
"#{PROJECT_ROOT}/lib/#{split.join('/')}/version.rb"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def module_name
|
|
68
|
+
if PROJECT_NAME =~ /-/
|
|
69
|
+
PROJECT_NAME.split('-').map(&:capitalize).join('::')
|
|
70
|
+
elsif PROJECT_NAME =~ /_/
|
|
71
|
+
PROJECT_NAME.split('_').map(&:capitalize).join
|
|
72
|
+
else
|
|
73
|
+
PROJECT_NAME.capitalize
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def read_version
|
|
78
|
+
silence_warnings do
|
|
79
|
+
load version_file_path
|
|
80
|
+
end
|
|
81
|
+
text = eval("#{module_name}::VERSION")
|
|
82
|
+
text.split('.')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def write_version_file(version_array)
|
|
86
|
+
version = version_array.join('.')
|
|
87
|
+
new_version = %( VERSION = '#{version}'.freeze)
|
|
88
|
+
lines = File.readlines(version_file_path)
|
|
89
|
+
File.open(version_file_path, 'w') do |f|
|
|
90
|
+
lines.each do |line|
|
|
91
|
+
if line =~ /VERSION/
|
|
92
|
+
f.write("#{new_version}\n")
|
|
93
|
+
else
|
|
94
|
+
f.write(line)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def update_readme_version_strings
|
|
101
|
+
version_string = read_version.join('.')
|
|
102
|
+
readme = open('README.md').read
|
|
103
|
+
regex = /^\*\*Version: [0-9\.]+\*\*$/i
|
|
104
|
+
return nil unless readme =~ regex
|
|
105
|
+
File.open('README.md', 'w') do |f|
|
|
106
|
+
f.write(readme.gsub(regex, "**Version: #{version_string}**"))
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def changelog
|
|
111
|
+
return @changelog_path if @changelog_path
|
|
112
|
+
@changelog_path = File.join(PROJECT_ROOT, 'CHANGELOG')
|
|
113
|
+
FileUtils.touch(@changelog_path)
|
|
114
|
+
@changelog_path
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def prepend_changelog(text_array)
|
|
118
|
+
old = File.read(changelog).to_s.chomp
|
|
119
|
+
text_array.push(old)
|
|
120
|
+
File.open(changelog, 'w') do |f|
|
|
121
|
+
text_array.flatten.each do |line|
|
|
122
|
+
f.puts(line)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# rubocop:disable Lint/AssignmentInCondition
|
|
128
|
+
def ask(message)
|
|
129
|
+
response = []
|
|
130
|
+
puts message
|
|
131
|
+
puts 'Hit <Control>-D when finished:'
|
|
132
|
+
while line = Readline.readline('* ', false)
|
|
133
|
+
response << "* #{line.chomp}" unless line.nil?
|
|
134
|
+
end
|
|
135
|
+
response
|
|
136
|
+
end
|
|
137
|
+
# rubocop:enable Lint/AssignmentInCondition
|
|
138
|
+
|
|
139
|
+
def current_branch
|
|
140
|
+
`git symbolic-ref --short HEAD`.chomp
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def branch_warning_message
|
|
144
|
+
<<~STRING
|
|
145
|
+
You typically do not want to bump versions on the 'master' branch
|
|
146
|
+
unless you plan to rebase or back-merge into 'develop'.
|
|
147
|
+
|
|
148
|
+
If you don't care or don't know what I'm talking about just enter 'y'
|
|
149
|
+
and continue.
|
|
150
|
+
|
|
151
|
+
Optionally, you can hit 'n' to abort and switch your branch to 'develop'
|
|
152
|
+
or whatever branch you use for development, bump the version, merge to
|
|
153
|
+
'master' then 'rake release'.
|
|
154
|
+
|
|
155
|
+
Do you really want to bump the version on your 'master' branch? (y/n)
|
|
156
|
+
STRING
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def check_branch_and_warn
|
|
160
|
+
return true unless current_branch == 'master'
|
|
161
|
+
puts(branch_warning_message)
|
|
162
|
+
while (line = $stdin.gets.chomp)
|
|
163
|
+
return true if line =~ /[yY]/
|
|
164
|
+
puts 'Aborting version bump.'
|
|
165
|
+
return false
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def launch_editor(file)
|
|
170
|
+
system("#{ENV['EDITOR']} #{file}") if ENV['EDITOR']
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def silence_warnings
|
|
174
|
+
original_verbosity = $VERBOSE
|
|
175
|
+
$VERBOSE = nil
|
|
176
|
+
yield
|
|
177
|
+
ensure
|
|
178
|
+
$VERBOSE = original_verbosity
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
# rubocop:enable Metrics/BlockLength
|