1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+ require 'pathname'
5
+
6
+ module Indexer
7
+
8
+ # Convert index data into a gemspec.
9
+ #
10
+ # Notes:
11
+ # * Assumes all executables are in bin/.
12
+ # * Does not yet handle default_executable setting.
13
+ # * Does not yet handle platform setting.
14
+ # * Does not yet handle required_ruby_version.
15
+ # * Support for rdoc entries is weak.
16
+ #
17
+ class GemspecExporter
18
+
19
+ # File globs to include in package --unless a manifest file exists.
20
+ FILES = ".index .yardopts alt bin data demo ext features lib man spec test try* [A-Z]*.*" unless defined? ( FILES )
21
+
22
+ # File globs to omit from FILES.
23
+ OMIT = "Config.rb" unless defined? ( OMIT )
24
+
25
+ # Standard file patterns.
26
+ PATTERNS = {
27
+ :root => '{.index,Gemfile}' ,
28
+ :bin => 'bin/*' ,
29
+ :lib => 'lib/{**/}*' , #.rb',
30
+ :ext => 'ext/{**/}extconf.rb' ,
31
+ :doc => '*.{txt,rdoc,md,markdown,tt,textile}' ,
32
+ :test => '{test,spec}/{**/}*.rb'
33
+ } unless defined? ( PATTERNS )
34
+
35
+ # For which revision of indexer spec is this converter intended?
36
+ REVISION = 2013 unless defined? ( REVISION )
37
+
38
+ #
39
+ def self . gemspec
40
+ new . to_gemspec
41
+ end
42
+
43
+ #
44
+ attr :metadata
45
+
46
+ #
47
+ def initialize ( metadata = nil )
48
+ @root_check = false
49
+
50
+ if metadata
51
+ root_dir = metadata . delete ( :root )
52
+ if root_dir
53
+ @root = root_dir
54
+ @root_check = true
55
+ end
56
+ metadata = nil if metadata . empty?
57
+ end
58
+
59
+ @metadata = metadata || YAML . load_file ( root + '.index' )
60
+
61
+ if @metadata [ 'revision' ] . to_i != REVISION
62
+ warn "This gemspec exporter was not designed for this revision of index metadata."
63
+ end
64
+ end
65
+
66
+ #
67
+ def has_root?
68
+ root ? true : false
69
+ end
70
+
71
+ #
72
+ def root
73
+ return @root if @root || @root_check
74
+ @root_check = true
75
+ @root = find_root
76
+ end
77
+
78
+ #
79
+ def manifest
80
+ return nil unless root
81
+ @manifest ||= Dir . glob ( root + 'manifest{,.txt}' , File ::FNM_CASEFOLD ) . first
82
+ end
83
+
84
+ #
85
+ def scm
86
+ return nil unless root
87
+ @scm ||= %w{ git hg } . find { |m | ( root + ".#{ m } " ) . directory? } . to_sym
88
+ end
89
+
90
+ #
91
+ def files
92
+ return [ ] unless root
93
+ @files ||= \
94
+ if manifest
95
+ File . readlines ( manifest ) .
96
+ map { |line | line . strip } .
97
+ reject { |line | line . empty? || line [ 0 , 1 ] == '#' }
98
+ else
99
+ list = [ ]
100
+ Dir . chdir ( root ) do
101
+ FILES . split ( /\s +/ ) . each do |pattern |
102
+ list . concat ( glob ( pattern ) )
103
+ end
104
+ OMIT . split ( /\s +/ ) . each do |pattern |
105
+ list = list - glob ( pattern )
106
+ end
107
+ end
108
+ list
109
+ end . select { |path | File . file? ( path ) } . uniq
110
+ end
111
+
112
+ #
113
+ def glob_files ( pattern )
114
+ return [ ] unless root
115
+ Dir . chdir ( root ) do
116
+ Dir . glob ( pattern ) . select do |path |
117
+ File . file? ( path ) && files . include? ( path )
118
+ end
119
+ end
120
+ end
121
+
122
+ def patterns
123
+ PATTERNS
124
+ end
125
+
126
+ def executables
127
+ @executables ||= \
128
+ glob_files ( patterns [ :bin ] ) . map do |path |
129
+ File . basename ( path )
130
+ end
131
+ end
132
+
133
+ def extensions
134
+ @extensions ||= \
135
+ glob_files ( patterns [ :ext ] ) . map do |path |
136
+ File . basename ( path )
137
+ end
138
+ end
139
+
140
+ def name
141
+ metadata [ 'name' ] || metadata [ 'title' ] . downcase . gsub ( /\W +/ , '_' )
142
+ end
143
+
144
+ def homepage
145
+ page = (
146
+ metadata [ 'resources' ] . find { |r | r [ 'type' ] =~ /^home/i } ||
147
+ metadata [ 'resources' ] . find { |r | r [ 'name' ] =~ /^home/i } ||
148
+ metadata [ 'resources' ] . find { |r | r [ 'name' ] =~ /^web/i }
149
+ )
150
+ page ? page [ 'uri' ] : false
151
+ end
152
+
153
+ def licenses
154
+ metadata [ 'copyrights' ] . map { |c | c [ 'license' ] } . compact
155
+ end
156
+
157
+ def require_paths
158
+ paths = metadata [ 'paths' ] || { }
159
+ paths [ 'load' ] || [ 'lib' ]
160
+ end
161
+
162
+ #
163
+ # Convert to gemnspec.
164
+ #
165
+ def to_gemspec
166
+ if has_root?
167
+ Gem ::Specification . new do |gemspec |
168
+ to_gemspec_data ( gemspec )
169
+ to_gemspec_paths ( gemspec )
170
+ end
171
+ else
172
+ Gem ::Specification . new do |gemspec |
173
+ to_gemspec_data ( gemspec )
174
+ to_gemspec_paths ( gemspec )
175
+ end
176
+ end
177
+ end
178
+
179
+ #
180
+ # Convert pure data settings.
181
+ #
182
+ def to_gemspec_data ( gemspec )
183
+ gemspec . name = name
184
+ gemspec . version = metadata [ 'version' ]
185
+ gemspec . summary = metadata [ 'summary' ]
186
+ gemspec . description = metadata [ 'description' ]
187
+
188
+ metadata [ 'authors' ] . each do |author |
189
+ gemspec . authors << author [ 'name' ]
190
+
191
+ if author . has_key? ( 'email' )
192
+ if gemspec . email
193
+ gemspec . email << author [ 'email' ]
194
+ else
195
+ gemspec . email = [ author [ 'email' ] ]
196
+ end
197
+ end
198
+ end
199
+
200
+ gemspec . licenses = licenses
201
+
202
+ requirements = metadata [ 'requirements' ] || [ ]
203
+ requirements . each do |req |
204
+ next if req [ 'optional' ]
205
+ next if req [ 'external' ]
206
+
207
+ name = req [ 'name' ]
208
+ groups = req [ 'groups' ] || [ ]
209
+
210
+ version = gemify_version ( req [ 'version' ] )
211
+
212
+ if groups . empty? or groups . include? ( 'runtime' )
213
+ # populate runtime dependencies
214
+ if gemspec . respond_to? ( :add_runtime_dependency )
215
+ gemspec . add_runtime_dependency ( name , *version )
216
+ else
217
+ gemspec . add_dependency ( name , *version )
218
+ end
219
+ else
220
+ # populate development dependencies
221
+ if gemspec . respond_to? ( :add_development_dependency )
222
+ gemspec . add_development_dependency ( name , *version )
223
+ else
224
+ gemspec . add_dependency ( name , *version )
225
+ end
226
+ end
227
+ end
228
+
229
+ # convert external dependencies into gemspec requirements
230
+ requirements . each do |req |
231
+ next unless req [ 'external' ]
232
+ gemspec . requirements << ( "%s-%s" % req . values_at ( 'name' , 'version' ) )
233
+ end
234
+
235
+ gemspec . homepage = homepage
236
+ gemspec . require_paths = require_paths
237
+ gemspec . post_install_message = metadata [ 'install_message' ]
238
+ end
239
+
240
+ #
241
+ # Set gemspec settings that require a root directory path.
242
+ #
243
+ def to_gemspec_paths ( gemspec )
244
+ gemspec . files = files
245
+ gemspec . extensions = extensions
246
+ gemspec . executables = executables
247
+
248
+ if Gem ::VERSION < '1.7.'
249
+ gemspec . default_executable = gemspec . executables . first
250
+ end
251
+
252
+ gemspec . test_files = glob_files ( patterns [ :test ] )
253
+
254
+ unless gemspec . files . include? ( '.document' )
255
+ gemspec . extra_rdoc_files = glob_files ( patterns [ :doc ] )
256
+ end
257
+ end
258
+
259
+ #
260
+ # Return a copy of this file. This is used to generate a local
261
+ # .gemspec file that can automatically read the index file.
262
+ #
263
+ def self . source_code
264
+ File . read ( __FILE__ )
265
+ end
266
+
267
+ private
268
+
269
+ def find_root
270
+ root_files = patterns [ :root ]
271
+ if Dir . glob ( root_files ) . first
272
+ Pathname . new ( Dir . pwd )
273
+ elsif Dir . glob ( "../#{ root_files } " ) . first
274
+ Pathname . new ( Dir . pwd ) . parent
275
+ else
276
+ #raise "Can't find root of project containing `#{root_files}'."
277
+ warn "Can't find root of project containing `#{ root_files } '."
278
+ nil
279
+ end
280
+ end
281
+
282
+ def glob ( pattern )
283
+ if File . directory? ( pattern )
284
+ Dir . glob ( File . join ( pattern , '**' , '*' ) )
285
+ else
286
+ Dir . glob ( pattern )
287
+ end
288
+ end
289
+
290
+ def gemify_version ( version )
291
+ case version
292
+ when /^(.*?)\+ $/
293
+ ">= #{ $1} "
294
+ when /^(.*?)\- $/
295
+ "< #{ $1} "
296
+ when /^(.*?)\~ $/
297
+ "~> #{ $1} "
298
+ else
299
+ version
300
+ end
301
+ end
302
+
303
+ end
304
+
305
+ end
306
+
307
+ Indexer ::GemspecExporter . gemspec
0 commit comments