milestoner 19.1.0 → 19.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a8bf5d3a4c7b698072a246f82b192a61e6284d5cb4596c1a23f884f83acc96d
4
- data.tar.gz: ae0f82e2988be5295f10fb6c655d91763db92276950249738a6ee1c4ef7b7708
3
+ metadata.gz: 7d957f55958fe682de293955b8ae7d1762722654ab97323da41473e21a76c3b1
4
+ data.tar.gz: ed92635b6e20f6dbfb0ef8c43f11368bff7114aac753c8057753414e95e2e055
5
5
  SHA512:
6
- metadata.gz: c92da6d4112e257e62868a046e4716954074051c38eba7b3fe3d06c7eae4ade9901f4f606ec6f849037b553eb95c1f000271d5030e894b613d0487991a9b5991
7
- data.tar.gz: 913652d13c4e1a248e2f68becc4efe4eedd3c8af29d1563b660c284205f733a1719f14310552a69d281649d8ca05de5417797e60f18c63b65723670be91e33ac
6
+ metadata.gz: 7f78eb0132c2c5ea28e8d456bbd438cc0536785d085234cbc55fb431f712df51387bfa72102b127929cee70aba359b6bc507a97241498382f02be1a930334cd9
7
+ data.tar.gz: da79e4b9aa49da378d9e037799a85c63c66d5b9c743e1e0854157d9e525416eee21b4f24f65310b493984157ec2c61464c71e00d95c55c18575604ec8354c337
checksums.yaml.gz.sig CHANGED
Binary file
data/README.adoc CHANGED
@@ -171,6 +171,7 @@ build:
171
171
  format: "stream"
172
172
  index: true
173
173
  layout: "page"
174
+ manifest: false
174
175
  max: 1
175
176
  output: "tmp/milestones"
176
177
  stylesheet: true
@@ -236,7 +237,8 @@ The above can be customized as follows:
236
237
  ** `basename`: Required. The build file basename. Default: `index`. Used to customize the built file name.
237
238
  ** `format`: Required. The build output format. Default: `stream`. Used to determine what format to build the release notes as. Multiple formats are supported.
238
239
  ** `index`: Required. Enables (or disables) building the versions index page for web format only. Default: true.
239
- ** `layout`: Required. The {hanami_views_link} layout used when building release notes. Default: page. This can be disabled when using `false` or customized further -- via your own {xdg_link} configuration -- when providing your own templates and/or partials.
240
+ ** `layout`: Required. The {hanami_views_link} layout used when building release notes. Default: `page`. This can be disabled when using `false` or customized further -- via your own {xdg_link} configuration -- when providing your own templates and/or partials.
241
+ ** `manifest`: Required. The JSON manifest of milestones built. Handy for when you've built/cached previous milestones and want a quick way to check for differences (if any). Default: `false`.
240
242
  ** `max`: Required. The maximum number of {git_link} tags to build. Default: 1. By default, you are limited to building release notes for changes (commits) since the last tag but you can increase the maximum to build release notes for as many tags as you like.
241
243
  ** `output`: Required. The the directory for all generated output. Default: `tmp/milestones`. This can be a relative or absolute path and defaults to the current working directory. The path is automatically created if missing.
242
244
  ** `stylesheet`: Required. Enables (or disables) building the stylesheet for web format only. Default: true.
@@ -548,6 +550,97 @@ Milestone: patch
548
550
 
549
551
  Given the above, the resulting version would be: 2.0.0. This is because the highest milestone was a _major_ milestone.
550
552
 
553
+ ==== Manifest
554
+
555
+ When the manifest is enabled via the global/local {xdg_link} configuration or via the CLI, then a `manifest.json` file will be created for you in the root of your build output folder with contents similar to the following:
556
+
557
+ [source,json]
558
+ ----
559
+ {
560
+ "generator": {
561
+ "label": "Milestoner",
562
+ "version": "19.0.0"
563
+ },
564
+ "latest": "1.0.0",
565
+ "versions": [
566
+ "0.0.1",
567
+ "0.1.0",
568
+ "1.0.0"
569
+ ]
570
+ }
571
+ ----
572
+
573
+ The above breaks down as follows:
574
+
575
+ * `generator`: Captures generator details at time of manifest creation.
576
+ * `latest`: The most recent version built.
577
+ * `versions`: The array of versions built based on max build settings.
578
+
579
+ The manifest allows you to capture build details which can be useful when needing to compare differences between a previous and current build. For convience, you can use the `Milestoner::Tags::Manifest` class to manage manifests. Example:
580
+
581
+ [source,ruby]
582
+ ----
583
+ manifest = Milestoner::Tags::Manifest.new
584
+ latest_path = Pathname "$HOME/Engineering/OSS/demo/tmp/milestones/latest.json"
585
+
586
+ manifest.write generator: {label: "Milestoner", version: "19.0.0"},
587
+ latest: "0.0.1",
588
+ versions: %w[0.0.0 0.0.1]
589
+ # $HOME/Engineering/OSS/demo/tmp/milestones/manifest.json
590
+
591
+ manifest.write latest_path,
592
+ generator: {label: "Milestoner", version: "19.0.0"},
593
+ latest: "0.0.2",
594
+ versions: %w[0.0.0 0.0.1 0.0.2]
595
+ # $HOME/Engineering/OSS/demo/tmp/milestones/latest.json
596
+
597
+ manifest.read
598
+ # {
599
+ # :generator => {
600
+ # :label => "Milestoner",
601
+ # :version => "19.0.0"
602
+ # },
603
+ # :latest => "0.0.1",
604
+ # :versions => [
605
+ # "0.0.0",
606
+ # "0.0.1"
607
+ # ]
608
+ # }
609
+
610
+ manifest.read latest_path
611
+ # {
612
+ # :generator => {
613
+ # :label => "Milestoner",
614
+ # :version => "19.0.0"
615
+ # },
616
+ # :latest => "0.0.2",
617
+ # :versions => [
618
+ # "0.0.0",
619
+ # "0.0.1",
620
+ # "0.0.2"
621
+ # ]
622
+ # }
623
+
624
+ manifest.diff latest_path
625
+ # {
626
+ # :latest => [
627
+ # "0.0.2",
628
+ # "0.0.1"
629
+ # ],
630
+ # :versions => [
631
+ # [
632
+ # "0.0.0",
633
+ # "0.0.1",
634
+ # "0.0.2"
635
+ # ],
636
+ # [
637
+ # "0.0.0",
638
+ # "0.0.1"
639
+ # ]
640
+ # ]
641
+ # }
642
+ ----
643
+
551
644
  ==== Templates
552
645
 
553
646
  Template functionality is powered by {hanami_views_link} which means you can completely customize _all_ build formats, templates, partials, stylesheets, images, and much more.
@@ -10,6 +10,7 @@ module Milestoner
10
10
 
11
11
  register(:ascii_doc) { ASCIIDoc.new }
12
12
  register(:feed) { Feed.new }
13
+ register(:manifest) { Manifest.new }
13
14
  register(:markdown) { Markdown.new }
14
15
  register(:stream) { Stream.new }
15
16
  register(:web) { Web.new }
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/monads"
4
+
5
+ module Milestoner
6
+ module Builders
7
+ # Builds JSON manifest.
8
+ class Manifest
9
+ include Dry::Monads[:result]
10
+ include Milestoner::Dependencies[:settings, :git, :logger]
11
+
12
+ def initialize(writer: Tags::Manifest.new, path_resolver: PathResolver, **)
13
+ super(**)
14
+ @writer = writer
15
+ @path_resolver = path_resolver
16
+ end
17
+
18
+ def call
19
+ return Success writer.build_path unless settings.build_manifest
20
+
21
+ git.tags.either -> tags { write tags }, -> message { failure message }
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :writer, :path_resolver
27
+
28
+ def write tags
29
+ path_resolver.call writer.build_path, logger: do
30
+ versions = tags.map(&:version)
31
+ writer.write latest: versions.last, versions:
32
+ end
33
+ end
34
+
35
+ def failure message
36
+ logger.error { message }
37
+ Failure message
38
+ end
39
+ end
40
+ end
41
+ end
@@ -10,7 +10,7 @@ module Milestoner
10
10
 
11
11
  PathResolver = lambda do |path, logger:, &block|
12
12
  if path.exist?
13
- logger.warn { "Skipped (exists): #{path}." }
13
+ logger.warn { "Path exists: #{path}. Skipped." }
14
14
  else
15
15
  path.make_ancestors
16
16
  block.call path if block
@@ -20,6 +20,7 @@ module Milestoner
20
20
  return Success() unless settings.build_index
21
21
 
22
22
  path_resolver.call settings.build_output.join("index.html"), logger: do |path|
23
+ settings.project_version = nil
23
24
  path.write view.call(tags:, layout: settings.build_layout)
24
25
  end
25
26
  end
@@ -103,7 +103,7 @@ module Milestoner
103
103
 
104
104
  node.merge id: format(settings.syndication_id, id: version),
105
105
  title: format(settings.syndication_entry_label, id: version),
106
- link: format(settings.syndication_entry_uri, id: version),
106
+ link: format(settings.syndication_entry_uri, id: "#{version}/"),
107
107
  rights: committed_at.strftime("%Y"),
108
108
  published: committed_at,
109
109
  updated: committed_at
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sod"
4
+
5
+ module Milestoner
6
+ module CLI
7
+ module Actions
8
+ module Build
9
+ # Handles build manifest.
10
+ class Manifest < Sod::Action
11
+ include Dependencies[:settings]
12
+
13
+ description "Enable/disable manifest."
14
+
15
+ on "--[no-]manifest"
16
+
17
+ default { Container[:settings].build_manifest }
18
+
19
+ def call(boolean) = settings.build_manifest = boolean
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -8,7 +8,7 @@ module Milestoner
8
8
  # Handles the building of different milestone formats.
9
9
  class Build < Sod::Command
10
10
  include Dependencies[:settings, :logger, :io]
11
- include Builders::Dependencies[:ascii_doc, :feed, :markdown, :stream, :web]
11
+ include Builders::Dependencies[:ascii_doc, :feed, :manifest, :markdown, :stream, :web]
12
12
 
13
13
  handle "build"
14
14
 
@@ -19,6 +19,7 @@ module Milestoner
19
19
  on Actions::Build::Index
20
20
  on Actions::Build::Label
21
21
  on Actions::Build::Layout
22
+ on Actions::Build::Manifest
22
23
  on Actions::Build::Max
23
24
  on Actions::Build::Output
24
25
  on Actions::Build::Stylesheet
@@ -35,6 +36,8 @@ module Milestoner
35
36
  else
36
37
  logger.abort "Invalid build format: #{format}."
37
38
  end
39
+
40
+ manifest.call
38
41
  end
39
42
 
40
43
  private
@@ -14,6 +14,7 @@ module Milestoner
14
14
  required(:build_index).filled :bool
15
15
  required(:build_layout) { str? | bool? }
16
16
  required(:build_max).filled :integer
17
+ required(:build_manifest).filled :bool
17
18
  required(:build_output).filled Etcher::Types::Pathname
18
19
  required(:build_stylesheet).filled :bool
19
20
  required(:build_tail).filled :string
@@ -5,6 +5,7 @@ build:
5
5
  format: "stream"
6
6
  index: true
7
7
  layout: "page"
8
+ manifest: false
8
9
  max: 1
9
10
  output: "tmp/milestones"
10
11
  stylesheet: true
@@ -8,6 +8,7 @@ module Milestoner
8
8
  :build_format,
9
9
  :build_index,
10
10
  :build_layout,
11
+ :build_manifest,
11
12
  :build_max,
12
13
  :build_output,
13
14
  :build_stylesheet,
@@ -3,23 +3,30 @@
3
3
  require "refinements/array"
4
4
  require "refinements/string"
5
5
 
6
- # Computes duration (in seconds) into human readable days, hours, minutes, and seconds.
6
+ # Computes duration (in seconds) into human readable years, days, hours, minutes, and seconds.
7
7
  module Milestoner
8
8
  using Refinements::Array
9
9
  using Refinements::String
10
10
 
11
- DURATION_UNITS = {"second" => 60, "minute" => 60, "hour" => 24, "day" => 86_400}.freeze
11
+ DURATION_UNITS = {
12
+ "year" => 31_536_000, # 60 * 60 * 25 * 365
13
+ "day" => 86_400, # 60 * 60 * 25
14
+ "hour" => 3_600, # 60 * 60
15
+ "minute" => 60,
16
+ "second" => 1
17
+ }.freeze
12
18
 
13
19
  Durationer = lambda do |seconds, units: DURATION_UNITS|
14
- result = units.map do |unit, value|
15
- next unless seconds.positive?
20
+ return "0 seconds" if seconds.negative? || seconds.zero?
16
21
 
17
- seconds, number = seconds.divmod value
18
- number = number.to_i
22
+ result = units.map do |unit, divisor|
23
+ count, seconds = seconds.divmod divisor
19
24
 
20
- %(#{number} #{unit.pluralize "s", number}) if number.nonzero?
25
+ next if count.zero?
26
+
27
+ %(#{count} #{unit.pluralize "s", count})
21
28
  end
22
29
 
23
- result.compact.reverse.to_sentence
30
+ result.compact.to_sentence
24
31
  end
25
32
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "core"
4
+ require "json"
5
+ require "refinements/hash"
6
+ require "refinements/pathname"
7
+
8
+ module Milestoner
9
+ module Tags
10
+ # Manages build manifest.
11
+ class Manifest
12
+ include Dependencies[:settings, :git]
13
+
14
+ using Refinements::Hash
15
+ using Refinements::Pathname
16
+
17
+ def initialize(name: "manifest.json", **)
18
+ super(**)
19
+ @name = name
20
+ end
21
+
22
+ def build_path = settings.build_output.join name
23
+
24
+ def diff path = build_path
25
+ git.tags.value_or(Core::EMPTY_ARRAY).then do |tags|
26
+ return Core::EMPTY_HASH if tags.empty?
27
+
28
+ content_for(tags).diff read(path)
29
+ end
30
+ end
31
+
32
+ def read(path = build_path) = JSON(path.read, {symbolize_names: true})
33
+
34
+ def write(path = build_path, **)
35
+ path.make_ancestors.write JSON.pretty_generate(generator.deep_merge(**))
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :name
41
+
42
+ # :reek:FeatureEnvy
43
+ def content_for tags
44
+ generator.merge latest: tags.last.version, versions: tags.map(&:version)
45
+ end
46
+
47
+ def generator
48
+ {generator: {label: settings.generator_label, version: settings.generator_version.to_s}}
49
+ end
50
+ end
51
+ end
52
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  <html lang="en">
4
4
  <head>
5
- <title><%= project_title %></title>
5
+ <title><%= page_title %></title>
6
6
 
7
7
  <meta charset="utf-8">
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
@@ -30,8 +30,8 @@
30
30
  href="<%= stylesheet_uri %>.css"
31
31
  type="text/css">
32
32
 
33
- <script src="https://unpkg.com/alpinejs@3.14.3"
34
- integrity="sha384-iZD2X8o1Zdq0HR5H/7oa8W30WS4No+zWCKUPD7fHRay9I1Gf+C4F8sVmw7zec1wW"
33
+ <script src="https://unpkg.com/alpinejs@3.14.8"
34
+ integrity="sha384-X9kJyAubVxnP0hcA+AMMs21U445qsnqhnUF8EBlEpP3a42Kh/JwWjlv2ZcvGfphb"
35
35
  crossorigin="anonymous"
36
36
  defer>
37
37
  </script>
@@ -9,5 +9,5 @@ All versions adhere to the link:https://alchemists.io/articles/strict_semantic_v
9
9
  === History
10
10
 
11
11
  <% tags.each do |tag| %>
12
- * link:<%= tag.version %>[<%= tag.version %>] (<%= tag.committed_date %>)
12
+ * link:/<%= tag.version %>/[<%= tag.version %>] (<%= tag.committed_date %>)
13
13
  <% end %>
@@ -14,9 +14,9 @@
14
14
  <ul class="list">
15
15
  <% tags.each do |tag| %>
16
16
  <li class="item">
17
- <a href="<%= tag.version %>"
17
+ <a href="/<%= tag.version %>/"
18
18
  class="version"
19
- hx-get="<%= tag.version %>"
19
+ hx-get="/<%= tag.version %>/"
20
20
  hx-select=".milestone"
21
21
  hx-target=".milestones"
22
22
  hx-swap="outerHTML"
@@ -9,5 +9,5 @@ All versions adhere to the [Strict Semantic Versioning](https://alchemists.io/ar
9
9
  ## History
10
10
 
11
11
  <% tags.each do |tag| %>
12
- - [<%= tag.version %>](<%= tag.version %>) (<%= tag.committed_date %>)
12
+ - [<%= tag.version %>](/<%= tag.version %>/) (<%= tag.committed_date %>)
13
13
  <% end %>
@@ -13,5 +13,5 @@
13
13
  *<%= tag.total_commits %>. <%= tag.total_files %>. <%= tag.total_deletions %>. <%= tag.total_insertions %>.* +
14
14
  *<%= tag.total_duration %>.*
15
15
 
16
- link:<%= past.version %>[Previous (<%= past.version %>)] | link:<%= future.version %>[Next (<%= future.version %>)]
16
+ link:<%= past.version %>/[Previous (<%= past.version %>)] | link:<%= future.version %>/[Next (<%= future.version %>)]
17
17
  <% end %>
@@ -72,10 +72,10 @@
72
72
 
73
73
  <ul class="actions">
74
74
  <li>
75
- <a href="/<%= past.version %>"
75
+ <a href="/<%= past.version %>/"
76
76
  class="action"
77
77
  data-direction="previous"
78
- hx-get="/<%= past.version %>"
78
+ hx-get="/<%= past.version %>/"
79
79
  hx-select=".milestone"
80
80
  hx-trigger="click, keyup[key=='ArrowLeft'] from:body"
81
81
  hx-target=".milestone"
@@ -101,10 +101,10 @@
101
101
  </li>
102
102
 
103
103
  <li>
104
- <a href="/<%= future.version %>"
104
+ <a href="/<%= future.version %>/"
105
105
  class="action"
106
106
  data-direction="next"
107
- hx-get="/<%= future.version %>"
107
+ hx-get="/<%= future.version %>/"
108
108
  hx-select=".milestone"
109
109
  hx-trigger="click, keyup[key=='ArrowRight'] from:body"
110
110
  hx-target=".milestone"
@@ -12,5 +12,5 @@
12
12
  **<%= tag.total_commits %>. <%= tag.total_files %>. <%= tag.total_deletions %>. <%= tag.total_insertions %>.**
13
13
  **<%= tag.total_duration %>.**
14
14
 
15
- [Previous (<%= past.version %>)](<%= past.version %>) | [Next (<%= future.version %>)](<%= future.version %>)
15
+ [Previous (<%= past.version %>)](<%= past.version %>/) | [Next (<%= future.version %>)](<%= future.version %>/)
16
16
  <% end %>
@@ -621,6 +621,7 @@
621
621
  }
622
622
 
623
623
  .owner {
624
+ align-items: center;
624
625
  display: flex;
625
626
  gap: 0.5rem;
626
627
  justify-content: center;
@@ -646,6 +647,7 @@
646
647
 
647
648
  .summary {
648
649
  align-items: center;
650
+ border-radius: 0.5rem;
649
651
  display: grid;
650
652
  gap: 0 1rem;
651
653
  grid-template: 1fr / auto 5fr 1fr 1fr 1fr 1fr 1fr;
@@ -832,6 +834,7 @@
832
834
  }
833
835
 
834
836
  .line {
837
+ align-items: center;
835
838
  align-self: start;
836
839
  display: flex;
837
840
  gap: 0.5rem;
@@ -3,6 +3,7 @@
3
3
  require "core"
4
4
  require "forwardable"
5
5
  require "hanami/view"
6
+ require "refinements/array"
6
7
 
7
8
  module Milestoner
8
9
  module Views
@@ -12,11 +13,15 @@ module Milestoner
12
13
 
13
14
  include Dependencies[:settings]
14
15
 
16
+ using Refinements::Array
17
+
15
18
  delegate %i[
16
19
  build_stylesheet
17
20
  generator_label
18
21
  generator_uri
19
22
  generator_version
23
+ organization_label
24
+ organization_uri
20
25
  project_author
21
26
  project_description
22
27
  project_label
@@ -29,6 +34,10 @@ module Milestoner
29
34
  stylesheet_uri
30
35
  ] => :settings
31
36
 
37
+ def page_title delimiter: " | "
38
+ [project_title, organization_label].compress.join delimiter
39
+ end
40
+
32
41
  def project_slug
33
42
  [project_name, project_version].compact.join("_").tr ".", Core::EMPTY_STRING
34
43
  end
data/milestoner.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "milestoner"
5
- spec.version = "19.1.0"
5
+ spec.version = "19.2.0"
6
6
  spec.authors = ["Brooke Kuhlmann"]
7
7
  spec.email = ["brooke@alchemists.io"]
8
8
  spec.homepage = "https://alchemists.io/projects/milestoner"
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: milestoner
3
3
  version: !ruby/object:Gem::Version
4
- version: 19.1.0
4
+ version: 19.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brooke Kuhlmann
@@ -34,7 +34,7 @@ cert_chain:
34
34
  3n5C8/6Zh9DYTkpcwPSuIfAga6wf4nXc9m6JAw8AuMLaiWN/r/2s4zJsUHYERJEu
35
35
  gZGm4JqtuSg8pYjPeIJxS960owq+SfuC+jxqmRA54BisFCv/0VOJi7tiJVY=
36
36
  -----END CERTIFICATE-----
37
- date: 2025-01-25 00:00:00.000000000 Z
37
+ date: 2025-02-06 00:00:00.000000000 Z
38
38
  dependencies:
39
39
  - !ruby/object:Gem::Dependency
40
40
  name: asciidoctor
@@ -379,6 +379,7 @@ files:
379
379
  - lib/milestoner/builders/container.rb
380
380
  - lib/milestoner/builders/dependencies.rb
381
381
  - lib/milestoner/builders/feed.rb
382
+ - lib/milestoner/builders/manifest.rb
382
383
  - lib/milestoner/builders/markdown.rb
383
384
  - lib/milestoner/builders/md/container.rb
384
385
  - lib/milestoner/builders/md/dependencies.rb
@@ -401,6 +402,7 @@ files:
401
402
  - lib/milestoner/cli/actions/build/index.rb
402
403
  - lib/milestoner/cli/actions/build/label.rb
403
404
  - lib/milestoner/cli/actions/build/layout.rb
405
+ - lib/milestoner/cli/actions/build/manifest.rb
404
406
  - lib/milestoner/cli/actions/build/max.rb
405
407
  - lib/milestoner/cli/actions/build/output.rb
406
408
  - lib/milestoner/cli/actions/build/stylesheet.rb
@@ -464,6 +466,7 @@ files:
464
466
  - lib/milestoner/tags/builder.rb
465
467
  - lib/milestoner/tags/creator.rb
466
468
  - lib/milestoner/tags/enricher.rb
469
+ - lib/milestoner/tags/manifest.rb
467
470
  - lib/milestoner/tags/publisher.rb
468
471
  - lib/milestoner/tags/pusher.rb
469
472
  - lib/milestoner/templates/layouts/page.adoc.erb
metadata.gz.sig CHANGED
Binary file