mps 0.5.0 → 1.0.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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +23 -18
- data/.gitignore +4 -3
- data/ARCHITECTURE.md +298 -0
- data/CLAUDE.md +6 -10
- data/GETTING_STARTED.md +176 -8
- data/Gemfile.lock +46 -0
- data/Rakefile +6 -5
- data/lib/cli/mps.rb +88 -333
- data/lib/mps/cli/commands/append.rb +32 -0
- data/lib/mps/cli/commands/config_cmd.rb +28 -0
- data/lib/mps/cli/commands/export.rb +51 -0
- data/lib/mps/cli/commands/git.rb +44 -0
- data/lib/mps/cli/commands/list.rb +51 -0
- data/lib/mps/cli/commands/open.rb +25 -0
- data/lib/mps/cli/commands/search.rb +34 -0
- data/lib/mps/cli/commands/stats.rb +65 -0
- data/lib/mps/cli/commands/tags.rb +33 -0
- data/lib/mps/cli/commands/update.rb +54 -0
- data/lib/mps/cli/commands.rb +5 -0
- data/lib/mps/config.rb +8 -3
- data/lib/mps/constants.rb +7 -5
- data/lib/mps/elements/element.rb +41 -11
- data/lib/mps/elements/elements.rb +8 -6
- data/lib/mps/elements/log.rb +2 -4
- data/lib/mps/elements/mps.rb +1 -4
- data/lib/mps/elements/note.rb +1 -4
- data/lib/mps/elements/reminder.rb +1 -4
- data/lib/mps/elements/task.rb +1 -4
- data/lib/mps/engines/engines.rb +3 -1
- data/lib/mps/engines/mps.rb +20 -10
- data/lib/mps/interpolators/interpolators.rb +3 -1
- data/lib/mps/mps.rb +9 -19
- data/lib/mps/presenter.rb +128 -0
- data/lib/mps/query.rb +71 -0
- data/lib/mps/ref_resolver.rb +76 -0
- data/lib/mps/store.rb +95 -6
- data/lib/mps/version.rb +1 -1
- data/lib/mps.rb +11 -9
- data/mps.gemspec +15 -24
- data/prompt.txt +64 -0
- metadata +28 -90
|
@@ -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,51 @@
|
|
|
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
|
+
dates = if options[:all]
|
|
24
|
+
store.all_files.map { |f| Date.strptime(File.basename(f)[0, 8], "%Y%m%d") }.uniq.sort
|
|
25
|
+
elsif options[:since]
|
|
26
|
+
date = ::MPS.get_date(datesign)
|
|
27
|
+
date_range(options[:since], date)
|
|
28
|
+
else
|
|
29
|
+
[::MPS.get_date(datesign).to_date]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
per_line = options[:per_line_space].to_i
|
|
33
|
+
per_date = options[:per_date_space].to_i
|
|
34
|
+
shown = 0
|
|
35
|
+
multi = dates.size > 1
|
|
36
|
+
|
|
37
|
+
dates.each_with_index do |d, idx|
|
|
38
|
+
all = store.parse_date(d)
|
|
39
|
+
next if all.empty?
|
|
40
|
+
resolver = options[:refs] ? ::MPS::RefResolver.new(all) : nil
|
|
41
|
+
count = print_tree(all, options, resolver: resolver, per_line_space: per_line,
|
|
42
|
+
header: multi ? d.strftime("%Y-%m-%d") : nil)
|
|
43
|
+
shown += count
|
|
44
|
+
per_date.times { say "" } if multi && count > 0 && idx < dates.size - 1
|
|
45
|
+
end
|
|
46
|
+
say set_color("(no elements found)", :yellow) if shown.zero?
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
raise Thor::Error, e
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
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,65 @@
|
|
|
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",
|
|
6
|
+
desc: "Stats from SINCE up to DATESIGN"
|
|
7
|
+
def stats(datesign = "today")
|
|
8
|
+
init
|
|
9
|
+
begin
|
|
10
|
+
date = ::MPS.get_date(datesign)
|
|
11
|
+
dates = options[:since] ? date_range(options[:since], date) : [date.to_date]
|
|
12
|
+
|
|
13
|
+
total = Hash.new(0)
|
|
14
|
+
total_log_mins = 0
|
|
15
|
+
any = false
|
|
16
|
+
|
|
17
|
+
dates.each do |d|
|
|
18
|
+
elements = store.parse_date(d).values
|
|
19
|
+
.reject { |e| e.is_a?(::MPS::Elements::MPS) || e.is_a?(::MPS::Engines::Parser::Unknown) }
|
|
20
|
+
next if elements.empty?
|
|
21
|
+
any = true
|
|
22
|
+
counts = elements.group_by { |e| e.class::SIGNATURE_STAMP }.transform_values(&:size)
|
|
23
|
+
log_mins = elements.select { |e| e.is_a?(::MPS::Elements::Log) }
|
|
24
|
+
.sum { |e| e.duration_minutes || 0 }
|
|
25
|
+
tasks = elements.select { |e| e.is_a?(::MPS::Elements::Task) }
|
|
26
|
+
|
|
27
|
+
parts = []
|
|
28
|
+
if (n = counts["task"])
|
|
29
|
+
open_n = tasks.count(&:open?)
|
|
30
|
+
done_n = tasks.count(&:done?)
|
|
31
|
+
parts << "#{n} task#{n != 1 ? 's' : ''} " \
|
|
32
|
+
"(#{set_color("#{open_n} open", :yellow)}, " \
|
|
33
|
+
"#{set_color("#{done_n} done", :green)})"
|
|
34
|
+
end
|
|
35
|
+
parts << "#{counts['note']} note#{counts['note'] != 1 ? 's' : ''}" if counts["note"]
|
|
36
|
+
parts << "#{counts['reminder']} reminder#{counts['reminder'] != 1 ? 's' : ''}" if counts["reminder"]
|
|
37
|
+
if (n = counts["log"])
|
|
38
|
+
dur_str = format_duration(log_mins)
|
|
39
|
+
parts << "#{n} log#{n != 1 ? 's' : ''}#{dur_str.empty? ? '' : " (#{dur_str})"}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
say "#{set_color(d.strftime('%Y-%m-%d'), :white)} — #{parts.join(', ')}"
|
|
43
|
+
counts.each { |k, v| total[k] += v }
|
|
44
|
+
total_log_mins += log_mins
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
say set_color("(no data found)", :yellow) unless any
|
|
48
|
+
|
|
49
|
+
if dates.size > 1 && any
|
|
50
|
+
say set_color("─" * 44, :white)
|
|
51
|
+
tparts = []
|
|
52
|
+
tparts << "#{total['task']} tasks" if total["task"] > 0
|
|
53
|
+
tparts << "#{total['note']} notes" if total["note"] > 0
|
|
54
|
+
tparts << "#{total['reminder']} reminders" if total["reminder"] > 0
|
|
55
|
+
if total["log"] > 0
|
|
56
|
+
dur_str = format_duration(total_log_mins)
|
|
57
|
+
tparts << "#{total['log']} logs#{dur_str.empty? ? '' : " (#{dur_str} total)"}"
|
|
58
|
+
end
|
|
59
|
+
say "Total: #{tparts.join(', ')}"
|
|
60
|
+
end
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
raise Thor::Error, e
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
MPS::CLI::MPS.class_eval do
|
|
4
|
+
desc "tags [DATESIGN]", "Show tag usage counts (equivalent to mps list tags)"
|
|
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
|
+
def tags(datesign = "today")
|
|
9
|
+
init
|
|
10
|
+
begin
|
|
11
|
+
date = ::MPS.get_date(datesign)
|
|
12
|
+
dates = options[:since] ? date_range(options[:since], date) : [date.to_date]
|
|
13
|
+
q = ::MPS::Query.new(options)
|
|
14
|
+
|
|
15
|
+
counts = Hash.new(0)
|
|
16
|
+
dates.each do |d|
|
|
17
|
+
q.apply(store.parse_date(d)).each_value do |el|
|
|
18
|
+
el.tags.each { |t| counts[t] += 1 }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if counts.empty?
|
|
23
|
+
say set_color("(no tags found)", :yellow)
|
|
24
|
+
else
|
|
25
|
+
counts.sort_by { |_, v| -v }.each do |tag, n|
|
|
26
|
+
say " #{set_color(tag, :white)} (#{n})"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
raise Thor::Error, e
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Collect all schema-declared attribute flags for the update command.
|
|
4
|
+
# This runs at class-load time so options are available to Thor.
|
|
5
|
+
_all_schema_attrs = ::MPS::Elements.constants
|
|
6
|
+
.map { |k| ::MPS::Elements.const_get(k) }
|
|
7
|
+
.select { |x| x.class == Class }
|
|
8
|
+
.flat_map { |klass| klass.schema.to_a }
|
|
9
|
+
.uniq { |name, _| name }
|
|
10
|
+
.select { |_, defn| defn[:flag] }
|
|
11
|
+
|
|
12
|
+
MPS::CLI::MPS.class_eval do
|
|
13
|
+
desc "update REFPATH", "Update an element's attributes in-place"
|
|
14
|
+
_all_schema_attrs.each do |_name, defn|
|
|
15
|
+
method_option defn[:flag], type: :string, desc: "Set #{defn[:flag]}"
|
|
16
|
+
end
|
|
17
|
+
method_option :date, type: :string, aliases: "-d",
|
|
18
|
+
desc: "Date context for human refs (default: today)"
|
|
19
|
+
def update(ref_path)
|
|
20
|
+
init
|
|
21
|
+
begin
|
|
22
|
+
date = options[:date] ? ::MPS.get_date(options[:date]).to_date : Date.today
|
|
23
|
+
new_attrs = {}
|
|
24
|
+
_all_schema_attrs.each do |name, defn|
|
|
25
|
+
flag_sym = defn[:flag].tr("-", "_").to_sym
|
|
26
|
+
new_attrs[name] = options[flag_sym] if options[flag_sym]
|
|
27
|
+
end
|
|
28
|
+
raise Thor::Error, "No attributes specified." if new_attrs.empty?
|
|
29
|
+
|
|
30
|
+
unless store.rewrite_element(ref_path, new_attrs, date: date)
|
|
31
|
+
raise Thor::Error, "Could not update '#{ref_path}'. Check the ref is correct."
|
|
32
|
+
end
|
|
33
|
+
say_status :updated, ref_path, :green
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
raise Thor::Error, e
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
desc "done REFPATH", "Mark a task as done (shorthand for update REFPATH --status done)"
|
|
40
|
+
method_option :date, type: :string, aliases: "-d",
|
|
41
|
+
desc: "Date context for human refs (default: today)"
|
|
42
|
+
def done(ref_path)
|
|
43
|
+
init
|
|
44
|
+
begin
|
|
45
|
+
date = options[:date] ? ::MPS.get_date(options[:date]).to_date : Date.today
|
|
46
|
+
unless store.rewrite_element(ref_path, { status: "done" }, date: date)
|
|
47
|
+
raise Thor::Error, "Could not mark '#{ref_path}' as done. Check the ref is correct."
|
|
48
|
+
end
|
|
49
|
+
say_status :done, ref_path, :green
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
raise Thor::Error, e
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
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
|
|
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
|
|
25
|
-
@git_branch
|
|
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:
|
|
52
|
-
storage_dir:
|
|
53
|
-
log_file:
|
|
54
|
-
git_remote:
|
|
55
|
-
git_branch:
|
|
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
|
|
data/lib/mps/elements/element.rb
CHANGED
|
@@ -2,7 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
module MPS
|
|
4
4
|
module Element
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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"
|
data/lib/mps/elements/log.rb
CHANGED
|
@@ -7,10 +7,8 @@ module MPS
|
|
|
7
7
|
SIGNATURE_REGEX = /\Alog\z/
|
|
8
8
|
include Element
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
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]
|
data/lib/mps/elements/mps.rb
CHANGED
data/lib/mps/elements/note.rb
CHANGED
data/lib/mps/elements/task.rb
CHANGED
|
@@ -7,10 +7,7 @@ module MPS
|
|
|
7
7
|
SIGNATURE_REGEX = /\Atask\z/
|
|
8
8
|
include Element
|
|
9
9
|
|
|
10
|
-
|
|
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?
|
data/lib/mps/engines/engines.rb
CHANGED
data/lib/mps/engines/mps.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
data/lib/mps/mps.rb
CHANGED
|
@@ -1,32 +1,22 @@
|
|
|
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
|
-
|
|
5
|
+
Chronic.parse(str).to_date
|
|
14
6
|
end
|
|
7
|
+
|
|
15
8
|
def self.get_filename_from_date(date)
|
|
16
|
-
|
|
9
|
+
Constants::MPS_NEW_FILE_NAME_GEN.call(date)
|
|
17
10
|
end
|
|
11
|
+
|
|
18
12
|
def self.get_filenames_from_date_range(s_date, e_date)
|
|
19
|
-
(s_date..e_date).collect
|
|
20
|
-
MPS.get_filename_from_date(date)
|
|
21
|
-
end
|
|
13
|
+
(s_date..e_date).collect { |date| MPS.get_filename_from_date(date) }
|
|
22
14
|
end
|
|
15
|
+
|
|
23
16
|
def self.open_editor(text_file)
|
|
24
|
-
init_size = 0
|
|
25
|
-
init_size = File.size(text_file) if File.exist?(text_file)
|
|
17
|
+
init_size = File.exist?(text_file) ? File.size(text_file) : 0
|
|
26
18
|
TTY::Editor.open(text_file, command: "vim")
|
|
27
|
-
curr_size = 0
|
|
28
|
-
curr_size
|
|
29
|
-
return curr_size-init_size
|
|
19
|
+
curr_size = File.exist?(text_file) ? File.size(text_file) : 0
|
|
20
|
+
curr_size - init_size
|
|
30
21
|
end
|
|
31
22
|
end
|
|
32
|
-
|