milestoner 16.2.1 → 17.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -1
  3. data/README.adoc +274 -47
  4. data/lib/milestoner/builders/container.rb +15 -0
  5. data/lib/milestoner/builders/import.rb +9 -0
  6. data/lib/milestoner/builders/stream.rb +29 -0
  7. data/lib/milestoner/builders/web.rb +41 -0
  8. data/lib/milestoner/cli/actions/build/format.rb +24 -0
  9. data/lib/milestoner/cli/actions/build/label.rb +26 -0
  10. data/lib/milestoner/cli/actions/build/layout.rb +36 -0
  11. data/lib/milestoner/cli/actions/build/root.rb +25 -0
  12. data/lib/milestoner/cli/actions/build/version.rb +31 -0
  13. data/lib/milestoner/cli/actions/cache/create.rb +35 -0
  14. data/lib/milestoner/cli/actions/cache/delete.rb +34 -0
  15. data/lib/milestoner/cli/actions/cache/find.rb +34 -0
  16. data/lib/milestoner/cli/actions/cache/info.rb +29 -0
  17. data/lib/milestoner/cli/actions/cache/list.rb +33 -0
  18. data/lib/milestoner/cli/actions/publish.rb +9 -5
  19. data/lib/milestoner/cli/commands/build.rb +55 -0
  20. data/lib/milestoner/cli/commands/cache.rb +27 -0
  21. data/lib/milestoner/cli/shell.rb +3 -2
  22. data/lib/milestoner/commits/categorizer.rb +21 -29
  23. data/lib/milestoner/commits/collector.rb +18 -0
  24. data/lib/milestoner/commits/enricher.rb +52 -0
  25. data/lib/milestoner/commits/enrichers/author.rb +26 -0
  26. data/lib/milestoner/commits/enrichers/body.rb +28 -0
  27. data/lib/milestoner/commits/enrichers/colleague.rb +33 -0
  28. data/lib/milestoner/commits/enrichers/container.rb +25 -0
  29. data/lib/milestoner/commits/enrichers/format.rb +23 -0
  30. data/lib/milestoner/commits/enrichers/import.rb +11 -0
  31. data/lib/milestoner/commits/enrichers/issue.rb +28 -0
  32. data/lib/milestoner/commits/enrichers/milestone.rb +24 -0
  33. data/lib/milestoner/commits/enrichers/note.rb +28 -0
  34. data/lib/milestoner/commits/enrichers/review.rb +23 -0
  35. data/lib/milestoner/commits/enrichers/uri.rb +14 -0
  36. data/lib/milestoner/commits/versioner.rb +48 -0
  37. data/lib/milestoner/configuration/contract.rb +29 -3
  38. data/lib/milestoner/configuration/defaults.yml +31 -8
  39. data/lib/milestoner/configuration/model.rb +24 -1
  40. data/lib/milestoner/configuration/transformers/build/root.rb +21 -0
  41. data/lib/milestoner/configuration/transformers/build/template_paths.rb +35 -0
  42. data/lib/milestoner/configuration/transformers/citations/description.rb +37 -0
  43. data/lib/milestoner/configuration/transformers/citations/label.rb +37 -0
  44. data/lib/milestoner/configuration/transformers/gems/description.rb +36 -0
  45. data/lib/milestoner/configuration/transformers/gems/label.rb +36 -0
  46. data/lib/milestoner/configuration/transformers/gems/name.rb +36 -0
  47. data/lib/milestoner/configuration/transformers/gems/uri.rb +36 -0
  48. data/lib/milestoner/configuration/transformers/project/author.rb +33 -0
  49. data/lib/milestoner/configuration/transformers/project/generator.rb +28 -0
  50. data/lib/milestoner/configuration/transformers/project/label.rb +23 -0
  51. data/lib/milestoner/configuration/transformers/project/name.rb +19 -0
  52. data/lib/milestoner/configuration/transformers/project/version.rb +31 -0
  53. data/lib/milestoner/configuration/transformers/uri/avatar.rb +21 -0
  54. data/lib/milestoner/configuration/transformers/uri/commit.rb +24 -0
  55. data/lib/milestoner/configuration/transformers/uri/profile.rb +21 -0
  56. data/lib/milestoner/configuration/transformers/uri/review.rb +24 -0
  57. data/lib/milestoner/configuration/transformers/uri/tracker.rb +24 -0
  58. data/lib/milestoner/container.rb +51 -9
  59. data/lib/milestoner/models/commit.rb +42 -0
  60. data/lib/milestoner/models/link.rb +12 -0
  61. data/lib/milestoner/models/user.rb +12 -0
  62. data/lib/milestoner/renderers/asciidoc.rb +20 -0
  63. data/lib/milestoner/renderers/markdown.rb +20 -0
  64. data/lib/milestoner/renderers/universal.rb +26 -0
  65. data/lib/milestoner/tags/creator.rb +31 -23
  66. data/lib/milestoner/tags/publisher.rb +5 -5
  67. data/lib/milestoner/tags/pusher.rb +9 -3
  68. data/lib/milestoner/templates/layouts/page.html.erb +29 -0
  69. data/lib/milestoner/templates/layouts/page.stream.erb +1 -0
  70. data/lib/milestoner/templates/milestones/_avatar.html.erb +5 -0
  71. data/lib/milestoner/templates/milestones/_commit.html.erb +104 -0
  72. data/lib/milestoner/templates/milestones/_commit.stream.erb +1 -0
  73. data/lib/milestoner/templates/milestones/_icon.html.erb +6 -0
  74. data/lib/milestoner/templates/milestones/_profile.html.erb +5 -0
  75. data/lib/milestoner/templates/milestones/show.html.erb +46 -0
  76. data/lib/milestoner/templates/milestones/show.stream.erb +5 -0
  77. data/lib/milestoner/templates/public/page.css.erb +356 -0
  78. data/lib/milestoner/views/context.rb +25 -0
  79. data/lib/milestoner/views/milestones/show.rb +46 -0
  80. data/lib/milestoner/views/parts/commit.rb +85 -0
  81. data/lib/milestoner.rb +1 -1
  82. data/milestoner.gemspec +17 -11
  83. data.tar.gz.sig +0 -0
  84. metadata +172 -26
  85. metadata.gz.sig +0 -0
  86. data/lib/milestoner/cli/actions/status.rb +0 -37
  87. data/lib/milestoner/presenters/commit.rb +0 -36
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/monads"
4
+ require "pathname"
5
+
6
+ module Milestoner
7
+ module Configuration
8
+ module Transformers
9
+ module URI
10
+ Review = lambda do |content, key = :review_uri|
11
+ owner, name, domain, uri = content.values_at :project_owner,
12
+ :project_name,
13
+ :review_domain,
14
+ key
15
+
16
+ return Dry::Monads::Success content unless uri
17
+
18
+ content[key] = format uri, domain:, owner:, name:, id: "%<id>s"
19
+ Dry::Monads::Success content
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/monads"
4
+ require "pathname"
5
+
6
+ module Milestoner
7
+ module Configuration
8
+ module Transformers
9
+ module URI
10
+ Tracker = lambda do |content, key = :tracker_uri|
11
+ owner, name, domain, uri = content.values_at :project_owner,
12
+ :project_name,
13
+ :tracker_domain,
14
+ key
15
+
16
+ return Dry::Monads::Success content unless uri
17
+
18
+ content[key] = format uri, domain:, owner:, name:, id: "%<id>s"
19
+ Dry::Monads::Success content
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -4,7 +4,9 @@ require "cogger"
4
4
  require "dry-container"
5
5
  require "etcher"
6
6
  require "gitt"
7
+ require "lode"
7
8
  require "runcom"
9
+ require "sanitize"
8
10
  require "spek"
9
11
 
10
12
  module Milestoner
@@ -12,21 +14,61 @@ module Milestoner
12
14
  module Container
13
15
  extend Dry::Container::Mixin
14
16
 
15
- register :configuration do
16
- self[:defaults].add_loader(Etcher::Loaders::YAML.new(self[:xdg_config].active))
17
+ namespace :xdg do
18
+ register(:cache, memoize: true) { Runcom::Cache.new "milestoner/database.store" }
19
+ register(:config, memoize: true) { Runcom::Config.new "milestoner/configuration.yml" }
20
+ end
21
+
22
+ register :cache, memoize: true do
23
+ # :nocov:
24
+ Lode.new self["xdg.cache"].passive do |config|
25
+ config.mode = :max
26
+ config.table = Lode::Tables::Value
27
+ config.register :users, model: Models::User, primary_key: :name
28
+ end
29
+ end
30
+
31
+ register :configuration, memoize: true do
32
+ self[:defaults].add_loader(Etcher::Loaders::YAML.new(self["xdg.config"].active))
17
33
  .then { |registry| Etcher.call registry }
18
34
  end
19
35
 
20
- register :defaults do
36
+ register :defaults, memoize: true do
21
37
  Etcher::Registry.new(contract: Configuration::Contract, model: Configuration::Model)
22
38
  .add_loader(Etcher::Loaders::YAML.new(self[:defaults_path]))
39
+ .add_transformer(Configuration::Transformers::Build::Root)
40
+ .add_transformer(Configuration::Transformers::Build::TemplatePaths.new)
41
+ .add_transformer(Configuration::Transformers::Gems::Label.new)
42
+ .add_transformer(Configuration::Transformers::Gems::Description.new)
43
+ .add_transformer(Configuration::Transformers::Gems::Name.new)
44
+ .add_transformer(Configuration::Transformers::Gems::URI.new)
45
+ .add_transformer(Configuration::Transformers::Citations::Label.new)
46
+ .add_transformer(Configuration::Transformers::Citations::Description.new)
47
+ .add_transformer(Configuration::Transformers::Project::Author.new)
48
+ .add_transformer(Configuration::Transformers::Project::Generator.new)
49
+ .add_transformer(Configuration::Transformers::Project::Label)
50
+ .add_transformer(Configuration::Transformers::Project::Name)
51
+ .add_transformer(Configuration::Transformers::Project::Version.new)
52
+ .add_transformer(Configuration::Transformers::URI::Avatar)
53
+ .add_transformer(Configuration::Transformers::URI::Commit)
54
+ .add_transformer(Configuration::Transformers::URI::Profile)
55
+ .add_transformer(Configuration::Transformers::URI::Review)
56
+ .add_transformer(Configuration::Transformers::URI::Tracker)
57
+ end
58
+
59
+ register :specification, memoize: true do
60
+ self[:spec_loader].call "#{__dir__}/../../milestoner.gemspec"
61
+ end
62
+
63
+ register :sanitizer, memoize: true do
64
+ -> content { Sanitize.fragment content, Sanitize::Config::BASIC }
23
65
  end
24
66
 
25
- register(:git) { Gitt::Repository.new }
26
- register(:defaults_path) { Pathname(__dir__).join("configuration/defaults.yml") }
27
- register(:xdg_config) { Runcom::Config.new "milestoner/configuration.yml" }
28
- register(:specification) { Spek::Loader.call "#{__dir__}/../../milestoner.gemspec" }
29
- register(:kernel) { Kernel }
30
- register(:logger) { Cogger.new id: :milestoner }
67
+ register(:spec_loader, memoize: true) { Spek::Loader.new }
68
+ register(:git, memoize: true) { Gitt::Repository.new }
69
+ register(:input, memoize: true) { self[:configuration].dup }
70
+ register(:defaults_path, memoize: true) { Pathname(__dir__).join("configuration/defaults.yml") }
71
+ register(:logger, memoize: true) { Cogger.new id: :milestoner }
72
+ register :kernel, Kernel
31
73
  end
32
74
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "gitt"
4
+
5
+ module Milestoner
6
+ module Models
7
+ COMMIT_COMMON_ATTRIBUTES = %i[
8
+ authored_at
9
+ body
10
+ deletions
11
+ files_changed
12
+ insertions
13
+ notes
14
+ sha
15
+ signature
16
+ subject
17
+ ].freeze
18
+
19
+ COMMIT_ENRICHED_ATTRIBUTES = %i[
20
+ author
21
+ collaborators
22
+ format
23
+ issue
24
+ milestone
25
+ review
26
+ signers
27
+ uri
28
+ ].freeze
29
+
30
+ # Represents an enriched commit.
31
+ Commit = Struct.new(*COMMIT_COMMON_ATTRIBUTES, *COMMIT_ENRICHED_ATTRIBUTES) do
32
+ include Gitt::Directable
33
+
34
+ def self.for(commit, **) = new(**commit.to_h.slice(*COMMIT_COMMON_ATTRIBUTES), **)
35
+
36
+ def initialize(**)
37
+ super
38
+ freeze
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Milestoner
4
+ module Models
5
+ # Represents a hyperlink.
6
+ Link = Data.define :id, :uri do
7
+ def initialize id: nil, uri: nil
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Milestoner
4
+ module Models
5
+ # Represents an external user.
6
+ User = Data.define :external_id, :handle, :name do
7
+ def initialize external_id: nil, handle: nil, name: nil
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "asciidoctor"
4
+
5
+ module Milestoner
6
+ module Renderers
7
+ # Renders ASCII Doc as HTML.
8
+ class Asciidoc
9
+ def initialize client: Asciidoctor
10
+ @client = client
11
+ end
12
+
13
+ def call(content) = client.convert content
14
+
15
+ private
16
+
17
+ attr_reader :client
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redcarpet"
4
+
5
+ module Milestoner
6
+ module Renderers
7
+ # Renders Markdown as HTML.
8
+ class Markdown
9
+ def initialize client: Redcarpet::Markdown.new(Redcarpet::Render::HTML.new)
10
+ @client = client
11
+ end
12
+
13
+ def call(content) = client.render content
14
+
15
+ private
16
+
17
+ attr_reader :client
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Milestoner
4
+ module Renderers
5
+ # The primary renderer for multiple input formats as HTML.
6
+ class Universal
7
+ include Import[:input]
8
+
9
+ DELEGATES = {asciidoc: Asciidoc.new, markdown: Markdown.new}.freeze
10
+
11
+ def initialize(delegates: DELEGATES, **)
12
+ super(**)
13
+ @delegates = delegates
14
+ @default_format = input.commit_format.to_sym
15
+ end
16
+
17
+ def call content, for: default_format
18
+ delegates.fetch(binding.local_variable_get(:for)).call content
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :delegates, :default_format
24
+ end
25
+ end
26
+ end
@@ -1,38 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "gitt"
4
+ require "refinements/string_io"
4
5
  require "versionaire"
5
6
 
6
7
  module Milestoner
7
8
  module Tags
8
9
  # Handles the creation of project repository tags.
9
10
  class Creator
10
- include Import[:git, :logger]
11
+ include Import[:input, :git, :logger]
11
12
 
13
+ using Refinements::StringIO
12
14
  using Versionaire::Cast
13
15
 
14
- def initialize(categorizer: Commits::Categorizer.new, presenter: Presenters::Commit, **)
16
+ def initialize(
17
+ collector: Commits::Collector.new,
18
+ builder: Builders::Stream.new(kernel: StringIO.new),
19
+ **
20
+ )
21
+ @collector = collector
22
+ @builder = builder
15
23
  super(**)
16
- @categorizer = categorizer
17
- @presenter = presenter
18
24
  end
19
25
 
20
- def call configuration = Container[:configuration]
21
- return false if local? configuration
22
- fail Error, "Unable to tag without commits." if categorizer.call.empty?
26
+ def call override = nil
27
+ version = compute_version override
23
28
 
24
- create configuration
25
- rescue Versionaire::Error => error
26
- raise Error, error.message
29
+ return false if local? version
30
+ fail Error, "Unable to tag without commits." if collector.call.value_or([]).empty?
31
+
32
+ create version
27
33
  end
28
34
 
29
35
  private
30
36
 
31
- attr_reader :categorizer, :presenter
37
+ attr_reader :collector, :builder
32
38
 
33
- def local? configuration
34
- version = Version configuration.version
39
+ def compute_version value
40
+ Version value || input.project_version
41
+ rescue Versionaire::Error => error
42
+ raise Error, error
43
+ end
35
44
 
45
+ def local? version
36
46
  if git.tag_local? version
37
47
  logger.warn { "Local tag exists: #{version}. Skipped." }
38
48
  true
@@ -41,18 +51,16 @@ module Milestoner
41
51
  end
42
52
  end
43
53
 
44
- def create configuration
45
- git.tag_create(configuration.version, message(configuration))
46
- .or { |error| fail Error, error }
47
- .bind { true }
54
+ def create version
55
+ build(version).fmap { |body| git.tag_create version, body }
56
+ .or { |error| fail Error, error }
57
+ .bind { true }
48
58
  end
49
59
 
50
- def message configuration
51
- categorizer.call(configuration)
52
- .map { |record| presenter.new(record).line_item }
53
- .then do |line_items|
54
- %(Version #{configuration.version}\n\n#{line_items.join "\n"}\n\n)
55
- end
60
+ def build version
61
+ builder.call.fmap do |body|
62
+ "Version #{version}\n\n#{body.reread}\n\n"
63
+ end
56
64
  end
57
65
  end
58
66
  end
@@ -4,7 +4,7 @@ module Milestoner
4
4
  module Tags
5
5
  # Handles the tagging and pushing of a tag to a remote repository.
6
6
  class Publisher
7
- include Import[:logger]
7
+ include Import[:input, :logger]
8
8
 
9
9
  def initialize(creator: Tags::Creator.new, pusher: Tags::Pusher.new, **)
10
10
  super(**)
@@ -12,10 +12,10 @@ module Milestoner
12
12
  @pusher = pusher
13
13
  end
14
14
 
15
- def call configuration = Container[:configuration]
16
- creator.call configuration
17
- pusher.call configuration
18
- logger.info { "Published: #{configuration.version}!" }
15
+ def call override = nil
16
+ creator.call override
17
+ pusher.call override
18
+ logger.info { "Published: #{input.project_version}!" }
19
19
  end
20
20
 
21
21
  private
@@ -6,12 +6,12 @@ module Milestoner
6
6
  module Tags
7
7
  # Handles publishing of tags to a remote repository.
8
8
  class Pusher
9
- include Import[:git, :logger]
9
+ include Import[:input, :git, :logger]
10
10
 
11
11
  using Versionaire::Cast
12
12
 
13
- def call configuration = Container[:configuration]
14
- version = Version configuration.version
13
+ def call override = nil
14
+ version = compute_version override
15
15
 
16
16
  fail Error, "Remote repository not configured." unless git.origin?
17
17
  fail Error, "Remote tag exists: #{version}." if git.tag_remote? version
@@ -19,6 +19,12 @@ module Milestoner
19
19
 
20
20
  logger.debug "Local tag pushed: #{version}."
21
21
  end
22
+
23
+ def compute_version value
24
+ Version value || input.project_version
25
+ rescue Versionaire::Error => error
26
+ raise Error, error
27
+ end
22
28
  end
23
29
  end
24
30
  end
@@ -0,0 +1,29 @@
1
+ <!DOCTYPE html>
2
+
3
+ <html lang="en">
4
+ <head>
5
+ <title><%= project_title %></title>
6
+
7
+ <meta charset="utf-8">
8
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
9
+ <meta name="description" content="<%= project_description %>">
10
+ <meta name="author" content="<%= project_author %>">
11
+ <meta name="generator" content="<%= project_generator %>">
12
+
13
+ <link title="<%= project_label %>: Favorite Icon"
14
+ rel="icon"
15
+ href="https://alchemists.io/images/projects/milestoner/icons/brand/favicon.ico"
16
+ sizes="32x32">
17
+ <link title="<%= project_label %>: Apple Icon"
18
+ rel="apple-touch-icon"
19
+ href="https://alchemists.io/images/projects/milestoner/icons/brand/apple.png"
20
+ type="image/png">
21
+ <link title="<%= project_label %>: Stylesheet" rel="stylesheet" href="page.css" type="text/css">
22
+
23
+ <script src="https://unpkg.com/alpinejs@3.13" defer></script>
24
+ </head>
25
+
26
+ <body>
27
+ <%= yield %>
28
+ </body>
29
+ </html>
@@ -0,0 +1 @@
1
+ <%= yield %>
@@ -0,0 +1,5 @@
1
+ <% if name %>
2
+ <img src="<%= url %>" alt="<%= name %>" class="avatar" width="24" height="24" loading="lazy">
3
+ <% else %>
4
+ <%= render :icon, icon: "missing_avatar", kind: "Unknown" %>
5
+ <% end %>
@@ -0,0 +1,104 @@
1
+ <details id="<%= commit.sha %>" class="row" x-bind:open="open">
2
+ <summary class="summary <%= commit.kind %>">
3
+ <div class="title">
4
+ <%= render :icon, icon: commit.icon, kind: commit.kind %>
5
+ <span class="subject"><%= commit.subject %></span>
6
+ </div>
7
+
8
+ <div class="author">
9
+ <%= render :avatar, name: commit.author.name, url: commit.avatar_url(commit.author) %>
10
+ </div>
11
+
12
+ <a href="<%= commit.uri %>" class="files"><%= commit.files_changed %></a>
13
+
14
+ <span class="lines">
15
+ <span class="deletions"><%= commit.total_deletions %></span>
16
+ <span class="delimiter">/</span>
17
+ <span class="insertions"><%= commit.total_insertions %></span>
18
+ </span>
19
+
20
+ <% if commit.issue.id %>
21
+ <a href="<%= commit.issue.uri %>" class="issue"><%= commit.issue.id %></a>
22
+ <% else %>
23
+ <span class="issue">-</span>
24
+ <% end %>
25
+
26
+ <% if commit.review.id %>
27
+ <a href="<%= commit.review.uri %>" class="review"><%= commit.review.id %></a>
28
+ <% else %>
29
+ <span class="review">-</span>
30
+ <% end %>
31
+ </summary>
32
+
33
+ <div class="details">
34
+ <div class="tag <%= commit.tag %>"><%= commit.tag %></div>
35
+
36
+ <div class="message">
37
+ <h1 class="bar">Message</h1>
38
+
39
+ <div class="content">
40
+ <%= commit.safe_body %>
41
+ </div>
42
+ </div>
43
+
44
+ <% unless commit.notes.empty? %>
45
+ <div class="notes">
46
+ <h2 class="bar">Notes</h2>
47
+
48
+ <div class="content">
49
+ <%= commit.notes.html_safe %>
50
+ </div>
51
+ </div>
52
+ <% end %>
53
+
54
+ <div class="info">
55
+ <h2 class="bar">Info</h2>
56
+
57
+ <div class="panel">
58
+ <h3 class="label">Author</h3>
59
+
60
+ <p class="item">
61
+ <%= render :avatar, name: commit.author.name, url: commit.avatar_url(commit.author) %>
62
+ <%= render :profile, name: commit.author.name, url: commit.profile_url(commit.author) %>
63
+ </p>
64
+
65
+ <% if commit.collaborators.any? %>
66
+ <h3 class="label">Collaborators</h3>
67
+
68
+ <% commit.collaborators.each do |collaborator| %>
69
+ <p class="item">
70
+ <%= render :avatar, name: collaborator.name, url: commit.avatar_url(collaborator) %>
71
+ <%= render :profile, name: collaborator.name, url: commit.profile_url(collaborator) %>
72
+ </p>
73
+ <% end %>
74
+ <% end %>
75
+
76
+ <% if commit.signers.any? %>
77
+ <h3 class="label">Signers</h3>
78
+
79
+ <% commit.signers.each do |signer| %>
80
+ <p class="item">
81
+ <%= render :avatar, name: signer.name, url: commit.avatar_url(signer) %>
82
+ <%= render :profile, name: signer.name, url: commit.profile_url(signer) %>
83
+ </p>
84
+ <% end %>
85
+ <% end %>
86
+
87
+ <h3 class="label">Signature</h3>
88
+
89
+ <p class="item pill <%= commit.security %>">
90
+ <span class="signature"><%= commit.signature %></span>
91
+ </p>
92
+
93
+ <h3 class="label">Created</h3>
94
+
95
+ <p class="item at">
96
+ <time datetime="<%= commit.at %>" class="date"><%= commit.date %></time>
97
+ <time datetime="<%= commit.at %>" class="weekday"><%= commit.weekday %></time>
98
+ <time datetime="<%= commit.at %>" class="time"><%= commit.time %></time>
99
+ <time datetime="<%= commit.at %>" class="zone"><%= commit.zone %></time>
100
+ </p>
101
+ </div>
102
+ </div>
103
+ </div>
104
+ </details>
@@ -0,0 +1 @@
1
+ <%= commit.emoji %> <%= commit.subject %> - <%= commit.author.name %>
@@ -0,0 +1,6 @@
1
+ <img src="https://alchemists.io/images/projects/milestoner/icons/commits/<%= icon %>.png"
2
+ alt="<%= kind %>"
3
+ class="icon"
4
+ width="24"
5
+ height="24"
6
+ loading="lazy">
@@ -0,0 +1,5 @@
1
+ <% if name %>
2
+ <a href="<%= url %>"><%= name %></a>
3
+ <% else %>
4
+ <span>Unknown</span>
5
+ <% end %>
@@ -0,0 +1,46 @@
1
+ <article class="milestone">
2
+ <header class="header">
3
+ <h1 class="title">
4
+ <a href="<%= uri %>" class="label"><%= project_label %></a>
5
+ <span class="version"><%= project_version %></span>
6
+ </h1>
7
+
8
+ <time datetime="<%= at %>" class="date"><%= date %></time>
9
+ </header>
10
+
11
+ <% if commits.empty? %>
12
+ <p class="quiet"></p>
13
+ <% else %>
14
+ <section class="body" x-data="{open: false}">
15
+ <div class="actions">
16
+ <button class="toggle"
17
+ x-on:click="open = !open"
18
+ x-text="open ? 'Collapse' : 'Expand'">
19
+ Expand
20
+ </button>
21
+ </div>
22
+
23
+ <div class="columns">
24
+ <span class="label">Commit</span>
25
+ <span class="label">Author</span>
26
+ <span class="label">Files</span>
27
+ <span class="label">Lines</span>
28
+ <span class="label">Issue</span>
29
+ <span class="label">Review</span>
30
+ </div>
31
+
32
+ <% commits.each do |commit| %>
33
+ <%= commit.render :commit %>
34
+ <% end %>
35
+ </section>
36
+ <% end %>
37
+
38
+ <footer class="footer">
39
+ <p class="totals">
40
+ <span class="commits"><%= total_commits %>.</span>
41
+ <span class="files"><%= total_files %>.</span>
42
+ <span class="deletions"><%= total_deletions %>.</span>
43
+ <span class="insertions"><%= total_insertions %>.</span>
44
+ </p>
45
+ </footer>
46
+ </article>
@@ -0,0 +1,5 @@
1
+ <%= project_label %> <%= project_version %> (<%= date %>)
2
+
3
+ <% commits.each do |commit| %><%= commit.render :commit %><% end %>
4
+
5
+ <%= total_commits %>. <%= total_files %>. <%= total_deletions %>. <%= total_insertions %>.