doc 0.0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,38 @@
1
+ module Doc
2
+ class Configurator
3
+ class Ruby
4
+ class PathInfo < Struct.new(:path, :name, :type, :full_version, :parts)
5
+ private_class_method :new
6
+ def self.latest_matching(version, paths)
7
+ paths.map(&method(:for_path)).compact.grep(version).sort.last
8
+ end
9
+
10
+ def self.for_path(path)
11
+ name = path.basename.to_s
12
+ if name =~ /^ruby-(\d+\.\d+\.\d+-p\d+)(?i:\.(tar\.(?:gz|bz2)|tgz|tbz|zip))?$/
13
+ extension = $2 ? $2.downcase : :dir
14
+ type = ({'tar.bz2' => 'tbz', 'tar.gz' => 'tgz'}[extension] || extension).to_sym
15
+ new(path, name, type, $1, $1.scan(/\d+/).map(&:to_i))
16
+ end
17
+ end
18
+
19
+ def type_priority
20
+ @type_priority ||= {:zip => 0, :tgz => 1, :tbz => 2, :dir => 3}[type]
21
+ end
22
+
23
+ def sort_by
24
+ @sort_by ||= [parts, type_priority]
25
+ end
26
+
27
+ include Comparable
28
+ def <=>(other)
29
+ sort_by <=> other.sort_by
30
+ end
31
+
32
+ def ===(other)
33
+ parts === other.parts
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,216 @@
1
+ require 'net/ftp'
2
+ require 'net/ftp/list'
3
+ require 'tempfile'
4
+ require 'tmpdir'
5
+ require 'timeout'
6
+
7
+ module Doc
8
+ class Configurator
9
+ class Ruby
10
+ module Source
11
+ def by_binary(binary, update)
12
+ binary = (binary || 'ruby').to_s
13
+ version = VersionSpecifier.new(`#{binary} -e 'print "\#{RUBY_VERSION}-p\#{RUBY_PATCHLEVEL}"'`)
14
+ if $?.success?
15
+ if version.valid?
16
+ by_version(version, update)
17
+ else
18
+ raise "invalid version from `#{binary}`: #{version.to_s.inspect}"
19
+ end
20
+ else
21
+ raise "failed to get version from `#{binary}`"
22
+ end
23
+ end
24
+
25
+ def by_version(version, update)
26
+ version = VersionSpecifier.new(version)
27
+ if version.valid?
28
+ if (update && !version.full_version?) || !path_for_version(version)
29
+ download_version(version)
30
+ end
31
+ if path = path_for_version(version)
32
+ path.directory? ? from_dir(path) : from_archive(path)
33
+ else
34
+ raise "can't get ruby #{version}"
35
+ end
36
+ else
37
+ raise "version should be in format X.Y, X.Y.Z or X.Y.Z-pPPP, download archive if you need release candidate or other version"
38
+ end
39
+ end
40
+
41
+ def from_archive(path)
42
+ path = FSPath(path)
43
+ if path.file?
44
+ if path.basename.to_s =~ /^(.*)(?i:\.(tar\.(?:gz|bz2)|tgz|tbz|zip))$/
45
+ dir, extension = sources_dir / $1, $2.downcase
46
+ unless dir.exist?
47
+ Dir.mktmpdir 'ruby' do |d|
48
+ begin
49
+ case extension
50
+ when 'tbz', 'tar.bz2'
51
+ Command.run('tar', '-xjf', path, '-C', d)
52
+ when 'tgz', 'tar.gz'
53
+ Command.run('tar', '-xzf', path, '-C', d)
54
+ when 'zip'
55
+ Command.run('unzip', '-q', path, '-d', d)
56
+ end
57
+
58
+ children = FSPath(d).children
59
+ if children.length == 1
60
+ children.first.rename(dir)
61
+ else
62
+ dir.mkpath
63
+ FileUtils.mv children, dir
64
+ end
65
+ rescue SystemExit => e
66
+ raise "got #{e} trying to extract archive"
67
+ end
68
+ end
69
+ end
70
+ from_dir(dir)
71
+ else
72
+ raise "#{path} doesn't seem to be archive of known type"
73
+ end
74
+ else
75
+ raise "#{path} is not a file"
76
+ end
77
+ end
78
+
79
+ def from_dir(path)
80
+ path = FSPath(path)
81
+ if path.directory?
82
+ version_path = path / 'version.h'
83
+ if version_path.file? && version_path.read['RUBY_VERSION']
84
+ dot_document_path = path / '.document'
85
+ if dot_document_path.size?
86
+ path.expand_path
87
+ else
88
+ raise "#{path} doesn't contain .document file or it is empty"
89
+ end
90
+ else
91
+ raise "#{path} doesn't contain version.h file or it has no RUBY_VERSION in it"
92
+ end
93
+ else
94
+ raise "#{path} is not a directory"
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def path_for_version(version)
101
+ if path_info = PathInfo.latest_matching(version, sources_dir.children)
102
+ path_info.path
103
+ end
104
+ end
105
+
106
+ def tempfile_for(dst)
107
+ Tempfile.open 'ruby' do |f|
108
+ path = FSPath(f.path)
109
+ yield path
110
+ path.rename(dst)
111
+ end
112
+ end
113
+
114
+ def latest_version_from_tag_list(command, regexp, version)
115
+ IO.popen(command, &:readlines).map do |line|
116
+ if line.strip =~ regexp
117
+ VersionSpecifier.new($1)
118
+ end
119
+ end.compact.sort.grep(version).last
120
+ end
121
+
122
+ def tmpdir_for_latest_version_from_tag_list(command, regexp, version)
123
+ if tag_version = latest_version_from_tag_list(command, regexp, version)
124
+ unless path_for_version(tag_version)
125
+ Dir.mktmpdir 'ruby' do |d|
126
+ tmp_dir = FSPath(d) / tag_version.dir_name
127
+ if yield(tmp_dir, tag_version)
128
+ tmp_dir.rename(sources_dir / tag_version.dir_name)
129
+ end
130
+ end
131
+ end
132
+ path_for_version(tag_version)
133
+ end
134
+ end
135
+
136
+ def download_version(version)
137
+ download_version_via(:git_pull, version) || download_version_via(:git_tarball, version)
138
+ # git doesn't always have latest version so fast download from there, but verify latest from ftp/svn
139
+ download_version_via(:ftp, version) || download_version_via(:svn, version)
140
+ end
141
+
142
+ def download_version_via(type, version)
143
+ $stderr.puts "Checking/downloading ruby #{version} via #{type}"
144
+ Timeout.timeout(90) do
145
+ send("download_version_via_#{type}", version)
146
+ end
147
+ end
148
+
149
+ FTP_HOST = 'ftp.ruby-lang.org'
150
+ SVN_TAGS_URL = 'http://svn.ruby-lang.org/repos/ruby/tags/'
151
+ SVN_TAG_LIST_COMMAND = "svn list --non-interactive #{SVN_TAGS_URL}"
152
+ SVN_TAG_REGEXP = /^(v\d+(?:_\d+){3})\/$/
153
+ GIT_BARE_URL = 'github.com/ruby/ruby'
154
+ GIT_URL = "git://#{GIT_BARE_URL}.git"
155
+ GIT_TAG_LIST_COMMAND = "git ls-remote -t #{GIT_URL}"
156
+ GIT_TAG_REGEXP = /^.*\t(refs\/tags\/v\d+(?:_\d+){3})$/
157
+
158
+ def download_version_via_ftp(version)
159
+ Net::FTP.open(FTP_HOST) do |ftp|
160
+ ftp.passive = true
161
+ ftp.login
162
+
163
+ ftp_dir = FSPath('/pub/ruby') / version.parts[0, 2].join('.')
164
+ entries = ftp.list(ftp_dir.to_s).map{ |e| Net::FTP::List.parse(e) }
165
+ if archive_info = PathInfo.latest_matching(version, entries.select(&:file?))
166
+ unless path_for_version(archive_info)
167
+ entry = archive_info.path
168
+ tempfile_for(sources_dir / entry.basename) do |f|
169
+ ftp.getbinaryfile(ftp_dir / entry.basename, f)
170
+ end
171
+ end
172
+ path_for_version(archive_info)
173
+ end
174
+ end
175
+ end
176
+
177
+ def download_version_via_svn(version)
178
+ tmpdir_for_latest_version_from_tag_list(SVN_TAG_LIST_COMMAND, SVN_TAG_REGEXP, version) do |tmp_dir, tag_version|
179
+ Command.run *%W[svn export -q --non-interactive #{SVN_TAGS_URL}#{tag_version}/ #{tmp_dir}]
180
+ $?.success?
181
+ end
182
+ rescue SystemExit
183
+ end
184
+
185
+ def download_version_via_git_pull(version)
186
+ tmpdir_for_latest_version_from_tag_list(GIT_TAG_LIST_COMMAND, GIT_TAG_REGEXP, version) do |tmp_dir, tag_version|
187
+ tmp_dir.mkpath
188
+ Dir.chdir(tmp_dir) do
189
+ Command.run 'git init -q'
190
+ Command.run *%W[git pull -q --depth=1 #{GIT_URL} #{tag_version}]
191
+ (tmp_dir / '.git').rmtree
192
+ end
193
+ $?.success?
194
+ end
195
+ rescue SystemExit
196
+ end
197
+
198
+ def download_version_via_git_tarball(version)
199
+ if tag_version = latest_version_from_tag_list(GIT_TAG_LIST_COMMAND, GIT_TAG_REGEXP, version)
200
+ unless path_for_version(tag_version)
201
+ tempfile_for(sources_dir / "#{tag_version.dir_name}.tgz") do |f|
202
+ Command.run *%W[
203
+ curl -L -s
204
+ http://#{GIT_BARE_URL}/tarball/#{tag_version.to_s.split('/').last}
205
+ -o #{f}
206
+ ]
207
+ end
208
+ end
209
+ path_for_version(tag_version)
210
+ end
211
+ rescue SystemExit
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,32 @@
1
+ require 'net/http'
2
+ require 'tempfile'
3
+ require 'yaml'
4
+
5
+ module Doc
6
+ class Configurator
7
+ class Ruby
8
+ module Stdlib
9
+ STDLIB_CONFIG_URL = 'http://stdlib-doc.rubyforge.org/svn/trunk/data/gendoc.yaml'
10
+
11
+ def stdlib_config(update)
12
+ if update || !read_stdlib_config
13
+ download_stdlib_config
14
+ end
15
+ read_stdlib_config
16
+ end
17
+
18
+ def stdlib_config_path
19
+ sources_dir / 'stdlib-config.yaml'
20
+ end
21
+
22
+ def read_stdlib_config
23
+ YAML.load_file stdlib_config_path if stdlib_config_path.readable?
24
+ end
25
+
26
+ def download_stdlib_config
27
+ stdlib_config_path.write(Net::HTTP.get(URI.parse(STDLIB_CONFIG_URL)))
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,39 @@
1
+ module Doc
2
+ class Configurator
3
+ class Ruby
4
+ class VersionSpecifier
5
+ attr_reader :str, :parts
6
+ alias_method :to_s, :str
7
+ def initialize(o)
8
+ @str = o.to_s
9
+ @parts = str.scan(/\d+/).map(&:to_i)
10
+ end
11
+
12
+ def valid?
13
+ str =~ /^\d+\.\d+(?:\.\d+(?:-p\d+)?)?$/
14
+ end
15
+
16
+ def full_version?
17
+ valid? && parts.length == 4
18
+ end
19
+
20
+ def dir_name
21
+ 'ruby-%d.%d.%d-p%d' % parts
22
+ end
23
+
24
+ include Comparable
25
+ def <=>(other)
26
+ parts <=> other.parts
27
+ end
28
+
29
+ def ===(other)
30
+ if other.respond_to?(:parts)
31
+ parts == other.parts[0, parts.length]
32
+ else
33
+ str === other
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,75 @@
1
+ require 'fspath'
2
+
3
+ def glob_require(*globs)
4
+ globs.each do |glob|
5
+ $LOAD_PATH.each do |load_dir|
6
+ FSPath.glob(File.join(load_dir, glob)).each do |path|
7
+ require path.relative_path_from(FSPath(load_dir)).to_s
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ class Integer
14
+ def minutes
15
+ 60 * self
16
+ end
17
+ alias_method :minute, :minutes
18
+
19
+ def hours
20
+ 60 * minutes
21
+ end
22
+ alias_method :hour, :hours
23
+
24
+ def days
25
+ 24 * hours
26
+ end
27
+ alias_method :day, :days
28
+
29
+ def weeks
30
+ 7 * days
31
+ end
32
+ alias_method :week, :weeks
33
+ end
34
+
35
+ class String
36
+ def underscore
37
+ split(/::/).map{ |part| part.split(/(?=[A-Z])/).join('_') }.join('/').downcase
38
+ end
39
+ end
40
+
41
+ class Module
42
+ def smart_autoload(*names)
43
+ names.each do |name|
44
+ autoload name, "#{self}::#{name}".underscore
45
+ end
46
+ end
47
+
48
+ def abstract_method(*names)
49
+ names.each do |name|
50
+ class_eval <<-RUBY, __FILE__, __LINE__
51
+ def #{name}(*_)
52
+ raise NotImplementedError.new("\#{self.class.name} has no implementation for method `#{name}`")
53
+ end
54
+ RUBY
55
+ end
56
+ end
57
+ end
58
+
59
+ class Array
60
+ def select!(&block)
61
+ replace(select(&block))
62
+ end
63
+ end
64
+
65
+ class FSPath
66
+ def touch(atime = nil, mtime = nil)
67
+ open('w'){} unless exist?
68
+ utime(atime ||= Time.now, mtime || atime)
69
+ end
70
+
71
+ def rmtree_verbose
72
+ require 'fileutils'
73
+ FileUtils.rm_r(@path, :verbose => true)
74
+ end
75
+ end
@@ -0,0 +1,62 @@
1
+ require 'fspath'
2
+ require 'progress'
3
+
4
+ module Doc
5
+ class Documentor
6
+ attr_reader :title, :min_update_interval, :clean_after, :configurators
7
+ attr_reader :base_dir, :sources_dir, :docs_dir, :public_dir
8
+ def initialize(*arguments, &block)
9
+ config = RootConfig.new(self, *arguments, &block)
10
+ config.check_options!([], [:title, :min_update_interval, :clean_after, :public_dir])
11
+
12
+ @title = config[:title] || 'ruby documentation'
13
+ @min_update_interval = config[:min_update_interval] || 1.hour
14
+ @clean_after = config[:clean_after]
15
+ @configurators = config.configurators
16
+
17
+ @base_dir = FSPath('.').expand_path
18
+ @sources_dir = base_dir / 'sources'
19
+ @docs_dir = base_dir / 'docs'
20
+ if config[:public_dir]
21
+ config[:public_dir] = FSPath(config[:public_dir]).cleanpath.to_s
22
+ if config[:public_dir] == '.'
23
+ raise ConfigError.new(self, "can't use base dir: #{config[:public_dir].inspect}")
24
+ end
25
+ if config[:public_dir].split(FSPath::SEPARATOR_PAT).include?('..')
26
+ raise ConfigError.new(self, ".. in public_dir: #{config[:public_dir].inspect}")
27
+ end
28
+ end
29
+ @public_dir = base_dir / (config[:public_dir] || 'public')
30
+ end
31
+
32
+ def config(update = false)
33
+ last_updated_path = base_dir / '.last_updated'
34
+ update ||= last_updated_path.exist? ? (Time.now > last_updated_path.mtime + min_update_interval) : true
35
+
36
+ configurators.with_progress('config/update').each do |configurator|
37
+ configurator.configure(update)
38
+ end
39
+ last_updated_path.touch if update
40
+
41
+ RootMerger.new(self, {
42
+ :title => title,
43
+ :tasks => configurators.with_progress('tasks').map(&:tasks).flatten
44
+ })
45
+ end
46
+
47
+ def build(update = false)
48
+ started = Time.now
49
+ root_task = config(update)
50
+
51
+ root_task.run
52
+
53
+ if clean_after
54
+ (sources_dir.directory? ? sources_dir.children : [] + docs_dir.children).each do |dir|
55
+ if started - dir.mtime > clean_after
56
+ dir.rmtree_verbose
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end