mdlogbook 0.1.0

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.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/AGENTS.md +80 -0
  3. data/CHANGELOG.md +16 -0
  4. data/CLAUDE.md +5 -0
  5. data/CODE_OF_CONDUCT.md +10 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +99 -0
  8. data/Rakefile +21 -0
  9. data/docs/sample-logs/sample-2025-01.md +122 -0
  10. data/docs/sample-logs/sample-2025-02.md +123 -0
  11. data/docs/sample-logs/sample-2025-03.md +116 -0
  12. data/docs/sample-logs/sample-2025-04.md +128 -0
  13. data/docs/sample-logs/sample-2025-05.md +122 -0
  14. data/docs/sample-logs/sample-2025-06.md +113 -0
  15. data/docs/sample-logs/sample-2025-07.md +122 -0
  16. data/docs/sample-logs/sample-2025-08.md +94 -0
  17. data/docs/sample-logs/sample-2025-09.md +119 -0
  18. data/docs/sample-logs/sample-2025-10.md +123 -0
  19. data/docs/sample-logs/sample-2025-11.md +113 -0
  20. data/docs/sample-logs/sample-2025-12.md +121 -0
  21. data/docs/usage.md +778 -0
  22. data/exe/mlb +6 -0
  23. data/lib/mdlogbook/arg_parser.rb +372 -0
  24. data/lib/mdlogbook/brace_expander.rb +59 -0
  25. data/lib/mdlogbook/cli.rb +224 -0
  26. data/lib/mdlogbook/config.rb +247 -0
  27. data/lib/mdlogbook/date_filter.rb +22 -0
  28. data/lib/mdlogbook/editor_launcher.rb +90 -0
  29. data/lib/mdlogbook/env.rb +95 -0
  30. data/lib/mdlogbook/file_resolver.rb +43 -0
  31. data/lib/mdlogbook/formatter/anonymize.rb +75 -0
  32. data/lib/mdlogbook/formatter/anonymize_dict.rb +36 -0
  33. data/lib/mdlogbook/formatter/base.rb +138 -0
  34. data/lib/mdlogbook/formatter/calendar.rb +154 -0
  35. data/lib/mdlogbook/formatter/chart_series_builder.rb +57 -0
  36. data/lib/mdlogbook/formatter/extract.rb +41 -0
  37. data/lib/mdlogbook/formatter/grid.rb +256 -0
  38. data/lib/mdlogbook/formatter/heatmap.rb +142 -0
  39. data/lib/mdlogbook/formatter/line_chart.rb +156 -0
  40. data/lib/mdlogbook/formatter/simple.rb +42 -0
  41. data/lib/mdlogbook/option_def.rb +68 -0
  42. data/lib/mdlogbook/option_registry.rb +205 -0
  43. data/lib/mdlogbook/overlap_checker.rb +42 -0
  44. data/lib/mdlogbook/parser.rb +213 -0
  45. data/lib/mdlogbook/period_spec.rb +56 -0
  46. data/lib/mdlogbook/raw_extractor.rb +100 -0
  47. data/lib/mdlogbook/runner/base.rb +66 -0
  48. data/lib/mdlogbook/runner/editor_runner.rb +49 -0
  49. data/lib/mdlogbook/runner/extract_format_runner.rb +54 -0
  50. data/lib/mdlogbook/runner/standard_format_runner.rb +105 -0
  51. data/lib/mdlogbook/runner/visual_format_runner.rb +77 -0
  52. data/lib/mdlogbook/runner.rb +27 -0
  53. data/lib/mdlogbook/section_extractor.rb +140 -0
  54. data/lib/mdlogbook/version.rb +6 -0
  55. data/lib/mdlogbook/work_thresholds.rb +37 -0
  56. data/lib/mdlogbook.rb +41 -0
  57. data/sig/mdlogbook/arg_parser.rbs +54 -0
  58. data/sig/mdlogbook/brace_expander.rbs +4 -0
  59. data/sig/mdlogbook/cli.rbs +14 -0
  60. data/sig/mdlogbook/config.rbs +60 -0
  61. data/sig/mdlogbook/date_filter.rbs +4 -0
  62. data/sig/mdlogbook/editor_launcher.rbs +9 -0
  63. data/sig/mdlogbook/env.rbs +32 -0
  64. data/sig/mdlogbook/file_resolver.rbs +9 -0
  65. data/sig/mdlogbook/formatter/anonymize.rbs +4 -0
  66. data/sig/mdlogbook/formatter/anonymize_dict.rbs +4 -0
  67. data/sig/mdlogbook/formatter/base.rbs +15 -0
  68. data/sig/mdlogbook/formatter/calendar.rbs +4 -0
  69. data/sig/mdlogbook/formatter/chart_series_builder.rbs +9 -0
  70. data/sig/mdlogbook/formatter/extract.rbs +4 -0
  71. data/sig/mdlogbook/formatter/grid.rbs +8 -0
  72. data/sig/mdlogbook/formatter/heatmap.rbs +10 -0
  73. data/sig/mdlogbook/formatter/line_chart.rbs +7 -0
  74. data/sig/mdlogbook/formatter/simple.rbs +6 -0
  75. data/sig/mdlogbook/option_def.rbs +30 -0
  76. data/sig/mdlogbook/option_registry.rbs +18 -0
  77. data/sig/mdlogbook/overlap_checker.rbs +13 -0
  78. data/sig/mdlogbook/parser.rbs +32 -0
  79. data/sig/mdlogbook/period_spec.rbs +11 -0
  80. data/sig/mdlogbook/raw_extractor.rbs +15 -0
  81. data/sig/mdlogbook/runner/base.rbs +4 -0
  82. data/sig/mdlogbook/runner/editor_runner.rbs +5 -0
  83. data/sig/mdlogbook/runner/extract_format_runner.rbs +4 -0
  84. data/sig/mdlogbook/runner/standard_format_runner.rbs +4 -0
  85. data/sig/mdlogbook/runner/visual_format_runner.rbs +4 -0
  86. data/sig/mdlogbook/runner.rbs +4 -0
  87. data/sig/mdlogbook/section_extractor.rbs +24 -0
  88. data/sig/mdlogbook/work_thresholds.rbs +11 -0
  89. data/sig/mdlogbook.rbs +8 -0
  90. metadata +149 -0
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mdlogbook
4
+ module Runner
5
+ # Runs visual output modes: line chart, grid, and heatmap.
6
+ class VisualFormatRunner < Base
7
+ def run
8
+ if @env.line_chart_format?
9
+ run_line_chart
10
+ elsif @env.grid_format?
11
+ run_grid
12
+ elsif @env.heatmap_format?
13
+ run_heatmap
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def run_line_chart
20
+ unless all_month_unit?
21
+ raise ArgumentError, "--line-chart requires month-unit specifiers (e.g. 2026/1, m0, q0)"
22
+ end
23
+ if @specs.size > 3
24
+ raise ArgumentError, "--line-chart supports at most 3 months (got #{@specs.size})"
25
+ end
26
+
27
+ formatter = Formatter::LineChart.new
28
+ each_sorted_spec.with_index do |spec, index|
29
+ @env.puts if index.positive?
30
+ result = load_result(spec)
31
+ formatter.format_output(
32
+ result, work_threshold, @env.stdout,
33
+ spec: spec,
34
+ size: @env.opts.line_chart_size,
35
+ today: @env.today,
36
+ line_chart_config: line_chart_config
37
+ )
38
+ end
39
+ end
40
+
41
+ def run_grid
42
+ unless all_month_unit?
43
+ raise ArgumentError, "--grid requires month-unit specifiers (e.g. 2026/1, m0, q0)"
44
+ end
45
+ if @specs.size > 12
46
+ raise ArgumentError, "--grid supports at most 12 months (got #{@specs.size})"
47
+ end
48
+
49
+ threshold = work_threshold
50
+ formatter = Formatter::Grid.new
51
+ spec_results = each_sorted_spec.map { |spec| [spec, load_result(spec)] }
52
+ formatter.format_output(spec_results, threshold, @env.stdout, today: @env.today, line_chart_config: line_chart_config)
53
+ end
54
+
55
+ def run_heatmap
56
+ merged = Parser::Result.new
57
+ each_spec { |spec| merged.merge!(load_result(spec)) }
58
+
59
+ range_begin, range_end = spec_range
60
+ formatter = Formatter::Heatmap.new
61
+ formatter.format_output(merged, @env.stdout,
62
+ resolution: @env.opts.heatmap_resolution,
63
+ heatmap_config: @env.config.heatmap_settings,
64
+ range_begin: range_begin,
65
+ range_end: range_end)
66
+ end
67
+
68
+ def all_month_unit?
69
+ each_spec.all?(&:month_unit?)
70
+ end
71
+
72
+ def line_chart_config
73
+ @env.config.visual_settings
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mdlogbook
4
+ # Executes the resolved command after CLI has parsed options and loaded config.
5
+ #
6
+ # Responsible for dispatching to the appropriate output mode (summary views,
7
+ # line chart, grid, heatmap, raw extraction, grep, or editor launch) based
8
+ # on the parsed option flags.
9
+ module Runner
10
+ class << self
11
+ def dispatch(env:)
12
+ resolver = FileResolver.new(config: env.config, basedir: env.opts.path_override)
13
+ runner_class =
14
+ if env.edit?
15
+ EditorRunner
16
+ elsif env.extract_format?
17
+ ExtractFormatRunner
18
+ elsif env.visual_format?
19
+ VisualFormatRunner
20
+ else
21
+ StandardFormatRunner
22
+ end
23
+ runner_class.new(env: env, file_resolver: resolver).run
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Mdlogbook
6
+ # Extracts markdown sections whose level-2 or level-3 headings match a regexp.
7
+ class SectionExtractor
8
+ DATE_HEADER_RE = /^# (\d{4}-\d{2}-\d{2})$/
9
+ SECTION_HEADER_RE = /^(##|###) (.+)$/
10
+
11
+ # Immutable value object representing one extracted markdown section.
12
+ Section = Data.define(:date, :heading_level, :heading, :lines)
13
+
14
+ # Accumulates extracted sections in input order.
15
+ class Result
16
+ # @return [Array[Section]]
17
+ attr_reader :sections
18
+
19
+ # Creates an empty result.
20
+ def initialize
21
+ @sections = []
22
+ end
23
+
24
+ # Adds a section to the result.
25
+ # @param section [Section]
26
+ # @return [void]
27
+ def add_section(section)
28
+ @sections << section
29
+ end
30
+
31
+ # Merges another {Result} into this one in place.
32
+ # @param other [Result]
33
+ # @return [self]
34
+ def merge!(other)
35
+ @sections.concat(other.sections)
36
+ self
37
+ end
38
+
39
+ # Iterates over extracted sections in input order.
40
+ # @yield [section]
41
+ # @yieldparam section [Section]
42
+ # @return [void]
43
+ def each_section(&)
44
+ @sections.each(&)
45
+ end
46
+
47
+ # Returns true when no sections have been added.
48
+ # @return [Boolean]
49
+ def empty?
50
+ @sections.empty?
51
+ end
52
+ end
53
+
54
+ # Parses logbook content from a file path, extracting matching sections within +date_range+.
55
+ # @param filename [String]
56
+ # @param date_range [Range<Date>]
57
+ # @param regexp [Regexp]
58
+ # @return [Result]
59
+ def extract_file(filename, date_range, regexp:)
60
+ File.open(filename, "r") { |io| extract(io, date_range, regexp: regexp) }
61
+ end
62
+
63
+ # Parses logbook content from an IO object, extracting matching sections within +date_range+.
64
+ # @param io [IO]
65
+ # @param date_range [Range<Date>]
66
+ # @param regexp [Regexp]
67
+ # @return [Result]
68
+ def extract(io, date_range, regexp:)
69
+ result = Result.new
70
+ current_date = nil
71
+ current_date_header = nil
72
+ current_section = nil
73
+
74
+ io.each_line do |raw_line|
75
+ line = raw_line.chomp
76
+
77
+ if (date_match = DATE_HEADER_RE.match(line))
78
+ flush_section(result, current_section, date_range)
79
+ current_date = Date.parse(date_match[1])
80
+ current_date_header = line
81
+ current_section = nil
82
+ next
83
+ end
84
+
85
+ header_match = SECTION_HEADER_RE.match(line)
86
+ if header_match
87
+ current_section = advance_section(result, current_section, current_date, current_date_header, date_range, regexp, line, header_match)
88
+ next unless current_section
89
+
90
+ current_section[:lines] << line if nested_heading?(current_section, header_match)
91
+ next
92
+ end
93
+
94
+ next unless current_section
95
+
96
+ current_section[:lines] << line
97
+ end
98
+
99
+ flush_section(result, current_section, date_range)
100
+ result
101
+ end
102
+
103
+ private
104
+
105
+ def advance_section(result, current_section, current_date, current_date_header, date_range, regexp, line, header_match)
106
+ level = header_match[1].length
107
+ heading = header_match[2]
108
+
109
+ if current_section && level <= current_section[:heading_level]
110
+ flush_section(result, current_section, date_range)
111
+ current_section = nil
112
+ end
113
+
114
+ return current_section unless current_date && regexp.match?(heading)
115
+
116
+ {
117
+ date: current_date,
118
+ heading_level: level,
119
+ heading: heading,
120
+ lines: [current_date_header, "", line]
121
+ }
122
+ end
123
+
124
+ def flush_section(result, current_section, date_range)
125
+ return unless current_section
126
+ return unless date_range.cover?(current_section[:date])
127
+
128
+ result.add_section(Section.new(
129
+ date: current_section[:date],
130
+ heading_level: current_section[:heading_level],
131
+ heading: current_section[:heading],
132
+ lines: current_section[:lines].dup
133
+ ))
134
+ end
135
+
136
+ def nested_heading?(current_section, header_match)
137
+ header_match[1].length > current_section[:heading_level]
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mdlogbook
4
+ # Gem version string following Semantic Versioning.
5
+ VERSION = "0.1.0"
6
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mdlogbook
4
+ # Holds working-time thresholds derived from monthly targets.
5
+ class WorkThresholds
6
+ # @!attribute [r] target_hours
7
+ # @return [Numeric] Monthly target hours.
8
+ # @!attribute [r] working_days_per_month
9
+ # @return [Numeric] Assumed number of working days per month.
10
+ # @!attribute [r] daily_target
11
+ # @return [Float] Target hours per working day.
12
+ # @!attribute [r] high_threshold
13
+ # @return [Float] Upper threshold (120% of daily_target) for "excellent" rating.
14
+ # @!attribute [r] low_threshold
15
+ # @return [Float] Lower threshold (70% of daily_target) for "poor" rating.
16
+ attr_reader :target_hours, :working_days_per_month,
17
+ :daily_target, :high_threshold, :low_threshold
18
+
19
+ # @param target_hours [Numeric, nil] Monthly target hours, or +nil+ for no target.
20
+ # @param working_days_per_month [Numeric] Assumed working days per month (default: 20).
21
+ def initialize(target_hours:, working_days_per_month: 20)
22
+ @target_hours = target_hours
23
+ @working_days_per_month = working_days_per_month
24
+ if target_hours
25
+ @daily_target = target_hours.to_f / working_days_per_month
26
+ @high_threshold = @daily_target * 1.2
27
+ @low_threshold = @daily_target * 0.7
28
+ end
29
+ end
30
+
31
+ # Returns +true+ when a monthly target is configured.
32
+ # @return [Boolean]
33
+ def target_hours?
34
+ !@target_hours.nil?
35
+ end
36
+ end
37
+ end
data/lib/mdlogbook.rb ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Top-level namespace for the mdlogbook gem.
4
+ module Mdlogbook
5
+ # Base error class for mdlogbook-specific exceptions.
6
+ class Error < StandardError; end
7
+ end
8
+
9
+ require_relative "mdlogbook/version"
10
+ require_relative "mdlogbook/config"
11
+ require_relative "mdlogbook/parser"
12
+ require_relative "mdlogbook/work_thresholds"
13
+ require_relative "mdlogbook/file_resolver"
14
+ require_relative "mdlogbook/period_spec"
15
+ require_relative "mdlogbook/brace_expander"
16
+ require_relative "mdlogbook/arg_parser"
17
+ require_relative "mdlogbook/date_filter"
18
+ require_relative "mdlogbook/editor_launcher"
19
+ require_relative "mdlogbook/overlap_checker"
20
+ require_relative "mdlogbook/raw_extractor"
21
+ require_relative "mdlogbook/section_extractor"
22
+ require_relative "mdlogbook/formatter/base"
23
+ require_relative "mdlogbook/formatter/simple"
24
+ require_relative "mdlogbook/formatter/calendar"
25
+ require_relative "mdlogbook/formatter/anonymize"
26
+ require_relative "mdlogbook/formatter/anonymize_dict"
27
+ require_relative "mdlogbook/formatter/extract"
28
+ require_relative "mdlogbook/formatter/chart_series_builder"
29
+ require_relative "mdlogbook/formatter/line_chart"
30
+ require_relative "mdlogbook/formatter/grid"
31
+ require_relative "mdlogbook/formatter/heatmap"
32
+ require_relative "mdlogbook/runner"
33
+ require_relative "mdlogbook/runner/base"
34
+ require_relative "mdlogbook/runner/editor_runner"
35
+ require_relative "mdlogbook/runner/extract_format_runner"
36
+ require_relative "mdlogbook/runner/visual_format_runner"
37
+ require_relative "mdlogbook/runner/standard_format_runner"
38
+ require_relative "mdlogbook/env"
39
+ require_relative "mdlogbook/option_def"
40
+ require_relative "mdlogbook/option_registry"
41
+ require_relative "mdlogbook/cli"
@@ -0,0 +1,54 @@
1
+ # Parses command-line arguments into an ordered list of PeriodSpec objects.
2
+ class Mdlogbook::ArgParser
3
+ class Directive
4
+ def resolve_with: (ArgParser::DirectiveContext) -> Array[Mdlogbook::PeriodSpec]
5
+ end
6
+
7
+ class DateDirective < Directive
8
+ def initialize: (Date) -> void
9
+ def resolve_with: (ArgParser::DirectiveContext) -> Array[Mdlogbook::PeriodSpec]
10
+ end
11
+
12
+ class MonthDirective < Directive
13
+ def initialize: (Integer, Integer) -> void
14
+ def resolve_with: (ArgParser::DirectiveContext) -> Array[Mdlogbook::PeriodSpec]
15
+ end
16
+
17
+ class RelativeDurationDirective < Directive
18
+ def initialize: (Integer) -> void
19
+ def resolve_with: (ArgParser::DirectiveContext) -> Array[Mdlogbook::PeriodSpec]
20
+ end
21
+
22
+ class RelativePointDirective < Directive
23
+ def initialize: (String, Integer) -> void
24
+ def resolve_with: (ArgParser::DirectiveContext) -> Array[Mdlogbook::PeriodSpec]
25
+ end
26
+
27
+ class RelativeRangeDirective < Directive
28
+ def initialize: (String, Integer, Integer) -> void
29
+ def resolve_with: (ArgParser::DirectiveContext) -> Array[Mdlogbook::PeriodSpec]
30
+ end
31
+
32
+ class RelativeOpenEndedDirective < Directive
33
+ def initialize: (String, Integer) -> void
34
+ def resolve_with: (ArgParser::DirectiveContext) -> Array[Mdlogbook::PeriodSpec]
35
+ end
36
+
37
+ class WildcardDirective < Directive
38
+ def initialize: (String) -> void
39
+ def resolve_with: (ArgParser::DirectiveContext) -> Array[Mdlogbook::PeriodSpec]
40
+ end
41
+
42
+ class DirectiveContext
43
+ attr_reader today: Date
44
+ attr_reader expander: Mdlogbook::BraceExpander
45
+
46
+ def initialize: (parser: ArgParser, today: Date, file_resolver: Mdlogbook::FileResolver?, expander: Mdlogbook::BraceExpander) -> void
47
+ def resolve: (ArgParser::Directive) -> Array[Mdlogbook::PeriodSpec]
48
+ def list_available_months: () -> Array[[Integer, Integer]]
49
+ def new_parser: () -> ArgParser
50
+ end
51
+
52
+ def initialize: (?today: Date, ?file_resolver: Mdlogbook::FileResolver?) -> void
53
+ def parse: (Array[String]) -> Array[Mdlogbook::PeriodSpec]
54
+ end
@@ -0,0 +1,4 @@
1
+ # Expands brace patterns (bash/zsh style) into an array of strings.
2
+ class Mdlogbook::BraceExpander
3
+ def expand: (String) -> Array[String]
4
+ end
@@ -0,0 +1,14 @@
1
+ # Command-line interface for mlb.
2
+ class Mdlogbook::CLI
3
+ def self.init_template: (?path: String?) -> String
4
+
5
+ def initialize: (?argv: Array[String], ?stdout: IO, ?stderr: IO, ?today: Date) -> void
6
+ def run: () -> bool
7
+ def debug: () -> self
8
+
9
+ private
10
+
11
+ def print_completion: (String shell) -> void
12
+ def parse_options!: () -> void
13
+ def build_registry: () -> Mdlogbook::OptionRegistry
14
+ end
@@ -0,0 +1,60 @@
1
+ # Loads per-project configuration from a YAML file.
2
+ class Mdlogbook::Config
3
+ DEFAULT_PROJECT: String
4
+ DEFAULT_ANONYMIZE_FORMAT: String
5
+ DEFAULT_HEATMAP_START_HOUR: Integer
6
+ DEFAULT_VISUAL_SETTINGS: Hash[String, untyped]
7
+ VALID_VISUAL_COLORS: Array[Symbol]
8
+
9
+ # Immutable value object holding anonymize formatter settings.
10
+ class AnonymizeSettings < Data
11
+ attr_reader project: String
12
+ attr_reader format: String
13
+ attr_reader salt: String
14
+ attr_reader passthrough: Array[String]
15
+ attr_reader preamble: String?
16
+ attr_reader postamble: String?
17
+
18
+ def initialize: (project: String, format: String, salt: String, passthrough: Array[String], preamble: String?, postamble: String?) -> void
19
+ end
20
+
21
+ # Immutable value object holding visual formatter settings.
22
+ class VisualSettings < Data
23
+ attr_reader show_today: bool
24
+ attr_reader show_weekday: bool
25
+ attr_reader show_linear: bool
26
+ attr_reader colors: Hash[String, Symbol]
27
+
28
+ def initialize: (?show_today: bool, ?show_weekday: bool, ?show_linear: bool, ?colors: Hash[String, Symbol]) -> void
29
+ end
30
+
31
+ # Immutable value object holding heatmap formatter settings.
32
+ class HeatmapSettings < Data
33
+ attr_reader start_hour: Integer
34
+
35
+ def initialize: (start_hour: Integer) -> void
36
+ end
37
+
38
+ # Immutable value object holding extract formatter settings.
39
+ class ExtractSettings < Data
40
+ attr_reader preamble: String?
41
+ attr_reader postamble: String?
42
+
43
+ def initialize: (preamble: String?, postamble: String?) -> void
44
+ end
45
+
46
+ attr_reader config_path: String
47
+
48
+ def self.default_config_path: () -> String
49
+ def initialize: (?config_path: String?, ?project: String?) -> void
50
+ def current_project: () -> String
51
+ def projects: () -> Array[String]
52
+ def basedir: (?String) -> String
53
+ def target_hours: (?String) -> Numeric?
54
+ def default_view: (?String) -> String?
55
+ def extract_settings: (?String) -> Config::ExtractSettings
56
+ def anonymize_settings: (?String) -> Config::AnonymizeSettings
57
+ def heatmap_settings: (?String) -> Config::HeatmapSettings
58
+ def editor_args: (?String) -> String?
59
+ def visual_settings: (?String) -> Config::VisualSettings
60
+ end
@@ -0,0 +1,4 @@
1
+ # Filters a Parser::Result to only include entries within a given PeriodSpec.
2
+ module Mdlogbook::DateFilter
3
+ def self.filter: (Mdlogbook::Parser::Result, Mdlogbook::PeriodSpec) -> Mdlogbook::Parser::Result
4
+ end
@@ -0,0 +1,9 @@
1
+ # Builds and runs an editor command for a logbook file.
2
+ class Mdlogbook::EditorLauncher
3
+ class NoEditorError < Mdlogbook::Error
4
+ end
5
+
6
+ def initialize: (file: String, ?today: Date, ?args_template: String?) -> void
7
+ def build_command: () -> Array[String]
8
+ def launch: () -> bool
9
+ end
@@ -0,0 +1,32 @@
1
+ # Immutable execution context shared by Runner and Executor layers.
2
+ class Mdlogbook::Env
3
+ attr_reader opts: Mdlogbook::CLI::Options
4
+ attr_reader argv: Array[String]
5
+ attr_reader stdout: IO
6
+ attr_reader stderr: IO
7
+ attr_reader today: Date
8
+ attr_reader config: Mdlogbook::Config
9
+
10
+ def initialize: (opts: Mdlogbook::CLI::Options, argv: Array[String], stdout: IO, stderr: IO, today: Date, config: Mdlogbook::Config) -> void
11
+
12
+ def raw_format?: () -> bool
13
+ def grep_format?: () -> bool
14
+ def line_chart_format?: () -> bool
15
+ def grid_format?: () -> bool
16
+ def heatmap_format?: () -> bool
17
+ def overlap_reporting_format?: () -> bool
18
+ def warning_reporting_format?: () -> bool
19
+ def anonymize_format?: () -> bool
20
+ def extract_format?: () -> bool
21
+ def visual_format?: () -> bool
22
+
23
+ def anonymize_settings: () -> Mdlogbook::Config::AnonymizeSettings?
24
+
25
+ def edit?: () -> bool
26
+ def debug?: () -> bool
27
+ def tty?: () -> bool
28
+
29
+ def puts: (*untyped args) -> void
30
+ def warn: (*untyped args) -> void
31
+ def alert: (*untyped args) -> void
32
+ end
@@ -0,0 +1,9 @@
1
+ # Resolves logbook file paths from config and a year/month pair.
2
+ class Mdlogbook::FileResolver
3
+ attr_reader basedir: String
4
+ attr_reader project: String
5
+
6
+ def initialize: (?config: Mdlogbook::Config, ?basedir: String?) -> void
7
+ def resolve: (Integer, Integer) -> String
8
+ def list_available_months: () -> Array[[Integer, Integer]]
9
+ end
@@ -0,0 +1,4 @@
1
+ # Formats logbook entries as anonymized markdown for AI analysis.
2
+ class Mdlogbook::Formatter::Anonymize < Mdlogbook::Formatter::Base
3
+ def format_output: (Mdlogbook::Parser::Result, Mdlogbook::WorkThresholds, ?IO, ?anonymize_settings: Mdlogbook::Config::AnonymizeSettings?, ?range_begin: Date?, ?range_end: Date?, **untyped) -> void
4
+ end
@@ -0,0 +1,4 @@
1
+ # Outputs a reverse-lookup dictionary for anonymized job names.
2
+ class Mdlogbook::Formatter::AnonymizeDict < Mdlogbook::Formatter::Base
3
+ def format_output: (Mdlogbook::Parser::Result, Mdlogbook::WorkThresholds, ?IO, ?anonymize_settings: Mdlogbook::Config::AnonymizeSettings?, **untyped) -> void
4
+ end
@@ -0,0 +1,15 @@
1
+ # Abstract base class for output formatters.
2
+ class Mdlogbook::Formatter::Base
3
+ PLAIN_SIGNS: Hash[Symbol, String]
4
+
5
+ attr_reader color_reset: String
6
+ attr_reader row_colors: Array[String]
7
+ attr_reader calendar_signs: Hash[Symbol, String]
8
+ attr_reader calendar_colors: Hash[Symbol, String]
9
+
10
+ def initialize: (?color: bool) -> void
11
+ def format_output: (Mdlogbook::Parser::Result, Mdlogbook::WorkThresholds, IO, **untyped) -> void
12
+
13
+ private
14
+ def passthrough?: (String, Array[String]) -> bool
15
+ end
@@ -0,0 +1,4 @@
1
+ # Formats logbook entries as a weekly calendar grid with daily totals and ratings.
2
+ class Mdlogbook::Formatter::Calendar < Mdlogbook::Formatter::Base
3
+ def format_output: (Mdlogbook::Parser::Result, Mdlogbook::WorkThresholds, ?IO, ?today: Date, ?debug: bool, **untyped) -> void
4
+ end
@@ -0,0 +1,9 @@
1
+ # Shared methods for building cumulative chart series.
2
+ module Mdlogbook::Formatter::ChartSeriesBuilder
3
+ private
4
+
5
+ def build_actual: (Mdlogbook::Parser::Result, Date, Integer) -> Array[Float]
6
+ def build_linear_target: (Numeric, Integer) -> Array[Float]
7
+ def build_weekday_target: (Mdlogbook::WorkThresholds, Date, Integer) -> Array[Float]
8
+ def working_day?: (Date) -> bool
9
+ end
@@ -0,0 +1,4 @@
1
+ # Outputs raw logbook content for AI summarization.
2
+ class Mdlogbook::Formatter::Extract
3
+ def format_output: (Mdlogbook::RawExtractor::Result, ?IO, ?extract_config: Mdlogbook::Config::ExtractSettings?, ?range_begin: Date?, ?range_end: Date?) -> void
4
+ end
@@ -0,0 +1,8 @@
1
+ # Renders multiple months as a grid of line charts.
2
+ class Mdlogbook::Formatter::Grid
3
+ MIN_CHART_WIDTH: Integer
4
+ OVERHEAD: Integer
5
+ GAP: Integer
6
+
7
+ def format_output: (Array[[Mdlogbook::PeriodSpec, Mdlogbook::Parser::Result]], Mdlogbook::WorkThresholds, ?IO, ?today: Date, ?line_chart_config: Mdlogbook::Config::VisualSettings?) -> void
8
+ end
@@ -0,0 +1,10 @@
1
+ # Renders a day-of-week x hour-of-day density plot showing work patterns.
2
+ class Mdlogbook::Formatter::Heatmap
3
+ SAMPLE_INTERVAL: Integer
4
+ HEIGHT: Integer
5
+ RESOLUTIONS: Hash[String, Integer]
6
+ DEFAULT_RESOLUTION: String
7
+ DAY_LABELS: Array[String]
8
+
9
+ def format_output: (Mdlogbook::Parser::Result, ?IO, ?resolution: String?, ?heatmap_config: Mdlogbook::Config::HeatmapSettings, ?range_begin: Date?, ?range_end: Date?) -> void
10
+ end
@@ -0,0 +1,7 @@
1
+ # Renders a cumulative-hours line chart for a single calendar month.
2
+ class Mdlogbook::Formatter::LineChart
3
+ SIZES: Hash[String, {width: Integer, height: Integer}?]
4
+ SIZE_ALIASES: Hash[String, String]
5
+
6
+ def format_output: (Mdlogbook::Parser::Result, Mdlogbook::WorkThresholds, ?IO, spec: Mdlogbook::PeriodSpec, ?size: String, ?today: Date, ?line_chart_config: Mdlogbook::Config::VisualSettings?, **untyped) -> void
7
+ end
@@ -0,0 +1,6 @@
1
+ # Formats logbook entries as a flat list with a total summary line.
2
+ class Mdlogbook::Formatter::Simple < Mdlogbook::Formatter::Base
3
+ ROW_FORMAT: String
4
+
5
+ def format_output: (Mdlogbook::Parser::Result, Mdlogbook::WorkThresholds, ?IO, **untyped) -> void
6
+ end
@@ -0,0 +1,30 @@
1
+ # Describes a single CLI option with metadata for OptionParser and shell completion.
2
+ class Mdlogbook::OptionDef
3
+ attr_reader short: String?
4
+ attr_reader long: String
5
+ attr_reader arg_name: String?
6
+ attr_reader arg_optional: bool
7
+ attr_reader description: String
8
+ attr_reader type: Class?
9
+ attr_reader values: Array[String]?
10
+ attr_reader completion: (Symbol | String)?
11
+
12
+ def initialize: (
13
+ long: String,
14
+ description: String,
15
+ ?short: String?,
16
+ ?arg_name: String?,
17
+ ?arg_optional: bool,
18
+ ?type: Class?,
19
+ ?values: Array[String]?,
20
+ ?completion: (Symbol | String)?
21
+ ) -> void
22
+
23
+ def flag?: () -> bool
24
+ def long_name: () -> String
25
+ def optparse_args: () -> Array[untyped]
26
+
27
+ private
28
+
29
+ def optparse_long_flag: () -> String
30
+ end
@@ -0,0 +1,18 @@
1
+ # Collects CLI option definitions and generates OptionParser instances and shell completion scripts.
2
+ class Mdlogbook::OptionRegistry
3
+ def initialize: () -> void
4
+ def separator: (String text) -> void
5
+ def option: (**untyped kwargs) ?{ (untyped) -> void } -> Mdlogbook::OptionDef
6
+ def definitions: () -> Array[Mdlogbook::OptionDef]
7
+ def to_option_parser: (banner: String) -> OptionParser
8
+ def completion_bash: () -> String
9
+ def completion_zsh: () -> String
10
+
11
+ private
12
+
13
+ def bash_compgen: (Mdlogbook::OptionDef d) -> String
14
+ def zsh_specs: (Mdlogbook::OptionDef d) -> Array[String]
15
+ def zsh_escape_desc: (String desc) -> String
16
+ def zsh_arg_part: (Mdlogbook::OptionDef d) -> String
17
+ def zsh_action: (Mdlogbook::OptionDef d) -> String
18
+ end