doc 0.0.0.0 → 0.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.
@@ -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