Skip to content

Commit

Permalink
Refactor Docs::Generator source link generation (crystal-lang#9119)
Browse files Browse the repository at this point in the history
* Replace Docs::Generator#repository_name with ProjectInfo#name

* Add ProjectInfo#refname

* Add ProjectInfo#source_url_pattern

* Refactor docs generator to use ProjectInfo#source_url

* Add CLI options for refname and url pattern
  • Loading branch information
straight-shoota authored May 13, 2020
1 parent 32a6282 commit 2898564
Show file tree
Hide file tree
Showing 11 changed files with 342 additions and 190 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ compiler_spec: $(O)/compiler_spec ## Run compiler specs

.PHONY: docs
docs: ## Generate standard library documentation
$(BUILD_PATH) ./bin/crystal docs src/docs_main.cr $(DOCS_OPTIONS) --project-name=Crystal --project-version=$(CRYSTAL_VERSION)
$(BUILD_PATH) ./bin/crystal docs src/docs_main.cr $(DOCS_OPTIONS) --project-name=Crystal --project-version=$(CRYSTAL_VERSION) --source-refname=$(CRYSTAL_CONFIG_BUILD_COMMIT)

.PHONY: crystal
crystal: $(O)/crystal ## Build the compiler
Expand Down
24 changes: 24 additions & 0 deletions man/crystal.1
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,36 @@ Options:
.Pp
.It Fl -project-name Ar NAME
Set the project name. The default value is extracted from shard.yml if available.

In case no default can be found, this option is mandatory.
.It Fl -project-version Ar VERSION
Set the project version. The default value is extracted from current git commit or shard.yml if available.

In case no default can be found, this option is mandatory.
.It Fl -json-config-url Ar URL
Set the URL pointing to a config file (used for discovering versions).
.It Fl -source-refname Ar REFNAME
Set source refname (e.g. git tag, commit hash). The default value is extracted from current git commit if available.

If this option is missing and can't be automatically determined, the generator can't produce source code links.
.It Fl -source-url-pattern Ar URL
Set URL pattern for source code links. The default value is extracted from git remotes ("origin" or first one) if available and the provider's URL pattern is recognized.

.Pp
Supported replacement tags:
.Pp
.Bl -tag -width "%{refname}" -compact
.It Sy %{refname}
commit reference
.It Sy %{path}
path to source file inside the repository
.It Sy %{filename}
basename of the source file
.It Sy %{line}
line number
.El
.Pp
If this option is missing and can't be automatically determined, the generator can't produce source code links.
.It Fl o Ar DIR, Fl -output Ar DIR
Set the output directory (default: ./docs).
.It Fl b Ar URL, Fl -sitemap-base-url Ar URL
Expand Down
162 changes: 140 additions & 22 deletions spec/compiler/crystal/tools/doc/project_info_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ describe Crystal::Doc::ProjectInfo do

describe "#fill_with_defaults" do
it "empty folder" do
assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new(nil, nil))
assert_with_defaults(ProjectInfo.new("foo", "1.0"), ProjectInfo.new("foo", "1.0"))
assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new(nil, nil, refname: nil))
assert_with_defaults(ProjectInfo.new("foo", "1.0"), ProjectInfo.new("foo", "1.0", refname: nil))
end

context "with shard.yml" do
Expand All @@ -34,17 +34,17 @@ describe Crystal::Doc::ProjectInfo do
end

it "no git" do
assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", "1.0"))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0"))
assert_with_defaults(ProjectInfo.new(nil, "2.0"), ProjectInfo.new("foo", "2.0"))
assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", "1.0", refname: nil))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0", refname: nil))
assert_with_defaults(ProjectInfo.new(nil, "2.0"), ProjectInfo.new("foo", "2.0", refname: nil))
end

it "git but no commit" do
run_git "init"

assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", nil))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0"))
assert_with_defaults(ProjectInfo.new(nil, "2.0"), ProjectInfo.new("foo", "2.0"))
assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", nil, refname: nil))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0", refname: nil))
assert_with_defaults(ProjectInfo.new(nil, "2.0"), ProjectInfo.new("foo", "2.0", refname: nil))
end

it "git tagged version" do
Expand All @@ -53,8 +53,9 @@ describe Crystal::Doc::ProjectInfo do
run_git "commit -m 'Initial commit' --no-gpg-sign"
run_git "tag v3.0"

assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", "3.0"))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0"))
assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", "3.0", refname: "v3.0"))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0", refname: "v3.0"))
assert_with_defaults(ProjectInfo.new("bar", "2.0", refname: "12345"), ProjectInfo.new("bar", "2.0", refname: "12345"))
end

it "git tagged version dirty" do
Expand All @@ -64,19 +65,21 @@ describe Crystal::Doc::ProjectInfo do
run_git "tag v3.0"
File.write("foo.txt", "bar")

assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", "3.0-dev"))
assert_with_defaults(ProjectInfo.new(nil, "1.1"), ProjectInfo.new("foo", "1.1"))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0"))
assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", "3.0-dev", refname: nil))
assert_with_defaults(ProjectInfo.new(nil, "1.1"), ProjectInfo.new("foo", "1.1", refname: nil))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0", refname: nil))
end

it "git non-tagged commit" do
run_git "init"
run_git "add shard.yml"
run_git "commit -m 'Initial commit' --no-gpg-sign"
commit_sha = `git rev-parse HEAD`.chomp

assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", "master"))
assert_with_defaults(ProjectInfo.new(nil, "1.1"), ProjectInfo.new("foo", "1.1"))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0"))
assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", "master", refname: commit_sha))
assert_with_defaults(ProjectInfo.new(nil, "1.1"), ProjectInfo.new("foo", "1.1", refname: commit_sha))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0", refname: commit_sha))
assert_with_defaults(ProjectInfo.new("bar", "2.0", refname: "12345"), ProjectInfo.new("bar", "2.0", refname: "12345"))
end

it "git non-tagged commit dirty" do
Expand All @@ -85,9 +88,20 @@ describe Crystal::Doc::ProjectInfo do
run_git "commit -m 'Initial commit' --no-gpg-sign"
File.write("foo.txt", "bar")

assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", "master-dev"))
assert_with_defaults(ProjectInfo.new(nil, "1.1"), ProjectInfo.new("foo", "1.1"))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0"))
assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", "master-dev", refname: nil))
assert_with_defaults(ProjectInfo.new(nil, "1.1"), ProjectInfo.new("foo", "1.1", refname: nil))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0", refname: nil))
end

it "git with remote" do
run_git "init"
run_git "remote add origin [email protected]:foo/bar"

url_pattern = "https://github.com/foo/bar/blob/%{refname}/%{path}#L%{line}"
assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", nil, refname: nil, source_url_pattern: url_pattern))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0", refname: nil, source_url_pattern: url_pattern))
assert_with_defaults(ProjectInfo.new(nil, "2.0"), ProjectInfo.new("foo", "2.0", refname: nil, source_url_pattern: url_pattern))
assert_with_defaults(ProjectInfo.new(nil, "2.0", source_url_pattern: "foo_bar"), ProjectInfo.new("foo", "2.0", refname: nil, source_url_pattern: "foo_bar"))
end
end

Expand All @@ -98,9 +112,10 @@ describe Crystal::Doc::ProjectInfo do
run_git "commit -m 'Remove shard.yml' --no-gpg-sign"
run_git "tag v4.0"

assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new(nil, "4.0"))
assert_with_defaults(ProjectInfo.new("foo", nil), ProjectInfo.new("foo", "4.0"))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0"))
assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new(nil, "4.0", refname: "v4.0"))
assert_with_defaults(ProjectInfo.new("foo", nil), ProjectInfo.new("foo", "4.0", refname: "v4.0"))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0", refname: "v4.0"))
assert_with_defaults(ProjectInfo.new("bar", "2.0", refname: "12345"), ProjectInfo.new("bar", "2.0", refname: "12345"))
end
end

Expand Down Expand Up @@ -149,6 +164,40 @@ describe Crystal::Doc::ProjectInfo do
ProjectInfo.find_git_version.should eq "0.1.0"
end

describe ".git_remote" do
it "no git workdir" do
ProjectInfo.git_remote.should be_nil
end

it "no remote" do
run_git "init"
ProjectInfo.git_remote.should be_nil
end

it "simple origin" do
run_git "init"
run_git "remote add origin https://example.com/foo.git"
ProjectInfo.git_remote.should eq "https://example.com/foo.git"
end

it "origin plus other" do
run_git "init"
run_git "remote add bar https://example.com/bar.git"
run_git "remote add origin https://example.com/foo.git"
run_git "remote add baz https://example.com/baz.git"
`git remote -v`
ProjectInfo.git_remote.should eq "https://example.com/foo.git"
end

it "no origin remote" do
run_git "init"
run_git "remote add bar https://example.com/bar.git"
run_git "remote add baz https://example.com/baz.git"
`git remote -v`
ProjectInfo.git_remote.should eq "https://example.com/bar.git"
end
end

describe ".read_shard_properties" do
it "no shard.yml" do
ProjectInfo.read_shard_properties.should eq({nil, nil})
Expand Down Expand Up @@ -202,4 +251,73 @@ describe Crystal::Doc::ProjectInfo do
ProjectInfo.read_shard_properties.should eq({nil, nil})
end
end

it ".find_source_url_pattern" do
ProjectInfo.find_source_url_pattern("no a uri").should be_nil
ProjectInfo.find_source_url_pattern("[email protected]:foo/bar").should be_nil
ProjectInfo.find_source_url_pattern("http://example.com/foo/bar").should be_nil

ProjectInfo.find_source_url_pattern("[email protected]:foo/bar/").should eq "https://github.com/foo/bar/blob/%{refname}/%{path}#L%{line}"
ProjectInfo.find_source_url_pattern("[email protected]:foo/bar.git").should eq "https://github.com/foo/bar.git/blob/%{refname}/%{path}#L%{line}"

ProjectInfo.find_source_url_pattern("[email protected]:foo/bar").should eq "https://github.com/foo/bar/blob/%{refname}/%{path}#L%{line}"
ProjectInfo.find_source_url_pattern("http://github.com/foo/bar").should eq "https://github.com/foo/bar/blob/%{refname}/%{path}#L%{line}"
ProjectInfo.find_source_url_pattern("https://github.com/foo/bar").should eq "https://github.com/foo/bar/blob/%{refname}/%{path}#L%{line}"
ProjectInfo.find_source_url_pattern("http://www.github.com/foo/bar").should eq "https://github.com/foo/bar/blob/%{refname}/%{path}#L%{line}"
ProjectInfo.find_source_url_pattern("https://www.github.com/foo/bar").should eq "https://github.com/foo/bar/blob/%{refname}/%{path}#L%{line}"

ProjectInfo.find_source_url_pattern("https://github.com/foo/bar.git").should eq "https://github.com/foo/bar.git/blob/%{refname}/%{path}#L%{line}"
ProjectInfo.find_source_url_pattern("https://github.com/foo/bar.cr").should eq "https://github.com/foo/bar.cr/blob/%{refname}/%{path}#L%{line}"
ProjectInfo.find_source_url_pattern("https://github.com/foo/bar.cr.git").should eq "https://github.com/foo/bar.cr.git/blob/%{refname}/%{path}#L%{line}"

ProjectInfo.find_source_url_pattern("[email protected]:foo/bar").should eq "https://gitlab.com/foo/bar/blob/%{refname}/%{path}#L%{line}"
ProjectInfo.find_source_url_pattern("http://gitlab.com/foo/bar").should eq "https://gitlab.com/foo/bar/blob/%{refname}/%{path}#L%{line}"

ProjectInfo.find_source_url_pattern("[email protected]:foo/bar").should eq "https://bitbucket.com/foo/bar/src/%{refname}/%{path}#%{filename}-%{line}"
ProjectInfo.find_source_url_pattern("http://bitbucket.com/foo/bar").should eq "https://bitbucket.com/foo/bar/src/%{refname}/%{path}#%{filename}-%{line}"

ProjectInfo.find_source_url_pattern("[email protected]:~foo/bar").should eq "https://git.sr.ht/~foo/bar/tree/%{refname}/%{path}#L%{line}"
ProjectInfo.find_source_url_pattern("http://git.sr.ht/~foo/bar").should eq "https://git.sr.ht/~foo/bar/tree/%{refname}/%{path}#L%{line}"
end

describe "#source_url" do
it "fails if refname is missing" do
location = Crystal::Doc::RelativeLocation.new("foo/bar.baz", 42)
info = ProjectInfo.new("test", "v1.0", refname: nil, source_url_pattern: "http://git.example.com/test.git/src/%{refname}/%{path}#L%{line}")
info.source_url(location).should be_nil
end

it "fails if pattern is missing" do
location = Crystal::Doc::RelativeLocation.new("foo/bar.baz", 42)
info = ProjectInfo.new("test", "v1.0", refname: "master")
info.source_url(location).should be_nil
end

it "builds url" do
info = ProjectInfo.new("test", "v1.0", refname: "master", source_url_pattern: "http://git.example.com/test.git/src/%{refname}/%{path}#L%{line}")
location = Crystal::Doc::RelativeLocation.new("foo/bar.baz", 42)
info.source_url(location).should eq "http://git.example.com/test.git/src/master/foo/bar.baz#L42"
end

it "returns nil for empty pattern" do
info = ProjectInfo.new("test", "v1.0", refname: "master", source_url_pattern: "")
location = Crystal::Doc::RelativeLocation.new("foo/bar.baz", 42)
info.source_url(location).should be_nil
end

it "fails if pattern is missing" do
location = Crystal::Doc::RelativeLocation.new("foo/bar.baz", 42)
info = ProjectInfo.new("test", "v1.0")
info.refname = "master"
info.source_url(location).should be_nil
end

it "builds url" do
info = ProjectInfo.new("test", "v1.0")
info.refname = "master"
info.source_url_pattern = "http://git.example.com/test.git/src/%{refname}/%{path}#L%{line}"
location = Crystal::Doc::RelativeLocation.new("foo/bar.baz", 42)
info.source_url(location).should eq "http://git.example.com/test.git/src/master/foo/bar.baz#L42"
end
end
end
45 changes: 0 additions & 45 deletions spec/compiler/crystal/tools/doc_spec.cr
Original file line number Diff line number Diff line change
@@ -1,51 +1,6 @@
require "../../../spec_helper"

private def assert_matches_pattern(url, **options)
match = Crystal::Doc::Generator::GIT_REMOTE_PATTERNS.each_key.compact_map(&.match(url)).first?
if match
options.each { |k, v| match[k.to_s].should eq(v) }
end
end

describe Crystal::Doc::Generator do
describe "GIT_REMOTE_PATTERNS" do
it "matches github repos" do
assert_matches_pattern "https://www.github.com/foo/bar", user: "foo", repo: "bar"
assert_matches_pattern "http://www.github.com/foo/bar", user: "foo", repo: "bar"
assert_matches_pattern "http://github.com/foo/bar", user: "foo", repo: "bar"

assert_matches_pattern "https://github.com/foo/bar", user: "foo", repo: "bar"
assert_matches_pattern "https://github.com/foo/bar.git", user: "foo", repo: "bar"
assert_matches_pattern "https://github.com/foo/bar.cr", user: "foo", repo: "bar.cr"
assert_matches_pattern "https://github.com/foo/bar.cr.git", user: "foo", repo: "bar.cr"

assert_matches_pattern "origin\thttps://github.com/foo/bar.cr.git (fetch)\n", user: "foo", repo: "bar.cr"
assert_matches_pattern "origin\t[email protected]/foo/bar.cr.git (fetch)\n", user: "foo", repo: "bar.cr"

assert_matches_pattern "https://github.com/fOO-Bar/w00den-baRK.ab.cd", user: "fOO-Bar", repo: "w00den-baRK.ab.cd"
assert_matches_pattern "https://github.com/fOO-Bar/w00den-baRK.ab.cd.git", user: "fOO-Bar", repo: "w00den-baRK.ab.cd"
assert_matches_pattern "https://github.com/foo_bar/_baz-buzz.cx", user: "foo_bar", repo: "_baz-buzz.cx"
end

it "matches gitlab repos" do
assert_matches_pattern "https://www.gitlab.com/foo/bar", user: "foo", repo: "bar"
assert_matches_pattern "http://www.gitlab.com/foo/bar", user: "foo", repo: "bar"
assert_matches_pattern "http://gitlab.com/foo/bar", user: "foo", repo: "bar"

assert_matches_pattern "https://gitlab.com/foo/bar", user: "foo", repo: "bar"
assert_matches_pattern "https://gitlab.com/foo/bar.git", user: "foo", repo: "bar"
assert_matches_pattern "https://gitlab.com/foo/bar.cr", user: "foo", repo: "bar.cr"
assert_matches_pattern "https://gitlab.com/foo/bar.cr.git", user: "foo", repo: "bar.cr"

assert_matches_pattern "origin\thttps://gitlab.com/foo/bar.cr.git (fetch)\n", user: "foo", repo: "bar.cr"
assert_matches_pattern "origin\t[email protected]/foo/bar.cr.git (fetch)\n", user: "foo", repo: "bar.cr"

assert_matches_pattern "https://gitlab.com/fOO-Bar/w00den-baRK.ab.cd", user: "fOO-Bar", repo: "w00den-baRK.ab.cd"
assert_matches_pattern "https://gitlab.com/fOO-Bar/w00den-baRK.ab.cd.git", user: "fOO-Bar", repo: "w00den-baRK.ab.cd"
assert_matches_pattern "https://gitlab.com/foo_bar/_baz-buzz.cx", user: "foo_bar", repo: "_baz-buzz.cx"
end
end

describe ".anchor_link" do
it "generates the correct anchor link" do
Crystal::Doc.anchor_link("anchor").should eq(
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/crystal/command/docs.cr
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ class Crystal::Command
project_info.version = value
end

opts.on("--source-refname=REFNAME", "Set source refname (e.g. git tag, commit hash)") do |value|
project_info.refname = value
end

opts.on("--source-url-pattern=REFNAME", "Set URL pattern for source code links") do |value|
project_info.source_url_pattern = value
end

opts.on("--output=DIR", "-o DIR", "Set the output directory (default: #{output_directory})") do |value|
output_directory = value
end
Expand Down
Loading

0 comments on commit 2898564

Please sign in to comment.