mps 0.5.0 → 1.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,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ MPS::CLI::MPS.class_eval do
4
+ desc "git GITCOMMAND", "Run git commands inside storage_dir"
5
+ def git(*commands)
6
+ init
7
+ begin
8
+ git_command = if commands.first == "auto"
9
+ auto_git_cmd
10
+ elsif commands.first == "autocommit"
11
+ "git add . && git commit -m \"$(date)\""
12
+ elsif commands.size > 0
13
+ cmds = commands.map { |c| c.include?(" ") ? "\"#{c}\"" : c }
14
+ "git #{cmds.join(' ')}"
15
+ else
16
+ "git status"
17
+ end
18
+ inside(@config.storage_dir) { run git_command }
19
+ rescue StandardError => e
20
+ raise Thor::Error, e
21
+ end
22
+ end
23
+
24
+ desc "autogit", "Auto stage, commit, pull and push"
25
+ def autogit
26
+ init
27
+ begin
28
+ inside(@config.storage_dir) { run auto_git_cmd }
29
+ rescue StandardError => e
30
+ raise Thor::Error, e
31
+ end
32
+ end
33
+
34
+ desc "cmd COMMAND", "Run shell commands inside storage_dir"
35
+ def cmd(*commands)
36
+ init
37
+ begin
38
+ cmds = commands.map { |c| c.include?(" ") ? "\"#{c}\"" : c }
39
+ inside(@config.storage_dir) { run cmds.join(" ") }
40
+ rescue StandardError => e
41
+ raise Thor::Error, e
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ MPS::CLI::MPS.class_eval do
4
+ desc "list [DATESIGN]", "List elements for a date (default: today)"
5
+ method_option :type, type: :string, aliases: "-t",
6
+ desc: "Filter by type: task, note, log, reminder"
7
+ method_option :tag, type: :string, aliases: "-g", desc: "Filter by tag"
8
+ method_option :status, type: :string, aliases: "-s",
9
+ desc: "Filter tasks by status: open, done"
10
+ method_option :since, type: :string, aliases: "-S",
11
+ desc: "Show elements from SINCE up to DATESIGN"
12
+ method_option :refs, type: :boolean, aliases: "-r", default: false,
13
+ desc: "Show human-readable ref column"
14
+ method_option :all, type: :boolean, aliases: "-a", default: false,
15
+ desc: "List elements across all dates"
16
+ method_option :per_line_space, type: :numeric, default: 0,
17
+ desc: "Blank lines after each element (default: 0)"
18
+ method_option :per_date_space, type: :numeric, default: 0,
19
+ desc: "Blank lines after each date block"
20
+ def list(datesign = "today")
21
+ init
22
+ begin
23
+ all_dates = store.all_files
24
+ .map { |f| Date.strptime(File.basename(f)[0, 8], "%Y%m%d") }
25
+ .uniq.sort
26
+ dates = if options[:all] && options[:since]
27
+ since_date = ::MPS.get_date(options[:since]).to_date
28
+ all_dates.select { |d| d >= since_date }
29
+ elsif options[:all]
30
+ all_dates
31
+ elsif options[:since]
32
+ date_range(options[:since], ::MPS.get_date(datesign))
33
+ else
34
+ [::MPS.get_date(datesign).to_date]
35
+ end
36
+
37
+ per_line = options[:per_line_space].to_i
38
+ per_date = options[:per_date_space].to_i
39
+ shown = 0
40
+ multi = dates.size > 1
41
+
42
+ dates.each_with_index do |d, idx|
43
+ all = store.parse_date(d)
44
+ next if all.empty?
45
+ resolver = options[:refs] ? ::MPS::RefResolver.new(all) : nil
46
+ count = print_tree(all, options, resolver: resolver, per_line_space: per_line,
47
+ header: multi ? d.strftime("%Y-%m-%d") : nil)
48
+ shown += count
49
+ per_date.times { say "" } if multi && count > 0 && idx < dates.size - 1
50
+ end
51
+ say set_color("(no elements found)", :yellow) if shown.zero?
52
+ rescue StandardError => e
53
+ raise Thor::Error, e
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ MPS::CLI::MPS.class_eval do
4
+ desc "open [DATESIGN]", "Open .mps file in editor (default: today)"
5
+ def open(datesign = "today")
6
+ init
7
+ begin
8
+ date = ::MPS.get_date(datesign)
9
+ files = store.find_files(date)
10
+ file_path = if files.size > 1
11
+ ::CLI::UI::Prompt.ask("#{files.size} files found:") do |h|
12
+ files.each { |f| h.option(File.basename(f)) { |_| f } }
13
+ end
14
+ else
15
+ store.find_or_create_path(date)
16
+ end
17
+ @config.logger.info("Open MPS in text editor\n")
18
+ written = ::MPS.open_editor(file_path)
19
+ @config.logger.info("Done written Size: #{written} bytes\n")
20
+ say_status :written, "#{written} bytes", :green
21
+ rescue StandardError => e
22
+ raise Thor::Error, e
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ MPS::CLI::MPS.class_eval do
4
+ desc "search QUERY", "Full-text search across all .mps files"
5
+ method_option :type, type: :string, aliases: "-t", desc: "Filter by type"
6
+ method_option :tag, type: :string, aliases: "-g", desc: "Filter by tag"
7
+ method_option :since, type: :string, aliases: "-S", desc: "Search from this date onward"
8
+ def search(query)
9
+ init
10
+ begin
11
+ since_date = options[:since] ? ::MPS.get_date(options[:since]).to_date : nil
12
+ results = store.search(
13
+ query,
14
+ type_filter: options[:type]&.downcase,
15
+ tag_filter: options[:tag],
16
+ since_date: since_date
17
+ )
18
+ if results.empty?
19
+ say set_color("No results for '#{query}'", :yellow)
20
+ return
21
+ end
22
+ results.each do |r|
23
+ el = r[:element]
24
+ tags_str = el.tags.empty? ? "" : " #{set_color("[#{el.tags.join(', ')}]", :white)}"
25
+ body_line = el.body_str.strip.lines.first&.strip
26
+ say "#{set_color(r[:date_str], :white)} #{type_badge(el.class::SIGNATURE_STAMP)} " \
27
+ "#{element_extra(el)}#{body_line}#{tags_str}"
28
+ end
29
+ say set_color("(#{results.size} result#{results.size == 1 ? '' : 's'})", :white)
30
+ rescue StandardError => e
31
+ raise Thor::Error, e
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ MPS::CLI::MPS.class_eval do
4
+ desc "stats [DATESIGN]", "Show element counts and log durations"
5
+ method_option :since, type: :string, aliases: "-S", desc: "Stats from SINCE up to DATESIGN"
6
+ method_option :all, type: :boolean, aliases: "-a", default: false,
7
+ desc: "Stats across all dates"
8
+ def stats(datesign = "today")
9
+ init
10
+ begin
11
+ dates = if options[:all] && options[:since]
12
+ since_date = ::MPS.get_date(options[:since]).to_date
13
+ store.all_files.map { |f| Date.strptime(File.basename(f)[0, 8], "%Y%m%d") }
14
+ .uniq.sort.select { |d| d >= since_date }
15
+ elsif options[:all]
16
+ store.all_files.map { |f| Date.strptime(File.basename(f)[0, 8], "%Y%m%d") }.uniq.sort
17
+ elsif options[:since]
18
+ date = ::MPS.get_date(datesign)
19
+ date_range(options[:since], date)
20
+ else
21
+ date = ::MPS.get_date(datesign)
22
+ [date.to_date]
23
+ end
24
+
25
+ total = Hash.new(0)
26
+ total_log_mins = 0
27
+ any = false
28
+
29
+ dates.each do |d|
30
+ elements = store.parse_date(d).values
31
+ .reject { |e| e.is_a?(::MPS::Elements::MPS) || e.is_a?(::MPS::Engines::Parser::Unknown) }
32
+ next if elements.empty?
33
+ any = true
34
+ counts = elements.group_by { |e| e.class::SIGNATURE_STAMP }.transform_values(&:size)
35
+ log_mins = elements.select { |e| e.is_a?(::MPS::Elements::Log) }
36
+ .sum { |e| e.duration_minutes || 0 }
37
+ tasks = elements.select { |e| e.is_a?(::MPS::Elements::Task) }
38
+
39
+ parts = []
40
+ if (n = counts["task"])
41
+ open_n = tasks.count(&:open?)
42
+ done_n = tasks.count(&:done?)
43
+ parts << "#{n} task#{n != 1 ? 's' : ''} " \
44
+ "(#{set_color("#{open_n} open", :yellow)}, " \
45
+ "#{set_color("#{done_n} done", :green)})"
46
+ end
47
+ parts << "#{counts['note']} note#{counts['note'] != 1 ? 's' : ''}" if counts["note"]
48
+ parts << "#{counts['reminder']} reminder#{counts['reminder'] != 1 ? 's' : ''}" if counts["reminder"]
49
+ if (n = counts["log"])
50
+ dur_str = format_duration(log_mins)
51
+ parts << "#{n} log#{n != 1 ? 's' : ''}#{dur_str.empty? ? '' : " (#{dur_str})"}"
52
+ end
53
+
54
+ say "#{set_color(d.strftime('%Y-%m-%d'), :white)} — #{parts.join(', ')}"
55
+ counts.each { |k, v| total[k] += v }
56
+ total_log_mins += log_mins
57
+ end
58
+
59
+ say set_color("(no data found)", :yellow) unless any
60
+
61
+ if dates.size > 1 && any
62
+ say set_color("─" * 44, :white)
63
+ tparts = []
64
+ tparts << "#{total['task']} tasks" if total["task"] > 0
65
+ tparts << "#{total['note']} notes" if total["note"] > 0
66
+ tparts << "#{total['reminder']} reminders" if total["reminder"] > 0
67
+ if total["log"] > 0
68
+ dur_str = format_duration(total_log_mins)
69
+ tparts << "#{total['log']} logs#{dur_str.empty? ? '' : " (#{dur_str} total)"}"
70
+ end
71
+ say "Total: #{tparts.join(', ')}"
72
+ end
73
+ rescue StandardError => e
74
+ raise Thor::Error, e
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ MPS::CLI::MPS.class_eval do
4
+ desc "tags [DATESIGN]", "Show tag usage table with frequency bars"
5
+ method_option :type, type: :string, aliases: "-t", desc: "Filter by element type"
6
+ method_option :since, type: :string, aliases: "-S", desc: "From SINCE up to DATESIGN"
7
+ method_option :status, type: :string, aliases: "-s", desc: "Filter tasks by status"
8
+ method_option :all, type: :boolean, aliases: "-a", default: false,
9
+ desc: "Count tags across all dates"
10
+ def tags(datesign = "today")
11
+ init
12
+ begin
13
+ dates = if options[:all] && options[:since]
14
+ since_date = ::MPS.get_date(options[:since]).to_date
15
+ store.all_files.map { |f| Date.strptime(File.basename(f)[0, 8], "%Y%m%d") }
16
+ .uniq.sort.select { |d| d >= since_date }
17
+ elsif options[:all]
18
+ store.all_files.map { |f| Date.strptime(File.basename(f)[0, 8], "%Y%m%d") }.uniq.sort
19
+ elsif options[:since]
20
+ date_range(options[:since], ::MPS.get_date(datesign))
21
+ else
22
+ [::MPS.get_date(datesign).to_date]
23
+ end
24
+
25
+ q = ::MPS::Query.new(options)
26
+ counts = Hash.new(0)
27
+ dates.each do |d|
28
+ q.apply(store.parse_date(d)).each_value do |el|
29
+ el.tags.each { |t| counts[t] += 1 }
30
+ end
31
+ end
32
+
33
+ if counts.empty?
34
+ say set_color("(no tags found)", :yellow)
35
+ return
36
+ end
37
+
38
+ sorted = counts.sort_by { |_, v| -v }
39
+ max_cnt = sorted.first[1]
40
+ max_tag = sorted.map(&:first).map(&:length).max
41
+ bar_max = 24
42
+
43
+ say set_color(" #{"Tag".ljust(max_tag)} Count Frequency", :white)
44
+ say set_color(" #{"-" * max_tag} ----- #{"-" * bar_max}", :white)
45
+ sorted.each do |tag, n|
46
+ bar_len = (n.to_f / max_cnt * bar_max).ceil
47
+ bar = set_color("█" * bar_len, :cyan)
48
+ say " #{set_color(tag.ljust(max_tag), :white)} #{n.to_s.rjust(5)} #{bar}"
49
+ end
50
+ total = counts.values.sum
51
+ say ""
52
+ say set_color(" #{sorted.size} tag#{sorted.size == 1 ? '' : 's'}, #{total} total uses", :white)
53
+ rescue StandardError => e
54
+ raise Thor::Error, e
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ MPS::CLI::MPS.class_eval do
4
+ # Returns all schema-declared attribute definitions across every element type.
5
+ # Called at class-load time (for method_option registration) and at call time
6
+ # (inside the update method body). A class method survives both contexts.
7
+ def self._schema_update_attrs
8
+ ::MPS::Elements.constants
9
+ .map { |k| ::MPS::Elements.const_get(k) }
10
+ .select { |x| x.class == Class }
11
+ .flat_map { |klass| klass.schema.to_a }
12
+ .uniq { |name, _| name }
13
+ .select { |_, defn| defn[:flag] }
14
+ end
15
+
16
+ desc "update REFPATH", "Update an element's attributes in-place"
17
+ _schema_update_attrs.each do |_name, defn|
18
+ method_option defn[:flag], type: :string, desc: "Set #{defn[:flag]}"
19
+ end
20
+ method_option :date, type: :string, aliases: "-d",
21
+ desc: "Date context for human refs (default: today)"
22
+ def update(ref_path)
23
+ init
24
+ begin
25
+ date = options[:date] ? ::MPS.get_date(options[:date]).to_date : Date.today
26
+ new_attrs = {}
27
+ self.class._schema_update_attrs.each do |name, defn|
28
+ flag_sym = defn[:flag].tr("-", "_").to_sym
29
+ new_attrs[name] = options[flag_sym] if options[flag_sym]
30
+ end
31
+ raise Thor::Error, "No attributes specified." if new_attrs.empty?
32
+
33
+ unless store.rewrite_element(ref_path, new_attrs, date: date)
34
+ raise Thor::Error, "Could not update '#{ref_path}'. Check the ref is correct."
35
+ end
36
+ say_status :updated, ref_path, :green
37
+ rescue StandardError => e
38
+ raise Thor::Error, e
39
+ end
40
+ end
41
+
42
+ desc "done REFPATH", "Mark a task as done (shorthand for update REFPATH --status done)"
43
+ method_option :date, type: :string, aliases: "-d",
44
+ desc: "Date context for human refs (default: today)"
45
+ def done(ref_path)
46
+ init
47
+ begin
48
+ date = options[:date] ? ::MPS.get_date(options[:date]).to_date : Date.today
49
+ unless store.rewrite_element(ref_path, { status: "done" }, date: date)
50
+ raise Thor::Error, "Could not mark '#{ref_path}' as done. Check the ref is correct."
51
+ end
52
+ say_status :done, ref_path, :green
53
+ rescue StandardError => e
54
+ raise Thor::Error, e
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Auto-discovers and loads all command files from the commands/ directory.
4
+ # Adding a command = adding a file here; no other changes required.
5
+ Dir[File.join(File.dirname(__FILE__), "commands", "*.rb")].sort.each { |f| require f }
data/lib/mps/config.rb CHANGED
@@ -12,17 +12,22 @@ module MPS
12
12
  class MPSDirectoryNotFound < StandardError;end;
13
13
  class MPSStorageDirectoryNotFound < StandardError;end;
14
14
 
15
+ attr_reader :mps_dir
15
16
  attr_reader :storage_dir
16
17
  attr_reader :logger
17
18
  attr_reader :log_file
18
19
  attr_reader :git_remote
19
20
  attr_reader :git_branch
21
+ attr_reader :default_command
22
+ attr_reader :type_aliases
20
23
  def initialize(**conf_hash)
21
- @mps_dir = conf_hash[:mps_dir]
24
+ @mps_dir = conf_hash[:mps_dir]
22
25
  @storage_dir = conf_hash[:storage_dir]
23
26
  @log_file = conf_hash[:log_file]
24
- @git_remote = conf_hash.fetch(:git_remote, "origin")
25
- @git_branch = conf_hash.fetch(:git_branch, "master")
27
+ @git_remote = conf_hash.fetch(:git_remote, "origin")
28
+ @git_branch = conf_hash.fetch(:git_branch, "master")
29
+ @default_command = conf_hash.fetch(:default_command, "open")
30
+ @type_aliases = conf_hash.fetch(:aliases, {})
26
31
  @logger = Logger.new(File.open(@log_file, "a+"))
27
32
  @logger.formatter = proc do |sev, time, pn, msg|
28
33
  time = time.strftime("[%Y-%m-%d %H:%M:%S]")
data/lib/mps/constants.rb CHANGED
@@ -48,11 +48,13 @@ module MPS
48
48
 
49
49
  # default conf hash
50
50
  DEFAULT_CONF_HASH = {
51
- mps_dir: MPS_DIR,
52
- storage_dir: MPS_STORAGE_DIR,
53
- log_file: MPS_LOG_FILE,
54
- git_remote: "origin",
55
- git_branch: "master"
51
+ mps_dir: MPS_DIR,
52
+ storage_dir: MPS_STORAGE_DIR,
53
+ log_file: MPS_LOG_FILE,
54
+ git_remote: "origin",
55
+ git_branch: "master",
56
+ default_command: "open",
57
+ aliases: {}
56
58
  }
57
59
 
58
60
 
@@ -2,7 +2,46 @@
2
2
 
3
3
  module MPS
4
4
  module Element
5
- PADDING = ' '
5
+ # Called when Element is included in a class. Extends the class with
6
+ # ClassMethods, enabling the `attribute` schema DSL.
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ # Declare a typed attribute for this element class.
13
+ #
14
+ # @param name [Symbol] parsed_args key
15
+ # @param type [Symbol] :string, :time, :integer (for future coercion)
16
+ # @param default value when the attribute is absent from args
17
+ # @param flag [String] CLI flag name (without --); nil = not filterable
18
+ # @param aliases [Array<String>] short CLI aliases for this flag
19
+ def attribute(name, type: :string, default: nil, flag: nil, aliases: [])
20
+ _schema[name] = { type: type, default: default, flag: flag, aliases: Array(aliases) }
21
+ end
22
+
23
+ # Internal mutable schema hash.
24
+ def _schema
25
+ @_schema ||= {}
26
+ end
27
+
28
+ # Frozen public view of the schema.
29
+ def schema
30
+ _schema.dup.freeze
31
+ end
32
+
33
+ # Schema-driven parse_args. Subclasses do NOT need to override this.
34
+ # Any key: value pair in raw whose key matches a declared attribute name
35
+ # is extracted; remaining bare words become tags.
36
+ def parse_args(raw)
37
+ split = ::MPS::Element.split_args(raw)
38
+ result = { tags: split[:tags] }
39
+ _schema.each do |name, defn|
40
+ result[name] = split[:attrs].fetch(name, defn[:default])
41
+ end
42
+ result
43
+ end
44
+ end
6
45
 
7
46
  # Parses "work, release, status: done" → { attrs: { status: "done" }, tags: ["work", "release"] }
8
47
  # Parts containing ":" become named attrs; bare words become tags.
@@ -23,8 +62,7 @@ module MPS
23
62
  { attrs: attrs, tags: tags }
24
63
  end
25
64
 
26
- attr_accessor :disp_str
27
- attr_reader :body_str, :raw_args, :parsed_args
65
+ attr_reader :body_str, :raw_args, :parsed_args
28
66
 
29
67
  def initialize(args:, refs:, body_str:)
30
68
  @raw_args = args.to_s
@@ -37,13 +75,5 @@ module MPS
37
75
  def tags
38
76
  @parsed_args.fetch(:tags, [])
39
77
  end
40
-
41
- def display_str(padding_size = @refs.size - 1)
42
- strs = @body_str.strip.lines.map(&:strip)
43
- header = strs.first
44
- res_strs = [(PADDING * padding_size) + header]
45
- strs[1..].each { |str| res_strs << (PADDING * padding_size) + str }
46
- res_strs.join("\n")
47
- end
48
78
  end
49
79
  end
@@ -1,6 +1,8 @@
1
- ir "./element"
2
- ir "./mps"
3
- ir "./note"
4
- ir "./task"
5
- ir "./reminder"
6
- ir "./log"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "element"
4
+ require_relative "mps"
5
+ require_relative "note"
6
+ require_relative "task"
7
+ require_relative "reminder"
8
+ require_relative "log"
@@ -7,10 +7,8 @@ module MPS
7
7
  SIGNATURE_REGEX = /\Alog\z/
8
8
  include Element
9
9
 
10
- def self.parse_args(raw)
11
- p = Element.split_args(raw)
12
- { tags: p[:tags], start: p[:attrs][:start], end: p[:attrs][:end] }
13
- end
10
+ attribute :start, type: :time, default: nil, flag: "start-time"
11
+ attribute :end, type: :time, default: nil, flag: "end-time"
14
12
 
15
13
  def duration_minutes
16
14
  s = parsed_args[:start]
@@ -6,10 +6,7 @@ module MPS
6
6
  SIGNATURE_STAMP = "mps"
7
7
  SIGNATURE_REGEX = /\Amps\z/
8
8
  include Element
9
-
10
- def self.parse_args(raw)
11
- { tags: Element.split_args(raw)[:tags] }
12
- end
9
+ # MPS is a grouping container; carries only tags.
13
10
  end
14
11
  end
15
12
  end
@@ -6,10 +6,7 @@ module MPS
6
6
  SIGNATURE_STAMP = "note"
7
7
  SIGNATURE_REGEX = /\Anote\z/
8
8
  include Element
9
-
10
- def self.parse_args(raw)
11
- { tags: Element.split_args(raw)[:tags] }
12
- end
9
+ # Notes carry only tags; no typed attributes.
13
10
  end
14
11
  end
15
12
  end
@@ -7,10 +7,7 @@ module MPS
7
7
  SIGNATURE_REGEX = /\Areminder\z/
8
8
  include Element
9
9
 
10
- def self.parse_args(raw)
11
- p = Element.split_args(raw)
12
- { tags: p[:tags], at: p[:attrs][:at] }
13
- end
10
+ attribute :at, type: :string, default: nil, flag: "at"
14
11
  end
15
12
  end
16
13
  end
@@ -7,10 +7,7 @@ module MPS
7
7
  SIGNATURE_REGEX = /\Atask\z/
8
8
  include Element
9
9
 
10
- def self.parse_args(raw)
11
- p = Element.split_args(raw)
12
- { tags: p[:tags], status: p[:attrs].fetch(:status, "open") }
13
- end
10
+ attribute :status, type: :string, default: "open", flag: "status", aliases: ["-s"]
14
11
 
15
12
  def done? = parsed_args[:status] == "done"
16
13
  def open? = !done?
@@ -1 +1,3 @@
1
- ir "./mps"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "mps"
@@ -28,17 +28,10 @@ module MPS
28
28
  element_classes.find { |ec| str =~ ec::SIGNATURE_REGEX }
29
29
  end
30
30
 
31
- # Peeks ahead in +str_scanner+ for +regex_la+ without consuming input.
32
- # Returns the position of the match, or string size if no match.
33
- def self.look_ahead_pos(str_scanner, regex_la)
34
- return str_scanner.string.size unless str_scanner.scan_until(regex_la)
35
- pos = str_scanner.pos
36
- str_scanner.unscan
37
- pos
38
- end
39
-
40
31
  # Parses +mps_file_path+ into a flat hash of ref-path => element instances.
41
- def self.parse_mps_file_to_elements_hash(mps_file_path, element_classes)
32
+ # Applies interpolators to body strings if +interpolator_classes+ are provided.
33
+ def self.parse_mps_file_to_elements_hash(mps_file_path, element_classes,
34
+ interpolator_classes: [])
42
35
  content = File.read(mps_file_path)
43
36
  wrapped = "@#{::MPS::Elements::MPS::SIGNATURE_STAMP}[]{\n#{content}\n}"
44
37
  base_ref = ::MPS::Constants::MPS_FILE_NAME_CLIPPER
@@ -94,9 +87,26 @@ module MPS
94
87
  end
95
88
  end
96
89
 
90
+ _apply_interpolations(elements, interpolator_classes) unless interpolator_classes.empty?
97
91
  elements
98
92
  end
99
93
 
94
+ # Apply registered interpolators to the body_str of each element.
95
+ def self._apply_interpolations(elements, interpolator_classes)
96
+ return if interpolator_classes.empty?
97
+ elements.each_value do |el|
98
+ next unless el.respond_to?(:body_str)
99
+ body = el.instance_variable_get(:@body_str)
100
+ next unless body
101
+ interpolator_classes.each do |ic|
102
+ obj = ic.new
103
+ new_body = body.gsub(ic::SIGNATURE_REGEX) { obj.get_str }
104
+ el.instance_variable_set(:@body_str, new_body) if new_body != body
105
+ body = new_body
106
+ end
107
+ end
108
+ end
109
+
100
110
  class << self
101
111
  alias parse_mps_file_to_elments_hash parse_mps_file_to_elements_hash
102
112
  end
@@ -1 +1,3 @@
1
- ir "./time"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "time"
data/lib/mps/mps.rb CHANGED
@@ -1,32 +1,24 @@
1
1
  # frozen_string_literal: true
2
- #
3
2
 
4
- def ir(rltv_rb_fp)
5
- clf = caller_locations.first
6
- f, l = clf.path, clf.lineno
7
- rltv_rb_fp = File.expand_path(rltv_rb_fp, File.dirname(f))
8
- r = require_relative(rltv_rb_fp)
9
- puts("#{f} at #{l} for #{rltv_rb_fp} #=> #{r}") if ENV["MPS_DEBUG"]=="true"
10
- end
11
3
  module MPS
12
4
  def self.get_date(str)
13
- return Chronic.parse(str).to_date
5
+ result = Chronic.parse(str)
6
+ raise ArgumentError, "Cannot parse date: #{str.inspect}" unless result
7
+ result.to_date
14
8
  end
9
+
15
10
  def self.get_filename_from_date(date)
16
- "#{date.strftime('%Y%m%d')}.#{Constants::MPS_EXT}"
11
+ Constants::MPS_NEW_FILE_NAME_GEN.call(date)
17
12
  end
13
+
18
14
  def self.get_filenames_from_date_range(s_date, e_date)
19
- (s_date..e_date).collect do |date|
20
- MPS.get_filename_from_date(date)
21
- end
15
+ (s_date..e_date).collect { |date| MPS.get_filename_from_date(date) }
22
16
  end
17
+
23
18
  def self.open_editor(text_file)
24
- init_size = 0
25
- init_size = File.size(text_file) if File.exist?(text_file)
19
+ init_size = File.exist?(text_file) ? File.size(text_file) : 0
26
20
  TTY::Editor.open(text_file, command: "vim")
27
- curr_size = 0
28
- curr_size = File.size(text_file) if File.exist?(text_file)
29
- return curr_size-init_size
21
+ curr_size = File.exist?(text_file) ? File.size(text_file) : 0
22
+ curr_size - init_size
30
23
  end
31
24
  end
32
-