rake-deveiate 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 53275d426cc35203e19ffb6354a49628d385b4b457b312863f98de52c1c205ae
4
+ data.tar.gz: 9cc9b5659a6612aef89b69d153777ac1f2c29fb9ad8504c4e80d66dfc295243a
5
+ SHA512:
6
+ metadata.gz: 305c88f5e61673c7b898ac213daff8bb6013b0a9ea243c4eb46b175ea59f44e2913d0b632782a38f1f211a6afe9ac0dc640febfb3915564916c7dd22a99cebc2
7
+ data.tar.gz: 063f316622af921ba648aaf65ebf65efd486f71c17fedae4baab90e95c2a82eb6c407350984711c8d7b8084bc6ae6742d9d6ddd5de3e6e6b0a35903010405a85
checksums.yaml.gz.sig ADDED
Binary file
data/History.md ADDED
@@ -0,0 +1,7 @@
1
+ # Release History for rake-deveiate
2
+
3
+ ---
4
+
5
+ ## v0.1.0 [2019/10/02] Michael Granger <ged@FaerieMUD.org>
6
+
7
+ First release.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # Rake Tasks for DevEiate Libraries
2
+
3
+ home
4
+ : https://hg.sr.ht/~ged/rake-deveiate
5
+
6
+ github
7
+ : https://github.com/ged/rake-deveiate
8
+
9
+ docs
10
+ : https://deveiate.org/code/rake-deveiate/
11
+
12
+
13
+ ## Description
14
+
15
+ This is a collection of Rake tasks I use for development. I distribute them as
16
+ a gem mostly so people who wish to contribute to the other Open Source
17
+ libraries I maintain can do so easily, but of course you're welcome to use them
18
+ yourself if you find them useful.
19
+
20
+
21
+ ## Prerequisites
22
+
23
+ * Ruby 2.6+
24
+
25
+
26
+ ## Installation
27
+
28
+ $ gem install rake-deveiate
29
+
30
+
31
+ ## Usage
32
+
33
+ Make a Rakefile with the following content:
34
+
35
+ require 'rake/deveiate'
36
+
37
+ Rake::DevEiate.setup( 'gemname' )
38
+
39
+ You can also pass a block to customize the project settings:
40
+
41
+ Rake::DevEiate.setup( 'gemname' ) do |project|
42
+ project.description = <<~END_DESC
43
+ This is a gem that does some stuff. It does a few things well, a lot of
44
+ things sufficiently, and nothing badly.
45
+ END_DESC
46
+ project.authors = [ 'J. Random Hacker <jrandom@example.com>' ]
47
+ end
48
+
49
+
50
+
51
+ ## Authors
52
+
53
+ - Michael Granger <ged@FaerieMUD.org>
54
+
55
+
56
+ ## License
57
+
58
+ Copyright (c) 2019, Michael Granger
59
+ All rights reserved.
60
+
61
+ Redistribution and use in source and binary forms, with or without
62
+ modification, are permitted provided that the following conditions are met:
63
+
64
+ * Redistributions of source code must retain the above copyright notice,
65
+ this list of conditions and the following disclaimer.
66
+
67
+ * Redistributions in binary form must reproduce the above copyright notice,
68
+ this list of conditions and the following disclaimer in the documentation
69
+ and/or other materials provided with the distribution.
70
+
71
+ * Neither the name of the author/s, nor the names of the project's
72
+ contributors may be used to endorse or promote products derived from this
73
+ software without specific prior written permission.
74
+
75
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
76
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
77
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
78
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
79
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
80
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
81
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
82
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
83
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
84
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,5 @@
1
+ <%= header_char %> Release History for <%= project.name %>
2
+
3
+ ---
4
+
5
+
@@ -0,0 +1,64 @@
1
+ <%= header_char %> <%= project.name.gsub(/\b([a-z])/) {|m| m.upcase } %>
2
+
3
+ home
4
+ : https://hg.sr.ht/~ged/<%= project.name %>
5
+
6
+ github
7
+ : https://github.com/ged/<%= project.name %>
8
+
9
+ docs
10
+ : https://deveiate.org/code/<%= project.name %>
11
+
12
+
13
+ <%= header_char * 2 %> Description
14
+
15
+ A gem of some sort.
16
+
17
+
18
+ <%= header_char * 2 %> Prerequisites
19
+
20
+ * Ruby <%= RUBY_VERSION.sub(/\.\d+$/, '') %>
21
+
22
+
23
+ <%= header_char * 2 %> Installation
24
+
25
+ $ gem install <%= project.name %>
26
+
27
+
28
+ <%= header_char * 2 %> Authors
29
+
30
+ <% project.authors.each do |author| -%>
31
+ - <%= author %>
32
+ <% end -%>
33
+
34
+
35
+ <%= header_char * 2 %> License
36
+
37
+ Copyright (c) <%= Date.today.year %>, <%= project.author_names.join(' and ') %>
38
+ All rights reserved.
39
+
40
+ Redistribution and use in source and binary forms, with or without
41
+ modification, are permitted provided that the following conditions are met:
42
+
43
+ * Redistributions of source code must retain the above copyright notice,
44
+ this list of conditions and the following disclaimer.
45
+
46
+ * Redistributions in binary form must reproduce the above copyright notice,
47
+ this list of conditions and the following disclaimer in the documentation
48
+ and/or other materials provided with the distribution.
49
+
50
+ * Neither the name of the author/s, nor the names of the project's
51
+ contributors may be used to endorse or promote products derived from this
52
+ software without specific prior written permission.
53
+
54
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
55
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
56
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
57
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
58
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
59
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
60
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
61
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
62
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
63
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
64
+
@@ -0,0 +1,200 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'rake'
5
+
6
+ require 'rake/deveiate' unless defined?( Rake::DevEiate )
7
+
8
+
9
+ # Sanity and quality check tasks
10
+ module Rake::DevEiate::Checks
11
+ extend Rake::DSL
12
+
13
+ # Emoji for style advisories
14
+ SADFACE = "\u{1f622}"
15
+
16
+ # Tab-arrow character
17
+ TAB_ARROW = "\u{21e5}"
18
+
19
+ # Regexp to match trailing whitespace
20
+ TRAILING_WHITESPACE_RE = /\p{Blank}+$/m
21
+
22
+ # Regexp to match lines with mixed indentation
23
+ MIXED_INDENT_RE = /(?<!#)([ ]\t)/
24
+
25
+
26
+ ### Set up task defaults
27
+ def setup( _name, **options )
28
+ super if defined?( super )
29
+
30
+ @quality_check_whitelist = Rake::FileList.new
31
+ end
32
+
33
+
34
+ ##
35
+ # The Rake::FileList containing files which should not be quality-checked.
36
+ attr_reader :quality_check_whitelist
37
+
38
+
39
+ ### Define check tasks
40
+ def define_tasks
41
+ super if defined?( super )
42
+
43
+ self.define_quality_checker_tasks
44
+ self.define_sanity_checker_tasks
45
+
46
+ desc "Run several quality-checks on the code"
47
+ task :quality_checks => [ 'quality_checks:all' ]
48
+
49
+ desc "Run several sanity-checks on the code"
50
+ task :sanity_checks => [ 'sanity_checks:all' ]
51
+
52
+ task :check => [ :quality_checks, :sanity_checks ]
53
+
54
+ end
55
+
56
+
57
+ ### Set up tasks that check for poor whitespace discipline
58
+ def define_quality_checker_tasks
59
+
60
+ namespace :quality_checks do
61
+
62
+ task :all => [ :whitespace ]
63
+
64
+ desc "Check source code for inconsistent whitespace"
65
+ task :whitespace => [
66
+ :for_trailing_whitespace,
67
+ :for_mixed_indentation,
68
+ ]
69
+
70
+ desc "Check source code for trailing whitespace"
71
+ task :for_trailing_whitespace do
72
+ lines = find_matching_source_lines do |line, _|
73
+ line =~ TRAILING_WHITESPACE_RE
74
+ end
75
+
76
+ unless lines.empty?
77
+ desc = "Found some trailing whitespace"
78
+ describe_lines_that_need_fixing( desc, lines, TRAILING_WHITESPACE_RE )
79
+ fail
80
+ end
81
+ end
82
+
83
+ desc "Check source code for mixed indentation"
84
+ task :for_mixed_indentation do
85
+ lines = find_matching_source_lines do |line, _|
86
+ line =~ MIXED_INDENT_RE
87
+ end
88
+
89
+ unless lines.empty?
90
+ desc = "Found mixed indentation"
91
+ describe_lines_that_need_fixing( desc, lines, /[ ]\t/ )
92
+ fail
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+
100
+
101
+ ### Set up some sanity-checks as dependencies of higher-level tasks
102
+ def define_sanity_checker_tasks
103
+
104
+ namespace :sanity_checks do
105
+
106
+ desc "Check source code for common problems"
107
+ task :all
108
+
109
+ end
110
+
111
+ end
112
+
113
+
114
+ # Return tuples of the form:
115
+ #
116
+ # [ <filename>, <line number>, <line> ]
117
+ #
118
+ # for every line in the Gemspec's source files for which the block
119
+ # returns true.
120
+ def find_matching_source_lines
121
+ matches = []
122
+
123
+ source_files = self.project_files.grep( /\.(h|c|rb)$/ )
124
+ source_files -= self.quality_check_whitelist
125
+
126
+ source_files.each do |filename|
127
+ previous_line = nil
128
+
129
+ IO.foreach( filename ).with_index do |line, i|
130
+ matches << [filename, i + 1, line] if yield( line, previous_line )
131
+ previous_line = line
132
+ end
133
+ end
134
+
135
+ return matches
136
+ end
137
+
138
+
139
+ ### Output a listing of the specified lines with the given +description+, highlighting
140
+ ### the characters matched by the specified +re+.
141
+ def describe_lines_that_need_fixing( description, lines, re )
142
+ self.prompt.say "\n"
143
+ self.prompt.say SADFACE + " "
144
+ self.prompt.error( "Uh-oh! " + description )
145
+
146
+ grouped_lines = group_line_matches( lines )
147
+
148
+ grouped_lines.each do |filename, linegroups|
149
+ linegroups.each do |group, lines|
150
+ if group.min == group.max
151
+ self.prompt.say( "%s:%d" % [ filename, group.min ], color: :bold )
152
+ else
153
+ self.prompt.say( "%s:%d-%d" % [ filename, group.min, group.max ], color: :bold )
154
+ end
155
+
156
+ lines.each_with_index do |line, i|
157
+ self.prompt.say "%s: %s" % [
158
+ self.pastel.dark.white( group.to_a[i].to_s ),
159
+ highlight_problems( line, re )
160
+ ]
161
+ end
162
+ self.prompt.say "\n"
163
+ end
164
+ end
165
+ end
166
+
167
+
168
+ # Return a Hash, keyed by filename, whose values are tuples of Ranges
169
+ # and lines extracted from the given [filename, linenumber, line] +tuples+.
170
+ def group_line_matches( tuples )
171
+ by_file = tuples.group_by {|tuple| tuple.first }
172
+
173
+ return by_file.each_with_object({}) do |(filename, lines), hash|
174
+ last_linenum = 0
175
+ linegroups = lines.slice_before do |filename, linenum|
176
+ gap = linenum > last_linenum + 1
177
+ last_linenum = linenum
178
+ gap
179
+ end
180
+
181
+ hash[ filename ] = linegroups.map do |group|
182
+ rng = group.first[1] .. group.last[1]
183
+ grouplines = group.transpose.last
184
+ [ rng, grouplines ]
185
+ end
186
+ end
187
+ end
188
+
189
+
190
+ ### Transform invisibles in the specified line into visible analogues.
191
+ def highlight_problems( line, re )
192
+ line.
193
+ gsub( re ) { self.pastel.on_red( $& ) }.
194
+ gsub( /\t+/ ) { self.pastel.dark.white( "#{TAB_ARROW} " * $&.length ) }
195
+ end
196
+
197
+
198
+ end # module Rake::DevEiate::Docs
199
+
200
+
@@ -0,0 +1,34 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'rake'
5
+ require 'rake/phony'
6
+ require 'rdoc/task'
7
+
8
+ require 'rake/deveiate' unless defined?( Rake::DevEiate )
9
+
10
+
11
+ # Documentation-generation tasks
12
+ module Rake::DevEiate::Docs
13
+ extend Rake::DSL
14
+
15
+
16
+ ### Define documentation tasks
17
+ def define_tasks
18
+ super if defined?( super )
19
+
20
+ task :docs => :phony
21
+
22
+ RDoc::Task.new( 'docs' ) do |rdoc|
23
+ rdoc.main = self.readme_file.to_s
24
+ rdoc.rdoc_files = self.rdoc_files
25
+ rdoc.generator = :fivefish
26
+ rdoc.title = self.title
27
+ rdoc.rdoc_dir = Rake::DevEiate::DOCS_DIR.to_s
28
+ end
29
+
30
+ end
31
+
32
+ end # module Rake::DevEiate::Docs
33
+
34
+
@@ -0,0 +1,97 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'set'
5
+ require 'rubygems'
6
+ require 'rake/deveiate' unless defined?( Rake::DevEiate )
7
+
8
+
9
+ # A dependency finder that groks the GemDependencyApi
10
+ class Rake::DevEiate::GemDepFinder
11
+
12
+ ### Create a new GemDepFinder that will find dependencies in the given
13
+ ### +depfile+.
14
+ def initialize( depfile )
15
+ @depfile = Pathname( depfile )
16
+ @dependencies = Set.new
17
+ @current_groups = Set.new
18
+ end
19
+
20
+
21
+
22
+ ######
23
+ public
24
+ ######
25
+
26
+ ##
27
+ # The Pathname of the file to find dependencies in
28
+ attr_reader :depfile
29
+
30
+ ##
31
+ # The Set of Gem::Dependency objects that describe the loaded dependencies
32
+ attr_reader :dependencies
33
+
34
+ ##
35
+ # The current set of groups to add to any declared gems
36
+ attr_reader :current_groups
37
+
38
+
39
+ ### Load the dependencies file.
40
+ def load
41
+ source = self.depfile.read
42
+ self.instance_eval( source, self.depfile.to_s, 1 )
43
+ end
44
+
45
+
46
+ #
47
+ # Gem Dependency API methods
48
+ #
49
+
50
+
51
+ ### Declare a dependency on a gem. Ignores every option except :group.
52
+ def gem( name, *requirements, **options )
53
+ if options[:group] == :development ||
54
+ options[:groups]&.include?( :development ) ||
55
+ self.current_groups.include?( :development )
56
+
57
+ requirements.push( :development )
58
+ end
59
+
60
+ dependency = Gem::Dependency.new( name, *requirements )
61
+
62
+ self.dependencies.add( dependency )
63
+ end
64
+
65
+
66
+ ### Declare a group block.
67
+ def group( *names )
68
+ options = names.pop if names.last.is_a?( Hash )
69
+ previous_groups = self.current_groups.dup
70
+ self.current_groups.replace( names )
71
+
72
+ yield
73
+ ensure
74
+ self.current_groups.replace( previous_groups ) if previous_groups
75
+ end
76
+
77
+
78
+ ### Raise, as the gemdeps file should be the authoritative source.
79
+ def gemspec( * )
80
+ raise "Circular dependency: can't depend on the gemspec to build itself"
81
+ end
82
+
83
+
84
+ ### Ignore a gem dependency API call.
85
+ def no_op_method( * ) # :nodoc:
86
+ yield if block_given?
87
+ end
88
+
89
+ alias_method :source, :no_op_method
90
+ alias_method :git, :no_op_method
91
+ alias_method :platform, :no_op_method
92
+ alias_method :platforms, :no_op_method
93
+ alias_method :ruby, :no_op_method
94
+
95
+
96
+ end # class Rake::DevEiate::GemDepFinder
97
+
@@ -0,0 +1,177 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'etc'
5
+ require 'rubygems'
6
+ require 'rake'
7
+ require 'rake/clean'
8
+ require 'rake/deveiate' unless defined?( Rake::DevEiate )
9
+
10
+
11
+ # Gemspec-generation tasks
12
+ module Rake::DevEiate::Gemspec
13
+ extend Rake::DSL
14
+
15
+ # Pattern for splitting parsed authors list items into name and email
16
+ AUTHOR_PATTERN = /^(?<name>.*)\s<(?<email>.*)>$/
17
+
18
+
19
+ ##
20
+ # The path to the file used to sign released gems
21
+ attr_accessor :signing_key
22
+
23
+
24
+ ### Set some defaults when the task lib is set up.
25
+ def setup( _name, **options )
26
+ super if defined?( super )
27
+
28
+ @signing_key = options[:signing_key] || Gem.default_key_path
29
+
30
+ @gemspec = nil
31
+ end
32
+
33
+
34
+ ### Reset any cached data when project attributes change.
35
+ def reset
36
+ super if defined?( super )
37
+ @gemspec = nil
38
+ end
39
+
40
+
41
+ ### Define gemspec tasks
42
+ def define_tasks
43
+ super if defined?( super )
44
+
45
+ gemspec_file = "#{self.name}.gemspec"
46
+
47
+ if self.has_manifest?
48
+ file( self.manifest_file )
49
+ file( gemspec_file => self.manifest_file )
50
+ else
51
+ file( gemspec_file )
52
+ end
53
+
54
+ task( gemspec_file ) do |task|
55
+ self.prompt.say "Updating gemspec"
56
+
57
+ spec = self.make_prerelease_gemspec
58
+
59
+ File.open( task.name, 'w' ) do |fh|
60
+ fh.write( spec.to_ruby )
61
+ end
62
+ end
63
+
64
+ desc "(Re)Generate the gemspec file"
65
+ task :gemspec => gemspec_file
66
+
67
+ CLEAN.include( gemspec_file.to_s )
68
+
69
+ task :checkin => :gemspec
70
+
71
+ task( :gemspec_debug, &method(:do_gemspec_debug) )
72
+ task :debug => :gemspec_debug
73
+ end
74
+
75
+
76
+ ### Task function -- output debugging for gemspec tasks.
77
+ def do_gemspec_debug( task, args )
78
+ gemspec = self.gemspec
79
+ gemspec_src = gemspec.to_yaml
80
+
81
+ self.prompt.say( "Gemspec:", color: :bright_green )
82
+ self.prompt.say( indent(gemspec_src, 4) )
83
+ self.prompt.say( "\n" )
84
+ end
85
+
86
+
87
+ ### Return the project's Gem::Specification, creating it if necessary.
88
+ def gemspec
89
+ return @gemspec ||= self.make_gemspec
90
+ end
91
+
92
+
93
+ ### Validate the gemspec, raising a Gem::InvalidSpecificationException if it's
94
+ ### not valid.
95
+ def validate_gemspec( packaging=true, strict=false )
96
+ return self.gemspec.validate( packaging, strict )
97
+ end
98
+
99
+
100
+ ### Return a Gem::Specification created from the project's metadata.
101
+ def make_gemspec
102
+ spec = Gem::Specification.new
103
+
104
+ spec.name = self.name
105
+ spec.description = self.description
106
+ spec.homepage = self.homepage
107
+ spec.summary = self.summary || self.extract_summary
108
+ spec.files = self.project_files
109
+ spec.signing_key = self.resolve_signing_key.to_s
110
+ spec.cert_chain = self.cert_files.map( &File.method(:expand_path) ).to_a
111
+ spec.version = self.version
112
+ spec.licenses = self.licenses
113
+ spec.date = Date.today
114
+
115
+ self.authors.each do |author|
116
+ if ( m = author.match(AUTHOR_PATTERN) )
117
+ spec.authors ||= []
118
+ spec.authors << m[:name]
119
+ spec.email ||= []
120
+ spec.email << m[:email] if m[:email]
121
+ else
122
+ self.prompt.warn "Couldn't extract author name + email from %p" % [ author ]
123
+ end
124
+ end
125
+
126
+ self.dependencies.each do |dep|
127
+ if dep.runtime?
128
+ spec.add_runtime_dependency( dep )
129
+ else
130
+ spec.add_development_dependency( dep )
131
+ end
132
+ end
133
+
134
+ return spec
135
+ end
136
+
137
+
138
+ ### Return a Gem::Specification with its properties modified to be suitable for
139
+ ### a pre-release gem.
140
+ def make_prerelease_gemspec
141
+ spec = self.make_gemspec
142
+
143
+ spec.version = self.prerelease_version
144
+ spec.signing_key = nil
145
+ spec.cert_chain = []
146
+
147
+ return spec
148
+ end
149
+
150
+
151
+ ### Return a version string
152
+ def prerelease_version
153
+ return "#{self.version.bump}.0.pre.#{Time.now.strftime("%Y%m%d%H%M%S")}"
154
+ end
155
+
156
+
157
+ ### Resolve the path of the signing key
158
+ def resolve_signing_key
159
+ path = Pathname( self.signing_key ).expand_path
160
+ path = path.readlink if path.symlink?
161
+ return path
162
+ end
163
+
164
+
165
+ #######
166
+ private
167
+ #######
168
+
169
+ ### Return a copy of the given text prefixed by +spaces+ number of spaces.
170
+ def indent( text, spaces=4 )
171
+ prefix = ' ' * spaces
172
+ return text.gsub( /(?<=\A|\n)/m, prefix )
173
+ end
174
+
175
+ end # module Rake::DevEiate::Gemspec
176
+
177
+