mps 0.5.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 +7 -0
- data/.github/workflows/main.yml +29 -0
- data/.gitignore +13 -0
- data/CLAUDE.md +102 -0
- data/GETTING_STARTED.md +447 -0
- data/Gemfile +6 -0
- data/IMPROVEMENTS.md +90 -0
- data/README.md +183 -0
- data/Rakefile +18 -0
- data/bin/console +38 -0
- data/bin/setup +8 -0
- data/exe/mps +5 -0
- data/lib/cli/mps.rb +442 -0
- data/lib/mps/config.rb +64 -0
- data/lib/mps/constants.rb +68 -0
- data/lib/mps/elements/element.rb +49 -0
- data/lib/mps/elements/elements.rb +6 -0
- data/lib/mps/elements/log.rb +32 -0
- data/lib/mps/elements/mps.rb +15 -0
- data/lib/mps/elements/note.rb +15 -0
- data/lib/mps/elements/reminder.rb +16 -0
- data/lib/mps/elements/task.rb +19 -0
- data/lib/mps/engines/engines.rb +1 -0
- data/lib/mps/engines/mps.rb +108 -0
- data/lib/mps/interpolators/interpolators.rb +1 -0
- data/lib/mps/interpolators/time.rb +11 -0
- data/lib/mps/mps.rb +32 -0
- data/lib/mps/store.rb +75 -0
- data/lib/mps/version.rb +5 -0
- data/lib/mps.rb +21 -0
- data/mps.gemspec +49 -0
- data/rust_rollout_spec.md +935 -0
- metadata +258 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MPS
|
|
4
|
+
module Elements
|
|
5
|
+
class Note
|
|
6
|
+
SIGNATURE_STAMP = "note"
|
|
7
|
+
SIGNATURE_REGEX = /\Anote\z/
|
|
8
|
+
include Element
|
|
9
|
+
|
|
10
|
+
def self.parse_args(raw)
|
|
11
|
+
{ tags: Element.split_args(raw)[:tags] }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MPS
|
|
4
|
+
module Elements
|
|
5
|
+
class Reminder
|
|
6
|
+
SIGNATURE_STAMP = "reminder"
|
|
7
|
+
SIGNATURE_REGEX = /\Areminder\z/
|
|
8
|
+
include Element
|
|
9
|
+
|
|
10
|
+
def self.parse_args(raw)
|
|
11
|
+
p = Element.split_args(raw)
|
|
12
|
+
{ tags: p[:tags], at: p[:attrs][:at] }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MPS
|
|
4
|
+
module Elements
|
|
5
|
+
class Task
|
|
6
|
+
SIGNATURE_STAMP = "task"
|
|
7
|
+
SIGNATURE_REGEX = /\Atask\z/
|
|
8
|
+
include Element
|
|
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
|
|
14
|
+
|
|
15
|
+
def done? = parsed_args[:status] == "done"
|
|
16
|
+
def open? = !done?
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ir "./mps"
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MPS
|
|
4
|
+
module Engines
|
|
5
|
+
class EngineError < StandardError; end
|
|
6
|
+
|
|
7
|
+
class Parser
|
|
8
|
+
# Holds an unknown element type (sign not in registered element_classes).
|
|
9
|
+
Unknown = Struct.new(:ecn, :args, :refs, :body_str)
|
|
10
|
+
|
|
11
|
+
attr_reader :logger
|
|
12
|
+
attr_reader :element_classes
|
|
13
|
+
attr_reader :interpolator_classes
|
|
14
|
+
|
|
15
|
+
def initialize(config)
|
|
16
|
+
@config = config
|
|
17
|
+
@element_classes = ::MPS::Elements.constants
|
|
18
|
+
.map { |k| ::MPS::Elements.const_get(k) }
|
|
19
|
+
.select { |x| x.class == Class }
|
|
20
|
+
@interpolator_classes = ::MPS::Interpolators.constants
|
|
21
|
+
.map { |k| ::MPS::Interpolators.const_get(k) }
|
|
22
|
+
.select { |x| x.class == Class }
|
|
23
|
+
@logger = @config.logger
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns the element class whose SIGNATURE_REGEX matches +str+, or nil.
|
|
27
|
+
def self.matched_element_class(str, element_classes)
|
|
28
|
+
element_classes.find { |ec| str =~ ec::SIGNATURE_REGEX }
|
|
29
|
+
end
|
|
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
|
+
# 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)
|
|
42
|
+
content = File.read(mps_file_path)
|
|
43
|
+
wrapped = "@#{::MPS::Elements::MPS::SIGNATURE_STAMP}[]{\n#{content}\n}"
|
|
44
|
+
base_ref = ::MPS::Constants::MPS_FILE_NAME_CLIPPER
|
|
45
|
+
.call(File.basename(mps_file_path)).to_i
|
|
46
|
+
|
|
47
|
+
open_re = ::MPS::Constants::AT_REGEXP
|
|
48
|
+
close_re = ::MPS::Constants::END_CURLY_REGEXP
|
|
49
|
+
|
|
50
|
+
elements = {}
|
|
51
|
+
stack = []
|
|
52
|
+
pos = 0
|
|
53
|
+
|
|
54
|
+
while pos < wrapped.size
|
|
55
|
+
open_m = open_re.match(wrapped, pos)
|
|
56
|
+
close_m = close_re.match(wrapped, pos)
|
|
57
|
+
|
|
58
|
+
break if open_m.nil? && close_m.nil?
|
|
59
|
+
|
|
60
|
+
use_open = open_m && (close_m.nil? || open_m.begin(0) < close_m.begin(0))
|
|
61
|
+
|
|
62
|
+
if use_open
|
|
63
|
+
ref_path = if stack.empty?
|
|
64
|
+
[base_ref]
|
|
65
|
+
else
|
|
66
|
+
parent = stack.last
|
|
67
|
+
parent[:child_counter] += 1
|
|
68
|
+
parent[:ref_path] + [parent[:child_counter]]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
stack.push(
|
|
72
|
+
sign: open_m[:element_sign],
|
|
73
|
+
args: open_m[:args],
|
|
74
|
+
body_start: open_m.end(0),
|
|
75
|
+
child_counter: 0,
|
|
76
|
+
ref_path: ref_path
|
|
77
|
+
)
|
|
78
|
+
pos = open_m.end(0)
|
|
79
|
+
else
|
|
80
|
+
break if stack.empty?
|
|
81
|
+
|
|
82
|
+
frame = stack.pop
|
|
83
|
+
body_str = wrapped[frame[:body_start]...close_m.begin(0)]
|
|
84
|
+
ref_key = frame[:ref_path].join(".")
|
|
85
|
+
ec = matched_element_class(frame[:sign], element_classes)
|
|
86
|
+
|
|
87
|
+
elements[ref_key] = if ec
|
|
88
|
+
ec.new(args: frame[:args], refs: frame[:ref_path], body_str: body_str)
|
|
89
|
+
else
|
|
90
|
+
Unknown.new(frame[:sign], frame[:args], frame[:ref_path], body_str)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
pos = close_m.end(0)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
elements
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class << self
|
|
101
|
+
alias parse_mps_file_to_elments_hash parse_mps_file_to_elements_hash
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Backward-compatible alias — existing code using Engines::MPS still works.
|
|
106
|
+
MPS = Parser
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ir "./time"
|
data/lib/mps/mps.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
|
|
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
|
+
module MPS
|
|
12
|
+
def self.get_date(str)
|
|
13
|
+
return Chronic.parse(str).to_date
|
|
14
|
+
end
|
|
15
|
+
def self.get_filename_from_date(date)
|
|
16
|
+
"#{date.strftime('%Y%m%d')}.#{Constants::MPS_EXT}"
|
|
17
|
+
end
|
|
18
|
+
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
|
|
22
|
+
end
|
|
23
|
+
def self.open_editor(text_file)
|
|
24
|
+
init_size = 0
|
|
25
|
+
init_size = File.size(text_file) if File.exist?(text_file)
|
|
26
|
+
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
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
data/lib/mps/store.rb
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MPS
|
|
4
|
+
class Store
|
|
5
|
+
def initialize(storage_dir)
|
|
6
|
+
@storage_dir = storage_dir
|
|
7
|
+
@element_classes = Elements.constants
|
|
8
|
+
.map { |k| Elements.const_get(k) }
|
|
9
|
+
.select { |x| x.class == Class }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# First .mps file found for +date+, or nil.
|
|
13
|
+
def find_file(date)
|
|
14
|
+
find_files(date).first
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# All .mps files matching +date+ (handles multiple files per day).
|
|
18
|
+
def find_files(date)
|
|
19
|
+
date_str = date.strftime("%Y%m%d")
|
|
20
|
+
Dir[File.join(@storage_dir, "#{date_str}*.#{Constants::MPS_EXT}")]
|
|
21
|
+
.select { |f| File.basename(f) =~ Constants::MPS_FILE_NAME_REGEXP }
|
|
22
|
+
.sort
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Existing file for +date+, or a generated new path (file not yet created).
|
|
26
|
+
def find_or_create_path(date)
|
|
27
|
+
find_file(date) || File.join(@storage_dir, Constants::MPS_NEW_FILE_NAME_GEN.call(date))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Parsed elements hash for +date+. Returns {} when no file exists.
|
|
31
|
+
def parse_date(date)
|
|
32
|
+
path = find_file(date)
|
|
33
|
+
return {} unless path
|
|
34
|
+
Engines::Parser.parse_mps_file_to_elements_hash(path, @element_classes)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Appends a new element to today's (or +date+'s) file. Returns the file path.
|
|
38
|
+
def append(type:, body:, tags: [], attrs: {}, date: Date.today)
|
|
39
|
+
args_parts = attrs.map { |k, v| "#{k}: #{v}" } + Array(tags)
|
|
40
|
+
args_str = args_parts.join(", ")
|
|
41
|
+
path = find_or_create_path(date)
|
|
42
|
+
File.open(path, "a") { |f| f.write("\n@#{type}[#{args_str}]{\n #{body}\n}\n") }
|
|
43
|
+
path
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# All .mps files in storage, sorted by filename (chronological).
|
|
47
|
+
def all_files
|
|
48
|
+
Dir[File.join(@storage_dir, "*.#{Constants::MPS_EXT}")]
|
|
49
|
+
.select { |f| File.basename(f) =~ Constants::MPS_FILE_NAME_REGEXP }
|
|
50
|
+
.sort
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Files whose date-stamp is >= +since_date+.
|
|
54
|
+
def files_since(since_date)
|
|
55
|
+
since_str = since_date.strftime("%Y%m%d")
|
|
56
|
+
all_files.select { |f| File.basename(f).slice(0, 8) >= since_str }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Full-text search across files. Returns [{element:, file:, date_str:}].
|
|
60
|
+
# +since_date+ is a Date; +type_filter+ and +tag_filter+ are strings.
|
|
61
|
+
def search(query, type_filter: nil, tag_filter: nil, since_date: nil)
|
|
62
|
+
files = since_date ? files_since(since_date) : all_files
|
|
63
|
+
files.flat_map do |file|
|
|
64
|
+
date_str = File.basename(file).slice(0, 8)
|
|
65
|
+
Engines::Parser.parse_mps_file_to_elements_hash(file, @element_classes)
|
|
66
|
+
.values
|
|
67
|
+
.reject { |e| e.is_a?(Elements::MPS) }
|
|
68
|
+
.select { |e| type_filter.nil? || e.class::SIGNATURE_STAMP == type_filter }
|
|
69
|
+
.select { |e| tag_filter.nil? || e.tags.include?(tag_filter) }
|
|
70
|
+
.select { |e| query.nil? || e.body_str.downcase.include?(query.downcase) }
|
|
71
|
+
.map { |e| { element: e, file: file, date_str: date_str } }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/mps/version.rb
ADDED
data/lib/mps.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "logger"
|
|
5
|
+
require "chronic"
|
|
6
|
+
require "tty-editor"
|
|
7
|
+
require "strscan"
|
|
8
|
+
require_relative "mps/version"
|
|
9
|
+
require_relative "mps/mps"
|
|
10
|
+
|
|
11
|
+
ir "mps/constants"
|
|
12
|
+
ir "mps/config"
|
|
13
|
+
ir "mps/interpolators/interpolators"
|
|
14
|
+
ir "mps/elements/elements"
|
|
15
|
+
ir "mps/engines/engines"
|
|
16
|
+
ir "mps/store"
|
|
17
|
+
ir "cli/mps"
|
|
18
|
+
module MPS
|
|
19
|
+
class Error < StandardError; end
|
|
20
|
+
# Your code goes here...
|
|
21
|
+
end
|
data/mps.gemspec
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/mps/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "mps"
|
|
7
|
+
spec.version = MPS::VERSION
|
|
8
|
+
spec.authors = ["mash-97"]
|
|
9
|
+
spec.email = ["itzmashz@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "MPS (MonoPsyches)"
|
|
12
|
+
spec.description = "Manage MonoPsyches."
|
|
13
|
+
spec.homepage = "https://github.com/mash-97/mps"
|
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
|
15
|
+
|
|
16
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
|
17
|
+
|
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
20
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
|
21
|
+
|
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
|
26
|
+
end
|
|
27
|
+
spec.bindir = "exe"
|
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
29
|
+
spec.require_paths = ["lib"]
|
|
30
|
+
|
|
31
|
+
# Uncomment to register a new dependency of your gem
|
|
32
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
|
33
|
+
spec.add_runtime_dependency "strscan", ">= 3.0"
|
|
34
|
+
spec.add_runtime_dependency "thor", "~> 1.3"
|
|
35
|
+
spec.add_runtime_dependency "tty-editor", "~> 0.7.0"
|
|
36
|
+
spec.add_runtime_dependency "chronic", "~> 0.10.2"
|
|
37
|
+
spec.add_runtime_dependency "cli-ui", "~> 2.2"
|
|
38
|
+
|
|
39
|
+
spec.add_development_dependency "rake", "~> 13.2"
|
|
40
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
|
41
|
+
spec.add_development_dependency "fakefs", "~> 2.5"
|
|
42
|
+
spec.add_development_dependency "tmpdir", ">= 0.1.3"
|
|
43
|
+
spec.add_development_dependency "yard", "~> 0.9.37"
|
|
44
|
+
spec.add_development_dependency "rack", "~> 3.1"
|
|
45
|
+
spec.add_development_dependency "webrick", "~> 1.8"
|
|
46
|
+
spec.add_development_dependency "rackup", "~> 2.1"
|
|
47
|
+
# For more information and examples about making a new gem, checkout our
|
|
48
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
49
|
+
end
|