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.
@@ -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"
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module MPS
3
+ module Interpolators
4
+ class Time
5
+ SIGNATURE_REGEX = /:time/
6
+ def get_str(**ref)
7
+ Chronic.parse(ref[:args].first).strftime("Date: %Y-%m-%d, Time: %H:%M")
8
+ end
9
+ end
10
+ end
11
+ end
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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MPS
4
+ VERSION = "0.5.0"
5
+ end
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