{@: rooto:"YARD::CodeObjects::RootObject:@childrenIC:&YARD::CodeObjects::CodeObjectList[ o:$YARD::CodeObjects::ModuleObject;IC;[o:#YARD::CodeObjects::ClassObject;IC;[o:$YARD::CodeObjects::MethodObject:@module_functionF: @scope: instance:@visibility: public: @pathI"Doing::Item#date:EF:@parameters[: @files[[I"lib/doing/item.rb;Ti :@current_file_has_commentsF: @name: date:@source_type: ruby: @tags[:@docstrings{:@docstringIC:YARD::Docstring")Returns the value of attribute date. ;T;[:@ref_tags[: @allI")Returns the value of attribute date.;T:@unresolved_reference0: @object@ :@hash_flagF: @summary0:@namespace@ : @sourceI"def date @date end;T:@signatureI" def date;T: @dynamicTo; ; F; ;;;;I"Doing::Item#date=;F;[[I" value;T0;[[@i ;F;: date=;;;[;{;IC;"Sets the attribute date ;T;[o:YARD::Tags::Tag :@tag_nameI" param;F: @textI",the value to set the attribute date to.;T;I" value;T: @types0;!@;[;I"QSets the attribute date @param value the value to set the attribute date to.;T; 0;!@;"F;#0;$@ ;%I")def date=(value) @date = value end;T;&I"def date=(value);T;'To; ; F; ;;;;I"Doing::Item#title;F;[;[[@i ;F;: title;;;[;{;IC;"*Returns the value of attribute title. ;T;[;[;I"*Returns the value of attribute title.;T; 0;!@-;"F;#0;$@ ;%I"def title @title end;T;&I"def title;T;'To; ; F; ;;;;I"Doing::Item#title=;F;[[@0;[[@i ;F;: title=;;;[;{;IC;"Sets the attribute title ;T;[o;) ;*I" param;F;+I"-the value to set the attribute title to.;T;I" value;T;,0;!@:;[;I"SSets the attribute title @param value the value to set the attribute title to.;T; 0;!@:;"F;#0;$@ ;%I"+def title=(value) @title = value end;T;&I"def title=(value);T;'To; ; F; ;;;;I"Doing::Item#section;F;[;[[@i ;F;: section;;;[;{;IC;",Returns the value of attribute section. ;T;[;[;I",Returns the value of attribute section.;T; 0;!@L;"F;#0;$@ ;%I"def section @section end;T;&I"def section;T;'To; ; F; ;;;;I"Doing::Item#section=;F;[[@0;[[@i ;F;: section=;;;[;{;IC;"Sets the attribute section ;T;[o;) ;*I" param;F;+I"/the value to set the attribute section to.;T;I" value;T;,0;!@Y;[;I"WSets the attribute section @param value the value to set the attribute section to.;T; 0;!@Y;"F;#0;$@ ;%I"/def section=(value) @section = value end;T;&I"def section=(value);T;'To; ; F; ;;;;I"Doing::Item#note;F;[;[[@i ;F;: note;;;[;{;IC;")Returns the value of attribute note. ;T;[;[;I")Returns the value of attribute note.;T; 0;!@k;"F;#0;$@ ;%I"def note @note end;T;&I" def note;T;'To; ; F; ;;;;I"Doing::Item#note=;F;[[@0;[[@i ;F;: note=;;;[;{;IC;"Sets the attribute note ;T;[o;) ;*I" param;F;+I",the value to set the attribute note to.;T;I" value;T;,0;!@x;[;I"QSets the attribute note @param value the value to set the attribute note to.;T; 0;!@x;"F;#0;$@ ;%I")def note=(value) @note = value end;T;&I"def note=(value);T;'To; ; F; ;;;;I"Doing::Item#initialize;F;[ [I" date;T0[I" title;T0[I" section;T0[I" note;TI"nil;T;[[@i;T;:initialize;;;[;{;IC;"DInitialize an item with date, title, section, and optional note;T;[ o;) ;*I" param;F;+I"The item's start date;T;I" date;T;,[I" Time;T;!@o;) ;*I" param;F;+I"The title;T;I" title;T;,[I" String;T;!@o;) ;*I" param;F;+I"*The section to which the item belongs;T;I" section;T;,[I" String;T;!@o;) ;*I" param;F;+I"The note (optional);T;I" note;T;,[I"Array or String;T;!@o;) ;*I" return;F;+I"a new instance of Item;T;0;,[I" Item;F;!@;[;I"E Initialize an item with date, title, section, and optional note @param date [Time] The item's start date @param title [String] The title @param section [String] The section to which the item belongs @param note [Array or String] The note (optional) ;T; 0;!@:@ref_tag_recurse_counti;"T:@line_rangeo: Range: exclF: begini:endi;$@ :@explicitT;%I"def initialize(date, title, section, note = nil) @date = date.is_a?(Time) ? date : Time.parse(date) @title = title @section = section @note = Note.new(note) end;T;&I"5def initialize(date, title, section, note = nil);T;'To; ; F; ;;;;I"Doing::Item#interval;F;[;[[@i+;T;: interval;;;[;{;IC;"aGet the difference between the item's start date and the value of its @done tag (if present);T;[o;) ;*I" return;F;+I"Interval in seconds;T;0;,0;!@;[;I" Get the difference between the item's start date and the value of its @done tag (if present) @return Interval in seconds ;T; 0;!@;4i;"T;5o;6;7F;8i%;9i*;$@ ;:T;%I"3def interval @interval ||= calc_interval end;T;&I"def interval;T;'To; ; F; ;;;;I"Doing::Item#end_date;F;[;[[@i4;T;: end_date;;;[;{;IC;"*Get the value of the item's @done tag;T;[o;) ;*I" return;F;+I"@done value;T;0;,[I" Time;T;!@;[;I"L Get the value of the item's @done tag @return [Time] @done value ;T; 0;!@;4i;"T;5o;6;7F;8i/;9i3;$@ ;:T;%I"}def end_date @end_date ||= Time.parse(Regexp.last_match(1)) if @title =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ end;T;&I"def end_date;T;'To; ; F; ;;;;I"Doing::Item#equal?;F;[[I" other;T0;[[@i?;T;: equal?;;;[;{;IC;"$Test for equality between items;T;[o;) ;*I" param;F;+I"The other item;T;I" other;T;,[I" Item;T;!@o;) ;*I" return;F;+I"is equal?;T;0;,[I" Boolean;T;!@;[;I"p Test for equality between items @param other [Item] The other item @return [Boolean] is equal? ;T; 0;!@;4i;"T;5o;6;7F;8i8;9i>;$@ ;:T;%I"def equal?(other) return false if @title.strip != other.title.strip return false if @date != other.date return false unless @note.equal?(other.note) true end;T;&I"def equal?(other);T;'To; ; F; ;;;;I"Doing::Item#same_time?;F;[[I" item_b;T0;[[@iP;T;:same_time?;;;[;{;IC;"RTest if two items occur at the same time (same start date and equal duration);T;[o;) ;*I" param;F;+I"The item to compare;T;I" item_b;T;,[I" Item;T;!@o;) ;*I" return;F;+I"is equal?;T;0;,[I" Boolean;T;!@;[;I" Test if two items occur at the same time (same start date and equal duration) @param item_b [Item] The item to compare @return [Boolean] is equal? ;T; 0;!@;4i;"T;5o;6;7F;8iI;9iO;$@ ;:T;%I"[def same_time?(item_b) date == item_b.date ? interval == item_b.interval : false end;T;&I"def same_time?(item_b);T;'To; ; F; ;;;;I""Doing::Item#overlapping_time?;F;[[I" item_b;T0;[[@i\;T;:overlapping_time?;;;[;{;IC;"YTest if the interval between start date and @done value overlaps with another item's;T;[o;) ;*I" param;F;+I"The item to compare;T;I" item_b;T;,[I" Item;T;!@o;) ;*I" return;F;+I"overlaps?;T;0;,[I" Boolean;T;!@;[;I" Test if the interval between start date and @done value overlaps with another item's @param item_b [Item] The item to compare @return [Boolean] overlaps? ;T; 0;!@;4i;"T;5o;6;7F;8iT;9i[;$@ ;:T;%I"def overlapping_time?(item_b) return true if same_time?(item_b) start_a = date interval = interval end_a = interval ? start_a + interval.to_i : start_a start_b = item_b.date interval = item_b.interval end_b = interval ? start_b + interval.to_i : start_b (start_a >= start_b && start_a <= end_b) || (end_a >= start_b && end_a <= end_b) || (start_a < start_b && end_a > end_b) end;T;&I""def overlapping_time?(item_b);T;'To; ; F; ;;;;I"Doing::Item#tag;F;[ [I"tag;T0[I" value:;TI"nil;T[I" remove:;TI" false;T[I"rename_to:;TI"nil;T[I" regex:;TI" false;T;[[@iq;T;:tag;;;[;{;IC;"4Add (or remove) tags from the title of the item;T;[ o;) ;*I" param;F;+I"The tag to add;T;I"tag;T;,[I" String;T;!@.o;) ;*I" param;F;+I"&A value to include as @tag(value);T;I" value;T;,[I" String;T;!@.o;) ;*I" param;F;+I"%if true remove instead of adding;T;I" remove;T;,[I" Boolean;T;!@.o;) ;*I" param;F;+I"3if not nil, rename target tag to this tag name;T;I"rename_to;T;,[I" String;T;!@.o;) ;*I" param;F;+I"-treat target tag string as regex pattern;T;I" regex;T;,[I" Boolean;T;!@.;[;I"~ Add (or remove) tags from the title of the item @param tag [String] The tag to add @param value [String] A value to include as @tag(value) @param remove [Boolean] if true remove instead of adding @param rename_to [String] if not nil, rename target tag to this tag name @param regex [Boolean] treat target tag string as regex pattern ;T; 0;!@.;4i;"T;5o;6;7F;8ih;9ip;$@ ;:T;%I"def tag(tag, value: nil, remove: false, rename_to: nil, regex: false) @title.tag!(tag, value: value, remove: remove, rename_to: rename_to, regex: regex).strip! end;T;&I"Jdef tag(tag, value: nil, remove: false, rename_to: nil, regex: false);T;'To; ; F; ;;;;I"Doing::Item#tags;F;[;[[@iz;T;: tags;;;[;{;IC;"#Get a list of tags on the item;T;[o;) ;*I" return;F;+I"array of tags (no values);T;0;,[I" Array;T;!@h;[;I"T Get a list of tags on the item @return [Array] array of tags (no values) ;T; 0;!@h;4i;"T;5o;6;7F;8iu;9iy;$@ ;:T;%I"Sdef tags @title.scan(/(?<= |\A)@([^\s(]+)/).map {|tag| tag[0]}.sort.uniq end;T;&I" def tags;T;'To; ; F; ;;;;I"Doing::Item#tags?;F;[[I" tags;T0[I" bool;TI" :and;T[I" negate:;TI" false;T;[[@i;T;: tags?;;;[;{;IC;"!Test if item contains tag(s);T;[ o;) ;*I" param;F;+I"CThe tags to test. Can be an array or a comma-separated string.;T;I" tags;T;,[I"Array or String;T;!@{o;) ;*I" param;F;+I";The boolean to use for multiple tags (:and, :or, :not);T;I" bool;T;,[I" Symbol;T;!@{o;) ;*I" param;F;+I"negate the result?;T;I" negate;T;,[I" Boolean;T;!@{o;) ;*I" return;F;+I"(true if tag/bool combination passes;T;0;,[I" Boolean;T;!@{;[;I"D Test if item contains tag(s) @param tags (Array or String) The tags to test. Can be an array or a comma-separated string. @param bool (Symbol) The boolean to use for multiple tags (:and, :or, :not) @param negate [Boolean] negate the result? @return [Boolean] true if tag/bool combination passes ;T; 0;!@{;4i;"T;5o;6;7F;8i~;9i;$@ ;:T;%I"Adef tags?(tags, bool = :and, negate: false) tags = split_tags(tags) bool = bool.normalize_bool matches = case bool when :and all_tags?(tags) when :not no_tags?(tags) else any_tags?(tags) end negate ? !matches : matches end;T;&I"0def tags?(tags, bool = :and, negate: false);T;'To; ; F; ;;;;I"Doing::Item#search;F;[[I" search;T0[I" negate:;TI" false;T[I"case_type:;TI" :smart;T;[[@i;T;: search;;;[;{;IC;"'Test if item matches search string;T;[ o;) ;*I" param;F;+I"The search string;T;I" search;T;,[I" String;T;!@o;) ;*I" param;F;+I"negate results;T;I" negate;T;,[I" Boolean;T;!@o;) ;*I" param;F;+I"AThe case-sensitivity type (:sensitive, :insensitive, :smart);T;I"case_type;T;,[I" Symbol;T;!@o;) ;*I" return;F;+I"matches search criteria;T;0;,[I" Boolean;T;!@;[;I"A Test if item matches search string @param search [String] The search string @param negate [Boolean] negate results @param case_type (Symbol) The case-sensitivity type (:sensitive, :insensitive, :smart) @return [Boolean] matches search criteria ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@ ;:T;%I"def search(search, negate: false, case_type: :smart) text = @title + @note.to_s pattern = case search.strip when %r{^/.*?/$} search.sub(%r{/(.*?)/}, '\1') when /^'/ case_sensitive = true search.sub(/^'(.*?)'?$/, '\1') else if case_type == :smart case_sensitive = true if search =~ /[A-Z]/ else case_sensitive = case_type == :sensitive end search.split('').join('.{0,3}') end rx = Regexp.new(pattern, !case_sensitive) negate ? text !~ rx : text =~ rx end;T;&I"9def search(search, negate: false, case_type: :smart);T;'To; ; F; ;;;;I"Doing::Item#should_finish?;F;[;[[@i;F;:should_finish?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+I";T;0;,[I" Boolean;T;!@;[;@; 0;!@;4i;$@ ;:T;%I"5def should_finish? should?('never_finish') end;T;&I"def should_finish?;T;'To; ; F; ;;;;I"Doing::Item#should_time?;F;[;[[@i;F;:should_time?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@;!@;[;@; 0;!@;4i;$@ ;:T;%I"1def should_time? should?('never_time') end;T;&I"def should_time?;T;'To; ; F; ;;: private;I"Doing::Item#should?;F;[[I"key;T0;[[@i;F;: should?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@;!@;[;@; 0;!@;4i;$@ ;:T;%I"def should?(key) config = Doing.config.settings return true unless config[key].is_a?(Array) config[key].each do |tag| if tag =~ /^@/ return false if tags?(tag.sub(/^@/, '').downcase) elsif section.downcase == tag.downcase return false end end true end;T;&I"def should?(key);T;'To; ; F; ;;;F;I"Doing::Item#calc_interval;F;[;[[@i;F;:calc_interval;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@ ;:T;%I"def calc_interval done = end_date return nil if done.nil? start = @date t = (done - start).to_i t > 0 ? t : nil end;T;&I"def calc_interval;T;'To; ; F; ;;;F;I"Doing::Item#all_tags?;F;[[I" tags;T0;[[@i;F;:all_tags?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@;!@;[;@; 0;!@;4i;$@ ;:T;%I"jdef all_tags?(tags) tags.each do |tag| return false unless @title =~ /@#{tag}/ end true end;T;&I"def all_tags?(tags);T;'To; ; F; ;;;F;I"Doing::Item#no_tags?;F;[[I" tags;T0;[[@i;F;: no_tags?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@;!@#;[;@; 0;!@#;4i;$@ ;:T;%I"edef no_tags?(tags) tags.each do |tag| return false if @title =~ /@#{tag}/ end true end;T;&I"def no_tags?(tags);T;'To; ; F; ;;;F;I"Doing::Item#any_tags?;F;[[I" tags;T0;[[@i;F;:any_tags?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@;!@4;[;@; 0;!@4;4i;$@ ;:T;%I"fdef any_tags?(tags) tags.each do |tag| return true if @title =~ /@#{tag}/ end false end;T;&I"def any_tags?(tags);T;'To; ; F; ;;;F;I"Doing::Item#split_tags;F;[[I" tags;T0;[[@i;F;:split_tags;;;[;{;IC;" ;T;[;[;@; 0;!@E;4i;$@ ;:T;%I"xdef split_tags(tags) tags = tags.split(/ *, */) if tags.is_a? String tags.map { |t| t.strip.sub(/^@/, '') } end;T;&I"def split_tags(tags);T;'T: @owner@ :@class_mixinsIC;[;M@ :@instance_mixinsIC;[;M@ :@attributesIC:SymbolHash{: classIC;Q{:@symbolize_valueT;IC;Q{ ;IC;Q{: read@ : write@;ST;-IC;Q{;T@-;U@:;ST;/IC;Q{;T@L;U@Y;ST;1IC;Q{;T@k;U@x;ST;ST;ST: @aliases{: @groups[;[[@i ;T;: Item;;;;;[;{;IC;",This class describes a single WWID item;T;[;[;I". This class describes a single WWID item ;T; 0;!@ ;4i;"T;5o;6;7F;8i ;9i ;$@;I"Doing::Item;F:@superclasso:YARD::CodeObjects::Proxy :@orignamespace0:@origname0: @imethod0;: Object;$@: @obj0: @type;R;'To; ;IC;[o; ; F; ;;;;I"Doing::Note#initialize;F;[[I" note;TI"[];T;[[I"lib/doing/note.rb;Ti ;F;;3;;;[;{;IC;" ;T;[o;) ;*I" return;F;+I"a new instance of Note;T;0;,[I" Note;F;!@k;[;@; 0;!@k;4i;$@i;:T;%I"Adef initialize(note = []) super() add(note) if note end;T;&I"def initialize(note = []);T;'To; ; F; ;;;;I"Doing::Note#add;F;[[I" note;T0[I" replace:;TI" false;T;[[@si;F;:add;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@i;:T;%I"def add(note, replace: false) clear if replace if note.is_a?(String) append_string(note) elsif note.is_a?(Array) append(note) end end;T;&I""def add(note, replace: false);T;'To; ; F; ;;;;I"Doing::Note#append;F;[[I" lines;T0;[[@si;F;: append;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@i;:T;%I"=def append(lines) concat(lines) replace compress end;T;&I"def append(lines);T;'To; ; F; ;;;;I"Doing::Note#append_string;F;[[I" input;T0;[[@si!;F;:append_string;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@i;:T;%I"]def append_string(input) concat(input.split(/\n/).map(&:strip)) replace compress end;T;&I"def append_string(input);T;'To; ; F; ;;;;I"Doing::Note#compress!;F;[;[[@si&;F;:compress!;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@i;:T;%I")def compress! replace compress end;T;&I"def compress!;T;'To; ; F; ;;;;I"Doing::Note#compress;F;[;[[@si*;F;: compress;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@i;:T;%I"Cdef compress delete_if { |l| l =~ /^\s*$/ || l =~ /^#/ } end;T;&I"def compress;T;'To; ; F; ;;;;I"Doing::Note#strip_lines!;F;[;[[@si.;F;:strip_lines!;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@i;:T;%I"/def strip_lines! replace strip_lines end;T;&I"def strip_lines!;T;'To; ; F; ;;;;I"Doing::Note#strip_lines;F;[;[[@si2;F;:strip_lines;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@i;:T;%I"'def strip_lines map(&:strip) end;T;&I"def strip_lines;T;'To; ; F; ;;;;I"Doing::Note#to_s;F;[;[[@si6;F;: to_s;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@i;:T;%I"3def to_s compress.strip_lines.join("\n") end;T;&I" def to_s;T;'To; ; F; ;;;;I"Doing::Note#equal?;F;[[I" other;T0;[[@si:;F;;=;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@;!@;[;@; 0;!@;4i;$@i;:T;%I"Xdef equal?(other) return false unless other.is_a?(Note) to_s == other.to_s end;T;&I"def equal?(other);T;'T;M@i;NIC;[;M@i;OIC;[;M@i;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@si ;T;: Note;;;;;[;{;IC;"'This class describes an item note.;T;[;[;I") This class describes an item note. ;T; 0;!@i;4i;"T;5o;6;7F;8i ;9i ;$@;I"Doing::Note;F;Yo;Z ;[0;\0;]0;: Array;$@;_o; ;IC;[;M@ ;NIC;[;M@ ;OIC;[;M@ ;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[I"lib/doing/array.rb;Ti ;T;;j;;;;;[;{;IC;"Array helpers;T;[;[;I" Array helpers ;T; 0;!@ ;4i;"T;5o;6;7F;8i ;9i ;$@;I" Array;F;Yo;Z ;[0;\0;]0;;^;$@;_0;`;R;'T;`;R;'To; ;IC;[o; ; F; ;;;;I"Doing::Util#user_home;F;[;[[I"lib/doing/util.rb;Ti ;F;:user_home;;;[;{;IC;" ;T;[;[;@; 0;!@#;4i;$@!;:T;%I"gdef user_home if Dir.respond_to?('home') Dir.home else File.expand_path('~') end end;T;&I"def user_home;T;'To; ; F; ;;;;I"Doing::Util#exec_available;F;[[I"cli;T0;[[@(i;T;:exec_available;;;[;{;IC;"+Test if command line tool is available;T;[o;) ;*I" param;F;+I" The name or path of the cli;T;I"cli;T;,[I" String;T;!@0;[;I"e Test if command line tool is available @param cli [String] The name or path of the cli ;T; 0;!@0;4i;"T;5o;6;7F;8i;9i;$@!;:T;%I"def exec_available(cli) return false if cli.nil? if File.exist?(File.expand_path(cli)) File.executable?(File.expand_path(cli)) else system "which #{cli}", out: File::NULL, err: File::NULL end end;T;&I"def exec_available(cli);T;'To; ; F; ;;;;I"#Doing::Util#merge_default_proc;F;[[I" target;T0[I"overwrite;T0;[[@(i$;F;:merge_default_proc;;;[;{;IC;" ;T;[;[;@; 0;!@F;4i;$@!;:T;%I"def merge_default_proc(target, overwrite) return unless target.is_a?(Hash) && overwrite.is_a?(Hash) && target.default_proc.nil? target.default_proc = overwrite.default_proc end;T;&I".def merge_default_proc(target, overwrite);T;'To; ; F; ;;;;I"(Doing::Util#duplicate_frozen_values;F;[[I" target;T0;[[@(i*;F;:duplicate_frozen_values;;;[;{;IC;" ;T;[;[;@; 0;!@V;4i;$@!;:T;%I"def duplicate_frozen_values(target) target.each do |key, val| target[key] = val.dup if val.frozen? && duplicable?(val) end end;T;&I"(def duplicate_frozen_values(target);T;'To; ; F; ;;;;I""Doing::Util#deep_merge_hashes;F;[[I"master_hash;T0[I"other_hash;T0;[[@(i8;T;:deep_merge_hashes;;;[;{;IC;"CNon-destructive version of deep_merge_hashes! See that method.;T;[o;) ;*I" return;F;+I"the merged hashes.;T;0;,0;!@do;) ;*I" param;F;+I"The master hash;T;I"master_hash;T;,[I" Hash;T;!@do;) ;*I" param;F;+I"The other hash;T;I"other_hash;T;,[I" Hash;T;!@d;[;I"Non-destructive version of deep_merge_hashes! See that method. @return the merged hashes. @param [Hash] master_hash The master hash @param [Hash] other_hash The other hash ;T; 0;!@d;4i;"F;5o;6;7F;8i0;9i7;$@!;:T;%I"idef deep_merge_hashes(master_hash, other_hash) deep_merge_hashes!(master_hash.dup, other_hash) end;T;&I"3def deep_merge_hashes(master_hash, other_hash);T;'To; ; F; ;;;;I"#Doing::Util#deep_merge_hashes!;F;[[I" target;T0[I"overwrite;T0;[[@(iE;T;:deep_merge_hashes!;;;[;{;IC;"IMerges a master hash with another hash, recursively. master_hash - the "parent" hash whose values will be overridden other_hash - the other hash whose values will be persisted after the merge This code was lovingly stolen from some random gem: http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html Thanks to whoever made it.;T;[;[;I"IMerges a master hash with another hash, recursively. master_hash - the "parent" hash whose values will be overridden other_hash - the other hash whose values will be persisted after the merge This code was lovingly stolen from some random gem: http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html Thanks to whoever made it.;T; 0;!@;4i;"F;5o;6;7F;8i<;9iD;$@!;:T;%I"def deep_merge_hashes!(target, overwrite) merge_values(target, overwrite) merge_default_proc(target, overwrite) duplicate_frozen_values(target) target end;T;&I".def deep_merge_hashes!(target, overwrite);T;'To; ; F; ;;;;I"Doing::Util#duplicable?;F;[[I"obj;T0;[[@(iM;F;:duplicable?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@;!@;[;@; 0;!@;4i;$@!;:T;%I"qdef duplicable?(obj) case obj when nil, false, true, Symbol, Numeric false else true end end;T;&I"def duplicable?(obj);T;'To; ; F; ;;;;I"Doing::Util#mergable?;F;[[I" value;T0;[[@(iV;F;:mergable?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@;!@;[;@; 0;!@;4i;$@!;:T;%I"1def mergable?(value) value.is_a?(Hash) end;T;&I"def mergable?(value);T;'To; ; F; ;;;;I"Doing::Util#merge_values;F;[[I" target;T0[I"overwrite;T0;[[@(iZ;F;:merge_values;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@!;:T;%I"def merge_values(target, overwrite) target.merge!(overwrite) do |_key, old_val, new_val| if new_val.nil? old_val elsif mergable?(old_val) && mergable?(new_val) deep_merge_hashes(old_val, new_val) else new_val end end end;T;&I"(def merge_values(target, overwrite);T;'To; ; F; ;;;;I"Doing::Util#write_to_file;F;[[I" file;T0[I" content;T0[I" backup:;TI" true;T;[[@(im;T;:write_to_file;;;[;{;IC;"Write content to a file;T;[o;) ;*I" param;F;+I"(The path to the file to (over)write;T;I" file;T;,[I" String;T;!@o;) ;*I" param;F;+I"%The content to write to the file;T;I" content;T;,[I" String;T;!@o;) ;*I" param;F;+I"create a ~ backup;T;I" backup;T;,[I" Boolean;T;!@;[;I" Write content to a file @param file [String] The path to the file to (over)write @param content [String] The content to write to the file @param backup [Boolean] create a ~ backup ;T; 0;!@;4i;"T;5o;6;7F;8if;9il;$@!;:T;%I"Zdef write_to_file(file, content, backup: true) unless file puts content return end file = File.expand_path(file) if File.exist?(file) && backup # Create a backup copy for the undo command FileUtils.cp(file, "#{file}~") end File.open(file, 'w+') do |f| f.puts content end Hooks.trigger :post_write, file end;T;&I"3def write_to_file(file, content, backup: true);T;'To; ; F; ;;;;I"Doing::Util#safe_load_file;F;[[I" filename;T0;[[@(i|;F;:safe_load_file;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@!;:T;%I"Jdef safe_load_file(filename) SafeYAML.load_file(filename) || {} end;T;&I"!def safe_load_file(filename);T;'To; ; F; ;;;;I"Doing::Util#default_editor;F;[;[[@(i;F;:default_editor;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@!;:T;%I"Cdef default_editor @default_editor = find_default_editor end;T;&I"def default_editor;T;'To; ; F; ;;;;I"!Doing::Util#editor_with_args;F;[;[[@(i;F;:editor_with_args;;;[;{;IC;" ;T;[;[;@; 0;!@ ;4i;$@!;:T;%I"?def editor_with_args args_for_editor(default_editor) end;T;&I"def editor_with_args;T;'To; ; F; ;;;;I" Doing::Util#args_for_editor;F;[[I" editor;T0;[[@(i;F;:args_for_editor;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@!;:T;%I"def args_for_editor(editor) return editor if editor =~ /-\S/ args = case editor when /^(subl|code|mate)$/ ['-w'] when /^(vim|mvim)$/ ['-f'] else [] end "#{editor} #{args.join(' ')}" end;T;&I" def args_for_editor(editor);T;'To; ; F; ;;;;I"$Doing::Util#find_default_editor;F;[[I"editor_for;TI"'default';T;[[@(i;F;:find_default_editor;;;[;{;IC;" ;T;[;[;@; 0;!@$;4i;$@!;:T;%I"def find_default_editor(editor_for = 'default') # return nil unless $stdout.isatty || ENV['DOING_EDITOR_TEST'] if ENV['DOING_EDITOR_TEST'] return ENV['EDITOR'] end editor_config = Doing.config.settings['editors'] if editor_config.is_a?(String) Doing.logger.warn('Deprecated:', "Please update your configuration, 'editors' should be a mapping. Delete the key and run `doing config --update`.") return editor_config end if editor_config[editor_for] editor = editor_config[editor_for] # Doing.logger.debug('Editor:', "Using #{editor} from config 'editors->#{editor_for}'") return editor unless editor.nil? || editor.empty? end if editor_for != 'editor' && editor_config['default'] editor = editor_config['default'] # Doing.logger.debug('Editor:', "Using #{editor} from config: 'editors->default'") return editor unless editor.nil? || editor.empty? end editor ||= ENV['DOING_EDITOR'] || ENV['GIT_EDITOR'] || ENV['EDITOR'] unless editor.nil? || editor.empty? # Doing.logger.debug('Editor:', "Found editor in environment variables: #{editor}") return editor end Doing.logger.debug('ENV:', 'No EDITOR environment variable, testing available editors') editors = %w[vim vi code subl mate mvim nano emacs] editors.each do |ed| return ed if exec_available(ed) Doing.logger.debug('ENV:', "#{ed} not available") end nil end;T;&I"4def find_default_editor(editor_for = 'default');T;'T;M@!;NIC;[@!;M@!;OIC;[;M@!;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@(i ;T;: Util;;;;;[;{;IC;"Utilities;T;[;[;I"Utilities;T; 0;!@!;4i;"F;5o;6;7F;8i ;9i ;$@;I"Doing::Util;F;'To; ;IC;[Mo; ; F; ;;;;I"#Doing::WWID#additional_configs;F;[;[[I"lib/doing/wwid.rb;Ti;F;:additional_configs;;;[;{;IC;"7Returns the value of attribute additional_configs. ;T;[;[;I"7Returns the value of attribute additional_configs.;T; 0;!@F;"F;#0;$@D;%I"5def additional_configs @additional_configs end;T;&I"def additional_configs;T;'To; ; F; ;;;;I" Doing::WWID#current_section;F;[;[[@Ki;F;:current_section;;;[;{;IC;"4Returns the value of attribute current_section. ;T;[;[;I"4Returns the value of attribute current_section.;T; 0;!@T;"F;#0;$@D;%I"/def current_section @current_section end;T;&I"def current_section;T;'To; ; F; ;;;;I"Doing::WWID#doing_file;F;[;[[@Ki;F;:doing_file;;;[;{;IC;"/Returns the value of attribute doing_file. ;T;[;[;I"/Returns the value of attribute doing_file.;T; 0;!@a;"F;#0;$@D;%I"%def doing_file @doing_file end;T;&I"def doing_file;T;'To; ; F; ;;;;I"Doing::WWID#content;F;[;[[@Ki;F;: content;;;[;{;IC;",Returns the value of attribute content. ;T;[;[;I",Returns the value of attribute content.;T; 0;!@n;"F;#0;$@D;%I"def content @content end;T;&I"def content;T;'To; ; F; ;;;;I"Doing::WWID#config;F;[;[[@Ki;F;: config;;;[;{;IC;"+Returns the value of attribute config. ;T;[;[;I"+Returns the value of attribute config.;T; 0;!@{;"F;#0;$@D;%I"def config @config end;T;&I"def config;T;'To; ; F; ;;;;I"Doing::WWID#config=;F;[[@0;[[@Ki;F;: config=;;;[;{;IC;"Sets the attribute config ;T;[o;) ;*I" param;F;+I".the value to set the attribute config to.;T;I" value;T;,0;!@;[;I"USets the attribute config @param value the value to set the attribute config to.;T; 0;!@;"F;#0;$@D;%I"-def config=(value) @config = value end;T;&I"def config=(value);T;'To; ; F; ;;;;I"Doing::WWID#config_file;F;[;[[@Ki;F;:config_file;;;[;{;IC;"0Returns the value of attribute config_file. ;T;[;[;I"0Returns the value of attribute config_file.;T; 0;!@;"F;#0;$@D;%I"'def config_file @config_file end;T;&I"def config_file;T;'To; ; F; ;;;;I"Doing::WWID#config_file=;F;[[@0;[[@Ki;F;:config_file=;;;[;{;IC;"#Sets the attribute config_file ;T;[o;) ;*I" param;F;+I"3the value to set the attribute config_file to.;T;I" value;T;,0;!@;[;I"_Sets the attribute config_file @param value the value to set the attribute config_file to.;T; 0;!@;"F;#0;$@D;%I"7def config_file=(value) @config_file = value end;T;&I"def config_file=(value);T;'To; ; F; ;;;;I"Doing::WWID#auto_tag;F;[;[[@Ki;F;: auto_tag;;;[;{;IC;"-Returns the value of attribute auto_tag. ;T;[;[;I"-Returns the value of attribute auto_tag.;T; 0;!@;"F;#0;$@D;%I"!def auto_tag @auto_tag end;T;&I"def auto_tag;T;'To; ; F; ;;;;I"Doing::WWID#auto_tag=;F;[[@0;[[@Ki;F;:auto_tag=;;;[;{;IC;" Sets the attribute auto_tag ;T;[o;) ;*I" param;F;+I"0the value to set the attribute auto_tag to.;T;I" value;T;,0;!@;[;I"YSets the attribute auto_tag @param value the value to set the attribute auto_tag to.;T; 0;!@;"F;#0;$@D;%I"1def auto_tag=(value) @auto_tag = value end;T;&I"def auto_tag=(value);T;'To; ; F; ;;;;I"Doing::WWID#default_option;F;[;[[@Ki;F;:default_option;;;[;{;IC;"3Returns the value of attribute default_option. ;T;[;[;I"3Returns the value of attribute default_option.;T; 0;!@;"F;#0;$@D;%I"-def default_option @default_option end;T;&I"def default_option;T;'To; ; F; ;;;;I" Doing::WWID#default_option=;F;[[@0;[[@Ki;F;:default_option=;;;[;{;IC;"&Sets the attribute default_option ;T;[o;) ;*I" param;F;+I"6the value to set the attribute default_option to.;T;I" value;T;,0;!@;[;I"eSets the attribute default_option @param value the value to set the attribute default_option to.;T; 0;!@;"F;#0;$@D;%I"=def default_option=(value) @default_option = value end;T;&I"def default_option=(value);T;'To; ; F; ;;;;I"Doing::WWID#initialize;F;[;[[@Ki;T;;3;;;[;{;IC;"Initializes the object.;T;[o;) ;*I" return;F;+I"a new instance of WWID;T;0;,[I" WWID;F;!@;[;I" Initializes the object. ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"def initialize @timers = {} @recorded_items = [] @content = {} @doingrc_needs_update = false @default_config_file = '.doingrc' @auto_tag = true @user_home = Util.user_home end;T;&I"def initialize;T;'To; ; F; ;;;;I"Doing::WWID#logger;F;[;[[@Ki0;T;: logger;;;[;{;IC;"Logger Responds to :debug, :info, :warn, and :error Each method takes a topic, and a message or block Example: debug('Hooks', 'Hook 1 triggered');T;[;[;I" Logger Responds to :debug, :info, :warn, and :error Each method takes a topic, and a message or block Example: debug('Hooks', 'Hook 1 triggered') ;T; 0;!@ ;4i;"T;5o;6;7F;8i';9i/;$@D;:T;%I".def logger @logger ||= Doing.logger end;T;&I"def logger;T;'To; ; F; ;;;;I" Doing::WWID#init_doing_file;F;[[I" path;TI"nil;T;[[@Ki9;T;:init_doing_file;;;[;{;IC;" Initializes the doing file.;T;[o;) ;*I" param;F;+I",Override path to a doing file, optional;T;I" path;T;,[I" String;T;!@;[;I"f Initializes the doing file. @param path [String] Override path to a doing file, optional ;T; 0;!@;4i;"T;5o;6;7F;8i4;9i8;$@D;:T;%I"def init_doing_file(path = nil) @doing_file = File.expand_path(@config['doing_file']) if path.nil? create(@doing_file) unless File.exist?(@doing_file) input = IO.read(@doing_file) input = input.force_encoding('utf-8') if input.respond_to? :force_encoding elsif File.exist?(File.expand_path(path)) && File.file?(File.expand_path(path)) && File.stat(File.expand_path(path)).size.positive? @doing_file = File.expand_path(path) input = IO.read(File.expand_path(path)) input = input.force_encoding('utf-8') if input.respond_to? :force_encoding elsif path.length < 256 @doing_file = File.expand_path(path) create(path) input = IO.read(File.expand_path(path)) input = input.force_encoding('utf-8') if input.respond_to? :force_encoding end @other_content_top = [] @other_content_bottom = [] section = 'Uncategorized' lines = input.split(/[\n\r]/) current = 0 lines.each do |line| next if line =~ /^\s*$/ if line =~ /^(\S[\S ]+):\s*(@\S+\s*)*$/ section = Regexp.last_match(1) @content[section] = {} @content[section][:original] = line @content[section][:items] = [] current = 0 elsif line =~ /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*)/ date = Regexp.last_match(1).strip title = Regexp.last_match(2).strip item = Item.new(date, title, section) @content[section][:items].push(item) current += 1 elsif current.zero? # if content[section][:items].length - 1 == current @other_content_top.push(line) elsif line =~ /^\S/ @other_content_bottom.push(line) else prev_item = @content[section][:items][current - 1] prev_item.note = Note.new unless prev_item.note prev_item.note.add(line) # end end end Hooks.trigger :post_read, self end;T;&I"$def init_doing_file(path = nil);T;'To; ; F; ;;;;I"Doing::WWID#create;F;[[I" filename;TI"nil;T;[[@Kit;T;: create;;;[;{;IC;"Create a new doing file;T;[;[;I" Create a new doing file ;T; 0;!@/;4i;"T;5o;6;7F;8iq;9is;$@D;:T;%I"def create(filename = nil) filename = @doing_file if filename.nil? return if File.exist?(filename) && File.stat(filename).size.positive? File.open(filename, 'w+') do |f| f.puts "#{@config['current_section']}:" end end;T;&I"def create(filename = nil);T;'To; ; F; ;;;;I"Doing::WWID#fork_editor;F;[[I" input;TI"'';T;[[@Ki};T;:fork_editor;;;[;{;IC;"JCreate a process for an editor and wait for the file handle to return;T;[o;) ;*I" param;F;+I"Text input for editor;T;I" input;T;,[I" String;T;!@@o;) ;*I" raise;F;+@;0;,[I"MissingEditor;T;!@@;[;I" Create a process for an editor and wait for the file handle to return @param input [String] Text input for editor ;T; 0;!@@;4i;"T;5o;6;7F;8i};9i|;$@D;:T;%I"def fork_editor(input = '') # raise NonInteractive, 'Non-interactive terminal' unless $stdout.isatty || ENV['DOING_EDITOR_TEST'] raise MissingEditor, 'No EDITOR variable defined in environment' if Util.default_editor.nil? tmpfile = Tempfile.new(['doing', '.md']) File.open(tmpfile.path, 'w+') do |f| f.puts input f.puts "\n# The first line is the entry title, any lines after that are added as a note" end pid = Process.fork { system("#{Util.editor_with_args} #{tmpfile.path}") } trap('INT') do begin Process.kill(9, pid) rescue StandardError Errno::ESRCH end tmpfile.unlink tmpfile.close! exit 0 end Process.wait(pid) begin if $?.exitstatus == 0 input = IO.read(tmpfile.path) else exit_now! 'Cancelled' end ensure tmpfile.close tmpfile.unlink end input.split(/\n/).delete_if(&:ignore?).join("\n") end;T;&I" def fork_editor(input = '');T;'To; ; F; ;;;;I"Doing::WWID#format_input;F;[[I" input;T0;[[@Ki;T;:format_input;;;[;{;IC;"9Takes a multi-line string and formats it as an entry;T;[o;) ;*I" param;F;+I"The string to parse;T;I" input;T;,[I" String;T;!@[o;) ;*I" return;F;+I" [[String]title, [Note]note];T;0;,[I" Array;T;!@[o;) ;*I" raise;F;+@;0;,[I"EmptyInput;T;!@[;[;I" Takes a multi-line string and formats it as an entry @param input [String] The string to parse @return [Array] [[String]title, [Note]note] ;T; 0;!@[;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"qdef format_input(input) raise EmptyInput, 'No content in entry' if input.nil? || input.strip.empty? input_lines = input.split(/[\n\r]+/).delete_if(&:ignore?) title = input_lines[0]&.strip raise EmptyInput, 'No content in first line' if title.nil? || title.strip.empty? note = Note.new note.add(input_lines[1..-1]) if input_lines.length > 1 # If title line ends in a parenthetical, use that as the note if note.empty? && title =~ /\s+\(.*?\)$/ title.sub!(/\s+\((.*?)\)$/) do m = Regexp.last_match note.add(m[1]) '' end end note.strip_lines! note.compress [title, note] end;T;&I"def format_input(input);T;'To; ; F; ;;;;I"Doing::WWID#chronify;F;[[I" input;T0[I" future:;TI" false;T[I" guess:;TI" :begin;T;[[@Ki;T;: chronify;;;[;{;IC;"Converts input string into a Time object when input takes on the following formats: - interval format e.g. '1d2h30m', '45m' etc. - a semantic phrase e.g. 'yesterday 5:30pm' - a strftime e.g. '2016-03-15 15:32:04 PDT';T;[o;) ;*I" param;F;+I"String to chronify;T;I" input;T;,[I" String;T;!@zo;) ;*I" return;F;+I" result;T;0;,[I" DateTime;T;!@zo;) ;*I" raise;F;+@;0;,[I"InvalidTimeExpression;T;!@z;[;I"Y Converts input string into a Time object when input takes on the following formats: - interval format e.g. '1d2h30m', '45m' etc. - a semantic phrase e.g. 'yesterday 5:30pm' - a strftime e.g. '2016-03-15 15:32:04 PDT' @param input [String] String to chronify @return [DateTime] result ;T; 0;!@z;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"def chronify(input, future: false, guess: :begin) now = Time.now raise InvalidTimeExpression, "Invalid time expression #{input.inspect}" if input.to_s.strip == '' secs_ago = if input.match(/^(\d+)$/) # plain number, assume minutes Regexp.last_match(1).to_i * 60 elsif (m = input.match(/^(?:(?\d+)d)?(?:(?\d+)h)?(?:(?\d+)m)?$/i)) # day/hour/minute format e.g. 1d2h30m [[m['day'], 24 * 3600], [m['hour'], 3600], [m['min'], 60]].map { |qty, secs| qty ? (qty.to_i * secs) : 0 }.reduce(0, :+) end if secs_ago now - secs_ago else Chronic.parse(input, { guess: guess, context: future ? :future : :past, ambiguous_time_range: 8 }) end end;T;&I"6def chronify(input, future: false, guess: :begin);T;'To; ; F; ;;;;I"Doing::WWID#chronify_qty;F;[[I"qty;T0;[[@Ki;T;:chronify_qty;;;[;{;IC;"XConverts simple strings into seconds that can be added to a Time object;T;[o;) ;*I" param;F;+I"DHH:MM or XX[dhm][[XXhm][XXm]] (1d2h30m, 45m, 1.5d, 1h20m, etc.);T;I"qty;T;,[I" String;T;!@o;) ;*I" return;F;+I" seconds;T;0;,[I" Integer;T;!@;[;I" Converts simple strings into seconds that can be added to a Time object @param qty [String] HH:MM or XX[dhm][[XXhm][XXm]] (1d2h30m, 45m, 1.5d, 1h20m, etc.) @return [Integer] seconds ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"Ndef chronify_qty(qty) minutes = 0 case qty.strip when /^(\d+):(\d\d)$/ minutes += Regexp.last_match(1).to_i * 60 minutes += Regexp.last_match(2).to_i when /^(\d+(?:\.\d+)?)([hmd])?$/ amt = Regexp.last_match(1) type = Regexp.last_match(2).nil? ? 'm' : Regexp.last_match(2) minutes = case type.downcase when 'm' amt.to_i when 'h' (amt.to_f * 60).round when 'd' (amt.to_f * 60 * 24).round else minutes end end minutes * 60 end;T;&I"def chronify_qty(qty);T;'To; ; F; ;;;;I"Doing::WWID#sections;F;[;[[@Ki ;T;: sections;;;[;{;IC;"List sections;T;[o;) ;*I" return;F;+I"section titles;T;0;,[I" Array;T;!@;[;I"8 List sections @return [Array] section titles ;T; 0;!@;4i;"T;5o;6;7F;8i;9i ;$@D;:T;%I"%def sections @content.keys end;T;&I"def sections;T;'To; ; F; ;;;;I"Doing::WWID#add_section;F;[[I" title;T0;[[@Ki;T;:add_section;;;[;{;IC;"Adds a section.;T;[o;) ;*I" param;F;+I"The new section title;T;I" title;T;,[I" String;T;!@;[;I"I Adds a section. @param title [String] The new section title ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"def add_section(title) if @content.key?(title.cap_first) raise InvalidSection, %(section "#{title.cap_first}" already exists) end @content[title.cap_first] = { :original => "#{title}:", :items => [] } logger.info('New section:', %("#{title.cap_first}")) end;T;&I"def add_section(title);T;'To; ; F; ;;;;I"Doing::WWID#guess_section;F;[[I" frag;T0[I" guessed:;TI" false;T[I" suggest:;TI" false;T;[[@Ki#;T;:guess_section;;;[;{;IC;"7Attempt to match a string with an existing section;T;[o;) ;*I" param;F;+I"The user-provided string;T;I" frag;T;,[I" String;T;!@o;) ;*I" param;F;+I"already guessed and failed;T;I" guessed;T;,[I" Boolean;T;!@;[;I" Attempt to match a string with an existing section @param frag [String] The user-provided string @param guessed [Boolean] already guessed and failed ;T; 0;!@;4i;"T;5o;6;7F;8i;9i";$@D;:T;%I"def guess_section(frag, guessed: false, suggest: false) return 'All' if frag =~ /^all$/i frag ||= @config['current_section'] sections.each { |sect| return sect.cap_first if frag.downcase == sect.downcase } section = false re = frag.split('').join('.*?') sections.each do |sect| next unless sect =~ /#{re}/i logger.debug('Match:', %(Assuming "#{sect}" from "#{frag}")) section = sect break end return section if suggest unless section || guessed alt = guess_view(frag, guessed: true, suggest: true) if alt meant_view = yn("#{Color.boldwhite}Did you mean `#{Color.yellow}doing view #{alt}#{Color.boldwhite}`?", default_response: 'n') raise WrongCommand.new("run again with #{"doing view #{alt}".boldwhite}", topic: 'Try again:') if meant_view end res = yn("#{Color.boldwhite}Section #{frag.yellow}#{Color.boldwhite} not found, create it", default_response: 'n') if res add_section(frag.cap_first) write(@doing_file) return frag.cap_first end raise InvalidSection.new("unknown section #{frag.yellow}", topic: 'Missing:') end section ? section.cap_first : guessed end;T;&I" ', multiple: false, sort: false, show_if_single: true }, include_section: opt[:section] =~ /^all$/i ) else last_entry = items.max_by { |item| item.date } end last_entry end;T;&I"def last_entry(opt = {});T;'To; ; F; ;;;;I"Doing::WWID#choose_from;F;[ [I" options;T0[I" prompt:;TI"'Make a selection: ';T[I"multiple:;TI" false;T[I" sorted:;TI" true;T[I"fzf_args:;TI"[];T;[[@Kik;T;:choose_from;;;[;{;IC;"8Generate a menu of options and allow user selection;T;[o;) ;*I" return;F;+I"The selected option;T;0;,[I" String;T;!@;[;I"d Generate a menu of options and allow user selection @return [String] The selected option ;T; 0;!@;4i;"T;5o;6;7F;8if;9ij;$@D;:T;%I"def choose_from(options, prompt: 'Make a selection: ', multiple: false, sorted: true, fzf_args: []) return nil unless $stdout.isatty fzf = File.join(File.dirname(__FILE__), '../helpers/fuzzyfilefinder') # fzf_args << '-1' # User is expecting a menu, and even if only one it seves as confirmation fzf_args << %(--prompt "#{prompt}") fzf_args << '--multi' if multiple header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm" fzf_args << %(--header "#{header}") options.sort! if sorted res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{fzf_args.join(' ')}` return false if res.strip.size.zero? res end;T;&I"hdef choose_from(options, prompt: 'Make a selection: ', multiple: false, sorted: true, fzf_args: []);T;'To; ; F; ;;;;I"Doing::WWID#all_tags;F;[[I" items;T0[I" opt:;TI"{};T;[[@Ki{;F;: all_tags;;;[;{;IC;" ;T;[;[;@; 0;!@=;4i;$@D;:T;%I"~def all_tags(items, opt: {}) all_tags = [] items.each { |item| all_tags.concat(item.tags).uniq! } all_tags.sort end;T;&I"!def all_tags(items, opt: {});T;'To; ; F; ;;;;I"Doing::WWID#tag_groups;F;[[I" items;T0[I" opt:;TI"{};T;[[@Ki;F;:tag_groups;;;[;{;IC;" ;T;[;[;@; 0;!@N;4i;$@D;:T;%I"def tag_groups(items, opt: {}) all_items = filter_items(items, opt: opt) tags = all_tags(all_items, opt: {}) tag_groups = {} tags.each do |tag| tag_groups[tag] ||= [] tag_groups[tag] = filter_items(all_items, opt: { tag: tag, tag_bool: :or }) end tag_groups end;T;&I"#def tag_groups(items, opt: {});T;'To; ; F; ;;;;I"Doing::WWID#filter_items;F;[[I" items;TI"[];T[I" opt:;TI"{};T;[[@Ki;T;:filter_items;;;[;{;IC;"*Filter items based on search criteria;T;[o;) ;*I" param;F;+I"6The items to filter (if empty, filters all items);T;I" items;T;,[I" Array;T;!@_o;) ;*I" param;F;+I"The filter parameters;T;I"opt;T;,[I" Hash;T;!@_o:YARD::Tags::OptionTag ;*I" option;F;+0;I"opt;T;,0: @pairo:YARD::Tags::DefaultTag ;*I" option;F;+0;I" :section;T;,[I" String;T:@defaults0;!@_o; ;*I" option;F;+0;I"opt;T;,0;o; ;*I" option;F;+0;I":unfinished;T;,[I" Boolean;T;0;!@_o; ;*I" option;F;+0;I"opt;T;,0;o; ;*I" option;F;+I";T;I" :tag;T;,[I"Array or String;T;[I"$Array or comma-separated string;T;!@_o; ;*I" option;F;+0;I"opt;T;,0;o; ;*I" option;F;+I";T;I":tag_bool;T;,[I" Symbol;T;[I" :and;TI":or;TI" :not;T;!@_o; ;*I" option;F;+0;I"opt;T;,0;o; ;*I" option;F;+I";T;I" :search;T;,[I" String;T;[I" string;TI"optional regex with //;T;!@_o; ;*I" option;F;+0;I"opt;T;,0;o; ;*I" option;F;+I"[[Time]start, [Time]end];T;I":date_filter;T;,[I" Array;T;0;!@_o; ;*I" option;F;+0;I"opt;T;,0;o; ;*I" option;F;+0;I":only_timed;T;,[I" Boolean;T;0;!@_o; ;*I" option;F;+0;I"opt;T;,0;o; ;*I" option;F;+I";T;I" :before;T;,[I" String;T;[I"Date/Time string;TI" unparsed;T;!@_o; ;*I" option;F;+0;I"opt;T;,0;o; ;*I" option;F;+I";T;I" :after;T;,[I" String;T;[I"Date/Time string;TI" unparsed;T;!@_o; ;*I" option;F;+0;I"opt;T;,0;o; ;*I" option;F;+0;I" :today;T;,[I" Boolean;T;0;!@_o; ;*I" option;F;+0;I"opt;T;,0;o; ;*I" option;F;+0;I":yesterday;T;,[I" Boolean;T;0;!@_o; ;*I" option;F;+0;I"opt;T;,0;o; ;*I" option;F;+I";T;I" :count;T;,[I" Number;T;[I"Number to return;T;!@_o; ;*I" option;F;+0;I"opt;T;,0;o; ;*I" option;F;+I";T;I" :age;T;,[I" String;T;[I"'old' or 'new';T;!@_;[;I"  Filter items based on search criteria @param items [Array] The items to filter (if empty, filters all items) @param opt [Hash] The filter parameters @option opt [String] :section @option opt [Boolean] :unfinished @option opt [Array or String] :tag (Array or comma-separated string) @option opt [Symbol] :tag_bool (:and, :or, :not) @option opt [String] :search (string, optional regex with //) @option opt [Array] :date_filter [[Time]start, [Time]end] @option opt [Boolean] :only_timed @option opt [String] :before (Date/Time string, unparsed) @option opt [String] :after (Date/Time string, unparsed) @option opt [Boolean] :today @option opt [Boolean] :yesterday @option opt [Number] :count (Number to return) @option opt [String] :age ('old' or 'new') ;T; 0;!@_;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"t def filter_items(items = [], opt: {}) if items.nil? || items.empty? section = opt[:section] ? guess_section(opt[:section]) : 'All' items = if section =~ /^all$/i @content.each_with_object([]) { |(_k, v), arr| arr.concat(v[:items].dup) } else @content[section][:items].dup end end items.sort_by! { |item| [item.date, item.title.downcase] }.reverse filtered_items = items.select do |item| keep = true if opt[:unfinished] finished = item.tags?('done', :and) finished = opt[:not] ? !finished : finished keep = false if finished end if keep && opt[:tag] opt[:tag_bool] ||= :and tag_match = opt[:tag].nil? || opt[:tag].empty? ? true : item.tags?(opt[:tag], opt[:tag_bool]) keep = false unless tag_match keep = opt[:not] ? !keep : keep end if keep && opt[:search] opt[:case] = opt[:case].normalize_case unless opt[:case].is_a?(Symbol) search_match = if opt[:search].nil? || opt[:search].empty? true else item.search(opt[:search], case_type: opt[:case]) end keep = false unless search_match keep = opt[:not] ? !keep : keep end if keep && opt[:date_filter]&.length == 2 start_date = opt[:date_filter][0] end_date = opt[:date_filter][1] in_date_range = if end_date item.date >= start_date && item.date <= end_date else item.date.strftime('%F') == start_date.strftime('%F') end keep = false unless in_date_range keep = opt[:not] ? !keep : keep end keep = false if keep && opt[:only_timed] && !item.interval if keep && opt[:tag_filter] && !opt[:tag_filter]['tags'].empty? keep = item.tags?(opt[:tag_filter]['tags'], opt[:tag_filter]['bool']) keep = opt[:not] ? !keep : keep end if keep && opt[:before] time_string = opt[:before] cutoff = chronify(time_string, guess: :begin) keep = cutoff && item.date <= cutoff keep = opt[:not] ? !keep : keep end if keep && opt[:after] time_string = opt[:after] cutoff = chronify(time_string, guess: :end) keep = cutoff && item.date >= cutoff keep = opt[:not] ? !keep : keep end if keep && opt[:today] keep = item.date >= Date.today.to_time && item.date < Date.today.next_day.to_time keep = opt[:not] ? !keep : keep elsif keep && opt[:yesterday] keep = item.date >= Date.today.prev_day.to_time && item.date < Date.today.to_time keep = opt[:not] ? !keep : keep end keep end count = opt[:count] && opt[:count].positive? ? opt[:count] : filtered_items.length if opt[:age] =~ /^o/i filtered_items.slice(0, count).reverse else filtered_items.reverse.slice(0, count) end end;T;&I"*def filter_items(items = [], opt: {});T;'To; ; F; ;;;;I"Doing::WWID#interactive;F;[[I"opt;TI"{};T;[[@Ki;T;:interactive;;;[;{;IC;"+Display an interactive menu of entries;T;[o;) ;*I" param;F;+I"Additional options;T;I"opt;T;,[I" Hash;T;!@o;) ;*I" raise;F;+@;0;,[I"NoResults;T;!@;[;I"Z Display an interactive menu of entries @param opt [Hash] Additional options ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"rdef interactive(opt = {}) section = opt[:section] ? guess_section(opt[:section]) : 'All' search = nil if opt[:search] search = opt[:search] search.sub!(/^'?/, "'") if opt[:exact] opt[:search] = search end opt[:query] = opt[:search] if opt[:search] && !opt[:query] opt[:query] = "!#{opt[:query]}" if opt[:not] opt[:multiple] = true items = filter_items([], opt: { section: section, search: opt[:search], case: opt[:case] }) selection = choose_from_items(items, opt, include_section: section =~ /^all$/i) raise NoResults, 'no items selected' if selection.empty? act_on(selection, opt) end;T;&I"def interactive(opt = {});T;'To; ; F; ;;;;I""Doing::WWID#choose_from_items;F;[[I" items;T0[I"opt;TI"{};T[I"include_section:;TI" false;T;[[@Ki;F;:choose_from_items;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@D;:T;%I"def choose_from_items(items, opt = {}, include_section: false) return nil unless $stdout.isatty return nil unless items.count.positive? opt[:header] ||= "Arrows: navigate, tab: mark for selection, ctrl-a: select all, enter: commit" opt[:prompt] ||= "Select entries to act on > " pad = items.length.to_s.length options = items.map.with_index do |item, i| out = [ format("%#{pad}d", i), ') ', format('%13s', item.date.relative_date), ' | ', item.title ] if include_section out.concat([ ' (', item.section, ') ' ]) end out.join('') end fzf = File.join(File.dirname(__FILE__), '../helpers/fuzzyfilefinder') fzf_args = [ %(--header="#{opt[:header]}"), %(--prompt="#{opt[:prompt].sub(/ *$/, ' ')}"), opt[:multiple] ? '--multi' : '--no-multi', '-0', '--bind ctrl-a:select-all', %(-q "#{opt[:query]}"), '--info=inline' ] fzf_args.push('-1') unless opt[:show_if_single] unless opt[:menu] raise InvalidArgument, "Can't skip menu when no query is provided" unless opt[:query] && !opt[:query].empty? fzf_args.concat([%(--filter="#{opt[:query]}"), opt[:sort] ? '' : '--no-sort']) end res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{fzf_args.join(' ')}` selected = [] res.split(/\n/).each do |item| idx = item.match(/^ *(\d+)\)/)[1].to_i selected.push(items[idx]) end opt[:multiple] ? selected : selected[0] end;T;&I"Cdef choose_from_items(items, opt = {}, include_section: false);T;'To; ; F; ;;;;I"Doing::WWID#act_on;F;[[I" items;T0[I"opt;TI"{};T;[[@Ki_;T;: act_on;;;[;{;IC;"Perform actions on a set of entries. If no valid action is included in the opt hash and the terminal is a TTY, a menu will be presented;T;[o;) ;*I" param;F;+I"The items to affect;T;I" items;T;,0;!@1o;) ;*I" param;F;+I"The actions and options. Can include :editor, :delete, :tag, :flag, :finish, :cancel, :archive, :output, :save_to, :again, and :resume;T;I"opt;T;,0;!@1;[;I" Perform actions on a set of entries. If no valid action is included in the opt hash and the terminal is a TTY, a menu will be presented @param items The items to affect @param opt The actions and options. Can include :editor, :delete, :tag, :flag, :finish, :cancel, :archive, :output, :save_to, :again, and :resume ;T; 0;!@1;4i;"T;5o;6;7F;8iR;9i^;$@D;:T;%I"ldef act_on(items, opt = {}) actions = %i[editor delete tag flag finish cancel archive output save_to again resume] has_action = false single = items.count == 1 actions.each do |a| if opt[a] has_action = true break end end unless has_action actions = [ 'add tag', 'remove tag', 'cancel', 'delete', 'finish', 'flag', 'archive', 'move', 'edit', 'output formatted' ] actions.concat(['resume/repeat', 'begin/reset']) if items.count == 1 choice = choose_from(actions, prompt: 'What do you want to do with the selected items? > ', multiple: true, sorted: false, fzf_args: ["--height=#{actions.count + 3}", '--tac', '--no-sort', '--info=hidden']) return unless choice to_do = choice.strip.split(/\n/) to_do.each do |action| case action when /resume/ opt[:resume] = true when /reset/ opt[:reset] = true when /(add|remove) tag/ type = action =~ /^add/ ? 'add' : 'remove' raise InvalidArgument, "'add tag' and 'remove tag' can not be used together" if opt[:tag] print "#{Color.yellow}Tag to #{type}: #{Color.reset}" tag = $stdin.gets next if tag =~ /^ *$/ opt[:tag] = tag.strip.sub(/^@/, '') opt[:remove] = true if type == 'remove' when /output formatted/ plugins = Plugins.available_plugins(type: :export).sort output_format = choose_from(plugins, prompt: 'Which output format? > ', fzf_args: ["--height=#{plugins.count + 3}", '--tac', '--no-sort', '--info=hidden']) next if tag =~ /^ *$/ raise UserCancelled unless output_format opt[:output] = output_format.strip res = opt[:force] ? false : yn('Save to file?', default_response: 'n') if res print "#{Color.yellow}File path/name: #{Color.reset}" filename = $stdin.gets.strip next if filename.empty? opt[:save_to] = filename end when /archive/ opt[:archive] = true when /delete/ opt[:delete] = true when /edit/ opt[:editor] = true when /finish/ opt[:finish] = true when /cancel/ opt[:cancel] = true when /move/ section = choose_section.strip opt[:move] = section.strip unless section =~ /^ *$/ when /flag/ opt[:flag] = true end end end if opt[:resume] || opt[:reset] if items.count > 1 raise InvalidArgument, 'resume and restart can only be used on a single entry' else item = items[0] if opt[:resume] && !opt[:reset] repeat_item(item, { editor: opt[:editor] }) elsif opt[:reset] if item.tags?('done', :and) && !opt[:resume] res = opt[:force] ? true : yn('Remove @done tag?', default_response: 'y') else res = opt[:resume] end update_item(item, reset_item(item, resume: res)) end write(@doing_file) end return end if opt[:delete] res = opt[:force] ? true : yn("Delete #{items.size} items?", default_response: 'y') if res items.each { |item| delete_item(item, single: items.count == 1) } write(@doing_file) end return end if opt[:flag] tag = @config['marker_tag'] || 'flagged' items.map! do |item| tag_item(item, tag, date: false, remove: opt[:remove], single: single) end end if opt[:finish] || opt[:cancel] tag = 'done' items.map! do |item| if item.should_finish? should_date = !opt[:cancel] && item.should_time? tag_item(item, tag, date: should_date, remove: opt[:remove], single: single) end end end if opt[:tag] tag = opt[:tag] items.map! do |item| tag_item(item, tag, date: false, remove: opt[:remove], single: single) end end if opt[:archive] || opt[:move] section = opt[:archive] ? 'Archive' : guess_section(opt[:move]) items.map! {|item| move_item(item, section) } end write(@doing_file) if opt[:editor] editable_items = [] items.each do |item| editable = "#{item.date} | #{item.title}" old_note = item.note ? item.note.to_s : nil editable += "\n#{old_note}" unless old_note.nil? editable_items << editable end divider = "\n-----------\n" input = editable_items.map(&:strip).join(divider) + "\n\n# You may delete entries, but leave all divider lines in place" new_items = fork_editor(input).split(/#{divider}/) new_items.each_with_index do |new_item, i| input_lines = new_item.split(/[\n\r]+/).delete_if(&:ignore?) title = input_lines[0]&.strip if title.nil? || title =~ /^#{divider.strip}$/ || title.strip.empty? delete_item(items[i], single: new_items.count == 1) else note = input_lines.length > 1 ? input_lines[1..-1] : [] note.map!(&:strip) note.delete_if(&:ignore?) date = title.match(/^([\d\-: ]+) \| /)[1] title.sub!(/^([\d\-: ]+) \| /, '') item = items[i] item.title = title item.note = note item.date = Time.parse(date) || items[i].date end end write(@doing_file) end if opt[:output] items.map! do |item| item.title = "#{item.title} @project(#{item.section})" item end @content = { 'Export' => { :original => 'Export:', :items => items } } options = { section: 'Export' } if opt[:output] =~ /doing/ options[:output] = 'template' options[:template] = '- %date | %title%note' else options[:output] = opt[:output] options[:template] = opt[:template] || nil end output = list_section(options) if opt[:save_to] file = File.expand_path(opt[:save_to]) if File.exist?(file) # Create a backup copy for the undo command FileUtils.cp(file, "#{file}~") end File.open(file, 'w+') do |f| f.puts output end logger.warn('File written:', file) else Doing::Pager.page output end end end;T;&I" def act_on(items, opt = {});T;'To; ; F; ;;;;I"Doing::WWID#tag_item;F;[ [I" item;T0[I" tags;T0[I" remove:;TI" false;T[I" date:;TI" false;T[I" single:;TI" false;T;[[@KiN;T;: tag_item;;;[;{;IC;"Tag an item from the index;T;[ o;) ;*I" param;F;+I"The item to tag;T;I" item;T;,[I" Item;T;!@Lo;) ;*I" param;F;+I"The tag to apply;T;I" tags;T;,[I" String;T;!@Lo;) ;*I" param;F;+I"remove tags?;T;I" remove;T;,[I" Boolean;T;!@Lo;) ;*I" param;F;+I"Include timestamp?;T;I" date;T;,[I" Boolean;T;!@Lo;) ;*I" param;F;+I"Log as a single change?;T;I" single;T;,[I" Boolean;T;!@Lo;) ;*I" return;F;+I"updated item;T;0;,[I" Item;T;!@L;[;I") Tag an item from the index @param item [Item] The item to tag @param tags [String] The tag to apply @param remove [Boolean] remove tags? @param date [Boolean] Include timestamp? @param single [Boolean] Log as a single change? @return [Item] updated item ;T; 0;!@L;4i;"T;5o;6;7F;8iC;9iM;$@D;:T;%I"def tag_item(item, tags, remove: false, date: false, single: false) added = [] removed = [] tags = tags.to_tags if tags.is_a? ::String done_date = Time.now tags.each do |tag| bool = remove ? :and : :not if item.tags?(tag, bool) item.tag(tag, remove: remove, value: date ? done_date.strftime('%F %R') : nil) remove ? removed.push(tag) : added.push(tag) end end log_change(tags_added: added, tags_removed: removed, count: 1, item: item, single: single) item end;T;&I"Hdef tag_item(item, tags, remove: false, date: false, single: false);T;'To; ; F; ;;;;I"Doing::WWID#tag_last;F;[[I"opt;TI"{};T;[[@Kil;T;: tag_last;;;[;{;IC;"$Tag the last entry or X entries;T;[o;) ;*I" param;F;+I"AAdditional Options (see #filter_items for filtering options);T;I"opt;T;,[I" Hash;T;!@o;) ;*I"see;F;+0;I"#filter_items;T;,0;!@o;) ;*I" raise;F;+@;0;,[I"NoResults;T;!@;[;I" Tag the last entry or X entries @param opt [Hash] Additional Options (see #filter_items for filtering options) @see #filter_items ;T; 0;!@;4i;"T;5o;6;7F;8ic;9ik;$@D;:T;%I"<def tag_last(opt = {}) opt[:count] ||= 1 opt[:archive] ||= false opt[:tags] ||= ['done'] opt[:sequential] ||= false opt[:date] ||= false opt[:remove] ||= false opt[:autotag] ||= false opt[:back] ||= false opt[:unfinished] ||= false opt[:section] = opt[:section] ? guess_section(opt[:section]) : 'All' items = filter_items([], opt: opt) if opt[:interactive] items = choose_from_items(items, { menu: true, header: '', prompt: 'Select entries to tag > ', multiple: true, sort: true, show_if_single: true }, include_section: opt[:section] =~ /^all$/i) raise NoResults, 'no items selected' if items.empty? end raise NoResults, 'no items matched your search' if items.empty? items.each do |item| added = [] removed = [] if opt[:autotag] new_title = autotag(item.title) if @auto_tag if new_title == item.title logger.count(:skipped, level: :debug, message: '%count unchaged %items') # logger.debug('Autotag:', 'No changes') else logger.count(:added_tags) logger.write(items.count == 1 ? :info : :debug, 'Tagged:', new_title) item.title = new_title end else if opt[:sequential] next_entry = next_item(item) done_date = if next_entry.nil? Time.now else next_entry.date - 60 end elsif opt[:took] if item.date + opt[:took] > Time.now item.date = Time.now - opt[:took] done_date = Time.now else done_date = item.date + opt[:took] end elsif opt[:back] done_date = if opt[:back].is_a? Integer item.date + opt[:back] else item.date + (opt[:back] - item.date) end else done_date = Time.now end opt[:tags].each do |tag| if tag == 'done' && !item.should_finish? Doing.logger.debug('Skipped:', "Item in never_finish: #{item.title}") logger.count(:skipped, level: :debug) next end tag = tag.strip if opt[:remove] || opt[:rename] rename_to = nil if opt[:rename] rename_to = tag tag = opt[:rename] end old_title = item.title.dup item.title.tag!(tag, remove: opt[:remove], rename_to: rename_to, regex: opt[:regex]) if old_title != item.title removed << tag added << rename_to if rename_to else logger.count(:skipped, level: :debug) end else old_title = item.title.dup should_date = opt[:date] && item.should_time? item.title.tag!('done', remove: true) if tag =~ /done/ && !should_date item.title.tag!(tag, value: should_date ? done_date.strftime('%F %R') : nil) added << tag if old_title != item.title end end end log_change(tags_added: added, tags_removed: removed, item: item, single: items.count == 1) item.note.add(opt[:note]) if opt[:note] if opt[:archive] && opt[:section] != 'Archive' && (opt[:count]).positive? move_item(item, 'Archive', label: true) elsif opt[:archive] && opt[:count].zero? logger.warn('Skipped:', 'Archiving is skipped when operating on all entries') end end write(@doing_file) end;T;&I"def tag_last(opt = {});T;'To; ; F; ;;;;I"Doing::WWID#move_item;F;[[I" item;T0[I" section;T0[I" label:;TI" true;T;[[@Ki;T;:move_item;;;[;{;IC;"FMove item from current section to destination section;T;[o;) ;*I" param;F;+I"The item to move;T;I" item;T;,[I" Item;T;!@o;) ;*I" param;F;+I"The destination section;T;I" section;T;,[I" String;T;!@o;) ;*I" return;F;+I"Updated item;T;0;,[I" Item;T;!@;[;I" Move item from current section to destination section @param item [Item] The item to move @param section [String] The destination section @return [Item] Updated item ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"def move_item(item, section, label: true) from = item.section new_item = @content[item.section][:items].delete(item) new_item.title.sub!(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{from})") if label new_item.section = section @content[section][:items].concat([new_item]) logger.count(section == 'Archive' ? :archived : :moved) logger.debug("#{section == 'Archive' ? 'Archived' : 'Moved'}:", "#{new_item.title.truncate(60)} from #{from} to #{section}") new_item end;T;&I".def move_item(item, section, label: true);T;'To; ; F; ;;;;I"Doing::WWID#next_item;F;[[I" item;T0[I" options;TI"{};T;[[@Ki;T;:next_item;;;[;{;IC;"Get next item in the index;T;[ o;) ;*I" param;F;+I"target item;T;I" item;T;,[I" Item;T;!@o;) ;*I" param;F;+I"additional options;T;I" options;T;,[I" Hash;T;!@o;) ;*I"see;F;+0;I"#filter_items;T;,0;!@o;) ;*I" return;F;+I"-the next chronological item in the index;T;0;,[I" Item;T;!@;[;I" Get next item in the index @param item [Item] target item @param options [Hash] additional options @see #filter_items @return [Item] the next chronological item in the index ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"def next_item(item, options = {}) items = filter_items([], opt: options) idx = items.index(item) idx.positive? ? items[idx - 1] : nil end;T;&I"&def next_item(item, options = {});T;'To; ; F; ;;;;I"Doing::WWID#delete_item;F;[[I" item;T0[I" single:;TI" false;T;[[@Ki;T;:delete_item;;;[;{;IC;""Delete an item from the index;T;[o;) ;*I" param;F;+I" The item;T;I" item;T;,0;!@;[;I"@ Delete an item from the index @param item The item ;T; 0;!@;4i;"T;5o;6;7F;8i ;9i ;$@D;:T;%I"def delete_item(item, single: false) section = item.section section_items = @content[section][:items] deleted = section_items.delete(item) logger.count(:deleted) logger.info('Entry deleted:', deleted.title) if single end;T;&I")def delete_item(item, single: false);T;'To; ; F; ;;;;I"Doing::WWID#update_item;F;[[I" old_item;T0[I" new_item;T0;[[@Ki;T;:update_item;;;[;{;IC;"5Update an item in the index with a modified item;T;[o;) ;*I" param;F;+I"The old item;T;I" old_item;T;,0;!@ o;) ;*I" param;F;+I"The new item;T;I" new_item;T;,0;!@ o;) ;*I" raise;F;+@;0;,[I"ItemNotFound;T;!@ ;[;I"~ Update an item in the index with a modified item @param old_item The old item @param new_item The new item ;T; 0;!@ ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"def update_item(old_item, new_item) section = old_item.section section_items = @content[section][:items] s_idx = section_items.index { |item| item.equal?(old_item) } raise ItemNotFound, 'Unable to find item in index, did it mutate?' unless s_idx return if section_items[s_idx].equal?(new_item) section_items[s_idx] = new_item logger.count(:updated) logger.info('Entry updated:', section_items[s_idx].title.truncate(60)) new_item end;T;&I"(def update_item(old_item, new_item);T;'To; ; F; ;;;;I"Doing::WWID#edit_last;F;[[I" section:;TI" 'All';T[I" options:;TI"{};T;[[@Ki2;T;:edit_last;;;[;{;IC;"Edit the last entry;T;[o;) ;*I" param;F;+I"The section, default "All";T;I" section;T;,[I" String;T;!@* ;[;I"T Edit the last entry @param section [String] The section, default "All" ;T; 0;!@* ;4i;"T;5o;6;7F;8i-;9i1;$@D;:T;%I"def edit_last(section: 'All', options: {}) options[:section] = guess_section(section) item = last_entry(options) if item.nil? logger.debug('Skipped:', 'No entries found') return end content = [item.title.dup] content << item.note.to_s unless item.note.empty? new_item = fork_editor(content.join("\n")) title, note = format_input(new_item) if title.nil? || title.empty? logger.debug('Skipped:', 'No content provided') elsif title == item.title && note.equal?(item.note) logger.debug('Skipped:', 'No change in content') else item.title = title item.note.add(note, replace: true) logger.info('Edited:', item.title) write(@doing_file) end end;T;&I"/def edit_last(section: 'All', options: {});T;'To; ; F; ;;;;I"Doing::WWID#stop_start;F;[[I"target_tag;T0[I"opt;TI"{};T;[[@KiX;T;:stop_start;;;[;{;IC;"Accepts one tag and the raw text of a new item if the passed tag is on any item, it's replaced with @done. if new_item is not nil, it's tagged with the passed tag and inserted. This is for use where only one instance of a given tag should exist (@meanwhile);T;[o;) ;*I" param;F;+I"Tag to replace;T;I"target_tag;T;,[I" String;T;!@D o;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@D ;[;I"f Accepts one tag and the raw text of a new item if the passed tag is on any item, it's replaced with @done. if new_item is not nil, it's tagged with the passed tag and inserted. This is for use where only one instance of a given tag should exist (@meanwhile) @param target_tag [String] Tag to replace @param opt [Hash] Additional Options ;T; 0;!@D ;4i;"T;5o;6;7F;8iN;9iW;$@D;:T;%I"def stop_start(target_tag, opt = {}) tag = target_tag.dup opt[:section] ||= @config['current_section'] opt[:archive] ||= false opt[:back] ||= Time.now opt[:new_item] ||= false opt[:note] ||= false opt[:section] = guess_section(opt[:section]) tag.sub!(/^@/, '') found_items = 0 @content[opt[:section]][:items].each_with_index do |item, i| next unless item.title =~ /@#{tag}/ item.title.add_tags!([tag, 'done'], remove: true) item.tag('done', value: opt[:back].strftime('%F %R')) found_items += 1 if opt[:archive] && opt[:section] != 'Archive' item.title = item.title.sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{item.section})") move_item(item, 'Archive', label: false) logger.count(:completed_archived) logger.info('Completed/archived:', item.title) else logger.count(:completed) logger.info('Completed:', item.title) end end logger.debug('Skipped:', "No active @#{tag} tasks found.") if found_items.zero? if opt[:new_item] title, note = format_input(opt[:new_item]) note.add(opt[:note]) if opt[:note] title.tag!(tag) add_item(title.cap_first, opt[:section], { note: note, back: opt[:back] }) end write(@doing_file) end;T;&I")def stop_start(target_tag, opt = {});T;'To; ; F; ;;;;I"Doing::WWID#write;F;[[I" file;TI"nil;T[I" backup:;TI" true;T;[[@Ki;T;;U;;;[;{;IC;"$Write content to file or STDOUT;T;[o;) ;*I" param;F;+I"The filepath to write to;T;I" file;T;,[I" String;T;!@c ;[;I"[ Write content to file or STDOUT @param file [String] The filepath to write to ;T; 0;!@c ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"def write(file = nil, backup: true) Hooks.trigger :pre_write, self, file output = combined_content if file.nil? $stdout.puts output else Util.write_to_file(file, output, backup: backup) run_after if @config.key?('run_after') end end;T;&I"(def write(file = nil, backup: true);T;'To; ; F; ;;;;I"Doing::WWID#restore_backup;F;[[I" file;T0;[[@Ki;T;:restore_backup;;;[;{;IC;"*Restore a backed up version of a file;T;[o;) ;*I" param;F;+I"The filepath to restore;T;I" file;T;,[I" String;T;!@} ;[;I"` Restore a backed up version of a file @param file [String] The filepath to restore ;T; 0;!@} ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"def restore_backup(file) if File.exist?("#{file}~") FileUtils.cp("#{file}~", file) logger.warn('File update:', "Restored #{file.sub(/^#{@user_home}/, '~')}") else logger.error('Restore error:', 'No backup file found') end end;T;&I"def restore_backup(file);T;'To; ; F; ;;;;I"Doing::WWID#rotate;F;[[I"opt;TI"{};T;[[@Ki;T;: rotate;;;[;{;IC;"4Rename doing file with date and start fresh one;T;[;[;I"6 Rename doing file with date and start fresh one ;T; 0;!@ ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"def rotate(opt = {}) keep = opt[:keep] || 0 tags = [] tags.concat(opt[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }) if opt[:tag] bool = opt[:bool] || :and sect = opt[:section] !~ /^all$/i ? guess_section(opt[:section]) : 'all' if sect =~ /^all$/i all_sections = sections.dup else all_sections = [sect] end counter = 0 new_content = {} all_sections.each do |section| items = @content[section][:items].dup new_content[section] = {} new_content[section][:original] = @content[section][:original] new_content[section][:items] = [] moved_items = [] if !tags.empty? || opt[:search] || opt[:before] if opt[:before] time_string = opt[:before] cutoff = chronify(time_string, guess: :begin) end items.delete_if do |item| if ((!tags.empty? && item.tags?(tags, bool)) || (opt[:search] && item.search(opt[:search].to_s)) || (opt[:before] && item.date < cutoff)) moved_items.push(item) counter += 1 true else false end end @content[section][:items] = items new_content[section][:items] = moved_items logger.warn('Rotated:', "#{moved_items.length} items from #{section}") else new_content[section][:items] = [] moved_items = [] count = items.length < keep ? items.length : keep if items.count > count moved_items.concat(items[count..-1]) else moved_items.concat(items) end @content[section][:items] = if count.zero? [] else items[0..count - 1] end new_content[section][:items] = moved_items logger.warn('Rotated:', "#{items.length - count} items from #{section}") end end write(@doing_file) file = @doing_file.sub(/(\.\w+)$/, "_#{Time.now.strftime('%Y-%m-%d')}\\1") if File.exist?(file) init_doing_file(file) @content.deep_merge(new_content) logger.warn('File update:', "added entries to existing file: #{file}") else @content = new_content logger.warn('File update:', "created new file: #{file}") end write(file, backup: false) end;T;&I"def rotate(opt = {});T;'To; ; F; ;;;;I"Doing::WWID#choose_section;F;[;[[@Ki;T;:choose_section;;;[;{;IC;"9Generate a menu of sections and allow user selection;T;[o;) ;*I" return;F;+I"The selected section name;T;0;,[I" String;T;!@ ;[;I"k Generate a menu of sections and allow user selection @return [String] The selected section name ;T; 0;!@ ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"def choose_section choice = choose_from(sections.sort, prompt: 'Choose a section > ', fzf_args: ['--height=60%']) choice ? choice.strip : choice end;T;&I"def choose_section;T;'To; ; F; ;;;;I"Doing::WWID#views;F;[;[[@Ki;T;: views;;;[;{;IC;"List available views;T;[o;) ;*I" return;F;+I"View names;T;0;,[I" Array;T;!@ ;[;I"; List available views @return [Array] View names ;T; 0;!@ ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"Kdef views @config.has_key?('views') ? @config['views'].keys : [] end;T;&I"def views;T;'To; ; F; ;;;;I"Doing::WWID#choose_view;F;[;[[@Ki;T;:choose_view;;;[;{;IC;"6Generate a menu of views and allow user selection;T;[o;) ;*I" return;F;+I"The selected view name;T;0;,[I" String;T;!@ ;[;I"e Generate a menu of views and allow user selection @return [String] The selected view name ;T; 0;!@ ;4i;"T;5o;6;7F;8i ;9i;$@D;:T;%I"def choose_view choice = choose_from(views.sort, prompt: 'Choose a view > ', fzf_args: ['--height=60%']) choice ? choice.strip : choice end;T;&I"def choose_view;T;'To; ; F; ;;;;I"Doing::WWID#get_view;F;[[I" title;T0;[[@Ki;T;: get_view;;;[;{;IC;"#Gets a view from configuration;T;[o;) ;*I" param;F;+I"&The title of the view to retrieve;T;I" title;T;,[I" String;T;!@ ;[;I"d Gets a view from configuration @param title [String] The title of the view to retrieve ;T; 0;!@ ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"jdef get_view(title) return @config['views'][title] if @config['views'].has_key?(title) false end;T;&I"def get_view(title);T;'To; ; F; ;;;;I"Doing::WWID#list_section;F;[[I"opt;TI"{};T;[[@Ki$;T;:list_section;;;[;{;IC;"3Display contents of a section based on options;T;[o;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@ ;[;I"b Display contents of a section based on options @param opt [Hash] Additional Options ;T; 0;!@ ;4i;"T;5o;6;7F;8i;9i#;$@D;:T;%I"def list_section(opt = {}) opt[:count] ||= 0 opt[:age] ||= 'newest' opt[:format] ||= @config.dig('templates', 'default', 'date_format') opt[:order] ||= @config.dig('templates', 'default', 'order') || 'asc' opt[:tag_order] ||= 'asc' opt[:tags_color] ||= false opt[:template] ||= @config.dig('templates', 'default', 'template') # opt[:highlight] ||= true title = '' is_single = true if opt[:section].nil? opt[:section] = choose_section title = opt[:section] elsif opt[:section].instance_of?(String) if opt[:section] =~ /^all$/i title = if opt[:page_title] opt[:page_title] elsif opt[:tag_filter] && opt[:tag_filter]['bool'].normalize_bool != :not opt[:tag_filter]['tags'].map { |tag| "@#{tag}" }.join(' + ') else 'doing' end else title = guess_section(opt[:section]) end end items = filter_items([], opt: opt).reverse items.reverse! if opt[:order] =~ /^d/i if opt[:interactive] opt[:menu] = !opt[:force] opt[:query] = '' # opt[:search] opt[:multiple] = true selected = choose_from_items(items, opt, include_section: opt[:section] =~ /^all$/i ) raise NoResults, 'no items selected' if selected.empty? act_on(selected, opt) return end opt[:output] ||= 'template' opt[:wrap_width] ||= @config['templates']['default']['wrap_width'] output(items, title, is_single, opt) end;T;&I"def list_section(opt = {});T;'To; ; F; ;;;;I"Doing::WWID#archive;F;[[I" section;TI"@config['current_section'];T[I" options;TI"{};T;[[@Ki`;T;: archive;;;[;{;IC;"RMove entries from a section to Archive or other specified section;T;[o;) ;*I" param;F;+I"The source section;T;I" section;T;,[I" String;T;!@ o;) ;*I" param;F;+I" Options;T;I" options;T;,[I" Hash;T;!@ ;[;I" Move entries from a section to Archive or other specified section @param section [String] The source section @param options [Hash] Options ;T; 0;!@ ;4i;"T;5o;6;7F;8iY;9i_;$@D;:T;%I"def archive(section = @config['current_section'], options = {}) count = options[:keep] || 0 destination = options[:destination] || 'Archive' tags = options[:tags] || [] bool = options[:bool] || :and section = choose_section if section.nil? || section =~ /choose/i archive_all = section =~ /^all$/i # && !(tags.nil? || tags.empty?) section = guess_section(section) unless archive_all add_section('Archive') if destination =~ /^archive$/i && !sections.include?('Archive') destination = guess_section(destination) if sections.include?(destination) && (sections.include?(section) || archive_all) do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before] }) write(doing_file) else raise InvalidArgument, 'Either source or destination does not exist' end end;T;&I"Ddef archive(section = @config['current_section'], options = {});T;'To; ; F; ;;;;I"Doing::WWID#today;F;[[I" times;TI" true;T[I" output;TI"nil;T[I"opt;TI"{};T;[[@Ki};T;: today;;;[;{;IC;"*Show all entries from the current day;T;[o;) ;*I" param;F;+I"show times;T;I" times;T;,[I" Boolean;T;!@* o;) ;*I" param;F;+I"output format;T;I" output;T;,[I" String;T;!@* o;) ;*I" param;F;+I" Options;T;I"opt;T;,[I" Hash;T;!@* ;[;I" Show all entries from the current day @param times [Boolean] show times @param output [String] output format @param opt [Hash] Options ;T; 0;!@* ;4i;"T;5o;6;7F;8iv;9i|;$@D;:T;%I"def today(times = true, output = nil, opt = {}) opt[:totals] ||= false opt[:sort_tags] ||= false cfg = @config['templates']['today'] options = { after: opt[:after], before: opt[:before], count: 0, format: cfg['date_format'], order: 'asc', output: output, section: opt[:section], sort_tags: opt[:sort_tags], template: cfg['template'], times: times, today: true, totals: opt[:totals], wrap_width: cfg['wrap_width'] } list_section(options) end;T;&I"4def today(times = true, output = nil, opt = {});T;'To; ; F; ;;;;I"Doing::WWID#list_date;F;[ [I" dates;T0[I" section;T0[I" times;TI"nil;T[I" output;TI"nil;T[I"opt;TI"{};T;[[@Ki;T;:list_date;;;[;{;IC;"(Display entries within a date range;T;[ o;) ;*I" param;F;+I"[start, end];T;I" dates;T;,[I" Array;T;!@S o;) ;*I" param;F;+I"The section;T;I" section;T;,[I" String;T;!@S o;) ;*I" param;F;+I"Show times;T;I" times;T;,[I" Bool;T;!@S o;) ;*I" param;F;+I"Output format;T;I" output;T;,[I" String;T;!@S o;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@S ;[;I" Display entries within a date range @param dates [Array] [start, end] @param section [String] The section @param times (Bool) Show times @param output [String] Output format @param opt [Hash] Additional Options ;T; 0;!@S ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"def list_date(dates, section, times = nil, output = nil, opt = {}) opt[:totals] ||= false opt[:sort_tags] ||= false section = guess_section(section) # :date_filter expects an array with start and end date dates = [dates, dates] if dates.instance_of?(String) list_section({ section: section, count: 0, order: 'asc', date_filter: dates, times: times, output: output, totals: opt[:totals], sort_tags: opt[:sort_tags] }) end;T;&I"Gdef list_date(dates, section, times = nil, output = nil, opt = {});T;'To; ; F; ;;;;I"Doing::WWID#yesterday;F;[ [I" section;T0[I" times;TI"nil;T[I" output;TI"nil;T[I"opt;TI"{};T;[[@Ki;T;:yesterday;;;[;{;IC;"'Show entries from the previous day;T;[ o;) ;*I" param;F;+I"The section;T;I" section;T;,[I" String;T;!@ o;) ;*I" param;F;+I"Show times;T;I" times;T;,[I" Bool;T;!@ o;) ;*I" param;F;+I"Output format;T;I" output;T;,[I" String;T;!@ o;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@ ;[;I" Show entries from the previous day @param section [String] The section @param times (Bool) Show times @param output [String] Output format @param opt [Hash] Additional Options ;T; 0;!@ ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"idef yesterday(section, times = nil, output = nil, opt = {}) opt[:totals] ||= false opt[:sort_tags] ||= false section = guess_section(section) y = (Time.now - (60 * 60 * 24)).strftime('%Y-%m-%d') opt[:after] = "#{y} #{opt[:after]}" if opt[:after] opt[:before] = "#{y} #{opt[:before]}" if opt[:before] options = { after: opt[:after], before: opt[:before], count: 0, order: opt[:order], output: output, section: section, sort_tags: opt[:sort_tags], tag_order: opt[:tag_order], times: times, totals: opt[:totals], yesterday: true } list_section(options) end;T;&I"@def yesterday(section, times = nil, output = nil, opt = {});T;'To; ; F; ;;;;I"Doing::WWID#recent;F;[[I" count;TI"10;T[I" section;TI"nil;T[I"opt;TI"{};T;[[@Ki;T;: recent;;;[;{;IC;"Show recent entries;T;[o;) ;*I" param;F;+I"The number to show;T;I" count;T;,[I" Integer;T;!@ o;) ;*I" param;F;+I"0The section to show from, default Currently;T;I" section;T;,[I" String;T;!@ o;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@ ;[;I" Show recent entries @param count [Integer] The number to show @param section [String] The section to show from, default Currently @param opt [Hash] Additional Options ;T; 0;!@ ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"def recent(count = 10, section = nil, opt = {}) times = opt[:t] || true opt[:totals] ||= false opt[:sort_tags] ||= false cfg = @config['templates']['recent'] section ||= @config['current_section'] section = guess_section(section) list_section({ section: section, wrap_width: cfg['wrap_width'], count: count, format: cfg['date_format'], template: cfg['template'], order: 'asc', times: times, totals: opt[:totals], sort_tags: opt[:sort_tags], tags_color: opt[:tags_color] }) end;T;&I"4def recent(count = 10, section = nil, opt = {});T;'To; ; F; ;;;;I"Doing::WWID#last;F;[[I" times:;TI" true;T[I" section:;TI"nil;T[I" options:;TI"{};T;[[@Ki;T;: last;;;[;{;IC;"Show the last entry;T;[o;) ;*I" param;F;+I"Show times;T;I" times;T;,[I" Bool;T;!@ o;) ;*I" param;F;+I",Section to pull from, default Currently;T;I" section;T;,[I" String;T;!@ ;[;I" Show the last entry @param times (Bool) Show times @param section [String] Section to pull from, default Currently ;T; 0;!@ ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"Wdef last(times: true, section: nil, options: {}) section = section.nil? || section =~ /all/i ? 'All' : guess_section(section) cfg = @config['templates']['last'] opts = { section: section, wrap_width: cfg['wrap_width'], count: 1, format: cfg['date_format'], template: cfg['template'], times: times } if options[:tag] opts[:tag_filter] = { 'tags' => options[:tag], 'bool' => options[:tag_bool] } end opts[:search] = options[:search] if options[:search] opts[:case] = options[:case] opts[:not] = options[:negate] list_section(opts) end;T;&I"5def last(times: true, section: nil, options: {});T;'To; ; F; ;;;;I"Doing::WWID#autotag;F;[[I" text;T0;[[@Ki;T;: autotag;;;[;{;IC;"Uses 'autotag' configuration to turn keywords into tags for time tracking. Does not repeat tags in a title, and only converts the first instance of an untagged keyword;T;[o;) ;*I" param;F;+I"The text to tag;T;I" text;T;,[I" String;T;!@ ;[;I" Uses 'autotag' configuration to turn keywords into tags for time tracking. Does not repeat tags in a title, and only converts the first instance of an untagged keyword @param text [String] The text to tag ;T; 0;!@ ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"def autotag(text) return unless text return text unless @auto_tag original = text.dup current_tags = text.scan(/@\w+/) whitelisted = [] @config['autotag']['whitelist'].each do |tag| next if text =~ /@#{tag}\b/i text.sub!(/(? Tag Totals project time EOS sorted_tags_data.reverse.each do |k, v| if v > 0 output += "#{k}#{'%02d:%02d:%02d' % format_time(v)}\n" end end tail = < Total #{'%02d:%02d:%02d' % format_time(total)} EOS output + tail when :markdown pad = sorted_tags_data.map {|k, v| k }.group_by(&:size).max.last[0].length output = <<~EOS | #{' ' * (pad - 7) }project | time | | #{'-' * (pad - 1)}: | :------- | EOS sorted_tags_data.reverse.each do |k, v| if v > 0 output += "| #{' ' * (pad - k.length)}#{k} | #{'%02d:%02d:%02d' % format_time(v)} |\n" end end tail = "[Tag Totals]" output + tail when :json output = [] sorted_tags_data.reverse.each do |k, v| d, h, m = format_time(v) output << { 'tag' => k, 'seconds' => v, 'formatted' => format('%02d:%02d:%02d', d: d, h: h, m: m) } end output when :human output = [] sorted_tags_data.reverse.each do |k, v| spacer = '' (max - k.length).times do spacer += ' ' end _d, h, m = format_time(v, human: true) output.push("┃ #{spacer}#{k}:#{format('% 4dh %02dm', h: h, m: m)} ┃") end header = '┏━━ Tag Totals ' (max - 2).times { header += '━' } header += '┓' footer = '┗' (max + 12).times { footer += '━' } footer += '┛' divider = '┣' (max + 12).times { divider += '━' } divider += '┫' output = output.empty? ? '' : "\n#{header}\n#{output.join("\n")}" d, h, m = format_time(total, human: true) output += "\n#{divider}" spacer = '' (max - 6).times do spacer += ' ' end total = "┃ #{spacer}total: " total += format('% 4dh %02dm', h: h, m: m) total += ' ┃' output += "\n#{total}" output += "\n#{footer}" output else output = [] sorted_tags_data.reverse.each do |k, v| spacer = '' (max - k.length).times do spacer += ' ' end d, h, m = format_time(v) output.push("#{k}:#{spacer}#{format('%02d:%02d:%02d', d: d, h: h, m: m)}") end output = output.empty? ? '' : "\n--- Tag Totals ---\n#{output.join("\n")}" d, h, m = format_time(total) output += "\n\nTotal tracked: #{format('%02d:%02d:%02d', d: d, h: h, m: m)}\n" output end end;T;&I"Idef tag_times(format: :text, sort_by_name: false, sort_order: 'asc');T;'To; ; F; ;;;;I"Doing::WWID#get_interval;F;[[I" item;T0[I"formatted:;TI" true;T[I" record:;TI" true;T;[[@Ki;T;:get_interval;;;[;{;IC;"LGets the interval between entry's start date and @done date;T;[ o;) ;*I" param;F;+I"The entry;T;I" item;T;,[I" Item;T;!@H o;) ;*I" param;F;+I"1Return human readable time (default seconds);T;I"formatted;T;,[I" Boolean;T;!@H o;) ;*I" param;F;+I"/Add the interval to the total for each tag;T;I" record;T;,[I" Boolean;T;!@H o;) ;*I" return;F;+I"hInterval in seconds, or [d, h, m] array if formatted is true. False if no end date or interval is 0;T;0;,0;!@H ;[;I" Gets the interval between entry's start date and @done date @param item [Item] The entry @param formatted [Boolean] Return human readable time (default seconds) @param record [Boolean] Add the interval to the total for each tag @return Interval in seconds, or [d, h, m] array if formatted is true. False if no end date or interval is 0 ;T; 0;!@H ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I"=def get_interval(item, formatted: true, record: true) if item.interval seconds = item.interval record_tag_times(item, seconds) if record return seconds.positive? ? seconds : false unless formatted return seconds.positive? ? format('%02d:%02d:%02d', *format_time(seconds)) : false end false end;T;&I":def get_interval(item, formatted: true, record: true);T;'To; ; F; ;;;;I"Doing::WWID#format_time;F;[[I" seconds;T0[I" human:;TI" false;T;[[@Ki;T;:format_time;;;[;{;IC;",Format human readable time from seconds;T;[o;) ;*I" param;F;+I" Seconds;T;I" seconds;T;,[I" Integer;T;!@s ;[;I"V Format human readable time from seconds @param seconds [Integer] Seconds ;T; 0;!@s ;4i;"T;5o;6;7F;8i;9i;$@D;:T;%I",def format_time(seconds, human: false) return [0, 0, 0] if seconds.nil? if seconds.class == String && seconds =~ /(\d+):(\d+):(\d+)/ h = Regexp.last_match(1) m = Regexp.last_match(2) s = Regexp.last_match(3) seconds = (h.to_i * 60 * 60) + (m.to_i * 60) + s.to_i end minutes = (seconds / 60).to_i hours = (minutes / 60).to_i if human minutes = (minutes % 60).to_i [0, hours, minutes] else days = (hours / 24).to_i hours = (hours % 24).to_i minutes = (minutes % 60).to_i [days, hours, minutes] end end;T;&I"+def format_time(seconds, human: false);T;'To; ; F; ;;;F;I"!Doing::WWID#combined_content;F;[;[[@Ki;T;:combined_content;;;[;{;IC;"OWraps doing file content with additional header/footer content;T;[o;) ;*I" return;F;+I"concatenated content;T;0;,[I" String;T;!@ ;[;I"| Wraps doing file content with additional header/footer content @return [String] concatenated content ;T; 0;!@ ;4i;"T;5o;6;7F;8i ;9i;$@D;:T;%I"{def combined_content output = @other_content_top ? "#{@other_content_top.join("\n")}\n" : '' @content.each do |title, section| output += "#{section[:original]}\n" output += list_section({ section: title, template: "\t- %date | %title%t2note", highlight: false, wrap_width: 0 }) end output + @other_content_bottom.join("\n") unless @other_content_bottom.nil? end;T;&I"def combined_content;T;'To; ; F; ;;;F;I"Doing::WWID#output;F;[ [I" items;T0[I" title;T0[I"is_single;T0[I"opt;TI"{};T;[[@Ki(;T;: output;;;[;{;IC;"3Generate output using available export plugins;T;[ o;) ;*I" param;F;+I"The items;T;I" items;T;,[I" Array;T;!@ o;) ;*I" param;F;+I"Page title;T;I" title;T;,[I" String;T;!@ o;) ;*I" param;F;+I" Indicates if single section;T;I"is_single;T;,[I" Boolean;T;!@ o;) ;*I" param;F;+I"Additional options;T;I"opt;T;,[I" Hash;T;!@ o;) ;*I" return;F;+I";T;:record_tag_times;;;[;{;IC;"Record times for item tags;T;[o;) ;*I" param;F;+I"The item to record;T;I" item;T;,[I" Item;T;!@ ;[;I"N Record times for item tags @param item [Item] The item to record ;T; 0;!@ ;4i;"T;5o;6;7F;8i9;9i=;$@D;:T;%I"def record_tag_times(item, seconds) item_hash = "#{item.date.strftime('%s')}#{item.title}#{item.section}" return if @recorded_items.include?(item_hash) item.title.scan(/(?mi)@(\S+?)(\(.*\))?(?=\s|$)/).each do |m| k = m[0] == 'done' ? 'All' : m[0].downcase if @timers.key?(k) @timers[k] += seconds else @timers[k] = seconds end @recorded_items.push(item_hash) end end;T;&I"(def record_tag_times(item, seconds);T;'To; ; F; ;;;F;I"Doing::WWID#do_archive;F;[[I" sect;T0[I"destination;T0[I"opt;TI"{};T;[[@KiT;T;:do_archive;;;[;{;IC;"3Helper function, performs the actual archiving;T;[o;) ;*I" param;F;+I"The source section;T;I" sect;T;,[I" String;T;!@ o;) ;*I" param;F;+I"The destination section;T;I"destination;T;,[I" String;T;!@ o;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@ ;[;I" Helper function, performs the actual archiving @param sect [String] The source section @param destination [String] The destination section @param opt [Hash] Additional Options ;T; 0;!@ ;4i;"T;5o;6;7F;8iL;9iS;$@D;:T;%I"V def do_archive(sect, destination, opt = {}) count = opt[:count] || 0 tags = opt[:tags] || [] bool = opt[:bool] || :and label = opt[:label] || true if sect =~ /^all$/i all_sections = sections.dup all_sections.delete(destination) else all_sections = [sect] end counter = 0 all_sections.each do |section| items = @content[section][:items].dup moved_items = [] if !tags.empty? || opt[:search] || opt[:before] if opt[:before] time_string = opt[:before] cutoff = chronify(time_string, guess: :begin) end items.delete_if do |item| if ((!tags.empty? && item.tags?(tags, bool)) || (opt[:search] && item.search(opt[:search].to_s)) || (opt[:before] && item.date < cutoff)) moved_items.push(item) counter += 1 true else false end end moved_items.each do |item| if label item.title = if section == @config['current_section'] item.title.sub(/(?: ?@from\(.*?\))?(.*)$/, '\1') else item.title.sub(/(?: ?@from\(.*?\))?(.*)$/, "\\1 @from(#{section})") end logger.debug('Moved:', "#{item.title} from #{section} to #{destination}") end end @content[section][:items] = items @content[destination][:items].concat(moved_items) if moved_items.length.positive? logger.count(destination == 'Archive' ? :archived : :moved, level: :info, count: moved_items.length, message: "%count %items from #{section} to #{destination}") else logger.info('Skipped:', 'No items were moved') end else count = items.length if items.length < count items.map! do |item| if label item.title = if section == @config['current_section'] item.title.sub(/(?: ?@from\(.*?\))?(.*)$/, '\1') else item.title.sub(/(?: ?@from\(.*?\))?(.*)$/, "\\1 @from(#{section})") end logger.debug('Moved:', "#{item.title} from #{section} to #{destination}") end item end if items.count > count @content[destination][:items].concat(items[count..-1]) else @content[destination][:items].concat(items) end @content[section][:items] = if count.zero? [] else items[0..count - 1] end logger.count(destination == 'Archive' ? :archived : :moved, level: :info, count: items.length - count, message: "%count %items from #{section} to #{destination}") end end end;T;&I"0def do_archive(sect, destination, opt = {});T;'To; ; F; ;;;F;I"Doing::WWID#run_after;F;[;[[@Ki;F;:run_after;;;[;{;IC;" ;T;[;[;@; 0;!@ ;4i;$@D;:T;%I"*def run_after return unless @config.key?('run_after') _, stderr, status = Open3.capture3(@config['run_after']) return unless status.exitstatus.positive? logger.log_now(:error, 'Script error:', "Error running #{@config['run_after']}") logger.log_now(:error, 'STDERR output:', stderr) end;T;&I"def run_after;T;'To; ; F; ;;;F;I"Doing::WWID#log_change;F;[ [I"tags_added:;TI"[];T[I"tags_removed:;TI"[];T[I" count:;TI"1;T[I" item:;TI"nil;T[I" single:;TI" false;T;[[@Ki;F;:log_change;;;[;{;IC;" ;T;[;[;@; 0;!@" ;4i;$@D;:T;%I"Ydef log_change(tags_added: [], tags_removed: [], count: 1, item: nil, single: false) if tags_added.empty? && tags_removed.empty? logger.count(:skipped, level: :debug, message: '%count %items with no change', count: count) else if tags_added.empty? logger.count(:skipped, level: :debug, message: 'no tags added to %count %items') else if single && item logger.info('Tagged:', %(added #{tags_added.count == 1 ? 'tag' : 'tags'} #{tags_added.map {|t| "@#{t}"}.join(', ')} to #{item.title})) else logger.count(:added_tags, level: :info, tag: tags_added, message: '%tags added to %count %items') end end if tags_removed.empty? logger.count(:skipped, level: :debug, message: 'no tags removed from %count %items') else if single && item logger.info('Untagged:', %(removed #{tags_removed.count == 1 ? 'tag' : 'tags'} #{tags_added.map {|t| "@#{t}"}.join(', ')} from #{item.title})) else logger.count(:removed_tags, level: :info, tag: tags_removed, message: '%tags removed from %count %items') end end end end;T;&I"Ydef log_change(tags_added: [], tags_removed: [], count: 1, item: nil, single: false);T;'T;M@D;NIC;[;M@D;OIC;[;M@D;PIC;Q{;RIC;Q{;ST;IC;Q{ ;{IC;Q{;T@F;U0;ST;|IC;Q{;T@T;U0;ST;}IC;Q{;T@a;U0;ST;~IC;Q{;T@n;U0;ST;IC;Q{;T@{;U@;ST;|IC;Q{;T@;U@;ST;~IC;Q{;T@;U@;ST;IC;Q{;T@;U@;ST;ST;ST;V{;W[;[[@Ki;T;: WWID;;;;;[;{;IC;"$Main "What Was I Doing" methods;T;[;[;I"& Main "What Was I Doing" methods ;T; 0;!@D;4i;"T;5o;6;7F;8i;9i;$@;I"Doing::WWID;F;Yo;Z ;[0;\0;]0;;^;$@;_0;`;R;'To; ;IC;[ o:&YARD::CodeObjects::ConstantObject;[[I"lib/doing/hooks.rb;Ti ;F;:DEFAULT_PRIORITY;;;;;[;{;IC;" ;T;[;[;@; 0;!@Y ;$@W ;I"#Doing::Hooks::DEFAULT_PRIORITY;F;%I"DEFAULT_PRIORITY = 20;T: @valueI"20;T;'To; ; F; ;R;;;I"Doing::Hooks.register;F;[[I" event;T0[I"priority:;TI"DEFAULT_PRIORITY;T[I" &block;T0;[[@\ i;T;: register;;;[;{;IC;"4register hook(s) to be called later, public API;T;[;[;I"4register hook(s) to be called later, public API;T; 0;!@e ;4i;"F;5o;6;7F;8i;9i;$@W ;:T;%I"}def self.register(event, priority: DEFAULT_PRIORITY, &block) register_one(event, priority_value(priority), &block) end;T;&I" e raise Errors::DoingStandardError, "Pager error, #{e}" end end begin read_io.close write_io.write(text) write_io.close rescue SystemCallError => e raise Errors::DoingStandardError, "Pager error, #{e}" end _, status = Process.waitpid2(pid) status.success? end;T;&I"def page(text);T;'To; ; F; ;R;;;I"Doing::Pager.which_pager;F;[;[[@ i;;F;:which_pager;;;[;{;IC;" ;T;[;[;@; 0;!@ ;4i;$@ ;:T;%I"def which_pager pagers = [ENV['GIT_PAGER'], ENV['PAGER']] if Util.exec_available('git') git_pager = `git config --get-all core.pager || true`.split.first git_pager && pagers.push(git_pager) end pagers.concat(%w[bat less more pager]) pagers.select! do |f| if f if f.strip =~ /[ |]/ f elsif f == 'most' Doing.logger.warn('most not allowed as pager') false else system "which #{f}", out: File::NULL, err: File::NULL end else false end end pg = pagers.first args = case pg when /^more$/ ' -r' when /^less$/ ' -Xr' when /^bat$/ ' -p --pager="less -Xr"' else '' end [pg, args] end;T;&I"def which_pager;T;'T;M@ ;NIC;[;M@ ;OIC;[;M@ ;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@ i ;T;: Pager;;;;;[;{;IC;"Pagination;T;[;[;I"Pagination;T; 0;!@ ;4i;"F;5o;6;7F;8i ;9i ;$@;I"Doing::Pager;F;'To; ;IC;[o;;[[I"lib/doing/colors.rb;Ti ;T;:ATTRIBUTES;;;;;[;{;IC;":stopdoc: ;T;[;[;I":stopdoc:;T; 0;!@ ;"F;5o;6;7F;8i ;9i ;$@ ;I"Doing::Color::ATTRIBUTES;F;%I"ATTRIBUTES = [ [ :clear , 0 ], # String#clear is already used to empty string in Ruby 1.9 [ :reset , 0 ], # synonym for :clear [ :bold , 1 ], [ :dark , 2 ], [ :italic , 3 ], # not widely implemented [ :underline , 4 ], [ :underscore , 4 ], # synonym for :underline [ :blink , 5 ], [ :rapid_blink , 6 ], # not widely implemented [ :negative , 7 ], # no reverse because of String#reverse [ :concealed , 8 ], [ :strikethrough , 9 ], # not widely implemented [ :black , 30 ], [ :red , 31 ], [ :green , 32 ], [ :yellow , 33 ], [ :blue , 34 ], [ :magenta , 35 ], [ :cyan , 36 ], [ :white , 37 ], [ :bgblack , 40 ], [ :bgred , 41 ], [ :bggreen , 42 ], [ :bgyellow , 43 ], [ :bgblue , 44 ], [ :bgmagenta , 45 ], [ :bgcyan , 46 ], [ :bgwhite , 47 ], [ :boldblack , 90 ], # High intensity, aixterm (works in OS X) [ :boldred , 91 ], [ :boldgreen , 92 ], [ :boldyellow , 93 ], [ :boldblue , 94 ], [ :boldmagenta , 95 ], [ :boldcyan , 96 ], [ :boldwhite , 97 ], [ :boldbgblack , 100 ], # High intensity background, aixterm (works in OS X) [ :boldbgred , 101 ], [ :boldbggreen , 102 ], [ :boldbgyellow , 103 ], [ :boldbgblue , 104 ], [ :boldbgmagenta , 105 ], [ :boldbgcyan , 106 ], [ :boldbgwhite , 107 ], [ :softpurple , '0;35;40'], [ :hotpants , '7;34;40'], [ :knightrider , '7;30;40'], [ :flamingo , '7;31;47'], [ :yeller , '1;37;43'], [ :whiteboard , '1;30;47'], [ :default , '0;39' ] ];T;I"[ [ :clear , 0 ], # String#clear is already used to empty string in Ruby 1.9 [ :reset , 0 ], # synonym for :clear [ :bold , 1 ], [ :dark , 2 ], [ :italic , 3 ], # not widely implemented [ :underline , 4 ], [ :underscore , 4 ], # synonym for :underline [ :blink , 5 ], [ :rapid_blink , 6 ], # not widely implemented [ :negative , 7 ], # no reverse because of String#reverse [ :concealed , 8 ], [ :strikethrough , 9 ], # not widely implemented [ :black , 30 ], [ :red , 31 ], [ :green , 32 ], [ :yellow , 33 ], [ :blue , 34 ], [ :magenta , 35 ], [ :cyan , 36 ], [ :white , 37 ], [ :bgblack , 40 ], [ :bgred , 41 ], [ :bggreen , 42 ], [ :bgyellow , 43 ], [ :bgblue , 44 ], [ :bgmagenta , 45 ], [ :bgcyan , 46 ], [ :bgwhite , 47 ], [ :boldblack , 90 ], # High intensity, aixterm (works in OS X) [ :boldred , 91 ], [ :boldgreen , 92 ], [ :boldyellow , 93 ], [ :boldblue , 94 ], [ :boldmagenta , 95 ], [ :boldcyan , 96 ], [ :boldwhite , 97 ], [ :boldbgblack , 100 ], # High intensity background, aixterm (works in OS X) [ :boldbgred , 101 ], [ :boldbggreen , 102 ], [ :boldbgyellow , 103 ], [ :boldbgblue , 104 ], [ :boldbgmagenta , 105 ], [ :boldbgcyan , 106 ], [ :boldbgwhite , 107 ], [ :softpurple , '0;35;40'], [ :hotpants , '7;34;40'], [ :knightrider , '7;30;40'], [ :flamingo , '7;31;47'], [ :yeller , '1;37;43'], [ :whiteboard , '1;30;47'], [ :default , '0;39' ] ];T;'To;;[[@" iA;F;:ATTRIBUTE_NAMES;;;;;[;{;IC;" ;T;[;[;@; 0;!@- ;$@ ;I""Doing::Color::ATTRIBUTE_NAMES;F;%I"1ATTRIBUTE_NAMES = ATTRIBUTES.transpose.first;T;I"ATTRIBUTES.transpose.first;T;'To; ; F; ;;;;I"Doing::Color#support?;F;[[I" feature;T0;[[@" iJ;T;: support?;;;[;{;IC;"Returns true if Doing::Color supports the +feature+. The feature :clear, that is mixing the clear color attribute into String, is only supported on ruby implementations, that do *not* already implement the String#clear method. It's better to use the reset color attribute instead.;T;[o;) ;*I" return;F;+@;0;,[@;!@8 ;[;I"Returns true if Doing::Color supports the +feature+. The feature :clear, that is mixing the clear color attribute into String, is only supported on ruby implementations, that do *not* already implement the String#clear method. It's better to use the reset color attribute instead.;T; 0;!@8 ;4i;"F;5o;6;7F;8iD;9iI;$@ ;:T;%I"def support?(feature) case feature when :clear !String.instance_methods(false).map(&:to_sym).include?(:clear) end end;T;&I"def support?(feature);T;'To; ; F; ;R;;;I"Doing::Color.coloring?;F;[;[[@" iR;T;:coloring?;;;[;{;IC;"[Returns true, if the coloring function of this module is switched on, false otherwise.;T;[o;) ;*I" return;F;+@;0;,[@;!@K ;[;I"[Returns true, if the coloring function of this module is switched on, false otherwise.;T; 0;!@K ;4i;"F;5o;6;7F;8iP;9iQ;$@ ;:T;%I"'def self.coloring? @coloring end;T;&I"def coloring?;T;'To; ; F; ;R;;;I"Doing::Color.coloring=;F;[[I"val;T0;[[@" iY;T;:coloring=;;;[;{;IC;"zTurns the coloring on or off globally, so you can easily do this for example: Doing::Color::coloring = STDOUT.isatty;T;[;[;I"zTurns the coloring on or off globally, so you can easily do this for example: Doing::Color::coloring = STDOUT.isatty;T; 0;!@\ ;4i;"F;5o;6;7F;8iV;9iX;$@ ;:T;%I"2def self.coloring=(val) @coloring = val end;T;&I"def coloring=(val);T;'To;;[[@" it;T;:COLORED_REGEXP;;;;;[;{;IC;"YRegular expression that is used to scan for ANSI-sequences while uncoloring strings. ;T;[;[;I"YRegular expression that is used to scan for ANSI-sequences while uncoloring strings.;T; 0;!@l ;"F;5o;6;7F;8ir;9is;$@ ;I"!Doing::Color::COLORED_REGEXP;F;%I"9COLORED_REGEXP = /\e\[(?:(?:[349]|10)[0-7]|[0-9])?m/;T;I"(/\e\[(?:(?:[349]|10)[0-7]|[0-9])?m/;T;'To; ; F; ;;;;I"Doing::Color#uncolor;F;[[I" string;TI"nil;T;[[@" ix;T;: uncolor;;;[;{;IC;"iReturns an uncolored version of the string, that is all ANSI-sequences are stripped from the string.;T;[;[;I"iReturns an uncolored version of the string, that is all ANSI-sequences are stripped from the string.;T; 0;!@y ;4i;"F;5o;6;7F;8iv;9iw;$@ ;:T;%I"def uncolor(string = nil) # :yields: if block_given? yield.to_str.gsub(COLORED_REGEXP, '') elsif string.respond_to?(:to_str) string.to_str.gsub(COLORED_REGEXP, '') elsif respond_to?(:to_str) to_str.gsub(COLORED_REGEXP, '') else '' end end;T;&I"def uncolor(string = nil);T;'To; ; F; ;;;F;I"Doing::Color#attributes;F;[;[[@" i;T;:attributes;;;[;{;IC;"@Returns an array of all Doing::Color attributes as symbols. ;T;[;[;I"@Returns an array of all Doing::Color attributes as symbols.;T; 0;!@ ;"F;#0;$@ ;:T;%I")def attributes ATTRIBUTE_NAMES end;T;&I"def attributes;T;'To; ; T; ;R;;;I"Doing::Color.attributes;F;@ ;@ ;T;;;;;@ ;{;IC;"@Returns an array of all Doing::Color attributes as symbols.;T;[;[;I"@Returns an array of all Doing::Color attributes as symbols.;T; 0;!@ ;4i;"F;5o;6;7F;8i;9i;$@ ;:T;%@ ;&@ ;'T;M@ ;NIC;[@ ;M@ ;OIC;[;M@ ;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@" i;F;: Color;;;;;[;{;IC;" ;T;[;[;@; 0;!@ ;4i;$@;I"Doing::Color;F;'To; ;IC;[o; ;IC;[o; ; F; ;;;;I",Doing::Errors::UserCancelled#initialize;F;[[I"msg;TI"'Cancelled';T[I" topic;TI"'Exited:';T;[[I"lib/doing/errors.rb;Ti ;F;;3;;;[;{;IC;" ;T;[o;) ;*I" return;F;+I"$a new instance of UserCancelled;T;0;,[I"UserCancelled;F;!@ ;[;@; 0;!@ ;4i;$@ ;:T;%I"def initialize(msg = 'Cancelled', topic = 'Exited:') Doing.logger.output_results Doing.logger.log_now(:warn, topic, msg) Process.exit 1 end;T;&I"9def initialize(msg = 'Cancelled', topic = 'Exited:');T;'T;M@ ;NIC;[;M@ ;OIC;[;M@ ;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@ i ;F;:UserCancelled;;;;;[;{;IC;" ;T;[;[;@; 0;!@ ;4i;$@ ;I"!Doing::Errors::UserCancelled;F;Yo;Z ;[0;\0;]0;:StandardError;$@;_0;`;R;'To; ;IC;[o; ; F; ;;;;I")Doing::Errors::EmptyInput#initialize;F;[[I"msg;TI"'No input';T[I" topic;TI"'Exited:';T;[[@ i;F;;3;;;[;{;IC;" ;T;[o;) ;*I" return;F;+I"!a new instance of EmptyInput;T;0;,[I"EmptyInput;F;!@ ;[;@; 0;!@ ;4i;$@ ;:T;%I"def initialize(msg = 'No input', topic = 'Exited:') Doing.logger.output_results Doing.logger.log_now(:warn, topic, msg) Process.exit 1 end;T;&I"8def initialize(msg = 'No input', topic = 'Exited:');T;'T;M@ ;NIC;[;M@ ;OIC;[;M@ ;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@ i;F;:EmptyInput;;;;;[;{;IC;" ;T;[;[;@; 0;!@ ;4i;$@ ;I"Doing::Errors::EmptyInput;F;Yo;Z ;[0;\0;]0;;;$@;_0;`;R;'To; ;IC;[o; ; F; ;;;;I"1Doing::Errors::DoingStandardError#initialize;F;[[I"msg;TI"'';T;[[@ i;F;;3;;;[;{;IC;" ;T;[o;) ;*I" return;F;+I")a new instance of DoingStandardError;T;0;,[I"DoingStandardError;F;!@;[;@; 0;!@;4i;$@;:T;%I"Hdef initialize(msg = '') Doing.logger.output_results super end;T;&I"def initialize(msg = '');T;'T;M@;NIC;[;M@;OIC;[;M@;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@ i;F;:DoingStandardError;;;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@ ;I"&Doing::Errors::DoingStandardError;F;Yo;Z ;[0;\0;]0;;;$@;_0;`;R;'To; ;IC;[o; ; F; ;;;;I"+Doing::Errors::WrongCommand#initialize;F;[[I"msg;TI"'wrong command';T[I" topic:;TI" 'Error:';T;[[@ i#;F;;3;;;[;{;IC;" ;T;[o;) ;*I" return;F;+I"#a new instance of WrongCommand;T;0;,[I"WrongCommand;F;!@+;[;@; 0;!@+;4i;$@);:T;%I"mdef initialize(msg = 'wrong command', topic: 'Error:') Doing.logger.warn(topic, msg) super(msg) end;T;&I";def initialize(msg = 'wrong command', topic: 'Error:');T;'T;M@);NIC;[;M@);OIC;[;M@);PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@ i";F;:WrongCommand;;;;;[;{;IC;" ;T;[;[;@; 0;!@);4i;$@ ;I" Doing::Errors::WrongCommand;F;Yo;Z ;[0;\0;]0;;;$@;_0;`;R;'To; ;IC;[o; ; F; ;;;;I"0Doing::Errors::DoingRuntimeError#initialize;F;[[I"msg;TI"'Runtime Error';T[I" topic:;TI" 'Error:';T;[[@ i+;F;;3;;;[;{;IC;" ;T;[o;) ;*I" return;F;+I"(a new instance of DoingRuntimeError;T;0;,[I"DoingRuntimeError;F;!@T;[;@; 0;!@T;4i;$@R;:T;%I"def initialize(msg = 'Runtime Error', topic: 'Error:') Doing.logger.output_results Doing.logger.log_now(:error, topic, msg) Process.exit 1 end;T;&I";def initialize(msg = 'Runtime Error', topic: 'Error:');T;'T;M@R;NIC;[;M@R;OIC;[;M@R;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@ i*;F;:DoingRuntimeError;;;;;[;{;IC;" ;T;[;[;@; 0;!@R;4i;$@ ;I"%Doing::Errors::DoingRuntimeError;F;Yo;Z ;[0;\0;]0;:RuntimeError;$@;_0;`;R;'To; ;IC;[o; ; F; ;;;;I"(Doing::Errors::NoResults#initialize;F;[[I"msg;TI"'No results';T[I" topic;TI"'Exited:';T;[[@ i3;F;;3;;;[;{;IC;" ;T;[o;) ;*I" return;F;+I" a new instance of NoResults;T;0;,[I"NoResults;F;!@};[;@; 0;!@};4i;$@{;:T;%I"def initialize(msg = 'No results', topic = 'Exited:') Doing.logger.output_results Doing.logger.log_now(:warn, topic, msg) Process.exit 0 end;T;&I":def initialize(msg = 'No results', topic = 'Exited:');T;'T;M@{;NIC;[;M@{;OIC;[;M@{;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@ i2;F;:NoResults;;;;;[;{;IC;" ;T;[;[;@; 0;!@{;4i;$@ ;I"Doing::Errors::NoResults;F;Yo;Z ;[0;\0;]0;;;$@;_0;`;R;'To; ;IC;[o; ; F; ;;;;I"0Doing::Errors::DoingNoTraceError#initialize;F;[[I"msg;TI"nil;T[I" level;TI"nil;T[I" topic;TI"nil;T;[[@ i<;F;;3;;;[;{;IC;" ;T;[o;) ;*I" return;F;+I"(a new instance of DoingNoTraceError;T;0;,[I"DoingNoTraceError;F;!@;[;@; 0;!@;4i;$@;:T;%I"def initialize(msg = nil, level = nil, topic = nil) level ||= :error Doing.logger.output_results if msg Doing.logger.log_now(level, topic, msg) end Process.exit 1 end;T;&I"8def initialize(msg = nil, level = nil, topic = nil);T;'T;M@;NIC;[;M@;OIC;[;M@;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@ i;;F;:DoingNoTraceError;;;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@ ;I"%Doing::Errors::DoingNoTraceError;F;Yo;Z ;[0;\0;]0;;;$@;_0;`;R;'To; ;IC;[o; ; F; ;;;;I"*Doing::Errors::PluginException#plugin;F;[;[[@ iH;F;: plugin;;;[;{;IC;"+Returns the value of attribute plugin. ;T;[;[;I"+Returns the value of attribute plugin.;T; 0;!@;"F;#0;$@;%I"def plugin @plugin end;T;&I"def plugin;T;'To; ; F; ;;;;I".Doing::Errors::PluginException#initialize;F;[[I"msg;TI"'Plugin error';T[I" type;TI"nil;T[I" plugin;TI"nil;T;[[@ iJ;F;;3;;;[;{;IC;" ;T;[o;) ;*I" return;F;+I"&a new instance of PluginException;T;0;,[I"PluginException;F;!@;[;@; 0;!@;4i;$@;:T;%I"def initialize(msg = 'Plugin error', type = nil, plugin = nil) @plugin = plugin || 'Unknown Plugin' type ||= 'Unknown' @type = case type.to_s when /^i/ 'Import plugin' when /^e/ 'Export plugin' else type.to_s end msg = "(#{@type}: #{@plugin}) #{msg}" Doing.logger.log_now(:error, 'Plugin:', msg) Process.exit 1 end;T;&I"Cdef initialize(msg = 'Plugin error', type = nil, plugin = nil);T;'T;M@;NIC;[;M@;OIC;[;M@;PIC;Q{;RIC;Q{;ST;IC;Q{;IC;Q{;T@;U0;ST;ST;ST;V{;W[;[[@ iG;F;:PluginException;;;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@ ;I"#Doing::Errors::PluginException;F;Yo;Z ;[0;\0;]0;;;$@;_0;`;R;'To;;[[@ i^;F;:HookUnavailable;;;;;[;{;IC;" ;T;[;[;@; 0;!@ ;$@ ;I"#Doing::Errors::HookUnavailable;F;%I"1HookUnavailable = Class.new(PluginException);T;I"Class.new(PluginException);T;'To;;[[@ i_;F;:InvalidPluginType;;;;;[;{;IC;" ;T;[;[;@; 0;!@;$@ ;I"%Doing::Errors::InvalidPluginType;F;%I"3InvalidPluginType = Class.new(PluginException);T;I"Class.new(PluginException);T;'To;;[[@ i`;F;:PluginUncallable;;;;;[;{;IC;" ;T;[;[;@; 0;!@ ;$@ ;I"$Doing::Errors::PluginUncallable;F;%I"2PluginUncallable = Class.new(PluginException);T;I"Class.new(PluginException);T;'To;;[[@ ib;F;:InvalidArgument;;;;;[;{;IC;" ;T;[;[;@; 0;!@+;$@ ;I"#Doing::Errors::InvalidArgument;F;%I"3InvalidArgument = Class.new(DoingRuntimeError);T;I"!Class.new(DoingRuntimeError);T;'To;;[[@ ic;F;:MissingArgument;;;;;[;{;IC;" ;T;[;[;@; 0;!@6;$@ ;I"#Doing::Errors::MissingArgument;F;%I"3MissingArgument = Class.new(DoingRuntimeError);T;I"!Class.new(DoingRuntimeError);T;'To;;[[@ id;F;:MissingFile;;;;;[;{;IC;" ;T;[;[;@; 0;!@A;$@ ;I"Doing::Errors::MissingFile;F;%I"/MissingFile = Class.new(DoingRuntimeError);T;I"!Class.new(DoingRuntimeError);T;'To;;[[@ ie;F;:MissingEditor;;;;;[;{;IC;" ;T;[;[;@; 0;!@L;$@ ;I"!Doing::Errors::MissingEditor;F;%I"1MissingEditor = Class.new(DoingRuntimeError);T;I"!Class.new(DoingRuntimeError);T;'To;;[[@ if;F;:NonInteractive;;;;;[;{;IC;" ;T;[;[;@; 0;!@W;$@ ;I""Doing::Errors::NonInteractive;F;%I".NonInteractive = Class.new(StandardError);T;I"Class.new(StandardError);T;'To;;[[@ ih;F;:NoEntryError;;;;;[;{;IC;" ;T;[;[;@; 0;!@b;$@ ;I" Doing::Errors::NoEntryError;F;%I"0NoEntryError = Class.new(DoingRuntimeError);T;I"!Class.new(DoingRuntimeError);T;'To;;[[@ ij;F;:InvalidTimeExpression;;;;;[;{;IC;" ;T;[;[;@; 0;!@m;$@ ;I")Doing::Errors::InvalidTimeExpression;F;%I"9InvalidTimeExpression = Class.new(DoingRuntimeError);T;I"!Class.new(DoingRuntimeError);T;'To;;[[@ ik;F;:InvalidSection;;;;;[;{;IC;" ;T;[;[;@; 0;!@x;$@ ;I""Doing::Errors::InvalidSection;F;%I"2InvalidSection = Class.new(DoingRuntimeError);T;I"!Class.new(DoingRuntimeError);T;'To;;[[@ il;F;:InvalidView;;;;;[;{;IC;" ;T;[;[;@; 0;!@;$@ ;I"Doing::Errors::InvalidView;F;%I"/InvalidView = Class.new(DoingRuntimeError);T;I"!Class.new(DoingRuntimeError);T;'To;;[[@ in;F;:ItemNotFound;;;;;[;{;IC;" ;T;[;[;@; 0;!@;$@ ;I" Doing::Errors::ItemNotFound;F;%I"0ItemNotFound = Class.new(DoingRuntimeError);T;I"!Class.new(DoingRuntimeError);T;'T;M@ ;NIC;[;M@ ;OIC;[;M@ ;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@ i ;F;: Errors;;;;;[;{;IC;" ;T;[;[;@; 0;!@ ;4i;$@;I"Doing::Errors;F;'To;;[[I"lib/doing/version.rb;Ti;F;: VERSION;;;;;[;{;IC;" ;T;[;[;@; 0;!@;$@;I"Doing::VERSION;F;%I"VERSION = '2.0.9.pre';T;I"'2.0.9.pre';T;'To; ;IC;[ o; ; F; ;;;;I"Doing::Items#from;F;[[I" path;T0;[[I"lib/doing/wwidfile.rb;Ti ;F;: from;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"def from(path) return [] unless path path = File.expand_path(path) if File.exist?(path) && File.file?(path) && File.stat(path).size.positive? input = IO.read(File.expand_path(input)) input = input.force_encoding('utf-8') if input.respond_to? :force_encoding end section = 'Uncategorized' lines = input.split(/[\n\r]/) current = 0 lines.each do |line| next if line =~ /^\s*$/ if line =~ /^(\S[\S ]+):\s*(@\S+\s*)*$/ section = Regexp.last_match(1) @sections << { original: line, title: section } current = 0 elsif line =~ /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*)/ date = Regexp.last_match(1).strip title = Regexp.last_match(2).strip item = Item.new(date, title, section) @items.push(item) current += 1 elsif current.zero? # if content[section][:items].length - 1 == current @other_content_top.push(line) elsif line =~ /^\S/ @other_content_bottom.push(line) else prev_item = @items[current - 1] prev_item.note = Note.new unless prev_item.note prev_item.note.add(line) # end end end Hooks.trigger :post_read, self end;T;&I"def from(path);T;'To; ; F; ;;;;I" Doing::Items#section_titles;F;[;[[@i4;F;:section_titles;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"=def section_titles @sections.map { |s| s[:title] } end;T;&I"def section_titles;T;'To; ; F; ;;;;I"Doing::Items#add_section;F;[[I" title;T0;[[@i=;T;;;;;[;{;IC;"Adds a section.;T;[o;) ;*I" param;F;+I"The new section title;T;I" title;T;,[I" String;T;!@;[;I"I Adds a section. @param title [String] The new section title ;T; 0;!@;4i;"T;5o;6;7F;8i8;9i<;$@;:T;%I" def add_section(title) if section_titles.include?(title.cap_first) Doing.logger.debug('Skipped': 'Section already exists') return end @sections << { original: "#{title}:", title: title } Doing.logger.info('Added section:', %("#{title.cap_first}")) end;T;&I"def add_section(title);T;'To; ; F; ;;;;I"Doing::Items#guess_section;F;[[I" frag;T0[I" guessed:;TI" false;T[I" suggest:;TI" false;T;[[@iM;T;;;;;[;{;IC;"7Attempt to match a string with an existing section;T;[o;) ;*I" param;F;+I"The user-provided string;T;I" frag;T;,[I" String;T;!@o;) ;*I" param;F;+I"already guessed and failed;T;I" guessed;T;,[I" Boolean;T;!@;[;I" Attempt to match a string with an existing section @param frag [String] The user-provided string @param guessed [Boolean] already guessed and failed ;T; 0;!@;4i;"T;5o;6;7F;8iG;9iL;$@;:T;%I"7def guess_section(frag, guessed: false, suggest: false) return 'All' if frag =~ /^all$/i frag ||= wwid.config['current_section'] @sections.each { |sect| return sect[:title].cap_first if frag.downcase == sect[:title].downcase } section = false re = frag.split('').join('.*?') sections.each do |sect| next unless sect =~ /#{re}/i Doing.logger.debug('Section match:', %(Assuming "#{sect}" from "#{frag}")) section = sect break end return section if suggest unless section || guessed alt = WWID.guess_view(frag, guessed: true, suggest: true) if alt meant_view = WWID.yn("Did you mean `doing view #{alt}`?", default_response: 'n') raise Errors::InvalidSection, "Run again with `doing view #{alt}`" if meant_view end res = WWID.yn("Section #{frag} not found, create it", default_response: 'n') if res add_section(frag.cap_first) WWID.write(@doing_file) return frag.cap_first end raise Errors::InvalidSection, "Unknown section: #{frag}" end section ? section.cap_first : guessed end;T;&I" Log a message. @param level_of_message [Symbol] the Symbol level of message, one of :debug, :info, :warn, :error @param topic [String] the String topic or full message @param message [String] the String message (optional) @param block a block containing the message (optional) @return [Boolean] false if the message was not written ;T; 0;!@;4i;"F;5o;6;7F;8i;9i;$@';:T;%I"def write(level_of_message, topic, message = nil, &block) @results << { level: level_of_message, message: message(topic, message, &block) } true end;T;&I">def write(level_of_message, topic, message = nil, &block);T;'To; ; F; ;;;;I"Doing::LogAdapter#log_now;F;[ [I" level;T0[I" topic;T0[I" message;TI"nil;T[I" &block;T0;[[@/i;T;: log_now;;;[;{;IC;"CLog to console immediately instead of writing messages on exit;T;[ o;) ;*I" param;F;+I"The level;T;I" level;T;,[I" Symbol;T;!@o;) ;*I" param;F;+I"The topic or full message;T;I" topic;T;,[I" String;T;!@o;) ;*I" param;F;+I"The message (optional);T;I" message;T;,[I" String;T;!@o;) ;*I" param;F;+I".a block containing the message (optional);T;I" block;T;,0;!@;[;I" Log to console immediately instead of writing messages on exit @param level [Symbol] The level @param topic [String] The topic or full message @param message [String] The message (optional) @param block a block containing the message (optional) ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@';:T;%I"def log_now(level, topic, message = nil, &block) return false unless write_message?(level) if @logdev == $stdout @logdev.puts message(topic, message, &block) else @logdev.puts color_message(level, topic, message, &block) end end;T;&I"5def log_now(level, topic, message = nil, &block);T;'To; ; F; ;;;;I"%Doing::LogAdapter#output_results;F;[;[[@/i;T;:output_results;;;[;{;IC;"(Output registers based on log level;T;[o;) ;*I" return;F;+I" nothing;T;0;,0;!@;[;I"? Output registers based on log level @return nothing ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@';:T;%I"<def output_results total_counters results = @results.select { |msg| write_message?(msg[:level]) }.uniq if @logdev == $stdout $stdout.print results.map {|res| res[:message].uncolor }.join("\n") else results.each do |msg| @logdev.puts color_message(msg[:level], msg[:message]) end end end;T;&I"def output_results;T;'To; ; F; ;;;F;I"%Doing::LogAdapter#format_counter;F;[[I"key;T0[I" data;T0;[[@/i;F;:format_counter;;;[;{;IC;" ;T;[;[;@; 0;!@(;4i;$@';:T;%I"idef format_counter(key, data) case key when :added_tags ['Tagged:', data[:message] || 'added %tags to %count %items'] when :removed_tags ['Untagged:', data[:message] || 'removed %tags from %count %items'] when :added ['Added:', data[:message] || 'added %count new %items'] when :updated ['Updated:', data[:message] || 'updated %count %items'] when :deleted ['Deleted:', data[:message] || 'deleted %count %items'] when :moved ['Moved:', data[:message] || 'moved %count %items'] when :completed ['Completed:', data[:message] || 'completed %count %items'] when :archived ['Archived:', data[:message] || 'archived %count %items'] when :completed_archived ['Archived:', data[:message] || 'completed and archived %count %items'] when :skipped ['Skipped:', data[:message] || '%count %items were unchanged'] end end;T;&I""def format_counter(key, data);T;'To; ; F; ;;;F;I"%Doing::LogAdapter#total_counters;F;[;[[@/i ;F;:total_counters;;;[;{;IC;" ;T;[;[;@; 0;!@8;4i;$@';:T;%I"def total_counters @counters.each do |key, data| next if data[:count].zero? count = data[:count] tags = data[:tag] ? data[:tag].uniq.map { |t| "@#{t}".cyan }.join(', ') : 'tags' topic, m = format_counter(key, data) message = m.dup message.sub!(/%count/, count.to_s) message.sub!(/%items/, count == 1 ? 'item' : 'items') message.sub!(/%tags/, tags) write(data[:level], topic, message) end end;T;&I"def total_counters;T;'To; ; F; ;;;F;I"%Doing::LogAdapter#write_message?;F;[[I"level_of_message;T0;[[@/i%;T;:write_message?;;;[;{;IC;"LCheck if the message should be written given the log level.;T;[o;) ;*I" param;F;+I"Ethe Symbol level of message, one of :debug, :info, :warn, :error;T;I"level_of_message;T;,0;!@Do;) ;*I" return;F;+I"+whether the message should be written.;T;0;,[@;!@D;[;I" Check if the message should be written given the log level. @param level_of_message the Symbol level of message, one of :debug, :info, :warn, :error @return whether the message should be written. ;T; 0;!@D;4i;"F;5o;6;7F;8i;9i$;$@';:T;%I"ndef write_message?(level_of_message) LOG_LEVELS.fetch(@level) <= LOG_LEVELS.fetch(level_of_message) end;T;&I")def write_message?(level_of_message);T;'To; ; F; ;;;F;I"Doing::LogAdapter#message;F;[[I" topic;T0[I" message;TI"nil;T;[[@/i2;T;: message;;;[;{;IC;"#Internal: Build a topic method;T;[ o;) ;*I" param;F;+I"Mthe topic of the message, e.g. "Configuration file", "Deprecation", etc.;T;I" topic;T;,0;!@\o;) ;*I" param;F;+I"the message detail;T;I" message;T;,0;!@\o;) ;*I" return;F;+I"the formatted message;T;0;,0;!@\o;) ;*I" raise;F;+@;0;,[I"ArgumentError;T;!@\;[;I"Internal: Build a topic method @param topic the topic of the message, e.g. "Configuration file", "Deprecation", etc. @param message the message detail @return the formatted message ;T; 0;!@\;4i;"F;5o;6;7F;8i);9i1;$@';:T;%I"def message(topic, message = nil) raise ArgumentError, 'block or message, not both' if block_given? && message message = yield if block_given? message = message.to_s.gsub(/\s+/, ' ') return topic.ljust(TOPIC_WIDTH) if topic && message.strip.empty? topic = formatted_topic(topic, colon: block_given?) message.truncmiddle!(@max_length - TOPIC_WIDTH - 5) out = topic + message out.truncate!(@max_length) if @max_length.positive? messages << out out end;T;&I"&def message(topic, message = nil);T;'To; ; F; ;;;F;I"$Doing::LogAdapter#color_message;F;[ [I" level;T0[I" topic;T0[I" message;TI"nil;T[I" &block;T0;[[@/iB;F;:color_message;;;[;{;IC;" ;T;[;[;@; 0;!@~;4i;$@';:T;%I"def color_message(level, topic, message = nil, &block) colors = Doing::Color message = message(topic, message, &block) prefix = ' ' topic_fg = colors.boldcyan message_fg = colors.boldwhite case level when :debug prefix = '> '.softpurple topic_fg = colors.softpurple message_fg = colors.white when :warn prefix = '> '.boldyellow topic_fg = colors.boldyellow message_fg = colors.yellow when :error prefix = '!!'.boldred topic_fg = colors.flamingo message_fg = colors.red end message.sub!(/^(\s*\S.*?): (.*?)$/) do m = Regexp.last_match msg = m[2] =~ /(\e\[[\d;]+m)/ ? msg : "#{message_fg}#{m[2]}" "#{topic_fg}#{m[1]}#{colors.reset}: #{message_fg}#{m[2]}" end "#{prefix} #{message.highlight_tags}#{colors.reset}" end;T;&I";def color_message(level, topic, message = nil, &block);T;'T;M@';NIC;[;M@';OIC;[;M@';PIC;Q{;RIC;Q{;ST;IC;Q{ : logdevIC;Q{;T0;U@);ST:max_lengthIC;Q{;T0;U@<;ST;IC;Q{;T@N;U0;ST;IC;Q{;T@[;U0;ST;IC;Q{;T@h;U0;ST;ST;ST;V{;W[;[[@/i ;T;:LogAdapter;;;;;[;{;IC;"Log adapter;T;[;[;I" Log adapter ;T; 0;!@';4i;"T;5o;6;7F;8i ;9i ;$@;I"Doing::LogAdapter;F;Yo;Z ;[0;\0;]0;;^;$@;_0;`;R;'To; ;IC;[o; ; F; ;;;;I""Doing::Configuration#settings;F;[;[[I"lib/doing/configuration.rb;Ti ;F;: settings;;;[;{;IC;"-Returns the value of attribute settings. ;T;[;[;I"-Returns the value of attribute settings.;T; 0;!@;"F;#0;$@;%I"!def settings @settings end;T;&I"def settings;T;'To; ; F; ;;;;I"'Doing::Configuration#ignore_local=;F;[[@0;[[@i;F;:ignore_local=;;;[;{;IC;"$Sets the attribute ignore_local ;T;[o;) ;*I" param;F;+I"4the value to set the attribute ignore_local to.;T;I" value;T;,0;!@;[;I"aSets the attribute ignore_local @param value the value to set the attribute ignore_local to.;T; 0;!@;"F;#0;$@;%I"9def ignore_local=(value) @ignore_local = value end;T;&I"def ignore_local=(value);T;'To;;[[@i;F;:MissingConfigFile;;;;;[;{;IC;" ;T;[;[;@; 0;!@;$@;I",Doing::Configuration::MissingConfigFile;F;%I"0MissingConfigFile = Class.new(RuntimeError);T;I"Class.new(RuntimeError);T;'To;;[[@i;F;: DEFAULTS;;;;;[;{;IC;" ;T;[;[;@; 0;!@;$@;I"#Doing::Configuration::DEFAULTS;F;%I"DEFAULTS = { 'autotag' => { 'whitelist' => [], 'synonyms' => {} }, 'editors' => { 'default' => ENV['DOING_EDITOR'] || ENV['GIT_EDITOR'] || ENV['EDITOR'], 'doing_file' => nil, 'config' => nil }, 'plugins' => { 'plugin_path' => File.join(Util.user_home, '.config', 'doing', 'plugins'), 'command_path' => File.join(Util.user_home, '.config', 'doing', 'commands') }, 'doing_file' => '~/what_was_i_doing.md', 'current_section' => 'Currently', 'paginate' => false, 'never_time' => [], 'never_finish' => [], 'timer_format' => 'text', 'templates' => { 'default' => { 'date_format' => '%Y-%m-%d %H:%M', 'template' => '%date | %title%note', 'wrap_width' => 0, 'order' => 'asc' }, 'today' => { 'date_format' => '%_I:%M%P', 'template' => '%date: %title %interval%note', 'wrap_width' => 0, 'order' => 'asc' }, 'last' => { 'date_format' => '%-I:%M%P on %a', 'template' => '%title (at %date)%odnote', 'wrap_width' => 88 }, 'recent' => { 'date_format' => '%_I:%M%P', 'template' => '%shortdate: %title (%section)', 'wrap_width' => 88, 'count' => 10, 'order' => 'asc' } }, 'export_templates' => {}, 'views' => { 'done' => { 'date_format' => '%_I:%M%P', 'template' => '%date | %title%note', 'wrap_width' => 0, 'section' => 'All', 'count' => 0, 'order' => 'desc', 'tags' => 'done complete cancelled', 'tags_bool' => 'OR' }, 'color' => { 'date_format' => '%F %_I:%M%P', 'template' => '%boldblack%date %boldgreen| %boldwhite%title%default%note', 'wrap_width' => 0, 'section' => 'Currently', 'count' => 10, 'order' => 'asc' } }, 'marker_tag' => 'flagged', 'marker_color' => 'red', 'default_tags' => [], 'tag_sort' => 'name', 'include_notes' => true };T;I"{ 'autotag' => { 'whitelist' => [], 'synonyms' => {} }, 'editors' => { 'default' => ENV['DOING_EDITOR'] || ENV['GIT_EDITOR'] || ENV['EDITOR'], 'doing_file' => nil, 'config' => nil }, 'plugins' => { 'plugin_path' => File.join(Util.user_home, '.config', 'doing', 'plugins'), 'command_path' => File.join(Util.user_home, '.config', 'doing', 'commands') }, 'doing_file' => '~/what_was_i_doing.md', 'current_section' => 'Currently', 'paginate' => false, 'never_time' => [], 'never_finish' => [], 'timer_format' => 'text', 'templates' => { 'default' => { 'date_format' => '%Y-%m-%d %H:%M', 'template' => '%date | %title%note', 'wrap_width' => 0, 'order' => 'asc' }, 'today' => { 'date_format' => '%_I:%M%P', 'template' => '%date: %title %interval%note', 'wrap_width' => 0, 'order' => 'asc' }, 'last' => { 'date_format' => '%-I:%M%P on %a', 'template' => '%title (at %date)%odnote', 'wrap_width' => 88 }, 'recent' => { 'date_format' => '%_I:%M%P', 'template' => '%shortdate: %title (%section)', 'wrap_width' => 88, 'count' => 10, 'order' => 'asc' } }, 'export_templates' => {}, 'views' => { 'done' => { 'date_format' => '%_I:%M%P', 'template' => '%date | %title%note', 'wrap_width' => 0, 'section' => 'All', 'count' => 0, 'order' => 'desc', 'tags' => 'done complete cancelled', 'tags_bool' => 'OR' }, 'color' => { 'date_format' => '%F %_I:%M%P', 'template' => '%boldblack%date %boldgreen| %boldwhite%title%default%note', 'wrap_width' => 0, 'section' => 'Currently', 'count' => 10, 'order' => 'asc' } }, 'marker_tag' => 'flagged', 'marker_color' => 'red', 'default_tags' => [], 'tag_sort' => 'name', 'include_notes' => true };T;'To; ; F; ;;;;I"$Doing::Configuration#initialize;F;[[I" file;TI"nil;T[I" options:;TI"{};T;[[@ia;F;;3;;;[;{;IC;" ;T;[o;) ;*I" return;F;+I"$a new instance of Configuration;T;0;,[I"Configuration;F;!@;[;@; 0;!@;4i;$@;:T;%I"def initialize(file = nil, options: {}) if file cf = File.expand_path(file) # raise MissingConfigFile, "Config not found (#{cf})" unless File.exist?(cf) @config_file = cf end @settings = configure(options) end;T;&I",def initialize(file = nil, options: {});T;'To; ; F; ;;;;I",Doing::Configuration#additional_configs;F;[;[[@il;F;;{;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"Kdef additional_configs @additional_configs ||= find_local_config end;T;&I"def additional_configs;T;'To; ; F; ;;;;I"'Doing::Configuration#value_for_key;F;[[I" keypath;TI"'';T;[[@ip;F;:value_for_key;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"def value_for_key(keypath = '') cfg = @settings unless keypath =~ /^[.*]?$/ paths = keypath.split(/[:.]/) while paths.length.positive? && !cfg.nil? path = paths.shift new_cfg = nil cfg.each do |key, val| next unless key =~ /#{path.to_rx(2)}/ new_cfg = val break end if new_cfg.nil? Doing.logger.error("Key match not found: #{path}") break end cfg = new_cfg end end cfg end;T;&I"$def value_for_key(keypath = '');T;'To; ; F; ;;;;I"Doing::Configuration#from;F;[[I"user_config;T0;[[@i;T;;;;;[;{;IC;"It takes the input, fills in the defaults where values do not exist. user_config - a Hash or Configuration of overrides. Returns a Configuration filled with defaults.;T;[;[;I"It takes the input, fills in the defaults where values do not exist. user_config - a Hash or Configuration of overrides. Returns a Configuration filled with defaults.;T; 0;!@;4i;"F;5o;6;7F;8i;9i;$@;:T;%I"ldef from(user_config) Util.deep_merge_hashes(DEFAULTS, Configuration[user_config].stringify_keys) end;T;&I"def from(user_config);T;'To; ; F; ;;;;I"%Doing::Configuration#config_file;F;[;[[@i;F;;|;;;[;{;IC;" ;T;[;[;@; 0;!@$;4i;$@;:T;%I"Qdef config_file @config_file ||= File.join(Util.user_home, '.doingrc') end;T;&I"def config_file;T;'To; ; F; ;;;;I"&Doing::Configuration#config_file=;F;[[I" file;T0;[[@i;F;;};;;[;{;IC;" ;T;[;[;@; 0;!@0;4i;$@;:T;%I"5def config_file=(file) @config_file = file end;T;&I"def config_file=(file);T;'To; ; F; ;;;;I"#Doing::Configuration#configure;F;[[I"opt;TI"{};T;[[@i;T;:configure;;;[;{;IC;"4Read user configuration and merge with defaults;T;[o;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@>;[;I"c Read user configuration and merge with defaults @param opt [Hash] Additional Options ;T; 0;!@>;4i;"T;5o;6;7F;8i;9i;$@;:T;%I"zdef configure(opt = {}) @ignore_local = opt[:ignore_local] if opt[:ignore_local] config = read_config.dup plugin_config = Util.deep_merge_hashes(DEFAULTS['plugins'], config['plugins'] || {}) load_plugins(plugin_config['plugin_path']) Plugins.plugins.each do |_type, plugins| plugins.each do |title, plugin| plugin_config[title] = plugin[:config] if plugin[:config] && !plugin[:config].empty? config['export_templates'][title] ||= nil if plugin[:templates] && !plugin[:templates].empty? end end config = Util.deep_merge_hashes({ 'plugins' => plugin_config }, config) config = find_deprecations(config) if !File.exist?(config_file) || opt[:rewrite] Util.write_to_file(config_file, YAML.dump(config), backup: true) Doing.logger.warn('Config:', "Config file written to #{config_file}") end Hooks.trigger :post_config, self # config = local_config.deep_merge(config) unless @ignore_local config = Util.deep_merge_hashes(config, local_config) unless @ignore_local Hooks.trigger :post_local_config, self config end;T;&I"def configure(opt = {});T;'To; ; F; ;;;F;I"+Doing::Configuration#find_deprecations;F;[[I" config;T0;[[@i;F;:find_deprecations;;;[;{;IC;" ;T;[;[;@; 0;!@U;4i;$@;:T;%I"#def find_deprecations(config) deprecated = false if config.key?('editor') deprecated = true config['editors']['default'] ||= config['editor'] config.delete('editor') Doing.logger.debug('Deprecated:', "config key 'editor' is now 'editors->default', please update your config.") end if config.key?('config_editor_app') && !config['editors']['config'] deprecated = true config['editors']['config'] = config['config_editor_app'] config.delete('config_editor_app') Doing.logger.debug('Deprecated:', "config key 'config_editor_app' is now 'editors->config', please update your config.") end if config.key?('editor_app') && !config['editors']['doing_file'] deprecated = true config['editors']['doing_file'] = config['editor_app'] config.delete('editor_app') Doing.logger.debug('Deprecated:', "config key 'editor_app' is now 'editors->doing_file', please update your config.") end Doing.logger.warn('Deprecated:', 'outdated keys found, please run `doing config --update`.') if deprecated config end;T;&I""def find_deprecations(config);T;'To; ; F; ;;;F;I"&Doing::Configuration#local_config;F;[;[[@i;T;:local_config;;;[;{;IC;"Read local configurations;T;[o;) ;*I" return;F;+I"Hash of config options;T;0;,0;!@c;[;I"D Read local configurations @return Hash of config options ;T; 0;!@c;4i;"T;5o;6;7F;8i;9i;$@;:T;%I";def local_config return {} if @ignore_local local_configs = read_local_configs || {} if additional_configs&.count file_list = additional_configs.map { |p| p.sub(/^#{Util.user_home}/, '~') }.join(', ') Doing.logger.debug('Config:', "Local config files found: #{file_list}") end local_configs end;T;&I"def local_config;T;'To; ; F; ;;;F;I",Doing::Configuration#read_local_configs;F;[;[[@i;F;:read_local_configs;;;[;{;IC;" ;T;[;[;@; 0;!@t;4i;$@;:T;%I"def read_local_configs local_configs = {} begin additional_configs.each do |cfg| local_configs.deep_merge(Util.safe_load_file(cfg)) end rescue StandardError Doing.logger.error('Config:', 'Error reading local configuration(s)') end local_configs end;T;&I"def read_local_configs;T;'To; ; F; ;;;F;I"%Doing::Configuration#read_config;F;[;[[@i;T;:read_config;;;[;{;IC;"Reads a configuration.;T;[;[;I" Reads a configuration. ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@;:T;%I"def read_config unless File.exist?(config_file) Doing.logger.info('Config:', 'Config file doesn\'t exist, using default configuration' ) return {}.deep_merge(DEFAULTS) end begin user_config = Util.safe_load_file(config_file) if user_config.key?('html_template') user_config['export_templates'] ||= {} user_config['export_templates'].deep_merge(user_config.delete('html_template')) end user_config['include_notes'] = user_config.delete(':include_notes') if user_config.key?(':include_notes') user_config.deep_merge(DEFAULTS) rescue StandardError => e Doing.logger.error('Config:', 'Error reading default configuration') Doing.logger.error('Error:', e.message) user_config = DEFAULTS end user_config end;T;&I"def read_config;T;'To; ; F; ;;;F;I"+Doing::Configuration#find_local_config;F;[;[[@i;T;:find_local_config;;;[;{;IC;"0Finds a project-specific configuration file;T;[o;) ;*I" return;F;+I"A file path;T;0;,[I" String;T;!@;[;I"T Finds a project-specific configuration file @return [String] A file path ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@;:T;%I":def find_local_config dir = Dir.pwd local_config_files = [] while dir != '/' && (dir =~ %r{[A-Z]:/}).nil? local_config_files.push(File.join(dir, '.doingrc')) if File.exist? File.join(dir, '.doingrc') dir = File.dirname(dir) end local_config_files.delete(config_file) local_config_files end;T;&I"def find_local_config;T;'To; ; F; ;;;F;I"&Doing::Configuration#load_plugins;F;[[I" add_dir;TI"nil;T;[[@i.;F;:load_plugins;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"def load_plugins(add_dir = nil) begin FileUtils.mkdir_p(add_dir) if add_dir && !File.exist?(add_dir) rescue nil end Plugins.load_plugins(add_dir) end;T;&I"$def load_plugins(add_dir = nil);T;'T;M@;NIC;[;M@;OIC;[;M@;PIC;Q{;RIC;Q{;ST;IC;Q{;IC;Q{;T@;U0;ST:ignore_localIC;Q{;T0;U@;ST;ST;ST;V{;W[;[[@i ;T;:Configuration;;;;;[;{;IC;"Configuration object;T;[;[;I" Configuration object ;T; 0;!@;4i;"T;5o;6;7F;8i ;9i ;$@;I"Doing::Configuration;F;Yo;Z ;[0;\0;]0;;^;$@;_0;`;R;'To; ;IC;[o; ; F; ;R;;;I"Doing::Plugins.user_home;F;[;[[I" lib/doing/plugin_manager.rb;Ti ;F;;k;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"6def user_home @user_home ||= Util.user_home end;T;&I"def user_home;T;'To; ; F; ;R;;;I"Doing::Plugins.plugins;F;[;[[@i;F;: plugins;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"Hdef plugins @plugins ||= { import: {}, export: {} } end;T;&I"def plugins;T;'To; ; F; ;R;;;I" Doing::Plugins.load_plugins;F;[[I" add_dir;TI"nil;T;[[@i;T;;;;;[;{;IC;"%Load plugins from plugins folder;T;[;[;I"' Load plugins from plugins folder ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@;:T;%I"def load_plugins(add_dir = nil) plugins_path(add_dir).each do |plugin_search_path| Dir.glob(File.join(plugin_search_path, '**', '*.rb')).sort.each do |plugin| require plugin end end plugins end;T;&I"$def load_plugins(add_dir = nil);T;'To; ; F; ;R;;;I" Doing::Plugins.plugins_path;F;[[I" add_dir;TI"nil;T;[[@i';T;:plugins_path;;;[;{;IC;"RPublic: Setup the plugin search path Returns an Array of plugin search paths;T;[;[;I"RPublic: Setup the plugin search path Returns an Array of plugin search paths;T; 0;!@;4i;"F;5o;6;7F;8i$;9i&;$@;:T;%I"def plugins_path(add_dir = nil) paths = Array(File.join(File.dirname(__FILE__), 'plugins')) paths << File.join(add_dir) if add_dir paths.map { |d| File.expand_path(d) } end;T;&I"$def plugins_path(add_dir = nil);T;'To; ; F; ;R;;;I"Doing::Plugins.register;F;[[I" title;T0[I" type;T0[I" klass;T0;[[@i9;T;;;;;[;{;IC;"Register a plugin param: +[String|Array]+ title The name of the plugin (can be an array of names) param: +type+ The plugin type (:import, :export) param: +klass+ The class responding to :render or :import returns: Success boolean;T;[;[;I" Register a plugin param: +[String|Array]+ title The name of the plugin (can be an array of names) param: +type+ The plugin type (:import, :export) param: +klass+ The class responding to :render or :import returns: Success boolean ;T; 0;!@;4i;"T;5o;6;7F;8i-;9i8;$@;:T;%I"def register(title, type, klass) type = validate_plugin(title, type, klass) return unless type if title.is_a?(Array) title.each { |t| register(t, type, klass) } return end settings = if klass.respond_to? :settings klass.settings else { trigger: title.normalize_trigger, config: {} } end plugins[type] ||= {} plugins[type][title] = { trigger: settings[:trigger].normalize_trigger || title.normalize_trigger, class: klass, templates: settings[:templates] || nil, config: settings[:config] || {} } return unless ENV['DOING_PLUGIN_DEBUG'] Doing.logger.debug('Plugin Manager:', "Registered #{type} plugin \"#{title}\"") end;T;&I"%def register(title, type, klass);T;'To; ; F; ;R;;;I"#Doing::Plugins.validate_plugin;F;[[I" title;T0[I" type;T0[I" klass;T0;[[@iU;F;:validate_plugin;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"def validate_plugin(title, type, klass) type = valid_type(type) if type == :import && !klass.respond_to?(:import) raise Errors::PluginUncallable.new('Import plugins must respond to :import', type: type, plugin: title) end if type == :export && !klass.respond_to?(:render) raise Errors::PluginUncallable.new('Export plugins must respond to :render', type: type, plugin: title) end type end;T;&I",def validate_plugin(title, type, klass);T;'To; ; F; ;R;;;I"Doing::Plugins.valid_type;F;[[I" type;T0[I" default:;TI"nil;T;[[@ib;F;:valid_type;;;[;{;IC;" ;T;[;[;@; 0;!@';4i;$@;:T;%I"<def valid_type(type, default: nil) type ||= default t = type.to_s type = case t when /^i(m(p(o(r(t)?)?)?)?)?$/ :import when /^e(x(p(o(r(t)?)?)?)?)?$/ :export else raise Errors::InvalidPluginType, 'Invalid plugin type' end type.to_sym end;T;&I"'def valid_type(type, default: nil);T;'To; ; F; ;R;;;I" Doing::Plugins.list_plugins;F;[[I" options;TI"{};T;[[@iw;T;:list_plugins;;;[;{;IC;"%List available plugins to stdout;T;[o;) ;*I" param;F;+I";T;I" options;T;,[I" type;TI"separator;T;!@8;[;I"Q List available plugins to stdout @param options { type, separator } ;T; 0;!@8;4i;"T;5o;6;7F;8ir;9iv;$@;:T;%I"def list_plugins(options = {}) separator = options[:column] ? "\n" : "\t" type = options[:type].nil? || options[:type] =~ /all/i ? 'all' : valid_type(options[:type]) case type when :import puts plugin_names(type: :import, separator: separator) when :export puts plugin_names(type: :export, separator: separator) else print 'Import plugins: ' puts plugin_names(type: :import, separator: ', ') print 'Export plugins: ' puts plugin_names(type: :export, separator: ', ') end end;T;&I"#def list_plugins(options = {});T;'To; ; F; ;R;;;I"%Doing::Plugins.available_plugins;F;[[I" type:;TI" :export;T;[[@i;T;:available_plugins;;;[;{;IC;"+Return array of available plugin names;T;[o;) ;*I" param;F;+I"#Plugin type (:import, :export);T;I" type;T;,0;!@Po;) ;*I" return;F;+I"plugin names;T;0;,[I"Array;T;!@P;[;I" Return array of available plugin names @param type Plugin type (:import, :export) @return [Array] plugin names ;T; 0;!@P;4i;"T;5o;6;7F;8i;9i;$@;:T;%I"adef available_plugins(type: :export) type = valid_type(type) plugins[type].keys.sort end;T;&I")def available_plugins(type: :export);T;'To; ; F; ;R;;;I" Doing::Plugins.plugin_names;F;[[I" type:;TI" :export;T[I"separator:;TI"'|';T;[[@i;T;:plugin_names;;;[;{;IC;"*Return string version of plugin names;T;[o;) ;*I" param;F;+I"#Plugin type (:import, :export);T;I" type;T;,0;!@jo;) ;*I" param;F;+I"%The separator to join names with;T;I"separator;T;,0;!@jo;) ;*I" return;F;+I"Plugin names;T;0;,[I" String;T;!@j;[;I" Return string version of plugin names @param type Plugin type (:import, :export) @param separator The separator to join names with @return [String] Plugin names ;T; 0;!@j;4i;"T;5o;6;7F;8i;9i;$@;:T;%I"}def plugin_names(type: :export, separator: '|') type = valid_type(type) available_plugins(type: type).join(separator) end;T;&I"4def plugin_names(type: :export, separator: '|');T;'To; ; F; ;R;;;I" Doing::Plugins.plugin_regex;F;[[I" type:;TI" :export;T;[[@i;T;:plugin_regex;;;[;{;IC;"LReturn a regular expression of all plugin triggers for type;T;[o;) ;*I" param;F;+I" The type :import or :export;T;I" type;T;,0;!@;[;I"} Return a regular expression of all plugin triggers for type @param type The type :import or :export ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@;:T;%I"def plugin_regex(type: :export) type = valid_type(type) pattern = [] plugins[type].each do |_, options| pattern << options[:trigger].normalize_trigger end Regexp.new("^(?:#{pattern.join('|')})$", true) end;T;&I"$def plugin_regex(type: :export);T;'To; ; F; ;R;;;I"$Doing::Plugins.plugin_templates;F;[[I" type:;TI" :export;T;[[@i;F;:plugin_templates;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"def plugin_templates(type: :export) type = valid_type(type) templates = [] plugs = plugins[type].clone plugs.delete_if { |_t, o| o[:templates].nil? }.each do |_, options| options[:templates].each do |t| templates << t[:name] end end templates end;T;&I"(def plugin_templates(type: :export);T;'To; ; F; ;R;;;I""Doing::Plugins.template_regex;F;[[I" type:;TI" :export;T;[[@i;F;:template_regex;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"Ddef template_regex(type: :export) type = valid_type(type) pattern = [] plugs = plugins[type].clone plugs.delete_if { |_t, o| o[:templates].nil? }.each do |_, options| options[:templates].each do |t| pattern << t[:trigger].normalize_trigger end end Regexp.new("^(?:#{pattern.join('|')})$", true) end;T;&I"&def template_regex(type: :export);T;'To; ; F; ;R;;;I"(Doing::Plugins.template_for_trigger;F;[[I" trigger;T0[I" type:;TI" :export;T;[[@i;F;:template_for_trigger;;;[;{;IC;" ;T;[o;) ;*I" raise;F;+@;0;,[I"Errors::InvalidArgument;T;!@;[;@; 0;!@;4i;$@;:T;%I"def template_for_trigger(trigger, type: :export) type = valid_type(type) plugs = plugins[type].clone plugs.delete_if { |_t, o| o[:templates].nil? }.each do |_, options| options[:templates].each do |t| return options[:class].template(trigger) if trigger =~ /^(?:#{t[:trigger].normalize_trigger})$/ end end raise Errors::InvalidArgument, "No template type matched \"#{trigger}\"" end;T;&I"5def template_for_trigger(trigger, type: :export);T;'T;M@;NIC;[;M@;OIC;[;M@;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@i ;T;: Plugins;;;;;[;{;IC;"Plugin handling;T;[;[;I"Plugin handling;T; 0;!@;4i;"F;5o;6;7F;8i ;9i ;$@;I"Doing::Plugins;F;'T;M@;NIC;[;M@;OIC;[;M@;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@" i[I"lib/doing/hash.rb;Ti[@i[@si[I"lib/doing/time.rb;Ti[@(i[@Ki[@i[@\ i[@ i[@ i[I"lib/doing/string.rb;Ti[I"lib/doing/symbol.rb;Ti[@i[@i[@/i[@i[@i;T;: Doing;;;;;[;{;IC;";Cribbed from ;T;[;[;I";Cribbed from ;T; 0;!@;4i;"F;5o;6;7F;8i;9i;$@;I" Doing;Fo; ;IC;[ o; ; F; ;;;;I"Hash#deep_freeze;F;[;[[@i;T;:deep_freeze;;;[;{;IC;" Freeze all values in a hash;T;[o;) ;*I" return;F;+I";T;0;,[I"$description_of_the_return_value;T;!@ ;[;I"S Freeze all values in a hash @return { description_of_the_return_value } ;T; 0;!@ ;4i;"T;5o;6;7F;8i ;9i;$@ ;:T;%I"Ydef deep_freeze map { |k, v| v.is_a?(Hash) ? v.deep_freeze : v.freeze }.freeze end;T;&I"def deep_freeze;T;'To; ; F; ;;;;I"Hash#deep_freeze!;F;[;[[@i;F;:deep_freeze!;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@ ;:T;%I"/def deep_freeze! replace deep_freeze end;T;&I"def deep_freeze!;T;'To; ; F; ;;;;I"Hash#stringify_keys;F;[;[[@i;T;:stringify_keys;;;[;{;IC;"XTurn all keys into string Return a copy of the hash where all its keys are strings;T;[;[;I"XTurn all keys into string Return a copy of the hash where all its keys are strings;T; 0;!@+;4i;"F;5o;6;7F;8i;9i;$@ ;:T;%I"wdef stringify_keys each_with_object({}) { |(k, v), hsh| hsh[k.to_s] = v.is_a?(Hash) ? v.stringify_keys : v } end;T;&I"def stringify_keys;T;'To; ; F; ;;;;I"Hash#symbolize_keys;F;[;[[@i ;T;:symbolize_keys;;;[;{;IC;"Turn all keys into symbols;T;[;[;I"Turn all keys into symbols;T; 0;!@9;4i;"F;5o;6;7F;8i;9i;$@ ;:T;%I"ydef symbolize_keys each_with_object({}) { |(k, v), hsh| hsh[k.to_sym] = v.is_a?(Hash) ? v.symbolize_keys : v } end;T;&I"def symbolize_keys;T;'T;M@ ;NIC;[;M@ ;OIC;[;M@ ;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@i ;T;: Hash;;;;;[;{;IC;"Hash helpers;T;[;[;I"Hash helpers;T; 0;!@ ;4i;"F;5o;6;7F;8i ;9i ;$@;I" Hash;F;Yo;Z ;[0;\0;]0;;^;$@;_0;`;R;'To; ;IC;[o; ; F; ;;;;I"Time#relative_date;F;[;[[@i ;F;:relative_date;;;[;{;IC;" ;T;[;[;@; 0;!@[;4i;$@Y;:T;%I"def relative_date if self > Date.today.to_time strftime('%_I:%M%P') elsif self > (Date.today - 6).to_time strftime('%a %_I:%M%P') elsif self.year == Date.today.year strftime('%m/%d %_I:%M%P') else strftime('%m/%d/%Y %_I:%M%P') end end;T;&I"def relative_date;T;'T;M@Y;NIC;[;M@Y;OIC;[;M@Y;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@i ;T;: Time;;;;;[;{;IC;"Date helpers;T;[;[;I" Date helpers ;T; 0;!@Y;4i;"T;5o;6;7F;8i;9i ;$@;I" Time;F;Yo;Z ;[0;\0;]0;;^;$@;_0;`;R;'T@ o; ;IC;[#o; ; F; ;;;;I"String#to_rx;F;[[I" distance;T0;[[@i;T;: to_rx;;;[;{;IC;""Convert string to fuzzy regex;T;[o;) ;*I" param;F;+I"(Allowed distance between characters;T;I" distance;T;,[I" Integer;T;!@{o;) ;*I" return;F;+I"Regex pattern;T;0;,[I" String;T;!@{;[;I" Convert string to fuzzy regex @param distance [Integer] Allowed distance between characters @return [String] Regex pattern ;T; 0;!@{;4i;"T;5o;6;7F;8i;9i;$@y;:T;%I"Adef to_rx(distance) gsub(/(.)/, "\\1.{0,#{distance}}") end;T;&I"def to_rx(distance);T;'To; ; F; ;;;;I"String#truthy?;F;[;[[@i ;T;: truthy?;;;[;{;IC;"oTest string for truthiness (0, "f", "false", "n", "no" all return false, case insensitive, otherwise true);T;[o;) ;*I" return;F;+I"String is truthy;T;0;,[I" Boolean;T;!@;[;I" Test string for truthiness (0, "f", "false", "n", "no" all return false, case insensitive, otherwise true) @return [Boolean] String is truthy ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@y;:T;%I"Ydef truthy? if self =~ /^(0|f(alse)?|n(o)?)$/i false else true end end;T;&I"def truthy?;T;'To; ; F; ;;;;I"String#highlight_tags!;F;[[I" color;TI" 'yellow';T;[[@i);T;:highlight_tags!;;;[;{;IC;";T;[;[o:YARD::Tags::RefTagList;Mo;Z ;[@y;\I"#highlight_tags;T;]T;:highlight_tags;$@y;_o; ; F; ;;;;I"String#highlight_tags;F;[[I" color;TI" 'yellow';T;[[@i4;T;;7;;;[;{;IC;"%Colorize @tags with ANSI escapes;T;[o;) ;*I" param;F;+I"color (see #Color);T;I" color;T;,[I" String;T;!@o;) ;*I" return;F;+I""string with @tags highlighted;T;0;,[I" String;T;!@;[;I" Colorize @tags with ANSI escapes @param color [String] color (see #Color) @return [String] string with @tags highlighted ;T; 0;!@;4i;"T;5o;6;7F;8i-;9i3;$@y;:T;%I"Fdef highlight_tags(color = 'yellow') escapes = scan(/(\e\[[\d;]+m)[^\e]+@/) tag_color = Doing::Color.send(color) last_color = if !escapes.empty? escapes[-1][0] else Doing::Color.default end gsub(/(\s|m)(@[^ ("']+)/, "\\1#{tag_color}\\2#{last_color}") end;T;&I")def highlight_tags(color = 'yellow');T;'T;`0;*I" param;T;0;I"!@param (see #highlight_tags);T; 0;!@;4i;"T;5o;6;7F;8i(;9i(;$@y;:T;%I"Ndef highlight_tags!(color = 'yellow') replace highlight_tags(color) end;T;&I"*def highlight_tags!(color = 'yellow');T;'T@o; ; F; ;;;;I"String#ignore?;F;[;[[@iD;T;: ignore?;;;[;{;IC;"#Test if line should be ignored;T;[o;) ;*I" return;F;+I"line is empty or comment;T;0;,[I" Boolean;T;!@;[;I"U Test if line should be ignored @return [Boolean] line is empty or comment ;T; 0;!@;4i;"T;5o;6;7F;8i?;9iC;$@y;:T;%I"Ddef ignore? line = self line =~ /^#/ || line =~ /^\s*$/ end;T;&I"def ignore?;T;'To; ; F; ;;;;I"String#truncate;F;[[I"len;T0[I"ellipsis:;TI" '...';T;[[@iN;T;: truncate;;;[;{;IC;"Truncate to nearest word;T;[o;) ;*I" param;F;+I"The length;T;I"len;T;,0;!@;[;I"= Truncate to nearest word @param len The length ;T; 0;!@;4i;"T;5o;6;7F;8iI;9iM;$@y;:T;%I"def truncate(len, ellipsis: '...') return self if length <= len total = 0 res = [] split(/ /).each do |word| break if total + 1 + word.length > len total += 1 + word.length res.push(word) end res.join(' ') + ellipsis end;T;&I"'def truncate(len, ellipsis: '...');T;'To; ; F; ;;;;I"String#truncate!;F;[[I"len;T0[I"ellipsis:;TI" '...';T;[[@i];F;:truncate!;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@y;:T;%I"Xdef truncate!(len, ellipsis: '...') replace truncate(len, ellipsis: ellipsis) end;T;&I"(def truncate!(len, ellipsis: '...');T;'To; ; F; ;;;;I"String#truncmiddle;F;[[I"len;T0[I"ellipsis:;TI" '...';T;[[@ig;T;:truncmiddle;;;[;{;IC;""Truncate string in the middle;T;[o;) ;*I" param;F;+I"The length;T;I"len;T;,0;!@o;) ;*I" param;F;+I"The ellipsis;T;I" ellipsis;T;,0;!@;[;I"i Truncate string in the middle @param len The length @param ellipsis The ellipsis ;T; 0;!@;4i;"T;5o;6;7F;8ia;9if;$@y;:T;%I"def truncmiddle(len, ellipsis: '...') return self if length <= len len -= (ellipsis.length / 2).to_i total = length half = total / 2 cut = (total - len) / 2 sub(/(.{#{half - cut}}).*?(.{#{half - cut}})$/, "\\1#{ellipsis}\\2") end;T;&I"*def truncmiddle(len, ellipsis: '...');T;'To; ; F; ;;;;I"String#truncmiddle!;F;[[I"len;T0[I"ellipsis:;TI" '...';T;[[@ip;F;:truncmiddle!;;;[;{;IC;" ;T;[;[;@; 0;!@0;4i;$@y;:T;%I"^def truncmiddle!(len, ellipsis: '...') replace truncmiddle(len, ellipsis: ellipsis) end;T;&I"+def truncmiddle!(len, ellipsis: '...');T;'To; ; F; ;;;;I"String#uncolor;F;[;[[@iy;T;;;;;[;{;IC;"Remove color escape codes;T;[o;) ;*I" return;F;+I"clean string;T;0;,0;!@A;[;I": Remove color escape codes @return clean string ;T; 0;!@A;4i;"T;5o;6;7F;8it;9ix;$@y;:T;%I"-def uncolor gsub(/\e\[[\d;]+m/,'') end;T;&I"def uncolor;T;'To; ; F; ;;;;I"String#uncolor!;F;[;[[@i};F;: uncolor!;;;[;{;IC;" ;T;[;[;@; 0;!@R;4i;$@y;:T;%I"'def uncolor! replace uncolor end;T;&I"def uncolor!;T;'To; ; F; ;;;;I"String#wrap;F;[ [I"len;T0[I" pad:;TI"0;T[I" indent:;TI" ' ';T[I" offset:;TI"0;T[I" prefix:;TI"'';T[I" after:;TI"'';T[I" reset:;TI"'';T;[[@i;T;: wrap;;;[;{;IC;"0Wrap string at word breaks, respecting tags;T;[o;) ;*I" param;F;+I"The length;T;I"len;T;,[I" Integer;T;!@^o;) ;*I" param;F;+I"5(Optional) The width to pad each subsequent line;T;I" offset;T;,[I" Integer;T;!@^o;) ;*I" param;F;+I",(Optional) A prefix to add to each line;T;I" prefix;T;,[I" String;T;!@^;[;I" Wrap string at word breaks, respecting tags @param len [Integer] The length @param offset [Integer] (Optional) The width to pad each subsequent line @param prefix [String] (Optional) A prefix to add to each line ;T; 0;!@^;4i;"T;5o;6;7F;8i|;9i;$@y;:T;%I"def wrap(len, pad: 0, indent: ' ', offset: 0, prefix: '', after: '', reset: '') note_rx = /(?i-m)(%(?:[io]d|(?:\^[\s\S])?(?:(?:[ _t]|[^a-z0-9])?\d+)?(?:[\s\S][ _t]?)?)?note)/ str = gsub(/@\w+\(.*?\)/) { |tag| tag.gsub(/\s/, '%%%%') } words = str.split(/ /).map { |word| word.gsub(/%%%%/, ' ') } out = [] line = [] words.each do |word| if line.join(' ').length + word.length + 1 > len out.push(line.join(' ')) line.clear end line << word end out.push(line.join(' ')) note = '' after.sub!(note_rx) do note = Regexp.last_match(0) '' end out[0] = format("%-#{pad}s%s", out[0], after) left_pad = ' ' * (offset) left_pad += indent out.map { |l| "#{left_pad}#{prefix}#{l}" }.join("\n").strip + " #{note}".chomp end;T;&I"Tdef wrap(len, pad: 0, indent: ' ', offset: 0, prefix: '', after: '', reset: '');T;'To; ; F; ;;;;I"String#cap_first;F;[;[[@i;T;:cap_first;;;[;{;IC;"0Capitalize on the first character on string;T;[o;) ;*I" return;F;+I"Capitalized string;T;0;,0;!@;[;I"R Capitalize on the first character on string @return Capitalized string ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@y;:T;%I"=def cap_first sub(/^\w/) do |m| m.upcase end end;T;&I"def cap_first;T;'To; ; F; ;;;;I"String#normalize_order!;F;[[I" default;TI" 'asc';T;[[@i;T;:normalize_order!;;;[;{;IC;"4Convert a sort order string to a qualified type;T;[o;) ;*I" return;F;+I"'asc' or 'desc';T;0;,[I" String;T;!@;[;I"\ Convert a sort order string to a qualified type @return [String] 'asc' or 'desc' ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@y;:T;%I"Qdef normalize_order!(default = 'asc') replace normalize_order(default) end;T;&I"*def normalize_order!(default = 'asc');T;'To; ; F; ;;;;I"String#normalize_order;F;[[I" default;TI" 'asc';T;[[@i;F;:normalize_order;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@y;:T;%I"|def normalize_order(default = 'asc') case self when /^a/i 'asc' when /^d/i 'desc' else default end end;T;&I")def normalize_order(default = 'asc');T;'To; ; F; ;;;;I"String#normalize_case!;F;[;[[@i;T;:normalize_case!;;;[;{;IC;"2Convert a case sensitivity string to a symbol;T;[o;) ;*I" return;F;+I",Symbol :smart, :sensitive, :insensitive;T;0;,0;!@;[;I"i Convert a case sensitivity string to a symbol @return Symbol :smart, :sensitive, :insensitive ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@y;:T;%I"5def normalize_case! replace normalize_case end;T;&I"def normalize_case!;T;'To; ; F; ;;;;I"String#normalize_case;F;[[I" default;TI" :smart;T;[[@i;F;:normalize_case;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@y;:T;%I"def normalize_case(default = :smart) case self when /^c/i :sensitive when /^i/i :insensitive when /^s/i :smart else default.is_a?(Symbol) ? default : default.normalize_case end end;T;&I")def normalize_case(default = :smart);T;'To; ; F; ;;;;I"String#normalize_bool!;F;[[I" default;TI" :and;T;[[@i;T;:normalize_bool!;;;[;{;IC;")Convert a boolean string to a symbol;T;[o;) ;*I" return;F;+I"Symbol :and, :or, or :not;T;0;,0;!@;[;I"R Convert a boolean string to a symbol @return Symbol :and, :or, or :not ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@y;:T;%I"Ndef normalize_bool!(default = :and) replace normalize_bool(default) end;T;&I"(def normalize_bool!(default = :and);T;'To; ; F; ;;;;I"String#normalize_bool;F;[[I" default;TI" :and;T;[[@i;F;:normalize_bool;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@y;:T;%I"def normalize_bool(default = :and) case self when /(and|all)/i :and when /(any|or)/i :or when /(not|none)/i :not else default.is_a?(Symbol) ? default : default.normalize_bool end end;T;&I"'def normalize_bool(default = :and);T;'To; ; F; ;;;;I"String#normalize_trigger!;F;[;[[@i;F;:normalize_trigger!;;;[;{;IC;" ;T;[;[;@; 0;!@ ;4i;$@y;:T;%I";def normalize_trigger! replace normalize_trigger end;T;&I"def normalize_trigger!;T;'To; ; F; ;;;;I"String#normalize_trigger;F;[;[[@i;F;:normalize_trigger;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@y;:T;%I"Bdef normalize_trigger gsub(/\((?!\?:)/, '(?:').downcase end;T;&I"def normalize_trigger;T;'To; ; F; ;;;;I"String#to_tags;F;[;[[@i;F;: to_tags;;;[;{;IC;" ;T;[;[;@; 0;!@#;4i;$@y;:T;%I"pdef to_tags gsub(/ *, */, ' ').gsub(/ +/, ' ').split(/ /).sort.uniq.map { |t| t.strip.sub(/^@/, '') } end;T;&I"def to_tags;T;'To; ; F; ;;;;I"String#add_tags!;F;[[I" tags;T0[I" remove:;TI" false;T;[[@i;F;:add_tags!;;;[;{;IC;" ;T;[;[;@; 0;!@/;4i;$@y;:T;%I"Tdef add_tags!(tags, remove: false) replace add_tags(tags, remove: remove) end;T;&I"'def add_tags!(tags, remove: false);T;'To; ; F; ;;;;I"String#add_tags;F;[[I" tags;T0[I" remove:;TI" false;T;[[@i;F;: add_tags;;;[;{;IC;" ;T;[;[;@; 0;!@@;4i;$@y;:T;%I"def add_tags(tags, remove: false) title = self.dup tags = tags.to_tags if tags.is_a?(String) tags.each { |tag| title.tag!(tag, remove: remove) } title end;T;&I"&def add_tags(tags, remove: false);T;'To; ; F; ;;;;I"String#tag!;F;[ [I"tag;T0[I" value:;TI"nil;T[I" remove:;TI" false;T[I"rename_to:;TI"nil;T[I" regex:;TI" false;T[I" single:;TI" false;T;[[@i;F;: tag!;;;[;{;IC;" ;T;[;[;@; 0;!@Q;4i;$@y;:T;%I"def tag!(tag, value: nil, remove: false, rename_to: nil, regex: false, single: false) replace tag(tag, value: value, remove: remove, rename_to: rename_to, regex: regex, single: single) end;T;&I"Zdef tag!(tag, value: nil, remove: false, rename_to: nil, regex: false, single: false);T;'To; ; F; ;;;;I"String#tag;F;[ [I"tag;T0[I" value:;TI"nil;T[I" remove:;TI" false;T[I"rename_to:;TI"nil;T[I" regex:;TI" false;T[I" single:;TI" false;T;[[@i;F;;@;;;[;{;IC;" ;T;[;[;@; 0;!@n;4i;$@y;:T;%I"def tag(tag, value: nil, remove: false, rename_to: nil, regex: false, single: false) log_level = single ? :info : :debug title = dup title.chomp! tag = tag.sub(/^@?/, '') case_sensitive = tag !~ /[A-Z]/ rx_tag = if regex tag.gsub(/\./, '\S') else tag.gsub(/\?/, '.').gsub(/\*/, '\S*?') end if remove || rename_to return title unless title =~ /#{rx_tag}(?=[ (]|$)/ rx = Regexp.new("(^| )@#{rx_tag}(\\([^)]*\\))?(?= |$)", case_sensitive) if title =~ rx title.gsub!(rx) do m = Regexp.last_match rename_to ? "#{m[1]}@#{rename_to}#{m[2]}" : m[1] end title.dedup_tags! title.chomp! if rename_to f = "@#{tag}".cyan t = "@#{rename_to}".cyan Doing.logger.write(log_level, 'Tag:', %(renamed #{f} to #{t} in "#{title}")) else f = "@#{tag}".cyan Doing.logger.write(log_level, 'Tag:', %(removed #{f} from "#{title}")) end else Doing.logger.debug('Skipped:', "not tagged #{"@#{tag}".cyan}") end elsif title =~ /@#{tag}(?=[ (]|$)/ Doing.logger.debug('Skipped:', "already tagged #{"@#{tag}".cyan}") return title else add = tag add += "(#{value})" unless value.nil? title.chomp! title += " @#{add}" title.dedup_tags! title.chomp! Doing.logger.write(log_level, 'Tag:', %(added #{('@' + tag).cyan} to "#{title}")) end title.gsub(/ +/, ' ') end;T;&I"Ydef tag(tag, value: nil, remove: false, rename_to: nil, regex: false, single: false);T;'To; ; F; ;;;;I"String#dedup_tags!;F;[;[[@i?;T;:dedup_tags!;;;[;{;IC;"9Remove duplicate tags, leaving only first occurrence;T;[o;) ;*I" return;F;+I"Deduplicated string;T;0;,0;!@;[;I"\ Remove duplicate tags, leaving only first occurrence @return Deduplicated string ;T; 0;!@;4i;"T;5o;6;7F;8i:;9i>;$@y;:T;%I"-def dedup_tags! replace dedup_tags end;T;&I"def dedup_tags!;T;'To; ; F; ;;;;I"String#dedup_tags;F;[;[[@iC;F;:dedup_tags;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@y;:T;%I"(def dedup_tags title = dup tags = title.scan(/(?<=^| )(@(\S+?)(\([^)]+\))?)(?= |$)/).uniq tags.each do |tag| found = false title.gsub!(/( |^)#{tag[1]}(\([^)]+\))?(?= |$)/) do |m| if found '' else found = true m end end end title end;T;&I"def dedup_tags;T;'To; ; F; ;;;;I"String#link_urls!;F;[[I"opt;TI"{};T;[[@iY;T;:link_urls!;;;[;{;IC;""Turn raw urls into HTML links;T;[o;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@;[;I"Q Turn raw urls into HTML links @param opt [Hash] Additional Options ;T; 0;!@;4i;"T;5o;6;7F;8iT;9iX;$@y;:T;%I":def link_urls!(opt = {}) replace link_urls(opt) end;T;&I"def link_urls!(opt = {});T;'To; ; F; ;;;;I"String#link_urls;F;[[I"opt;TI"{};T;[[@i];F;:link_urls;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@y;:T;%I"def link_urls(opt = {}) opt[:format] ||= :html str = self.dup if :format == :markdown # Remove formatting str.gsub!(/<(.*?)>/) do |match| m = Regexp.last_match if m[1] =~ /^https?:/ m[1] else match end end end # Replace qualified urls str.gsub!(%r{(?mi)(?[#{m[3]}]) when :markdown "[#{m[0]}](#{proto}#{m[0]})" else m[0] end end # Clean up unlinked str.gsub!(/<(\w+:.*?)>/) do |match| m = Regexp.last_match if m[1] =~ /[link]) end end str end;T;&I"def link_urls(opt = {});T;'T;M@y;NIC;[;M@y;OIC;[o;Z ;[@y;\I"Doing::Color;T;]0;;;$@;_@ ;`: module;M@y;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@i ;T;: String;;;;;[;{;IC;"String helpers;T;[;[;I" String helpers ;T; 0;!@y;4i;"T;5o;6;7F;8i ;9i ;$@;I" String;F;Yo;Z ;[0;\0;]0;;^;$@;_0;`;R;'To; ;IC;[o; ; F; ;;;;I"Symbol#normalize_bool;F;[[I" default;TI" :and;T;[[@i ;F;;E;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"Jdef normalize_bool(default = :and) to_s.normalize_bool(default) end;T;&I"'def normalize_bool(default = :and);T;'To; ; F; ;;;;I"Symbol#normalize_order;F;[[I" default;TI" 'asc';T;[[@i;F;;A;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"Mdef normalize_order(default = 'asc') to_s.normalize_order(default) end;T;&I")def normalize_order(default = 'asc');T;'To; ; F; ;;;;I"Symbol#normalize_case;F;[[I" default;TI" :smart;T;[[@i;F;;C;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"Ldef normalize_case(default = :smart) to_s.normalize_case(default) end;T;&I")def normalize_case(default = :smart);T;'T;M@;NIC;[;M@;OIC;[;M@;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@i ;T;: Symbol;;;;;[;{;IC;"Symbol helpers;T;[;[;I" Symbol helpers ;T; 0;!@;4i;"T;5o;6;7F;8i ;9i ;$@;I" Symbol;F;Yo;Z ;[0;\0;]0;;^;$@;_0;`;R;'To; ;IC;[ o; ; F; ;;;;I"Status#cols;F;[;[[I"lib/doing/cli_status.rb;Ti;F;: cols;;;[;{;IC;" ;T;[;[;@; 0;!@%;4i;$@#;:T;%I"4def cols @cols ||= `tput cols`.strip.to_i end;T;&I" def cols;T;'To; ; F; ;;;;I"Status#progress;F;[ [I"msg;T0[I"idx;T0[I" total;T0[I" tail;TI"[];T;[[@*i ;F;: progress;;;[;{;IC;" ;T;[;[;@; 0;!@2;4i;$@#;:T;%I"Adef progress(msg, idx, total, tail = []) status_width = format("> %s [%#{total.to_s.length}d/%d]: ", msg, 0, total).length max_width = cols - status_width if tail.is_a? Array tail.shift while tail.join(', ').length + 3 > max_width tail = tail.join(', ') end tail.ltrunc!(max_width) $stderr.print format("#{esc['kill']}#{esc['boldyellow']}> #{esc['boldgreen']}%s #{esc['white']}[#{esc['boldwhite']}%#{@commands.count.to_s.length}d#{esc['boldblack']}/#{esc['boldyellow']}%d#{esc['white']}]: #{esc['boldcyan']}%s#{esc['default']}\r", msg, idx, total, tail) end;T;&I"-def progress(msg, idx, total, tail = []);T;'To; ; F; ;;;;I"Status#status;F;[[I"msg;T0[I" reset:;TI" true;T;[[@*i;F;: status;;;[;{;IC;" ;T;[;[;@; 0;!@G;4i;$@#;:T;%I"def status(msg, reset: true) $stderr.print format("#{esc['kill']}#{esc['boldyellow']}> #{esc['whiteboard']}%s#{esc['default']}%s", msg, reset ? "\r" : "\n") end;T;&I"!def status(msg, reset: true);T;'To; ; F; ;;;;I"Status#clear;F;[;[[@*i;F;: clear;;;[;{;IC;" ;T;[;[;@; 0;!@X;4i;$@#;:T;%I"=def clear $stderr.print format("\r#{esc['kill']}") end;T;&I"def clear;T;'To; ; F; ;;;;I"Status#esc;F;[;[[@*i;F;:esc;;;[;{;IC;" ;T;[;[;@; 0;!@d;4i;$@#;:T;%I"&def esc e = {} e['kill'] = "\033[2K" e['reset'] = "\033[A\033[2K" e['black'] = "\033[0;0;30m" e['red'] = "\033[0;0;31m" e['green'] = "\033[0;0;32m" e['yellow'] = "\033[0;0;33m" e['blue'] = "\033[0;0;34m" e['magenta'] = "\033[0;0;35m" e['cyan'] = "\033[0;0;36m" e['white'] = "\033[0;0;37m" e['bgblack'] = "\033[40m" e['bgred'] = "\033[41m" e['bggreen'] = "\033[42m" e['bgyellow'] = "\033[43m" e['bgblue'] = "\033[44m" e['bgmagenta'] = "\033[45m" e['bgcyan'] = "\033[46m" e['bgwhite'] = "\033[47m" e['boldblack'] = "\033[1;30m" e['boldred'] = "\033[1;31m" e['boldgreen'] = "\033[0;1;32m" e['boldyellow'] = "\033[0;1;33m" e['boldblue'] = "\033[0;1;34m" e['boldmagenta'] = "\033[0;1;35m" e['boldcyan'] = "\033[0;1;36m" e['boldwhite'] = "\033[0;1;37m" e['boldbgblack'] = "\033[1;40m" e['boldbgred'] = "\033[1;41m" e['boldbggreen'] = "\033[1;42m" e['boldbgyellow'] = "\033[1;43m" e['boldbgblue'] = "\033[1;44m" e['boldbgmagenta'] = "\033[1;45m" e['boldbgcyan'] = "\033[1;46m" e['boldbgwhite'] = "\033[1;47m" e['softpurple'] = "\033[0;35;40m" e['hotpants'] = "\033[7;34;40m" e['knightrider'] = "\033[7;30;40m" e['flamingo'] = "\033[7;31;47m" e['yeller'] = "\033[1;37;43m" e['whiteboard'] = "\033[1;30;47m" e['default'] = "\033[0;39m" e end;T;&I" def esc;T;'T;M@#;NIC;[;M@#;OIC;[;M@#;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@*i;F;: Status;;;;;[;{;IC;" ;T;[;[;@; 0;!@#;4i;$@;I" Status;Fo; ;IC;[o; ;IC;[o; ;IC;[o; ; F; ;;;;I"7GLI::Commands::MarkdownDocumentListener#initialize;F;[ [I"_global_options;T0[I" _options;T0[I"_arguments;T0[I"app;T0;[[I",lib/doing/markdown_document_listener.rb;Ti;F;;3;;;[;{;IC;" ;T;[o;) ;*I" return;F;+I"/a new instance of MarkdownDocumentListener;T;0;,[I"MarkdownDocumentListener;F;!@;[;@; 0;!@;4i;$@;:T;%I"def initialize(_global_options, _options, _arguments, app) @exe = app.exe_name if File.exist?('COMMANDS.md') # Back up existing README FileUtils.mv('COMMANDS.md', 'COMMANDS.bak') $stderr.puts "Backing up existing COMMANDS.md" end @io = File.new('COMMANDS.md', 'w') @nest = '#' @arg_name_formatter = GLI::Commands::HelpModules::ArgNameFormatter.new @parent_command = [] end;T;&I"?def initialize(_global_options, _options, _arguments, app);T;'To; ; F; ;;;;I"6GLI::Commands::MarkdownDocumentListener#beginning;F;[;[[@i;F;:beginning;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"def beginning end;T;&I"def beginning;T;'To; ; F; ;;;;I"3GLI::Commands::MarkdownDocumentListener#ending;F;[;[[@i;T;: ending;;;[;{;IC;")Called when processing has completed;T;[;[;I")Called when processing has completed;T; 0;!@;4i;"F;5o;6;7F;8i;9i;$@;:T;%I"}def ending if File.exist?('CREDITS.md') @io.puts IO.read('CREDITS.md') @io.puts end if File.exist?('AUTHORS.md') @io.puts IO.read('AUTHORS.md') @io.puts end if File.exist?('LICENSE.md') @io.puts IO.read('LICENSE.md') @io.puts end @io.puts @io.puts "Documentation generated #{Time.now.strftime('%Y-%m-%d %H:%M')}" @io.puts @io.close end;T;&I"def ending;T;'To; ; F; ;;;;I"9GLI::Commands::MarkdownDocumentListener#program_desc;F;[[I" desc;T0;[[@i4;T;:program_desc;;;[;{;IC;"&Gives you the program description;T;[;[;I"&Gives you the program description;T; 0;!@;4i;"F;5o;6;7F;8i3;9i3;$@;:T;%I"`def program_desc(desc) @io.puts "# #{@exe} CLI" @io.puts @io.puts desc @io.puts end;T;&I"def program_desc(desc);T;'To; ; F; ;;;;I">GLI::Commands::MarkdownDocumentListener#program_long_desc;F;[[I" desc;T0;[[@i;;F;:program_long_desc;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"Fdef program_long_desc(desc) @io.puts "> #{desc}" @io.puts end;T;&I" def program_long_desc(desc);T;'To; ; F; ;;;;I"4GLI::Commands::MarkdownDocumentListener#version;F;[[I" version;T0;[[@iA;T;: version;;;[;{;IC;""Gives you the program version;T;[;[;I""Gives you the program version;T; 0;!@;4i;"F;5o;6;7F;8i@;9i@;$@;:T;%I"def version(version) @io.puts "*v#{version}*" @io.puts # Hacking in the overview file if File.exist?('OVERVIEW.md') @io.puts IO.read('OVERVIEW.md') @io.puts end end;T;&I"def version(version);T;'To; ; F; ;;;;I"4GLI::Commands::MarkdownDocumentListener#options;F;[;[[@iK;F;: options;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"def options if @nest.size == 1 @io.puts "## Global Options" else @io.puts header("Options", 1) end @io.puts end;T;&I"def options;T;'To; ; F; ;;;;I"1GLI::Commands::MarkdownDocumentListener#flag;F;[ [I" name;T0[I" aliases;T0[I" desc;T0[I"long_desc;T0[I"default_value;T0[I" arg_name;T0[I"must_match;T0[I" _type;T0;[[@iU;T;: flag;;;[;{;IC;",Gives you a flag in the current context;T;[;[;I",Gives you a flag in the current context;T; 0;!@;4i;"F;5o;6;7F;8iT;9iT;$@;:T;%I"6def flag(name, aliases, desc, long_desc, default_value, arg_name, must_match, _type) invocations = ([name] + Array(aliases)).map { |_| "`" + add_dashes(_) + "`" }.join(' | ') usage = "#{invocations} #{arg_name || 'arg'}" @io.puts header(usage, 2) @io.puts @io.puts String(desc).strip @io.puts "\n*Default Value:* `#{default_value || 'None'}`\n" unless default_value.nil? @io.puts "\n*Must Match:* `#{must_match.to_s}`\n" unless must_match.nil? cmd_desc = String(long_desc).strip @io.puts "> #{cmd_desc}\n" unless cmd_desc.length == 0 @io.puts end;T;&I"Ydef flag(name, aliases, desc, long_desc, default_value, arg_name, must_match, _type);T;'To; ; F; ;;;;I"3GLI::Commands::MarkdownDocumentListener#switch;F;[ [I" name;T0[I" aliases;T0[I" desc;T0[I"long_desc;T0[I"negatable;T0;[[@ic;T;: switch;;;[;{;IC;".Gives you a switch in the current context;T;[;[;I".Gives you a switch in the current context;T; 0;!@;4i;"F;5o;6;7F;8ib;9ib;$@;:T;%I"def switch(name, aliases, desc, long_desc, negatable) if negatable name = "[no-]#{name}" if name.to_s.length > 1 aliases = aliases.map { |_| _.to_s.length > 1 ? "[no-]#{_}" : _ } end invocations = ([name] + aliases).map { |_| "`" + add_dashes(_).strip + "`" }.join('|') @io.puts header("#{invocations}", 2) @io.puts @io.puts String(desc).strip cmd_desc = String(long_desc).strip @io.puts "\n> #{cmd_desc}\n" unless cmd_desc.length == 0 @io.puts end;T;&I":def switch(name, aliases, desc, long_desc, negatable);T;'To; ; F; ;;;;I"8GLI::Commands::MarkdownDocumentListener#end_options;F;[;[[@iq;F;:end_options;;;[;{;IC;" ;T;[;[;@; 0;!@);4i;$@;:T;%I"def end_options end;T;&I"def end_options;T;'To; ; F; ;;;;I"5GLI::Commands::MarkdownDocumentListener#commands;F;[;[[@it;F;: commands;;;[;{;IC;" ;T;[;[;@; 0;!@5;4i;$@;:T;%I"Rdef commands @io.puts header("Commands", 1) @io.puts increment_nest end;T;&I"def commands;T;'To; ; F; ;;;;I"4GLI::Commands::MarkdownDocumentListener#command;F;[ [I" name;T0[I" aliases;T0[I" desc;T0[I"long_desc;T0[I" arg_name;T0[I"arg_options;T0;[[@i{;T;: command;;;[;{;IC;"YGives you a command in the current context and creates a new context of this command;T;[;[;I"YGives you a command in the current context and creates a new context of this command;T; 0;!@A;4i;"F;5o;6;7F;8iz;9iz;$@;:T;%I"2def command(name, aliases, desc, long_desc, arg_name, arg_options) @parent_command.push ([name] + aliases).join('|') arg_name_fmt = @arg_name_formatter.format(arg_name, arg_options, []) arg_name_fmt = " `#{arg_name_fmt.strip}`" if arg_name_fmt @io.puts header("`$ #{@exe}` `#{@parent_command.join(' ')}`#{arg_name_fmt}", 1) @io.puts @io.puts "*#{String(desc).strip}*" @io.puts cmd_desc = String(long_desc).strip.split("\n").map { |_| "> #{_}" }.join("\n") @io.puts "#{cmd_desc}\n\n" unless cmd_desc.length == 0 increment_nest end;T;&I"Gdef command(name, aliases, desc, long_desc, arg_name, arg_options);T;'To; ; F; ;;;;I"8GLI::Commands::MarkdownDocumentListener#end_command;F;[[I" _name;T0;[[@i;T;:end_command;;;[;{;IC;"7Ends a command, and "pops" you back up one context;T;[;[;I"7Ends a command, and "pops" you back up one context;T; 0;!@[;4i;"F;5o;6;7F;8i;9i;$@;:T;%I"ydef end_command(_name) @parent_command.pop decrement_nest @io.puts "* * * * * *\n\n" unless @nest.size > 2 end;T;&I"def end_command(_name);T;'To; ; F; ;;;;I" 2 name end;T;&I"def add_dashes(name);T;'To; ; F; ;;;F;I"3GLI::Commands::MarkdownDocumentListener#header;F;[[I" content;T0[I"increment;T0;[[@i;F;: header;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"def header(content, increment) if @nest.size + increment > 6 "**#{content}**" else "#{@nest}#{'#'*increment} #{content}" end end;T;&I"#def header(content, increment);T;'To; ; F; ;;;F;I";GLI::Commands::MarkdownDocumentListener#increment_nest;F;[[I"increment;TI"1;T;[[@i;F;:increment_nest;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"Mdef increment_nest(increment=1) @nest = "#{@nest}#{'#'*increment}" end;T;&I"$def increment_nest(increment=1);T;'To; ; F; ;;;F;I";GLI::Commands::MarkdownDocumentListener#decrement_nest;F;[[I"increment;TI"1;T;[[@i;F;:decrement_nest;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"Odef decrement_nest(increment=1) @nest.gsub!(/#{'#'*increment}$/, '') end;T;&I"$def decrement_nest(increment=1);T;'T;M@;NIC;[;M@;OIC;[;M@;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@i ;T;:MarkdownDocumentListener;;;;;[;{;IC;";DocumentListener class for GLI documentation generator;T;[;[;I";DocumentListener class for GLI documentation generator;T; 0;!@;4i;"F;5o;6;7F;8i ;9i ;$@;I",GLI::Commands::MarkdownDocumentListener;F;Yo;Z ;[0;\0;]0;;^;$@;_0;`;R;'T;M@;NIC;[;M@;OIC;[;M@;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@i ;F;: Commands;;;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;I"GLI::Commands;F;'T;M@;NIC;[;M@;OIC;[;M@;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[[@i ;F;:GLI;;;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;I"GLI;F;M@;NIC;[;M@;OIC;[;M@;PIC;Q{;RIC;Q{;ST;IC;Q{;ST;ST;V{;W[;[;F;;;;;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$0;@;+@;0@ :Hash#deep_freeze@ :Hash#deep_freeze!@:Hash#stringify_keys@+:Hash#symbolize_keys@9:Doing::Item@ :Doing::Item#date@ :Doing::Item#date=@:Doing::Item#title@-:Doing::Item#title=@::Doing::Item#section@L:Doing::Item#section=@Y:Doing::Item#note@k:Doing::Item#note=@x:Doing::Item#initialize@:Doing::Item#interval@:Doing::Item#end_date@:Doing::Item#equal?@:Doing::Item#same_time?@:"Doing::Item#overlapping_time?@:Doing::Item#tag@.:Doing::Item#tags@h:Doing::Item#tags?@{:Doing::Item#search@:Doing::Item#should_finish?@:Doing::Item#should_time?@:Doing::Item#should?@:Doing::Item#calc_interval@:Doing::Item#all_tags?@:Doing::Item#no_tags?@#:Doing::Item#any_tags?@4:Doing::Item#split_tags@E:Doing::Note@i:Doing::Note#initialize@k:Doing::Note#add@:Doing::Note#append@:Doing::Note#append_string@:Doing::Note#compress!@:Doing::Note#compress@:Doing::Note#strip_lines!@:Doing::Note#strip_lines@:Doing::Note#to_s@:Doing::Note#equal?@;2@Y:Time#relative_date@[:Doing::Util@!:Doing::Util#user_home@#:Doing::Util#exec_available@0:#Doing::Util#merge_default_proc@F:(Doing::Util#duplicate_frozen_values@V:"Doing::Util#deep_merge_hashes@d:#Doing::Util#deep_merge_hashes!@:Doing::Util#duplicable?@:Doing::Util#mergable?@:Doing::Util#merge_values@:Doing::Util#write_to_file@:Doing::Util#safe_load_file@:Doing::Util#default_editor@:!Doing::Util#editor_with_args@ : Doing::Util#args_for_editor@:$Doing::Util#find_default_editor@$:Doing::WWID@D:#Doing::WWID#additional_configs@F: Doing::WWID#current_section@T:Doing::WWID#doing_file@a:Doing::WWID#content@n:Doing::WWID#config@{:Doing::WWID#config=@:Doing::WWID#config_file@:Doing::WWID#config_file=@:Doing::WWID#auto_tag@:Doing::WWID#auto_tag=@:Doing::WWID#default_option@: Doing::WWID#default_option=@:Doing::WWID#initialize@:Doing::WWID#logger@ : Doing::WWID#init_doing_file@:Doing::WWID#create@/:Doing::WWID#fork_editor@@:Doing::WWID#format_input@[:Doing::WWID#chronify@z:Doing::WWID#chronify_qty@:Doing::WWID#sections@:Doing::WWID#add_section@:Doing::WWID#guess_section@:Doing::WWID#yn@:Doing::WWID#guess_view@):Doing::WWID#add_item@K:Doing::WWID#dedup@s:Doing::WWID#import@:Doing::WWID#last_note@:Doing::WWID#reset_item@:Doing::WWID#repeat_item@:Doing::WWID#repeat_last@:Doing::WWID#last_entry@:Doing::WWID#choose_from@:Doing::WWID#all_tags@=:Doing::WWID#tag_groups@N:Doing::WWID#filter_items@_:Doing::WWID#interactive@:"Doing::WWID#choose_from_items@:Doing::WWID#act_on@1:Doing::WWID#tag_item@L:Doing::WWID#tag_last@:Doing::WWID#move_item@:Doing::WWID#next_item@:Doing::WWID#delete_item@:Doing::WWID#update_item@ :Doing::WWID#edit_last@* :Doing::WWID#stop_start@D :Doing::WWID#write@c :Doing::WWID#restore_backup@} :Doing::WWID#rotate@ :Doing::WWID#choose_section@ :Doing::WWID#views@ :Doing::WWID#choose_view@ :Doing::WWID#get_view@ :Doing::WWID#list_section@ :Doing::WWID#archive@ :Doing::WWID#today@* :Doing::WWID#list_date@S :Doing::WWID#yesterday@ :Doing::WWID#recent@ :Doing::WWID#last@ :Doing::WWID#autotag@ :Doing::WWID#tag_times@ :Doing::WWID#get_interval@H :Doing::WWID#format_time@s :!Doing::WWID#combined_content@ :Doing::WWID#output@ :!Doing::WWID#record_tag_times@ :Doing::WWID#do_archive@ :Doing::WWID#run_after@ :Doing::WWID#log_change@" ;j@ :Doing::Hooks@W :#Doing::Hooks::DEFAULT_PRIORITY@Y :Doing::Hooks.register@e : Doing::Hooks.priority_value@z :Doing::Hooks.register_one@ :Doing::Hooks.insert_hook@ :Doing::Hooks.trigger@ :Doing::Pager@ :Doing::Pager.paginate@ :Doing::Pager.paginate=@ :Doing::Pager.page@ :Doing::Pager.which_pager@ :Doing::Color@ :Doing::Color::ATTRIBUTES@ :"Doing::Color::ATTRIBUTE_NAMES@- :Doing::Color#support?@8 :Doing::Color.coloring?@K :Doing::Color.coloring=@\ :!Doing::Color::COLORED_REGEXP@l :Doing::Color#uncolor@y :Doing::Color#attributes@ :Doing::Color.attributes@ :Doing::Errors@ :!Doing::Errors::UserCancelled@ :,Doing::Errors::UserCancelled#initialize@ :Doing::Errors::EmptyInput@ :)Doing::Errors::EmptyInput#initialize@ :&Doing::Errors::DoingStandardError@:1Doing::Errors::DoingStandardError#initialize@: Doing::Errors::WrongCommand@):+Doing::Errors::WrongCommand#initialize@+:%Doing::Errors::DoingRuntimeError@R:0Doing::Errors::DoingRuntimeError#initialize@T:Doing::Errors::NoResults@{:(Doing::Errors::NoResults#initialize@}:%Doing::Errors::DoingNoTraceError@:0Doing::Errors::DoingNoTraceError#initialize@:#Doing::Errors::PluginException@:*Doing::Errors::PluginException#plugin@:.Doing::Errors::PluginException#initialize@:#Doing::Errors::HookUnavailable@ :%Doing::Errors::InvalidPluginType@:$Doing::Errors::PluginUncallable@ :#Doing::Errors::InvalidArgument@+:#Doing::Errors::MissingArgument@6:Doing::Errors::MissingFile@A:!Doing::Errors::MissingEditor@L:"Doing::Errors::NonInteractive@W: Doing::Errors::NoEntryError@b:)Doing::Errors::InvalidTimeExpression@m:"Doing::Errors::InvalidSection@x:Doing::Errors::InvalidView@: Doing::Errors::ItemNotFound@;Q@y:String#to_rx@{:String#truthy?@:String#highlight_tags!@:String#highlight_tags@:String#ignore?@:String#truncate@:String#truncate!@:String#truncmiddle@:String#truncmiddle!@0:String#uncolor@A:String#uncolor!@R:String#wrap@^:String#cap_first@:String#normalize_order!@:String#normalize_order@:String#normalize_case!@:String#normalize_case@:String#normalize_bool!@:String#normalize_bool@:String#normalize_trigger!@ :String#normalize_trigger@:String#to_tags@#:String#add_tags!@/:String#add_tags@@:String#tag!@Q:String#tag@n:String#dedup_tags!@:String#dedup_tags@:String#link_urls!@:String#link_urls@;R@:Symbol#normalize_bool@:Symbol#normalize_order@:Symbol#normalize_case@:Doing::VERSION@:Doing::Items@:Doing::Items#from@: Doing::Items#section_titles@:Doing::Items#add_section@:Doing::Items#guess_section@:Doing::Items#section_items@ ;X@#:Status#cols@%:Status#progress@2:Status#status@G:Status#clear@X:Status#esc@d:Doing::LogAdapter@':Doing::LogAdapter#logdev=@):"Doing::LogAdapter#max_length=@<:Doing::LogAdapter#messages@N:Doing::LogAdapter#level@[:Doing::LogAdapter#results@h:#Doing::LogAdapter::TOPIC_WIDTH@u:"Doing::LogAdapter::LOG_LEVELS@:"Doing::LogAdapter::COUNT_KEYS@:!Doing::LogAdapter#initialize@:!Doing::LogAdapter#log_level=@:'Doing::LogAdapter#adjust_verbosity@:Doing::LogAdapter#count@:Doing::LogAdapter#debug@:Doing::LogAdapter#info@:Doing::LogAdapter#warn@::Doing::LogAdapter#error@Z:!Doing::LogAdapter#abort_with@z:&Doing::LogAdapter#formatted_topic@:Doing::LogAdapter#write@:Doing::LogAdapter#log_now@:%Doing::LogAdapter#output_results@:%Doing::LogAdapter#format_counter@(:%Doing::LogAdapter#total_counters@8:%Doing::LogAdapter#write_message?@D:Doing::LogAdapter#message@\:$Doing::LogAdapter#color_message@~:Doing::Configuration@:"Doing::Configuration#settings@:'Doing::Configuration#ignore_local=@:,Doing::Configuration::MissingConfigFile@:#Doing::Configuration::DEFAULTS@:$Doing::Configuration#initialize@:,Doing::Configuration#additional_configs@:'Doing::Configuration#value_for_key@:Doing::Configuration#from@:%Doing::Configuration#config_file@$:&Doing::Configuration#config_file=@0:#Doing::Configuration#configure@>:+Doing::Configuration#find_deprecations@U:&Doing::Configuration#local_config@c:,Doing::Configuration#read_local_configs@t:%Doing::Configuration#read_config@:+Doing::Configuration#find_local_config@:&Doing::Configuration#load_plugins@:Doing::Plugins@:Doing::Plugins.user_home@:Doing::Plugins.plugins@: Doing::Plugins.load_plugins@: Doing::Plugins.plugins_path@:Doing::Plugins.register@:#Doing::Plugins.validate_plugin@:Doing::Plugins.valid_type@': Doing::Plugins.list_plugins@8:%Doing::Plugins.available_plugins@P: Doing::Plugins.plugin_names@j: Doing::Plugins.plugin_regex@:$Doing::Plugins.plugin_templates@:"Doing::Plugins.template_regex@:(Doing::Plugins.template_for_trigger@;m@:GLI::Commands@:,GLI::Commands::MarkdownDocumentListener@:7GLI::Commands::MarkdownDocumentListener#initialize@:6GLI::Commands::MarkdownDocumentListener#beginning@:3GLI::Commands::MarkdownDocumentListener#ending@:9GLI::Commands::MarkdownDocumentListener#program_desc@:>GLI::Commands::MarkdownDocumentListener#program_long_desc@:4GLI::Commands::MarkdownDocumentListener#version@:4GLI::Commands::MarkdownDocumentListener#options@:1GLI::Commands::MarkdownDocumentListener#flag@:3GLI::Commands::MarkdownDocumentListener#switch@:8GLI::Commands::MarkdownDocumentListener#end_options@):5GLI::Commands::MarkdownDocumentListener#commands@5:4GLI::Commands::MarkdownDocumentListener#command@A:8GLI::Commands::MarkdownDocumentListener#end_command@[: