{w: 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#id;F;[�;[[@i=[@i;T;:id;;;[�;{�;IC;".Generate a hash that represents the entry;T;[o;) ;*I"return;F;+I"entry hash;T;0;,[I"String;T;!@�;[�;I"KGenerate a hash that represents the entry @return [String] entry hash;T; 0;!@�:@ref_tag_recurse_counti�;"F:@line_rangeo: Range: exclF: begini::endi<;$@ :@explicitT;%I"def id @id end;T;&I"def id;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;!@�;4i�;"T;5o;6;7F;8i;9i;$@ ;:T;%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;[�;[[@i6;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;8i1;9i5;$@ ;: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;[[@iH;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;8iA;9iG;$@ ;: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;[[@iY;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;8iR;9iX;$@ ;: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;[[@ie;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;8i];9id;$@ ;: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" tags;T0[I"**options;T0;[[@i;T;:tag;;;[�;{�;IC;"4Add (or remove) tags from the title of the item;T;[o;) ;*I" param;F;+I"The tags to apply;T;I" tags;T;,[I" Array;T;!@Bo;) ;*I" param;F;+I"Additional options;T;I"**options;T;,0;!@Bo:YARD::Tags::OptionTag;*I"option;F;+0;I"options;T;,0: @pairo:YARD::Tags::DefaultTag ;*I"option;F;+I"Include timestamp?;T;I" :date;T;,[I"Boolean;T:@defaults0;!@Bo;B;*I"option;F;+0;I"options;T;,0;Co;D ;*I"option;F;+I"Log as a single change?;T;I":single;T;,[I"Boolean;T;E0;!@Bo;B;*I"option;F;+0;I"options;T;,0;Co;D ;*I"option;F;+I"&A value to include as @tag(value);T;I":value;T;,[I"String;T;E0;!@Bo;B;*I"option;F;+0;I"options;T;,0;Co;D ;*I"option;F;+I"%if true remove instead of adding;T;I":remove;T;,[I"Boolean;T;E0;!@Bo;B;*I"option;F;+0;I"options;T;,0;Co;D ;*I"option;F;+I"3if not nil, rename target tag to this tag name;T;I":rename_to;T;,[I"String;T;E0;!@Bo;B;*I"option;F;+0;I"options;T;,0;Co;D ;*I"option;F;+I"-treat target tag string as regex pattern;T;I":regex;T;,[I"Boolean;T;E0;!@Bo;B;*I"option;F;+0;I"options;T;,0;Co;D ;*I"option;F;+I"0with rename_to, add tag if it doesn't exist;T;I":force;T;,[I"Boolean;T;E0;!@Bo;) ;*I" param;F;+I""a customizable set of options;T;@[;,[I" Hash;T;!@B;[�;I"� Add (or remove) tags from the title of the item @param tags [Array] The tags to apply @param **options Additional options @option options :date [Boolean] Include timestamp? @option options :single [Boolean] Log as a single change? @option options :value [String] A value to include as @tag(value) @option options :remove [Boolean] if true remove instead of adding @option options :rename_to [String] if not nil, rename target tag to this tag name @option options :regex [Boolean] treat target tag string as regex pattern @option options :force [Boolean] with rename_to, add tag if it doesn't exist ;T; 0;!@B;4i�;"T;5o;6;7F;8iq;9i~;$@ ;:T;%I"zdef tag(tags, **options) added = [] removed = [] date = options.fetch(:date, false) options[:value] ||= date ? Time.now.strftime('%F %R') : nil options.delete(:date) single = options.fetch(:single, false) options.delete(:single) tags = tags.to_tags if tags.is_a? ::String remove = options.fetch(:remove, false) tags.each do |tag| bool = remove ? :and : :not if tags?(tag, bool) @title.tag!(tag, **options).strip! remove ? removed.push(tag) : added.push(tag) end end Doing.logger.log_change(tags_added: added, tags_removed: removed, count: 1, item: self, single: single) self end;T;&I"def tag(tags, **options);T;'To;;F; ;;;;I"Doing::Item#tags;F;[�;[[@i�;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;!@�;[�;I"T Get a list of tags on the item @return [Array] array of tags (no values) ;T; 0;!@�;4i�;"T;5o;6;7F;8i�;9i�;$@ ;:T;%I"Udef 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"distance:;TI"3;T[I"negate:;TI" false;T[I"case_type:;TI":smart;T[I"fuzzy:;TI" false;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"<The case-sensitivity type (:sensitive, :ignore, :smart);T;I"case_type;T;,[I"Symbol;T;!@�o;) ;*I"return;F;+I"matches search criteria;T;0;,[I"Boolean;T;!@�;[�;I"< 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, :ignore, :smart) @return [Boolean] matches search criteria ;T; 0;!@�;4i�;"T;5o;6;7F;8i�;9i�;$@ ;:T;%I"def search(search, distance: 3, negate: false, case_type: :smart, fuzzy: false) text = @title + @note.to_s matches = text =~ search.to_rx(distance: distance, case_type: case_type) # if search.is_rx? || !fuzzy # matches = text =~ search.to_rx(distance: distance, case_type: case_type) # else # distance = 0.25 if distance > 1 # score = if (case_type == :smart && search !~ /[A-Z]/) || case_type == :ignore # text.downcase.pair_distance_similar(search.downcase) # else # score = text.pair_distance_similar(search) # end # if score >= distance # matches = true # Doing.logger.debug('Fuzzy Match:', %(#{@title}, "#{search}" #{score})) # end # end negate ? !matches : matches end;T;&I"Tdef search(search, distance: 3, negate: false, case_type: :smart, fuzzy: false);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; ;;;;I"Doing::Item#move_to;F;[[I"new_section;T0[I"label:;TI" true;T[I" log:;TI" true;T;[[@i�;T;:move_to;;;[�;{�;IC;":Move item from current section to destination section;T;[ o;) ;*I" param;F;+I"The destination section;T;I"new_section;T;,[I"String;T;!@5o;) ;*I" param;F;+I"$add @from(original section) tag;T;I" label;T;,[I"Boolean;T;!@5o;) ;*I" param;F;+I"log this action;T;I"log;T;,[I"Boolean;T;!@5o;) ;*I"return;F;+I"nothing;T;0;,0;!@5;[�;I"/ Move item from current section to destination section @param new_section [String] The destination section @param label [Boolean] add @from(original section) tag @param log [Boolean] log this action @return nothing ;T; 0;!@5;4i�;"T;5o;6;7F;8i�;9i�;$@ ;:T;%I"�def move_to(new_section, label: true, log: true) from = @section tag('from', rename_to: 'from', value: from, force: true) if label @section = new_section Doing.logger.count(@section == 'Archive' ? :archived : :moved) if log Doing.logger.debug("#{@section == 'Archive' ? 'Archived' : 'Moved'}:", "#{@title.truncate(60)} from #{from} to #{@section}") self end;T;&I"5def move_to(new_section, label: true, log: true);T;'To;;F; ;;;;I"Doing::Item#to_s;F;[�;[[@i�;T;: to_s;;;[�;{�;IC;"=outputs item in Doing file format, including leading tab;T;[�;[�;I"=outputs item in Doing file format, including leading tab;T; 0;!@`;4i�;"F;5o;6;7F;8i�;9i�;$@ ;:T;%I"ldef to_s "\t- #{@date.strftime('%Y-%m-%d %H:%M')} | #{@title}#{@note.empty? ? '' : "\n#{@note}"}" end;T;&I" def to_s;T;'To;;F; ;;;;I"Doing::Item#inspect;F;[�;[[@i�;T;:inspect;;;[�;{�;IC;"�;T;[o;) ;*I"private;F;+I"�;T;0;,0;!@n;[�;I" @private;T; 0;!@n;4i�;"F;5o;6;7F;8i�;9i�;$@ ;:T;%I"�def inspect # %(<Doing::Item @date=#{@date} @title="#{@title}" @section:"#{@section}" @note:#{@note.to_s}>) %(<Doing::Item @date=#{@date}>) end;T;&I"def inspect;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; ;;;N;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; ;;;N;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; ;;;N;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; ;;;N;I"Doing::Item#any_tags?;F;[[I" tags;T0;[[@i+;F;:any_tags?;;;[�;{�;IC;"�;T;[o;) ;*I"return;F;+@ ;0;,[@";!@�;[�;@ ; 0;!@�;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; ;;;N;I"Doing::Item#split_tags;F;[[I" tags;T0;[[@i2;F;:split_tags;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;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;[�;U@ :@instance_mixinsIC;[�;U@ :@attributesIC:SymbolHash{: classIC;Y{�:@symbolize_valueT;IC;Y{ ;IC;Y{: read@: write@;[T;-IC;Y{;\@-;]@:;[T;/IC;Y{;\@L;]@Y;[T;1IC;Y{;\@k;]@x;[T;3IC;Y{;\@�;]0;[T;[T;[T: @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;Z;'To; ;IC;[o;;F; ;;;;I"Doing::Note#initialize;F;[[I" note;TI"[];T;[[I"lib/doing/note.rb;Ti;T;;;;;;[�;{�;IC;"Initializes a new note;T;[o;) ;*I" param;F;+I")Initial note, can be string or array;T;I" note;T;,[I" Array;T;!@�o;) ;*I"return;F;+I"a new instance of Note;T;0;,[I" Note;F;!@�;[�;I"o Initializes a new note @param note [Array] Initial note, can be string or array ;T; 0;!@�;4i�;"T;5o;6;7F;8i;9i;$@�;: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;[[@�i";T;:add;;;[�;{�;IC;":Add note contents, optionally replacing existing note;T;[o;) ;*I" param;F;+I"3The note to add, can be string or array (Note);T;I" note;T;,[I" Array;T;!@o;) ;*I" param;F;+I"replace existing content;T;I"replace;T;,[I"Boolean;T;!@;[�;I"� Add note contents, optionally replacing existing note @param note [Array] The note to add, can be string or array (Note) @param replace [Boolean] replace existing content ;T; 0;!@;4i�;"T;5o;6;7F;8i;9i!;$@�;: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;[[@�i0;T;:append;;;[�;{�;IC;"'Append an array of strings to note;T;[o;) ;*I" param;F;+I"Array of strings;T;I" lines;T;,[I" Array;T;!@2;[�;I"V Append an array of strings to note @param lines [Array] Array of strings ;T; 0;!@2;4i�;"T;5o;6;7F;8i+;9i/;$@�;: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;[[@�i;;T;:append_string;;;[�;{�;IC;"(Append a string to the note content;T;[o;) ;*I" param;F;+I"-The input string, newlines will be split;T;I" input;T;,[I"String;T;!@H;[�;I"~ Append a string to the note content @param input [String] The input string, newlines will be split ;T; 0;!@H;4i�;"T;5o;6;7F;8i5;9i:;$@�;: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;[�;[[@�i@;F;:compress!;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@^;4i�;$@�;:T;%I")def compress! replace compress end;T;&I"def compress!;T;'To;;F; ;;;;I"Doing::Note#compress;F;[�;[[@�iI;T;: compress;;;[�;{�;IC;"-Remove blank lines and comment lines (#);T;[o;) ;*I"return;F;+I"compressed array;T;0;,[I" Array;T;!@j;[�;I"U Remove blank lines and comment lines (#) @return [Array] compressed array ;T; 0;!@j;4i�;"T;5o;6;7F;8iD;9iH;$@�;:T;%I"Cdef compress delete_if { |l| l =~ /^\s*$/ || l =~ /^#/ } end;T;&I"def compress;T;'To;;F; ;;;;I"Doing::Note#strip_lines!;F;[�;[[@�iM;F;:strip_lines!;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@};4i�;$@�;:T;%I"/def strip_lines! replace strip_lines end;T;&I"def strip_lines!;T;'To;;F; ;;;;I"Doing::Note#strip_lines;F;[�;[[@�iW;T;:strip_lines;;;[�;{�;IC;"JRemove leading/trailing whitespace for every line of note;T;[o;) ;*I"return;F;+I"Stripped note;T;0;,[I" Array;T;!@�;[�;I"o Remove leading/trailing whitespace for every line of note @return [Array] Stripped note ;T; 0;!@�;4i�;"T;5o;6;7F;8iQ;9iV;$@�;:T;%I"'def strip_lines map(&:strip) end;T;&I"def strip_lines;T;'To;;F; ;;;;I"Doing::Note#to_s;F;[�;[[@�i];T;;L;;;[�;{�;IC;"Note as multi-line string;T;[�;[�;I" Note as multi-line string;T; 0;!@�;4i�;"T;5o;6;7F;8i[;9i\;$@�;:T;%I"Jdef to_s compress.strip_lines.map { |l| "\t\t#{l}" }.join("\n") end;T;&I" def to_s;T;'To;;F; ;;;;I"Doing::Note#inspect;F;[�;[[@�ib;T;;M;;;[�;{�;IC;"�;T;[o;) ;*I"private;F;+I"�;T;0;,0;!@�;[�;I" @private;T; 0;!@�;4i�;"F;5o;6;7F;8ia;9ia;$@�;:T;%I"kdef inspect "<Doing::Note - characters:#{compress.strip_lines.join(' ').length} lines:#{count}>" end;T;&I"def inspect;T;'To;;F; ;;;;I"Doing::Note#equal?;F;[[I" other;T0;[[@�in;T;;>;;;[�;{�;IC;"ITest if a note is equal (compare string representations);T;[o;) ;*I" param;F;+I"The other Note;T;I" other;T;,[I" Note;T;!@�o;) ;*I"return;F;+I"true if equal;T;0;,[I"Boolean;T;!@�;[�;I"� Test if a note is equal (compare string representations) @param other [Note] The other Note @return [Boolean] true if equal ;T; 0;!@�;4i�;"T;5o;6;7F;8if;9im;$@�;: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;U@�;VIC;[�;U@�;WIC;[�;U@�;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@�i;T;: Note;;;;;[�;{�;IC;"'This class describes an item note.;T;[�;[�;I") This class describes an item note. ;T; 0;!@�;4i�;"T;5o;6;7F;8i ;9i;$@;I"Doing::Note;F;ao;b;c0;d0;e0;: Array;$@;go; ;IC;[ o;;F; ;;;;I"Array#to_tags;F;[�;[[I"lib/doing/array.rb;Ti;T;:to_tags;;;[�;{�;IC;"Convert strings to @tags;T;[o;) ;*I"example;F;+I"�;F;I"'`['one', '@two', 'three'].to_tags`;T;,0;!@�o;) ;*I"example;F;+I"�;F;I"$`=> ['@one', '@two', '@three']`;T;,0;!@�o;) ;*I"return;F;+I"Array of @tags;T;0;,[I" Array;T;!@�;[�;I"�Convert strings to @tags @example `['one', '@two', 'three'].to_tags` @example `=> ['@one', '@two', '@three']` @return [Array] Array of @tags ;T; 0;!@�;4i�;"F;5o;6;7F;8i ;9i;$@�;:T;%I"4def to_tags map { |t| t.sub(/^@?/, '@') } end;T;&I"def to_tags;T;'To;;F; ;;;;I"Array#to_tags!;F;[�;[[@�i;F;: to_tags!;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@;4i�;$@�;:T;%I"'def to_tags! replace to_tags end;T;&I"def to_tags!;T;'To;;F; ;;;;I"Array#highlight_tags;F;[[I" color;TI"'cyan';T;[[@�i#;T;:highlight_tags;;;[�;{�;IC;"2Hightlight @tags in string for console output;T;[o;) ;*I" param;F;+I" the color to highlight with;T;I" color;T;,[I"String;T;!@o;) ;*I"return;F;+I""string with @tags highlighted;T;0;,[I"String;T;!@;[�;I"� Hightlight @tags in string for console output @param color [String] the color to highlight with @return [String] string with @tags highlighted ;T; 0;!@;4i�;"T;5o;6;7F;8i;9i";$@�;:T;%I"{def highlight_tags(color = 'cyan') tag_color = Doing::Color.send(color) to_tags.map { |t| "#{tag_color}#{t}" } end;T;&I"'def highlight_tags(color = 'cyan');T;'To;;F; ;;;;I"Array#log_tags;F;[�;[[@�i-;T;: log_tags;;;[�;{�;IC;"Tag array for logging;T;[o;) ;*I"return;F;+I",Highlighted tag array joined with comma;T;0;,[I"String;T;!@.;[�;I"Z Tag array for logging @return [String] Highlighted tag array joined with comma ;T; 0;!@.;4i�;"T;5o;6;7F;8i(;9i,;$@�;:T;%I"1def log_tags highlight_tags.join(', ') end;T;&I"def log_tags;T;'To;;F; ;;;;I"Array#nested_hash;F;[[I" value;T0;[[@�i6;T;:nested_hash;;;[�;{�;IC;"<Convert array to nested hash, setting last key to value;T;[o;) ;*I" param;F;+I"The value to set;T;I" value;T;,0;!@Ao;) ;*I" raise;F;+@ ;0;,[I"StandardError;T;!@A;[�;I"c Convert array to nested hash, setting last key to value @param value The value to set ;T; 0;!@A;4i�;"T;5o;6;7F;8i1;9i5;$@�;:T;%I"�def nested_hash(value) raise StandardError, 'Value can not be nil' if value.nil? hsh = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) } hsh.dig(*self[0..-2])[self.fetch(-1)] = value hsh end;T;&I"def nested_hash(value);T;'T;U@�;VIC;[�;U@�;WIC;[�;U@�;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@�i;T;;q;;;;;[�;{�;IC;"Array helpers;T;[�;[�;I" Array helpers ;T; 0;!@�;4i�;"T;5o;6;7F;8i ;9i;$@;I" Array;F;ao;b;c0;d0;e0;;f;$@;g0;h;Z;'T;h;Z;'To; ;IC;[o;;F; ;;;;I"Doing::Util#user_home;F;[�;[[I"lib/doing/util.rb;Ti ;F;:user_home;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@m;4i�;$@k;: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;[[@ri;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;!@z;[�;I"e Test if command line tool is available @param cli [String] The name or path of the cli ;T; 0;!@z;4i�;"T;5o;6;7F;8i;9i;$@k;:T;%I"Zdef exec_available(cli) return false if cli.nil? !TTY::Which.which(cli).nil? end;T;&I"def exec_available(cli);T;'To;;F; ;;;;I"%Doing::Util#first_available_exec;F;[[I"*commands;T0;[[@ri%;T;:first_available_exec;;;[�;{�;IC;">Return the first valid executable from a list of commands;T;[o;) ;*I"example;F;+I"�;F;I"L`Doing::Util.first_available_exec('bat', 'less -Xr', 'more -r', 'cat')`;T;,0;!@�;[�;I"� Return the first valid executable from a list of commands @example `Doing::Util.first_available_exec('bat', 'less -Xr', 'more -r', 'cat')` ;T; 0;!@�;4i�;"T;5o;6;7F;8i ;9i$;$@k;:T;%I"�def first_available_exec(*commands) commands.compact.map(&:strip).reject(&:empty?).uniq .find { |cmd| exec_available(cmd.split.first) } end;T;&I"(def first_available_exec(*commands);T;'To;;F; ;;;;I"#Doing::Util#merge_default_proc;F;[[I"target;T0[I"overwrite;T0;[[@ri*;F;:merge_default_proc;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@k;: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;[[@ri0;F;:duplicate_frozen_values;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@k;: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;[[@ri>;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;!@�o;) ;*I" param;F;+I"The master hash;T;I"master_hash;T;,[I" Hash;T;!@�o;) ;*I" param;F;+I"The other hash;T;I"other_hash;T;,[I" Hash;T;!@�;[�;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;!@�;4i�;"F;5o;6;7F;8i6;9i=;$@k;: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;[[@riK;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;8iB;9iJ;$@k;: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;[[@riS;F;:duplicable?;;;[�;{�;IC;"�;T;[o;) ;*I"return;F;+@ ;0;,[@";!@�;[�;@ ; 0;!@�;4i�;$@k;: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;[[@ri\;F;:mergable?;;;[�;{�;IC;"�;T;[o;) ;*I"return;F;+@ ;0;,[@";!@;[�;@ ; 0;!@;4i�;$@k;: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;[[@ri`;F;:merge_values;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@;4i�;$@k;: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;[[@ris;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;8il;9ir;$@k;:T;%I"�def 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 Doing.logger.debug('Write:', "File written: #{file}") 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;[[@ri�;F;:safe_load_file;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@N;4i�;$@k;: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;[�;[[@ri�;F;:default_editor;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@\;4i�;$@k;: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;[�;[[@ri�;F;:editor_with_args;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@h;4i�;$@k;: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;[[@ri�;F;:args_for_editor;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@t;4i�;$@k;: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;[[@ri�;F;:find_default_editor;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@k;: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 TTY::Which.which(ed) if TTY::Which.which(ed) Doing.logger.debug('ENV:', "#{ed} not available") end nil end;T;&I"4def find_default_editor(editor_for = 'default');T;'T;U@k;VIC;[@k;U@k;WIC;[�;U@k;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@ri ;T;: Util;;;;;[�;{�;IC;"Utilities;T;[�;[�;I"Utilities;T; 0;!@k;4i�;"F;5o;6;7F;8i ;9i ;$@;I"Doing::Util;F;'To; ;IC;[Co;;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;#0;$@�;%I"5def additional_configs @additional_configs end;T;&I"def additional_configs;T;'To;;F; ;;;;I" Doing::WWID#current_section;F;[�;[[@�i;F;:current_section;;;[�;{�;IC;"4Returns the value of attribute current_section. ;T;[�;[�;I"4Returns the value of attribute current_section.;T; 0;!@�;"F;#0;$@�;%I"/def current_section @current_section end;T;&I"def current_section;T;'To;;F; ;;;;I"Doing::WWID#doing_file;F;[�;[[@�i;F;:doing_file;;;[�;{�;IC;"/Returns the value of attribute doing_file. ;T;[�;[�;I"/Returns the value of attribute doing_file.;T; 0;!@�;"F;#0;$@�;%I"%def doing_file @doing_file end;T;&I"def doing_file;T;'To;;F; ;;;;I"Doing::WWID#content;F;[�;[[@�i;F;:content;;;[�;{�;IC;",Returns the value of attribute content. ;T;[�;[�;I",Returns the value of attribute content.;T; 0;!@�;"F;#0;$@�;%I"def content @content end;T;&I"def content;T;'To;;F; ;;;;I"Doing::WWID#config;F;[�;[[@�i;F;:config;;;[�;{�;IC;"+Returns the value of attribute config. ;T;[�;[�;I"+Returns the value of attribute config.;T; 0;!@�;"F;#0;$@�;%I"def config @config end;T;&I"def config;T;'To;;F; ;;;;I"Doing::WWID#config=;F;[[@0;[[@�i;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;$@�;%I"-def config=(value) @config = value end;T;&I"def config=(value);T;'To;;F; ;;;;I"Doing::WWID#config_file;F;[�;[[@�i;F;:config_file;;;[�;{�;IC;"0Returns the value of attribute config_file. ;T;[�;[�;I"0Returns the value of attribute config_file.;T; 0;!@�;"F;#0;$@�;%I"'def config_file @config_file end;T;&I"def config_file;T;'To;;F; ;;;;I"Doing::WWID#config_file=;F;[[@0;[[@�i;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;$@�;%I"7def config_file=(value) @config_file = value end;T;&I"def config_file=(value);T;'To;;F; ;;;;I"Doing::WWID#auto_tag;F;[�;[[@�i;F;: auto_tag;;;[�;{�;IC;"-Returns the value of attribute auto_tag. ;T;[�;[�;I"-Returns the value of attribute auto_tag.;T; 0;!@;"F;#0;$@�;%I"!def auto_tag @auto_tag end;T;&I"def auto_tag;T;'To;;F; ;;;;I"Doing::WWID#auto_tag=;F;[[@0;[[@�i;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;$@�;%I"1def auto_tag=(value) @auto_tag = value end;T;&I"def auto_tag=(value);T;'To;;F; ;;;;I"Doing::WWID#default_option;F;[�;[[@�i;F;:default_option;;;[�;{�;IC;"3Returns the value of attribute default_option. ;T;[�;[�;I"3Returns the value of attribute default_option.;T; 0;!@6;"F;#0;$@�;%I"-def default_option @default_option end;T;&I"def default_option;T;'To;;F; ;;;;I" Doing::WWID#default_option=;F;[[@0;[[@�i;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;!@C;[�;I"eSets the attribute default_option @param value the value to set the attribute default_option to.;T; 0;!@C;"F;#0;$@�;%I"=def default_option=(value) @default_option = value end;T;&I"def default_option=(value);T;'To;;F; ;;;;I"Doing::WWID#initialize;F;[�;[[@�i;T;;;;;;[�;{�;IC;"Initializes the object.;T;[o;) ;*I"return;F;+I"a new instance of WWID;T;0;,[I" WWID;F;!@U;[�;I" Initializes the object. ;T; 0;!@U;4i�;"T;5o;6;7F;8i;9i;$@�;:T;%I"gdef initialize @timers = {} @recorded_items = [] @content = Items.new @auto_tag = true end;T;&I"def initialize;T;'To;;F; ;;;;I"Doing::WWID#logger;F;[�;[[@�i.;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;!@h;4i�;"T;5o;6;7F;8i%;9i-;$@�;: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;[[@�i7;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;!@v;[�;I"f Initializes the doing file. @param path [String] Override path to a doing file, optional ;T; 0;!@v;4i�;"T;5o;6;7F;8i2;9i6;$@�;: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 logger.debug('Read:', "read file #{@doing_file}") 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 logger.debug('Read:', "read file #{File.expand_path(path)}") 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 logger.debug('Read:', "read file #{File.expand_path(path)}") end @other_content_top = [] @other_content_bottom = [] section = nil lines = input.split(/[\n\r]/) lines.each do |line| next if line =~ /^\s*$/ if line =~ /^(\S[\S ]+):\s*(@\S+\s*)*$/ section = Regexp.last_match(1) @content.add_section(Section.new(section, original: line), log: false) elsif line =~ /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*)/ if section.nil? section = 'Uncategorized' @content.add_section(Section.new(section, original: 'Uncategorized:'), log: false) end date = Regexp.last_match(1).strip title = Regexp.last_match(2).strip item = Item.new(date, title, section) @content.push(item) elsif @content.count.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.last 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;[[@�iv;T;:create;;;[�;{�;IC;"Create a new doing file;T;[�;[�;I" Create a new doing file ;T; 0;!@�;4i�;"T;5o;6;7F;8is;9iu;$@�;: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[I" message:;TI" :default;T;[[@�i;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~;$@�;:T;%I"�def fork_editor(input = '', message: :default) # 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 unless message.nil? f.puts message == :default ? "# The first line is the entry title, any lines after that are added as a note" : message end 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"3def fork_editor(input = '', message: :default);T;'To;;F; ;;;;I"Doing::WWID#format_input;F;[[I" input;T0;[[@�i�;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�;$@�;:T;%I"def 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? date = nil iso_rx = /\d{4}-\d\d-\d\d \d\d:\d\d/ done_rx = /(?<=^| )@(?<tag>done|finished|completed?)\((?<date>.*?)\)/i date_rx = /^(?:\s*- )?(?<date>.*?) \| (?=\S)/ title.gsub!(done_rx) do m = Regexp.last_match t = m['tag'] d = m['date'] parsed_date = d =~ date_rx ? Time.parse(d) : d.chronify(guess: :begin) parsed_date.nil? ? m[0] : "@#{t}(#{parsed_date.strftime('%F %R')})" end if title =~ date_rx m = title.match(date_rx) d = m['date'] date = if d =~ iso_rx Time.parse(d) else d.chronify(guess: :begin) end title.sub!(date_rx, '').strip! end 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+\((?<note>.*?)\)$/) do m = Regexp.last_match note.add(m['note']) '' end end note.strip_lines! note.compress [date, title, note] end;T;&I"def format_input(input);T;'To;;F; ;;;;I"Doing::WWID#sections;F;[�;[[@�i�;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�;$@�;:T;%I"/def sections @content.section_titles end;T;&I"def sections;T;'To;;F; ;;;;I"Doing::WWID#guess_section;F;[[I" frag;T0[I" guessed:;TI" false;T[I" suggest:;TI" false;T;[[@�i�;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�;$@�;:T;%I"def guess_section(frag, guessed: false, suggest: false) return 'All' if frag =~ /^all$/i frag ||= @config['current_section'] return frag.cap_first if @content.section?(frag) section = nil 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 = Prompt.yn("#{boldwhite("Did you mean")} `#{yellow("doing view #{alt}")}#{boldwhite}`?", default_response: 'n') raise WrongCommand.new("run again with #{"doing view #{alt}".boldwhite}", topic: 'Try again:') if meant_view end res = Prompt.yn("#{boldwhite}Section #{frag.yellow}#{boldwhite} not found, create it", default_response: 'n') if res @content.add_section(frag.cap_first, log: true) write(@doing_file) return frag.cap_first end raise InvalidSection.new("unknown section #{frag.bold.white}", topic: 'Missing:') end section ? section.cap_first : guessed end;T;&I"<def guess_section(frag, guessed: false, suggest: false);T;'To;;F; ;;;;I"Doing::WWID#guess_view;F;[[I" frag;T0[I" guessed:;TI" false;T[I" suggest:;TI" false;T;[[@�i;T;:guess_view;;;[�;{�;IC;"4Attempt to match a string with an existing view;T;[o;) ;*I" param;F;+I"The user-provided string;T;I" frag;T;,[I"String;T;!@o;) ;*I" param;F;+I"already guessed;T;I"guessed;T;,[I"Boolean;T;!@;[�;I"� Attempt to match a string with an existing view @param frag [String] The user-provided string @param guessed [Boolean] already guessed ;T; 0;!@;4i�;"T;5o;6;7F;8i;9i;$@�;:T;%I"def guess_view(frag, guessed: false, suggest: false) views.each { |view| return view if frag.downcase == view.downcase } view = false re = frag.split('').join('.*?') views.each do |v| next unless v =~ /#{re}/i logger.debug('Match:', %(Assuming "#{v}" from "#{frag}")) view = v break end unless view || guessed alt = guess_section(frag, guessed: true, suggest: true) raise InvalidView.new(%(unknown view #{frag.bold.white}), topic: 'Missing:') unless alt meant_view = Prompt.yn("Did you mean `doing show #{alt}`?", default_response: 'n') raise WrongCommand.new("run again with #{"doing show #{alt}".yellow}", topic: 'Try again:') if meant_view raise InvalidView.new(%(unknown view #{alt.bold.white}), topic: 'Missing:') end view end;T;&I"9def guess_view(frag, guessed: false, suggest: false);T;'To;;F; ;;;;I"Doing::WWID#add_item;F;[[I" title;T0[I"section;TI"nil;T[I"opt;TI"{};T;[[@�iC;T;: add_item;;;[�;{�;IC;"Adds an entry;T;[o;) ;*I" param;F;+I"The entry title;T;I" title;T;,[I"String;T;!@2o;) ;*I" param;F;+I"The section to add to;T;I"section;T;,[I"String;T;!@2o;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@2o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"item start date;T;I" :date;T;,[I" Date;T;E0;!@2o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"5item note (will be converted if value is String);T;I" :note;T;,[I" Array;T;E0;!@2o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I" backdate;T;I" :back;T;,[I" Date;T;E0;!@2o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I";new item is timed entry, marks previous entry as @done;T;I":timed;T;,[I"Boolean;T;E0;!@2;[�;I"� Adds an entry @param title [String] The entry title @param section [String] The section to add to @param opt [Hash] Additional Options @option opt :date [Date] item start date @option opt :note [Array] item note (will be converted if value is String) @option opt :back [Date] backdate @option opt :timed [Boolean] new item is timed entry, marks previous entry as @done ;T; 0;!@2;4i�;"T;5o;6;7F;8i7;9iB;$@�;:T;%I"�def add_item(title, section = nil, opt = {}) section ||= @config['current_section'] @content.add_section(section, log: false) opt[:back] ||= opt[:date] ? opt[:date] : Time.now opt[:date] ||= Time.now note = Note.new opt[:timed] ||= false note.add(opt[:note]) if opt[:note] title = [title.strip.cap_first] title = title.join(' ') if @auto_tag title = autotag(title) title.add_tags!(@config['default_tags']) unless @config['default_tags'].empty? end title.compress! entry = Item.new(opt[:back], title.strip, section) entry.note = note items = @content.dup if opt[:timed] items.reverse! items.each_with_index do |i, x| next if i.title =~ / @done/ items[x].title = "#{i.title} @done(#{opt[:back].strftime('%F %R')})" break end end @content.push(entry) # logger.count(:added, level: :debug) logger.info('New entry:', %(added "#{entry.title}" to #{section})) end;T;&I"1def add_item(title, section = nil, opt = {});T;'To;;F; ;;;;I"Doing::WWID#dedup;F;[[I" items;T0[I"no_overlap:;TI" false;T;[[@�io;T;: dedup;;;[�;{�;IC;"<Remove items from a list that already exist in @content;T;[o;) ;*I" param;F;+I"The items to deduplicate;T;I" items;T;,[I" Array;T;!@~o;) ;*I" param;F;+I"-Remove items with overlapping time spans;T;I"no_overlap;T;,[I"Boolean;T;!@~;[�;I"� Remove items from a list that already exist in @content @param items [Array] The items to deduplicate @param no_overlap [Boolean] Remove items with overlapping time spans ;T; 0;!@~;4i�;"T;5o;6;7F;8ii;9in;$@�;:T;%I"�def dedup(items, no_overlap: false) items.delete_if do |item| duped = false @content.each do |comp| duped = no_overlap ? item.overlapping_time?(comp) : item.same_time?(comp) break if duped end logger.count(:skipped, level: :debug, message: '%count overlapping %items') if duped # logger.log_now(:debug, 'Skipped:', "overlapping entry: #{item.title}") if duped duped end end;T;&I"(def dedup(items, no_overlap: false);T;'To;;F; ;;;;I"Doing::WWID#import;F;[[I" paths;T0[I"opt;TI"{};T;[[@�i�;T;:import;;;[�;{�;IC;"Imports external entries;T;[o;) ;*I" param;F;+I"Path to JSON report file;T;I" paths;T;,[I"String;T;!@�o;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@�;[�;I"} Imports external entries @param paths [String] Path to JSON report file @param opt [Hash] Additional Options ;T; 0;!@�;4i�;"T;5o;6;7F;8i|;9i�;$@�;:T;%I"ndef import(paths, opt = {}) Plugins.plugins[:import].each do |_, options| next unless opt[:type] =~ /^(#{options[:trigger].normalize_trigger})$/i if paths.count.positive? paths.each do |path| options[:class].import(self, path, options: opt) end else options[:class].import(self, nil, options: opt) end break end end;T;&I" def import(paths, opt = {});T;'To;;F; ;;;;I"Doing::WWID#last_note;F;[[I"section;TI" 'All';T;[[@�i�;T;:last_note;;;[�;{�;IC;"<Return the content of the last note for a given section;T;[o;) ;*I" param;F;+I".The section to retrieve from, default All;T;I"section;T;,[I"String;T;!@�o;) ;*I" raise;F;+@ ;0;,[I"NoEntryError;T;!@�;[�;I"� Return the content of the last note for a given section @param section [String] The section to retrieve from, default All ;T; 0;!@�;4i�;"T;5o;6;7F;8i�;9i�;$@�;:T;%I"Pdef last_note(section = 'All') section = guess_section(section) last_item = last_entry({ section: section }) raise NoEntryError, 'No entry found' unless last_item logger.log_now(:info, 'Edit note:', last_item.title) note = last_item.note&.to_s || '' "#{last_item.title}\n# EDIT BELOW THIS LINE ------------\n#{note}" end;T;&I"#def last_note(section = 'All');T;'To;;F; ;;;;I"Doing::WWID#reset_item;F;[[I" item;T0[I"resume:;TI" false;T;[[@�i�;T;:reset_item;;;[�;{�;IC;"JReset start date to current time, optionally remove done tag (resume);T;[o;) ;*I" param;F;+I"the item to reset/resume;T;I" item;T;,[I" Item;T;!@�o;) ;*I" param;F;+I"removing @done tag if true;T;I"resume;T;,[I"Boolean;T;!@�;[�;I"�Reset start date to current time, optionally remove done tag (resume) @param item [Item] the item to reset/resume @param resume [Boolean] removing @done tag if true ;T; 0;!@�;4i�;"F;5o;6;7F;8i�;9i�;$@�;:T;%I"�def reset_item(item, resume: false) item.date = Time.now item.tag('done', remove: true) if resume logger.info('Reset:', %(Reset #{resume ? 'and resumed ' : ''} "#{item.title}" in #{item.section})) item end;T;&I"(def reset_item(item, resume: false);T;'To;;F; ;;;;I"Doing::WWID#repeat_item;F;[[I" item;T0[I"opt;TI"{};T;[[@�i�;T;:repeat_item;;;[�;{�;IC;"/Duplicate an item and add it as a new item;T;[o;) ;*I" param;F;+I"the item to duplicate;T;I" item;T;,[I" Item;T;!@�o;) ;*I" param;F;+I"additional options;T;I"opt;T;,[I" Hash;T;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"open new item in editor;T;I":editor;T;,[I"Boolean;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"set start date;T;I" :date;T;,[I"String;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I" add new item to section :in;T;I":in;T;,[I"String;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"add note to new item;T;I" :note;T;,[I" Note;T;E0;!@�o;) ;*I"return;F;+I"nothing;T;0;,0;!@�;[�;I"lDuplicate an item and add it as a new item @param item [Item] the item to duplicate @param opt [Hash] additional options @option opt :editor [Boolean] open new item in editor @option opt :date [String] set start date @option opt :in [String] add new item to section :in @option opt :note [Note] add note to new item @return nothing ;T; 0;!@�;4i�;"F;5o;6;7F;8i�;9i�;$@�;:T;%I"�def repeat_item(item, opt = {}) if item.should_finish? if item.should_time? item.title.tag!('done', value: Time.now.strftime('%F %R')) else item.title.tag!('done') end end # Remove @done tag title = item.title.sub(/\s*@done(\(.*?\))?/, '').chomp section = opt[:in].nil? ? item.section : guess_section(opt[:in]) @auto_tag = false note = opt[:note] || Note.new if opt[:editor] start = opt[:date] ? opt[:date] : Time.now to_edit = "#{start.strftime('%F %R')} | #{title}" to_edit += "\n#{note.strip_lines.join("\n")}" unless note.empty? new_item = fork_editor(to_edit) date, title, note = format_input(new_item) opt[:date] = date unless date.nil? if title.nil? || title.empty? logger.warn('Skipped:', 'No content provided') return end end # @content.update_item(original, item) add_item(title, section, { note: note, back: opt[:date], timed: true }) end;T;&I"$def repeat_item(item, opt = {});T;'To;;F; ;;;;I"Doing::WWID#repeat_last;F;[[I"opt;TI"{};T;[[@�i�;T;:repeat_last;;;[�;{�;IC;"Restart the last entry;T;[o;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@<;[�;I"J Restart the last entry @param opt [Hash] Additional Options ;T; 0;!@<;4i�;"T;5o;6;7F;8i�;9i�;$@�;:T;%I"Gdef repeat_last(opt = {}) opt[:section] ||= 'all' opt[:section] = guess_section(opt[:section]) opt[:note] ||= [] opt[:tag] ||= [] opt[:tag_bool] ||= :and last = last_entry(opt) if last.nil? logger.warn('Skipped:', 'No previous entry found') return end repeat_item(last, opt) write(@doing_file) end;T;&I"def repeat_last(opt = {});T;'To;;F; ;;;;I"Doing::WWID#last_entry;F;[[I"opt;TI"{};T;[[@�i�;T;:last_entry;;;[�;{�;IC;"Get the last entry;T;[o;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@S;[�;I"F Get the last entry @param opt [Hash] Additional Options ;T; 0;!@S;4i�;"T;5o;6;7F;8i�;9i�;$@�;:T;%I"3def last_entry(opt = {}) opt[:tag_bool] ||= :and opt[:section] ||= @config['current_section'] items = filter_items(Items.new, opt: opt) logger.debug('Filtered:', "Parameters matched #{items.count} entries") if opt[:interactive] last_entry = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i, menu: true, header: '', prompt: 'Select an entry > ', multiple: false, sort: false, show_if_single: true ) 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#all_tags;F;[[I" items;T0[I" opt:;TI"{};T;[[@�i;F;: all_tags;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@j;4i�;$@�;: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;[[@�i;F;:tag_groups;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@{;4i�;$@�;: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#fuzzy_filter_items;F;[[I" items;T0[I" opt:;TI"{};T;[[@�i%;F;:fuzzy_filter_items;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@�;:T;%I"def fuzzy_filter_items(items, opt: {}) scannable = items.map.with_index { |item, idx| "#{item.title} #{item.note.join(' ')}".gsub(/[|*?!]/, '') + "|#{idx}" }.join("\n") fzf_args = [ '--multi', %(--filter="#{opt[:search].sub(/^'?/, "'")}"), '--no-sort', '-d "\|"', '--nth=1' ] if opt[:case] fzf_args << case opt[:case].normalize_case when :sensitive '+i' when :ignore '-i' end end # fzf_args << '-e' if opt[:exact] # puts fzf_args.join(' ') res = `echo #{Shellwords.escape(scannable)}|#{Prompt.fzf} #{fzf_args.join(' ')}` selected = Items.new res.split(/\n/).each do |item| idx = item.match(/\|(\d+)$/)[1].to_i selected.push(items[idx]) end selected end;T;&I"+def fuzzy_filter_items(items, opt: {});T;'To;;F; ;;;;I"Doing::WWID#filter_items;F;[[I" items;TI"Items.new;T[I" opt:;TI"{};T;[[@�iV;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;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I" :section;T;,[I"String;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":unfinished;T;,[I"Boolean;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"�;T;I" :tag;T;,[I"Array or String;T;E[I"$Array or comma-separated string;T;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"�;T;I":tag_bool;T;,[I"Symbol;T;E[I" :and;TI":or;TI" :not;T;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"�;T;I":search;T;,[I"String;T;E[I"string;TI"optional regex with //;T;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"[[Time]start, [Time]end];T;I":date_filter;T;,[I" Array;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":only_timed;T;,[I"Boolean;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"�;T;I":before;T;,[I"String;T;E[I"Date/Time string;TI" unparsed;T;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"�;T;I":after;T;,[I"String;T;E[I"Date/Time string;TI" unparsed;T;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":today;T;,[I"Boolean;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":yesterday;T;,[I"Boolean;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"�;T;I":count;T;,[I"Number;T;E[I"Number to return;T;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"�;T;I" :age;T;,[I"String;T;E[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;8iB;9iU;$@�;:T;%I"�def filter_items(items = Items.new, opt: {}) time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/ if items.nil? || items.empty? section = opt[:section] ? guess_section(opt[:section]) : 'All' items = section =~ /^all$/i ? @content.dup : @content.in_section(section) end opt[:time_filter] = [nil, nil] if opt[:from] && !opt[:date_filter] date_string = opt[:from] case date_string when / (to|through|thru|(un)?til|-+) / dates = date_string.split(/ (?:to|through|thru|(?:un)?til|-+) /) if dates[0].strip =~ time_rx && dates[-1].strip =~ time_rx time_start = dates[0].strip time_end = dates[-1].strip else start = dates[0].chronify(guess: :begin) finish = dates[-1].chronify(guess: :end) end when time_rx time_start = date_string time_end = nil else start = date_string.chronify(guess: :begin) finish = false end if time_start opt[:time_filter] = [time_start, time_end] Doing.logger.debug('Parser:', "--from string interpreted as time span, from #{time_start ? time_start : '12am'} to #{time_end ? time_end : '11:59pm'}") else raise InvalidTimeExpression, 'Unrecognized date string' unless start opt[:date_filter] = [start, finish] Doing.logger.debug('Parser:', "--from string interpreted as #{start.strftime('%F %R')} -- #{finish ? finish.strftime('%F %R') : 'now'}") end end if opt[:before] =~ time_rx opt[:time_filter][1] = opt[:before] opt[:before] = nil end if opt[:after] =~ time_rx opt[:time_filter][0] = opt[:after] opt[:after] = nil 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] search_match = if opt[:search].nil? || opt[:search].empty? true else item.search(opt[:search], case_type: opt[:case].normalize_case, fuzzy: opt[:fuzzy]) 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 if keep && opt[:time_filter][0] || opt[:time_filter][1] start_string = if opt[:time_filter][0].nil? "#{item.date.strftime('%Y-%m-%d')} 12am" else "#{item.date.strftime('%Y-%m-%d')} #{opt[:time_filter][0]}" end start_time = start_string.chronify(guess: :begin) end_string = if opt[:time_filter][1].nil? "#{item.date.next_day.strftime('%Y-%m-%d')} 12am" else "#{item.date.strftime('%Y-%m-%d')} #{opt[:time_filter][1]}" end end_time = end_string.chronify(guess: :end) in_time_range = item.date >= start_time && item.date <= end_time keep = false unless in_time_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] if time_string =~ time_rx cutoff = "#{item.date.strftime('%Y-%m-%d')} #{time_string}".chronify(guess: :begin) else cutoff = time_string.chronify(guess: :begin) end keep = cutoff && item.date <= cutoff keep = opt[:not] ? !keep : keep end if keep && opt[:after] time_string = opt[:after] if time_string =~ time_rx cutoff = "#{item.date.strftime('%Y-%m-%d')} #{time_string}".chronify(guess: :end) else cutoff = time_string.chronify(guess: :end) 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]&.positive? ? opt[:count] : filtered_items.length output = Items.new if opt[:age] =~ /^o/i output.concat(filtered_items.slice(0, count).reverse) else output.concat(filtered_items.reverse.slice(0, count)) end output end;T;&I"1def filter_items(items = Items.new, opt: {});T;'To;;F; ;;;;I"Doing::WWID#interactive;F;[[I"opt;TI"{};T;[[@�i�;T;:interactive;;;[�;{�;IC;"bDisplay an interactive menu of entries Options hash is shared with #filter_items and #act_on;T;[o;) ;*I" param;F;+I"Additional options;T;I"opt;T;,[I" Hash;T;!@@ o;) ;*I" raise;F;+@ ;0;,[I"NoResults;T;!@@ ;[�;I"� Display an interactive menu of entries @param opt [Hash] Additional options Options hash is shared with #filter_items and #act_on ;T; 0;!@@ ;4i�;"T;5o;6;7F;8i�;9i�;$@�;:T;%I"def interactive(opt = {}) 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 opt[:show_if_single] = true filter_options = %i[after before case date_filter from fuzzy not search section].each_with_object({}) { |k, hsh| hsh[k] = opt[k] } items = filter_items(Items.new, opt: filter_options) selection = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i, **opt) raise NoResults, 'no items selected' if selection.nil? || selection.empty? act_on(selection, opt) end;T;&I"def interactive(opt = {});T;'To;;F; ;;;;I"Doing::WWID#act_on;F;[[I" items;T0[I"opt;TI"{};T;[[@�i0;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"Array of Items to affect;T;I" items;T;,[I" Array;T;!@[ o;) ;*I" param;F;+I"#Options and actions to perform;T;I"opt;T;,[I" Hash;T;!@[ o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":editor;T;,[I"Boolean;T;E0;!@[ o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":delete;T;,[I"Boolean;T;E0;!@[ o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I" :tag;T;,[I"String;T;E0;!@[ o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I" :flag;T;,[I"Boolean;T;E0;!@[ o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":finish;T;,[I"Boolean;T;E0;!@[ o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":cancel;T;,[I"Boolean;T;E0;!@[ o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I" :archive;T;,[I"Boolean;T;E0;!@[ o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":output;T;,[I"String;T;E0;!@[ o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I" :save_to;T;,[I"String;T;E0;!@[ o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":again;T;,[I"Boolean;T;E0;!@[ o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":resume;T;,[I"Boolean;T;E0;!@[ ;[�;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 [Array] Array of Items to affect @param opt [Hash] Options and actions to perform @option opt [Boolean] :editor @option opt [Boolean] :delete @option opt [String] :tag @option opt [Boolean] :flag @option opt [Boolean] :finish @option opt [Boolean] :cancel @option opt [Boolean] :archive @option opt [String] :output @option opt [String] :save_to @option opt [Boolean] :again @option opt [Boolean] :resume ;T; 0;!@[ ;4i�;"T;5o;6;7F;8i;9i/;$@�;:T;%I"�def 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 = Prompt.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 "#{yellow("Tag to #{type}: ")}#{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 = Prompt.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 : Prompt.yn('Save to file?', default_response: 'n') if res print "#{yellow('File path/name: ')}#{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] raise InvalidArgument, 'resume and restart can only be used on a single entry' if items.count > 1 item = items[0] if opt[:resume] && !opt[:reset] repeat_item(item, { editor: opt[:editor] }) elsif opt[:reset] res = if item.tags?('done', :and) && !opt[:resume] opt[:force] ? true : Prompt.yn('Remove @done tag?', default_response: 'y') else opt[:resume] end @content.update_item(item, reset_item(item, resume: res)) end write(@doing_file) return end if opt[:delete] res = opt[:force] ? true : Prompt.yn("Delete #{items.size} items?", default_response: 'y') if res items.each { |i| @content.delete_item(i, single: items.count == 1) } write(@doing_file) end return end if opt[:flag] tag = @config['marker_tag'] || 'flagged' items.map! do |i| i.tag(tag, date: false, remove: opt[:remove], single: single) end end if opt[:finish] || opt[:cancel] tag = 'done' items.map! do |i| if i.should_finish? should_date = !opt[:cancel] && i.should_time? i.tag(tag, date: should_date, remove: opt[:remove], single: single) end end end if opt[:tag] tag = opt[:tag] items.map! do |i| i.tag(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! { |i| i.move_to(section, label: true) } end write(@doing_file) if opt[:editor] editable_items = [] items.each do |i| editable = "#{i.date.strftime('%F %R')} | #{i.title}" old_note = i.note ? i.note.strip_lines.join("\n") : nil editable += "\n#{old_note}" unless old_note.nil? editable_items << editable end divider = "\n-----------\n" notice =<<~EONOTICE # - You may delete entries, but leave all divider lines (---) in place. # - Start and @done dates replaced with a time string (yesterday 3pm) will # be parsed automatically. Do not delete the pipe (|) between start date # and entry title. EONOTICE input = "#{editable_items.map(&:strip).join(divider)}\n\n#{notice}" 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?) first_line = input_lines[0]&.strip if first_line.nil? || first_line =~ /^#{divider.strip}$/ || first_line.strip.empty? @content.delete_item(items[i], single: new_items.count == 1) Doing.logger.count(:deleted) else date, title, note = format_input(new_item) note.map!(&:strip) note.delete_if(&:ignore?) item = items[i] old_item = item.dup item.date = date || items[i].date item.title = title item.note = note if (item.equal?(old_item)) Doing.logger.count(:skipped, level: :debug) else Doing.logger.count(:updated) end end end write(@doing_file) end return unless opt[:output] items.map! do |i| i.title = "#{i.title} @project(#{i.section})" i end @content = Items.new @content.concat(items) @content.add_section(Section.new('Export'), log: false) 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;T;&I" def act_on(items, opt = {});T;'To;;F; ;;;;I"Doing::WWID#tag_last;F;[[I"opt;TI"{};T;[[@�i*;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;8i!;9i);$@�;: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(Items.new, opt: opt) if opt[:interactive] items = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i, menu: true, header: '', prompt: 'Select entries to tag > ', multiple: true, sort: true, show_if_single: true) 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 logger.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? item.move_to('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#next_item;F;[[I" item;T0[I"options;TI"{};T;[[@�i�;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�;$@�;:T;%I"�def next_item(item, options = {}) items = filter_items(Items.new, 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#edit_last;F;[[I" section:;TI" 'All';T[I" options:;TI"{};T;[[@�i�;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�;9i�;$@�;:T;%I"Ndef 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.date.strftime('%F %R')} | #{item.title.dup}"] content << item.note.strip_lines.join("\n") unless item.note.empty? new_item = fork_editor(content.join("\n")) date, title, note = format_input(new_item) date ||= item.date if title.nil? || title.empty? logger.debug('Skipped:', 'No content provided') elsif title == item.title && note.equal?(item.note) && date.equal?(item.date) logger.debug('Skipped:', 'No change in content') else item.date = date unless date.nil? 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;[[@�i�;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;!@1 o;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@1 o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"target section;T;I" :section;T;,[I"String;T;E0;!@1 o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"archive old item;T;I" :archive;T;,[I"Boolean;T;E0;!@1 o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"backdate new item;T;I" :back;T;,[I" Date;T;E0;!@1 o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I" content to use for new item;T;I":new_item;T;,[I"String;T;E0;!@1 o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"note content for new item;T;I" :note;T;,[I" Array;T;E0;!@1 ;[�;I"] 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 @option opt :section [String] target section @option opt :archive [Boolean] archive old item @option opt :back [Date] backdate new item @option opt :new_item [String] content to use for new item @option opt :note [Array] note content for new item;T; 0;!@1 ;4i�;"T;5o;6;7F;8i�;9i�;$@�;: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.each_with_index do |item, i| next unless item.section == opt[:section] || opt[:section] =~ /all/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})") item.move_to('Archive', label: false, log: 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] date, title, note = format_input(opt[:new_item]) opt[:back] = date unless date.nil? 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;[[@�i;T;;];;;[�;{�;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;!@} ;[�;I"[ Write content to file or STDOUT @param file [String] The filepath to write to ;T; 0;!@} ;4i�;"T;5o;6;7F;8i;9i;$@�;: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;[[@�i%;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$;$@�;:T;%I"�def restore_backup(file) if File.exist?("#{file}~") FileUtils.cp("#{file}~", file) logger.warn('File update:', "Restored #{file.sub(/^#{Util.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;[[@�i1;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.;9i0;$@�;:T;%I"Adef 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' section = guess_section(sect) section_items = @content.in_section(section) max = section_items.count - keep.to_i counter = 0 new_content = Items.new @content.each do |item| break if counter >= max if opt[:before] time_string = opt[:before] cutoff = time_string.chronify(guess: :begin) end unless ((!tags.empty? && !item.tags?(tags, bool)) || (opt[:search] && !item.search(opt[:search].to_s)) || (opt[:before] && item.date >= cutoff)) new_item = @content.delete(item) raise DoingRuntimeError, "Error deleting item: #{item}" if new_item.nil? new_content.add_section(new_item.section, log: false) new_content.push(new_item) counter += 1 end end if counter.positive? logger.count(:rotated, level: :info, count: counter, message: "Rotated %count %items") else logger.info('Skipped:', 'No items were rotated') 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.concat(new_content).uniq! 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;[�;[[@�in;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;8ii;9im;$@�;:T;%I"�def choose_section choice = Prompt.choose_from(@content.section_titles.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;[�;[[@�ix;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;8is;9iw;$@�;: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;[�;[[@�i�;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�;$@�;:T;%I"�def choose_view choice = Prompt.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;[[@�i�;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�;$@�;: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;[[@�i�;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�;$@�;:T;%I"�def list_section(opt = {}) opt[:config_template] ||= 'default' cfg = @config.dig('templates', opt[:config_template]).deep_merge({ 'wrap_width' => @config['wrap_width'] || 0, 'date_format' => @config['default_date_format'], 'order' => @config['order'] || 'asc', 'tags_color' => @config['tags_color'] }) opt[:count] ||= 0 opt[:age] ||= 'newest' opt[:format] ||= cfg['date_format'] opt[:order] ||= cfg['order'] || 'asc' opt[:tag_order] ||= 'asc' if opt[:tags_color].nil? opt[:tags_color] = cfg['tags_color'] || false end opt[:template] ||= cfg['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(Items.new, 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 = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i, **opt) 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'] || 0 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;[[@�i�;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;8i�;9i�;$@�;: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 @content.add_section(destination, log: true) # add_section(Section.new('Archive')) if destination =~ /^archive$/i && !@content.section?('Archive') destination = guess_section(destination) if @content.section?(destination) && (@content.section?(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;[[@�i�;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;!@Do;) ;*I" param;F;+I"output format;T;I"output;T;,[I"String;T;!@Do;) ;*I" param;F;+I"Options;T;I"opt;T;,[I" Hash;T;!@D;[�;I"� Show all entries from the current day @param times [Boolean] show times @param output [String] output format @param opt [Hash] Options ;T; 0;!@D;4i�;"T;5o;6;7F;8i�;9i�;$@�;:T;%I"Xdef today(times = true, output = nil, opt = {}) opt[:totals] ||= false opt[:sort_tags] ||= false cfg = @config['templates']['today'].deep_merge(@config['templates']['default']).deep_merge({ 'wrap_width' => @config['wrap_width'] || 0, 'date_format' => @config['default_date_format'], 'order' => @config['order'] || 'asc', 'tags_color' => @config['tags_color'] }) options = { after: opt[:after], before: opt[:before], count: 0, from: opt[:from], format: cfg['date_format'], order: cfg['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'], tags_color: cfg['tags_color'], config_template: 'today' } 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;[[@�i;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;!@mo;) ;*I" param;F;+I"The section;T;I"section;T;,[I"String;T;!@mo;) ;*I" param;F;+I"Show times;T;I" times;T;,[I" Bool;T;!@mo;) ;*I" param;F;+I"Output format;T;I"output;T;,[I"String;T;!@mo;) ;*I" param;F;+I"Additional Options;T;I"opt;T;,[I" Hash;T;!@m;[�;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;!@m;4i�;"T;5o;6;7F;8i;9i;$@�;: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], config_template: 'default' }) 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;[[@�i2;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*;9i1;$@�;:T;%I"�def 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, from: opt[:from], order: opt[:order], output: output, section: section, sort_tags: opt[:sort_tags], tag_order: opt[:tag_order], times: times, totals: opt[:totals], yesterday: true, config_template: 'today' } 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;[[@�iT;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;8iM;9iS;$@�;:T;%I"1def recent(count = 10, section = nil, opt = {}) times = opt[:t] || true opt[:totals] ||= false opt[:sort_tags] ||= false cfg = @config['templates']['recent'].deep_merge(@config['templates']['default']).deep_merge({ 'wrap_width' => @config['wrap_width'] || 0, 'date_format' => @config['default_date_format'], 'order' => @config['order'] || 'asc', 'tags_color' => @config['tags_color'] }) 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], config_template: 'recent' }) 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;[[@�in;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;8ih;9im;$@�;:T;%I"pdef last(times: true, section: nil, options: {}) section = section.nil? || section =~ /all/i ? 'All' : guess_section(section) cfg = @config['templates']['last'].deep_merge(@config['templates']['default']).deep_merge({ 'wrap_width' => @config['wrap_width'] || 0, 'date_format' => @config['default_date_format'], 'order' => @config['order'] || 'asc', 'tags_color' => @config['tags_color'] }) 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] opts[:config_template] = 'last' list_section(opts) end;T;&I"5def last(times: true, section: nil, options: {});T;'To;;F; ;;;;I"Doing::WWID#autotag;F;[[I" text;T0;[[@�i�;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�;$@�;:T;%I"� def autotag(text) return unless text return text unless @auto_tag original = text.dup current_tags = text.scan(/@\w+/).map { |t| t.sub(/^@/, '') } tagged = { whitelisted: [], synonyms: [], transformed: [], replaced: [] } @config['autotag']['whitelist'].each do |tag| next if text =~ /@#{tag}\b/i text.sub!(/(?<= |\A)(#{tag.strip})(?= |\Z)/i) do |m| m.downcase! unless tag =~ /[A-Z]/ tagged[:whitelisted].push(m) "@#{m}" end end @config['autotag']['synonyms'].each do |tag, v| v.each do |word| next unless text =~ /\b#{word}\b/i unless current_tags.include?(tag) || tagged[:whitelisted].include?(tag) tagged[:synonyms].push(tag) tagged[:synonyms] = tagged[:synonyms].uniq end end end if @config['autotag'].key? 'transform' @config['autotag']['transform'].each do |tag| next unless tag =~ /\S+:\S+/ rx, r = tag.split(/:/) flag_rx = %r{/([r]+)$} if r =~ flag_rx flags = r.match(flag_rx)[1].split(//) r.sub!(flag_rx, '') end r.gsub!(/\$/, '\\') rx.sub!(/^@?/, '@') regex = Regexp.new("(?<= |\\A)#{rx}(?= |\\Z)") text.sub!(regex) do m = Regexp.last_match new_tag = r m.to_a.slice(1, m.length - 1).each_with_index do |v, idx| new_tag.gsub!("\\#{idx + 1}", v) end # Replace original tag if /r if flags&.include?('r') tagged[:replaced].concat(new_tag.split(/ /).map { |t| t.sub(/^@/, '') }) new_tag.split(/ /).map { |t| t.sub(/^@?/, '@') }.join(' ') else tagged[:transformed].concat(new_tag.split(/ /).map { |t| t.sub(/^@/, '') }) tagged[:transformed] = tagged[:transformed].uniq m[0] end end end end logger.debug('Autotag:', "whitelisted tags: #{tagged[:whitelisted].log_tags}") unless tagged[:whitelisted].empty? logger.debug('Autotag:', "synonyms: #{tagged[:synonyms].log_tags}") unless tagged[:synonyms].empty? logger.debug('Autotag:', "transforms: #{tagged[:transformed].log_tags}") unless tagged[:transformed].empty? logger.debug('Autotag:', "transform replaced: #{tagged[:replaced].log_tags}") unless tagged[:replaced].empty? tail_tags = tagged[:synonyms].concat(tagged[:transformed]) tail_tags.sort! tail_tags.uniq! text.add_tags!(tail_tags) unless tail_tags.empty? if text == original logger.debug('Autotag:', "no change to \"#{text.strip}\"") else new_tags = tagged[:whitelisted].concat(tail_tags).concat(tagged[:replaced]) logger.debug('Autotag:', "added #{new_tags.log_tags} to \"#{text.strip}\"") logger.count(:autotag, level: :info, count: 1, message: 'autotag updated %count %items') end text.dedup_tags end;T;&I"def autotag(text);T;'To;;F; ;;;;I"Doing::WWID#tag_times;F;[[I"format:;TI" :text;T[I"sort_by_name:;TI" false;T[I"sort_order:;TI" 'asc';T;[[@�i�;T;:tag_times;;;[�;{�;IC;"AGet total elapsed time for all tags in selection;T;[o;) ;*I" param;F;+I"(return format (html, json, or text);T;I"format;T;,[I"String;T;!@9o;) ;*I" param;F;+I",Sort by name if true, otherwise by time;T;I"sort_by_name;T;,[I"Boolean;T;!@9o;) ;*I" param;F;+I"!The sort order (asc or desc);T;I"sort_order;T;,[I"String;T;!@9;[�;I", Get total elapsed time for all tags in selection @param format [String] return format (html, json, or text) @param sort_by_name [Boolean] Sort by name if true, otherwise by time @param sort_order [String] The sort order (asc or desc) ;T; 0;!@9;4i�;"T;5o;6;7F;8i�;9i�;$@�;:T;%I"� def tag_times(format: :text, sort_by_name: false, sort_order: 'asc') return '' if @timers.empty? max = @timers.keys.sort_by { |k| k.length }.reverse[0].length + 1 total = @timers.delete('All') tags_data = @timers.delete_if { |_k, v| v == 0 } sorted_tags_data = if sort_by_name tags_data.sort_by { |k, _v| k } else tags_data.sort_by { |_k, v| v } end sorted_tags_data.reverse! if sort_order =~ /^asc/i case format when :html output = <<EOS <table> <caption id="tagtotals">Tag Totals</caption> <colgroup> <col style="text-align:left;"/> <col style="text-align:left;"/> </colgroup> <thead> <tr> <th style="text-align:left;">project</th> <th style="text-align:left;">time</th> </tr> </thead> <tbody> EOS sorted_tags_data.reverse.each do |k, v| if v > 0 output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{'%02d:%02d:%02d' % format_time(v)}</td></tr>\n" end end tail = <<EOS <tr> <td style="text-align:left;" colspan="2"></td> </tr> </tbody> <tfoot> <tr> <td style="text-align:left;"><strong>Total</strong></td> <td style="text-align:left;">#{'%02d:%02d:%02d' % format_time(total)}</td> </tr> </tfoot> </table> 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('%<d>02d:%<h>02d:%<m>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('%<h> 4dh %<m>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('%<h> 4dh %<m>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('%<d>02d:%<h>02d:%<m>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('%<d>02d:%<h>02d:%<m>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;[[@�i�;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;!@bo;) ;*I" param;F;+I"1Return human readable time (default seconds);T;I"formatted;T;,[I"Boolean;T;!@bo;) ;*I" param;F;+I"/Add the interval to the total for each tag;T;I"record;T;,[I"Boolean;T;!@bo;) ;*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;!@b;[�;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;!@b;4i�;"T;5o;6;7F;8ix;9i�;$@�;: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;[[@�i�;T;:format_time;;;[�;{�;IC;",Format human readable time from seconds;T;[o;) ;*I" param;F;+I"Seconds;T;I"seconds;T;,[I"Integer;T;!@�;[�;I"V Format human readable time from seconds @param seconds [Integer] Seconds ;T; 0;!@�;4i�;"T;5o;6;7F;8i�;9i�;$@�;:T;%I"1def format_time(seconds, human: false) return [0, 0, 0] if seconds.nil? if seconds.instance_of?(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; ;;;N;I"!Doing::WWID#combined_content;F;[�;[[@�i�;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�;$@�;:T;%I"~def combined_content output = @other_content_top ? "#{@other_content_top.join("\n")}\n" : '' was_color = Color.coloring? Color.coloring = false output += @content.to_s output += @other_content_bottom.join("\n") unless @other_content_bottom.nil? # Just strip all ANSI colors from the content before writing to doing file Color.coloring = was_color output.uncolor end;T;&I"def combined_content;T;'To;;F; ;;;N;I"Doing::WWID#output;F;[ [I" items;T0[I" title;T0[I"is_single;T0[I"opt;TI"{};T;[[@�i�;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"<formatted output based on opt[:output] template trigger;T;0;,[I"String;T;!@�o;) ;*I" raise;F;+@ ;0;,[I"InvalidArgument;T;!@�;[�;I"d Generate output using available export plugins @param items [Array] The items @param title [String] Page title @param is_single [Boolean] Indicates if single section @param opt [Hash] Additional options @return [String] formatted output based on opt[:output] template trigger ;T; 0;!@�;4i�;"T;5o;6;7F;8i�;9i�;$@�;:T;%I"�def output(items, title, is_single, opt = {}) out = nil raise InvalidArgument, 'Unknown output format' unless opt[:output] =~ Plugins.plugin_regex(type: :export) export_options = { page_title: title, is_single: is_single, options: opt } Plugins.plugins[:export].each do |_, options| next unless opt[:output] =~ /^(#{options[:trigger].normalize_trigger})$/i out = options[:class].render(self, items, variables: export_options) break end out end;T;&I"2def output(items, title, is_single, opt = {});T;'To;;F; ;;;N;I"!Doing::WWID#record_tag_times;F;[[I" item;T0[I"seconds;T0;[[@�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;8i�;9i�;$@�;: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; ;;;N;I"Doing::WWID#do_archive;F;[[I"section;T0[I"destination;T0[I"opt;TI"{};T;[[@�i�;T;:do_archive;;;[�;{�;IC;"3Helper function, performs the actual archiving;T;[o;) ;*I" param;F;+I"The source section;T;I"section;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 section [String] The source section @param destination [String] The destination section @param opt [Hash] Additional Options ;T; 0;!@ ;4i�;"T;5o;6;7F;8i�;9i�;$@�;:T;%I"�def do_archive(section, destination, opt = {}) count = opt[:count] || 0 tags = opt[:tags] || [] bool = opt[:bool] || :and label = opt[:label] || true section = guess_section(section) destination = guess_section(destination) section_items = @content.in_section(section) max = section_items.count - count.to_i counter = 0 @content.map! do |item| break if counter >= max if opt[:before] time_string = opt[:before] cutoff = time_string.chronify(guess: :begin) end if (item.section.downcase != section.downcase && section != /^all$/i) || item.section.downcase == destination.downcase item elsif ((!tags.empty? && !item.tags?(tags, bool)) || (opt[:search] && !item.search(opt[:search].to_s)) || (opt[:before] && item.date >= cutoff)) item else counter += 1 item.move_to(destination, label: label, log: false) end end if counter.positive? logger.count(destination == 'Archive' ? :archived : :moved, level: :info, count: counter, message: "%count %items from #{section} to #{destination}") else logger.info('Skipped:', 'No items were moved') end end;T;&I"3def do_archive(section, destination, opt = {});T;'To;;F; ;;;N;I"Doing::WWID#run_after;F;[�;[[@�i";F;:run_after;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@0 ;4i�;$@�;: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;'T;U@�;VIC;[�;U@�;WIC;[o;b;c0;d0;e0;: Color;$@;go; ;IC;[o:&YARD::CodeObjects::ConstantObject;[[I"lib/doing/colors.rb;Ti ;T;:ATTRIBUTES;;;;;[�;{�;IC;":stopdoc: ;T;[�;[�;I":stopdoc:;T; 0;!@A ;"F;5o;6;7F;8i;9i;$@? ;I"Doing::Color::ATTRIBUTES;F;%I"ZATTRIBUTES = [ [: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'] ].map(&:freeze).freeze;T:@valueI"M[ [: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'] ].map(&:freeze).freeze;T;'To;�;[[@D iC;F;:ATTRIBUTE_NAMES;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@O ;$@? ;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;[[@D iK;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;,[@";!@Z ;[�;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;!@Z ;4i�;"F;5o;6;7F;8iE;9iJ;$@? ;: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; ;Z;;;I"Doing::Color.coloring?;F;[�;[[@D iU;T;:coloring?;;;[�;{�;IC;"[Returns true, if the coloring function of this module is switched on, false otherwise.;T;[o;) ;*I"return;F;+@ ;0;,[@";!@m ;[�;I"[Returns true, if the coloring function of this module is switched on, false otherwise.;T; 0;!@m ;4i�;"F;5o;6;7F;8iS;9iT;$@? ;:T;%I""def coloring? @coloring end;T;&I"def coloring?;T;'To;;F; ;Z;;;I"Doing::Color.coloring=;F;[[@0;[[@D i\;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;!@~ ;"F;5o;6;7F;8iY;9i[;$@? ;%I"1def coloring=(value) @coloring = value end;T;&I"def coloring=(value);T;'To;;F; ;Z;;;I"Doing::Color.coloring;F;[�;[[@D i^;F;: coloring;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@� ;4i�;$@? ;:T;%I"*def coloring @coloring ||= true end;T;&I"def coloring;T;'To;�;[[@D iy;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;!@� ;"F;5o;6;7F;8iw;9ix;$@? ;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;[[@D i};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;!@� ;4i�;"F;5o;6;7F;8i{;9i|;$@? ;: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; ;;;N;I"Doing::Color#attributes;F;[�;[[@D 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; ;Z;;;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;U@? ;VIC;[@? ;U@? ;WIC;[�;U@? ;XIC;Y{;ZIC;Y{;�IC;Y{;\@� ;]@~ ;[T;[T;IC;Y{�;[T;[T;^{�;_[�;[[@D i;T;;�;;;;;[�;{�;IC;"Terminal color functions;T;[�;[�;I"Terminal color functions;T; 0;!@? ;4i�;"F;5o;6;7F;8i ;9i ;$@;I"Doing::Color;F;'T;h:module;U@�;XIC;Y{;ZIC;Y{�;[T;IC;Y{ ;�IC;Y{;\@�;]0;[T;�IC;Y{;\@�;]0;[T;�IC;Y{;\@�;]0;[T;�IC;Y{;\@�;]0;[T;�IC;Y{;\@�;]@�;[T;�IC;Y{;\@�;]@;[T;�IC;Y{;\@;]@$;[T;�IC;Y{;\@6;]@C;[T;[T;[T;^{�;_[�;[[@�i;T;: WWID;;;;;[�;{�;IC;"$Main "What Was I Doing" methods;T;[�;[�;I"& Main "What Was I Doing" methods ;T; 0;!@�;4i�;"T;5o;6;7F;8i;9i;$@;I"Doing::WWID;F;ao;b;c0;d0;e0;;f;$@;g0;h;Z;'To; ;IC;[o;�;[[I"lib/doing/hooks.rb;Ti;F;:DEFAULT_PRIORITY;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@� ;$@� ;I"#Doing::Hooks::DEFAULT_PRIORITY;F;%I"DEFAULT_PRIORITY = 20;T;�I"20;T;'To;;F; ;Z;;;I"Doing::Hooks.register;F;[[I" event;T0[I"priority:;TI"DEFAULT_PRIORITY;T[I"█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;!@;4i�;"F;5o;6;7F;8i;9i;$@� ;:T;%I"}def self.register(event, priority: DEFAULT_PRIORITY, &block) register_one(event, priority_value(priority), &block) end;T;&I"<def register(event, priority: DEFAULT_PRIORITY, &block);T;'To;;F; ;Z;;;I" Doing::Hooks.priority_value;F;[[I" priority;T0;[[@� i;T;:priority_value;;;[�;{�;IC;"$Ensure the priority is a Fixnum;T;[�;[�;I"$Ensure the priority is a Fixnum;T; 0;!@;4i�;"F;5o;6;7F;8i;9i;$@� ;:T;%I"�def self.priority_value(priority) return priority if priority.is_a?(Integer) PRIORITY_MAP[priority] || DEFAULT_PRIORITY end;T;&I"!def priority_value(priority);T;'To;;F; ;Z;;;I"Doing::Hooks.register_one;F;[[I" event;T0[I" priority;T0[I"█T0;[[@� i%;T;:register_one;;;[�;{�;IC;"<register a single hook to be called later, internal API;T;[o;) ;*I" raise;F;+@ ;0;,[I"$Doing::Errors::PluginUncallable;T;!@);[�;I"<register a single hook to be called later, internal API;T; 0;!@);4i�;"F;5o;6;7F;8i$;9i$;$@� ;:T;%I"�def self.register_one(event, priority, &block) unless @registry[event] raise Doing::Errors::HookUnavailable, "Invalid hook. Doing only supports #{@registry.keys.inspect}" end raise Doing::Errors::PluginUncallable, 'Hooks must respond to :call' unless block.respond_to? :call Doing.logger.debug('Hook Manager:', "Registered #{event} hook") if ENV['DOING_PLUGIN_DEBUG'] insert_hook event, priority, &block end;T;&I".def register_one(event, priority, &block);T;'To;;F; ;Z;;;I"Doing::Hooks.insert_hook;F;[[I" event;T0[I" priority;T0[I"█T0;[[@� i1;F;:insert_hook;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@A;4i�;$@� ;:T;%I"�def self.insert_hook(event, priority, &block) @hook_priority[block] = [-priority, @hook_priority.size] @registry[event] << block end;T;&I"-def insert_hook(event, priority, &block);T;'To;;F; ;Z;;;I"Doing::Hooks.trigger;F;[[I" event;T0[I" *args;T0;[[@� i6;F;:trigger;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@S;4i�;$@� ;:T;%I"�def self.trigger(event, *args) hooks = @registry[event] return if hooks.nil? || hooks.empty? # sort and call hooks according to priority and load order hooks.sort_by { |h| @hook_priority[h] }.each do |hook| hook.call(*args) end end;T;&I"def trigger(event, *args);T;'T;U@� ;VIC;[�;U@� ;WIC;[�;U@� ;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@� i ;T;: Hooks;;;;;[�;{�;IC;"Hook manager;T;[�;[�;I"Hook manager;T; 0;!@� ;4i�;"F;5o;6;7F;8i ;9i ;$@;I"Doing::Hooks;F;'To; ;IC;[o;;F; ;;;;I"Doing::Items#sections;F;[�;[[I"lib/doing/items.rb;Ti;F;;�;;;[�;{�;IC;"-Returns the value of attribute sections. ;T;[�;[�;I"-Returns the value of attribute sections.;T; 0;!@v;"F;#0;$@t;%I"!def sections @sections end;T;&I"def sections;T;'To;;F; ;;;;I"Doing::Items#sections=;F;[[@0;[[@{i;F;:sections=;;;[�;{�;IC;" Sets the attribute sections ;T;[o;) ;*I" param;F;+I"0the value to set the attribute sections to.;T;I" value;T;,0;!@�;[�;I"YSets the attribute sections @param value the value to set the attribute sections to.;T; 0;!@�;"F;#0;$@t;%I"1def sections=(value) @sections = value end;T;&I"def sections=(value);T;'To;;F; ;;;;I"Doing::Items#initialize;F;[�;[[@{i ;F;;;;;;[�;{�;IC;"�;T;[o;) ;*I"return;F;+I"a new instance of Items;T;0;,[I" Items;F;!@�;[�;@ ; 0;!@�;4i�;$@t;:T;%I"0def initialize super @sections = [] end;T;&I"def initialize;T;'To;;F; ;;;;I" Doing::Items#section_titles;F;[�;[[@{i;T;:section_titles;;;[�;{�;IC;"List sections, title only;T;[o;) ;*I"return;F;+I"section titles;T;0;,[I" Array;T;!@�;[�;I"CList sections, title only @return [Array] section titles ;T; 0;!@�;4i�;"F;5o;6;7F;8i;9i;$@t;:T;%I"4def section_titles @sections.map(&:title) end;T;&I"def section_titles;T;'To;;F; ;;;;I"Doing::Items#section?;F;[[I"section;T0;[[@{i ;T;: section?;;;[�;{�;IC;"#Test if section already exists;T;[o;) ;*I" param;F;+I"section title;T;I"section;T;,[I"String;T;!@�o;) ;*I"return;F;+I"true if section exists;T;0;,[I"Boolean;T;!@�;[�;I"Test if section already exists @param section [String] section title @return [Boolean] true if section exists ;T; 0;!@�;4i�;"F;5o;6;7F;8i;9i;$@t;:T;%I"�def section?(section) has_section = false section = section.is_a?(Section) ? section.title.downcase : section.downcase @sections.each do |s| if s.title.downcase == section has_section = true break end end has_section end;T;&I"def section?(section);T;'To;;F; ;;;;I"Doing::Items#add_section;F;[[I"section;T0[I" log:;TI" false;T;[[@{i9;T;:add_section;;;[�;{�;IC;"�Add a new section to the sections array. Accepts either a Section object, or a title string that will be converted into a Section.;T;[o;) ;*I" param;F;+I"SThe section to add. A String value will be converted to Section automatically.;T;I"section;T;,[I"Section;T;!@�o;) ;*I" param;F;+I"LAdd a log message notifying the user about the creation of the section.;T;I"log;T;,[I"Boolean;T;!@�o;) ;*I"return;F;+I"nothing;T;0;,0;!@�;[�;I"�Add a new section to the sections array. Accepts either a Section object, or a title string that will be converted into a Section. @param section [Section] The section to add. A String value will be converted to Section automatically. @param log [Boolean] Add a log message notifying the user about the creation of the section. @return nothing ;T; 0;!@�;4i�;"F;5o;6;7F;8i,;9i8;$@t;:T;%I"�def add_section(section, log: false) section = section.is_a?(Section) ? section : Section.new(section.cap_first) return if section?(section) @sections.push(section) Doing.logger.info('New section:', %("#{section}" added)) if log end;T;&I")def add_section(section, log: false);T;'To;;F; ;;;;I"Doing::Items#in_section;F;[[I"section;T0;[[@{iI;T;:in_section;;;[�;{�;IC;"HGet a new Items object containing only items in a specified section;T;[o;) ;*I" param;F;+I"section title;T;I"section;T;,[I"String;T;!@�o;) ;*I"return;F;+I"Array of items;T;0;,[I" Items;T;!@�;[�;I"�Get a new Items object containing only items in a specified section @param section [String] section title @return [Items] Array of items ;T; 0;!@�;4i�;"F;5o;6;7F;8iB;9iH;$@t;:T;%I"�def in_section(section) if section =~ /^all$/i dup else items = Items.new.concat(select { |item| item.section == section }) items.add_section(section, log: false) items end end;T;&I"def in_section(section);T;'To;;F; ;;;;I"Doing::Items#delete_item;F;[[I" item;T0[I"single:;TI" false;T;[[@{iX;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;8iS;9iW;$@t;:T;%I"�def delete_item(item, single: false) deleted = delete(item) Doing.logger.count(:deleted) Doing.logger.info('Entry deleted:', deleted.title) if single end;T;&I")def delete_item(item, single: false);T;'To;;F; ;;;;I"Doing::Items#update_item;F;[[I" old_item;T0[I" new_item;T0;[[@{id;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^;9ic;$@t;:T;%I"`def update_item(old_item, new_item) s_idx = index { |item| item.equal?(old_item) } raise ItemNotFound, 'Unable to find item in index, did it mutate?' unless s_idx return if fetch(s_idx).equal?(new_item) self[s_idx] = new_item Doing.logger.count(:updated) Doing.logger.info('Entry updated:', self[s_idx].title.truncate(60)) new_item end;T;&I"(def update_item(old_item, new_item);T;'To;;F; ;;;;I"Doing::Items#to_s;F;[�;[[@{ir;T;;L;;;[�;{�;IC;"3Output sections and items in Doing file format;T;[�;[�;I"3Output sections and items in Doing file format;T; 0;!@G;4i�;"F;5o;6;7F;8iq;9iq;$@t;:T;%I"�def to_s out = [] @sections.each do |section| out.push(section.original) in_section(section.title).each { |item| out.push(item.to_s)} end out.join("\n") end;T;&I" def to_s;T;'To;;F; ;;;;I"Doing::Items#inspect;F;[�;[[@{i};T;;M;;;[�;{�;IC;"�;T;[o;) ;*I"private;F;+I"�;T;0;,0;!@U;[�;I" @private;T; 0;!@U;4i�;"F;5o;6;7F;8i|;9i|;$@t;:T;%I"�def inspect "#<Doing::Items #{count} items, #{@sections.count} sections: #{@sections.map { |s| "<Section:#{s.title} #{in_section(s.title).count} items>" }.join(', ')}>" end;T;&I"def inspect;T;'T;U@t;VIC;[�;U@t;WIC;[�;U@t;XIC;Y{;ZIC;Y{�;[T;IC;Y{;�IC;Y{;\@v;]@�;[T;[T;[T;^{�;_[�;[[@{i ;T;: Items;;;;;[�;{�;IC;"Items Array;T;[�;[�;I"Items Array;T; 0;!@t;4i�;"F;5o;6;7F;8i ;9i ;$@;I"Doing::Items;F;ao;b;c0;d0;e0;;q;$@;g@�;h0;'To; ;IC;[o;;F; ;Z;;;I"Doing::Pager.paginate;F;[�;[[I"lib/doing/pager.rb;Ti;T;: paginate;;;[�;{�;IC;"3Boolean determines whether output is paginated;T;[�;[�;I"3Boolean determines whether output is paginated;T; 0;!@{;4i�;"F;5o;6;7F;8i ;9i ;$@y;:T;%I"+def paginate @paginate ||= false end;T;&I"def paginate;T;'To;;F; ;Z;;;I"Doing::Pager.paginate=;F;[[I"should_paginate;T0;[[@�i;T;:paginate=;;;[�;{�;IC;"Enable/disable pagination;T;[o;) ;*I" param;F;+I"true to paginate;T;I"should_paginate;T;,[I"Boolean;T;!@�;[�;I"WEnable/disable pagination @param should_paginate [Boolean] true to paginate;T; 0;!@�;4i�;"F;5o;6;7F;8i;9i;$@y;:T;%I"Edef paginate=(should_paginate) @paginate = should_paginate end;T;&I"#def paginate=(should_paginate);T;'To;;F; ;Z;;;I"Doing::Pager.page;F;[[I" text;T0;[[@�i;T;: page;;;[�;{�;IC;"<Page output. If @paginate is false, just dump to STDOUT;T;[o;) ;*I" param;F;+I"text to paginate;T;I" text;T;,[I"String;T;!@�;[�;I"jPage output. If @paginate is false, just dump to STDOUT @param text [String] text to paginate ;T; 0;!@�;4i�;"F;5o;6;7F;8i;9i;$@y;:T;%I"�def page(text) unless @paginate puts text return end pager = which_pager Doing.logger.debug('Pager:', "Using #{pager}") read_io, write_io = IO.pipe input = $stdin pid = Kernel.fork do write_io.close input.reopen(read_io) read_io.close # Wait until we have input before we start the pager IO.select [input] begin exec(pager) rescue SystemCallError => 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; ;Z;;N;I" Doing::Pager.command_exist?;F;[[I"command;T0;[[@�iH;F;:command_exist?;;;[�;{�;IC;"�;T;[o;) ;*I"return;F;+@ ;0;,[@";!@�;[�;@ ; 0;!@�;4i�;$@y;:T;%I"�def command_exist?(command) exts = ENV.fetch("PATHEXT", "").split(::File::PATH_SEPARATOR) if Pathname.new(command).absolute? ::File.exist?(command) || exts.any? { |ext| ::File.exist?("#{command}#{ext}")} else ENV.fetch("PATH", "").split(::File::PATH_SEPARATOR).any? do |dir| file = ::File.join(dir, command) ::File.exist?(file) || exts.any? { |ext| ::File.exist?("#{file}#{ext}") } end end end;T;&I" def command_exist?(command);T;'To;;F; ;Z;;N;I"Doing::Pager.git_pager;F;[�;[[@�iV;F;:git_pager;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@y;:T;%I"Xdef git_pager command_exist?("git") ? `git config --get-all core.pager` : nil end;T;&I"def git_pager;T;'To;;F; ;Z;;N;I"Doing::Pager.pagers;F;[�;[[@�iZ;F;:pagers;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@y;:T;%I"{def pagers [ENV['GIT_PAGER'], ENV['PAGER'], git_pager, 'bat -p --pager="less -Xr"', 'less -Xr', 'more -r'].compact end;T;&I"def pagers;T;'To;;F; ;Z;;N;I"!Doing::Pager.find_executable;F;[[I"*commands;T0;[[@�i_;F;:find_executable;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@y;:T;%I"�def find_executable(*commands) execs = commands.empty? ? pagers : commands execs .compact.map(&:strip).reject(&:empty?).uniq .find { |cmd| command_exist?(cmd.split.first) } end;T;&I"#def find_executable(*commands);T;'To;;F; ;Z;;N;I"!Doing::Pager.exec_available?;F;[[I"*commands;T0;[[@�if;F;:exec_available?;;;[�;{�;IC;"�;T;[o;) ;*I"return;F;+@ ;0;,[@";!@�;[�;@ ; 0;!@�;4i�;$@y;:T;%I"Jdef exec_available?(*commands) !find_executable(*commands).nil? end;T;&I"#def exec_available?(*commands);T;'To;;F; ;Z;;N;I"Doing::Pager.which_pager;F;[�;[[@�ij;F;:which_pager;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@y;:T;%I"Ddef which_pager @which_pager ||= find_executable(*pagers) end;T;&I"def which_pager;T;'T;U@y;VIC;[�;U@y;WIC;[�;U@y;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@�i;T;: Pager;;;;;[�;{�;IC;"Pagination;T;[�;[�;I"Pagination;T; 0;!@y;4i�;"F;5o;6;7F;8i ;9i ;$@;I"Doing::Pager;F;'T@? o; ;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;;;;;;[�;{�;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;U@;VIC;[�;U@;WIC;[�;U@;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@*i ;F;:UserCancelled;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@;4i�;$@;I"!Doing::Errors::UserCancelled;F;ao;b;c0;d0;e0;:StandardError;$@;g0;h;Z;'To; ;IC;[o;;F; ;;;;I")Doing::Errors::EmptyInput#initialize;F;[[I"msg;TI"'No input';T[I" topic;TI"'Exited:';T;[[@*i;F;;;;;;[�;{�;IC;"�;T;[o;) ;*I"return;F;+I"!a new instance of EmptyInput;T;0;,[I"EmptyInput;F;!@I;[�;@ ; 0;!@I;4i�;$@G;: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;U@G;VIC;[�;U@G;WIC;[�;U@G;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@*i;F;:EmptyInput;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@G;4i�;$@;I"Doing::Errors::EmptyInput;F;ao;b;c0;d0;e0;;�;$@;g0;h;Z;'To; ;IC;[o;;F; ;;;;I"1Doing::Errors::DoingStandardError#initialize;F;[[I"msg;TI"'';T;[[@*i;F;;;;;;[�;{�;IC;"�;T;[o;) ;*I"return;F;+I")a new instance of DoingStandardError;T;0;,[I"DoingStandardError;F;!@r;[�;@ ; 0;!@r;4i�;$@p;:T;%I"Hdef initialize(msg = '') Doing.logger.output_results super end;T;&I"def initialize(msg = '');T;'T;U@p;VIC;[�;U@p;WIC;[�;U@p;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@*i;F;:DoingStandardError;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@p;4i�;$@;I"&Doing::Errors::DoingStandardError;F;ao;b;c0;d0;e0;;�;$@;g0;h;Z;'To; ;IC;[o;;F; ;;;;I"+Doing::Errors::WrongCommand#initialize;F;[[I"msg;TI"'wrong command';T[I"topic:;TI" 'Error:';T;[[@*i#;F;;;;;;[�;{�;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;U@�;VIC;[�;U@�;WIC;[�;U@�;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@*i";F;:WrongCommand;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@;I" Doing::Errors::WrongCommand;F;ao;b;c0;d0;e0;;�;$@;g0;h;Z;'To; ;IC;[o;;F; ;;;;I"0Doing::Errors::DoingRuntimeError#initialize;F;[[I"msg;TI"'Runtime Error';T[I"topic:;TI" 'Error:';T;[[@*i+;F;;;;;;[�;{�;IC;"�;T;[o;) ;*I"return;F;+I"(a new instance of DoingRuntimeError;T;0;,[I"DoingRuntimeError;F;!@�;[�;@ ; 0;!@�;4i�;$@�;: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;U@�;VIC;[�;U@�;WIC;[�;U@�;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@*i*;F;:DoingRuntimeError;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@;I"%Doing::Errors::DoingRuntimeError;F;ao;b;c0;d0;e0;:RuntimeError;$@;g0;h;Z;'To; ;IC;[o;;F; ;;;;I"(Doing::Errors::NoResults#initialize;F;[[I"msg;TI"'No results';T[I" topic;TI"'Exited:';T;[[@*i3;F;;;;;;[�;{�;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;U@�;VIC;[�;U@�;WIC;[�;U@�;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@*i2;F;:NoResults;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@;I"Doing::Errors::NoResults;F;ao;b;c0;d0;e0;;�;$@;g0;h;Z;'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;;;;;;[�;{�;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;U@;VIC;[�;U@;WIC;[�;U@;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@*i;;F;:DoingNoTraceError;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@;4i�;$@;I"%Doing::Errors::DoingNoTraceError;F;ao;b;c0;d0;e0;;�;$@;g0;h;Z;'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;;;;;;[�;{�;IC;"�;T;[o;) ;*I"return;F;+I"&a new instance of PluginException;T;0;,[I"PluginException;F;!@L;[�;@ ; 0;!@L;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;U@=;VIC;[�;U@=;WIC;[�;U@=;XIC;Y{;ZIC;Y{�;[T;IC;Y{;�IC;Y{;\@?;]0;[T;[T;[T;^{�;_[�;[[@*iG;F;:PluginException;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@=;4i�;$@;I"#Doing::Errors::PluginException;F;ao;b;c0;d0;e0;;�;$@;g0;h;Z;'To;�;[[@*i^;F;:HookUnavailable;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@w;$@;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;!@�;$@;I"#Doing::Errors::MissingArgument;F;%I"3MissingArgument = Class.new(DoingRuntimeError);T;�I"!Class.new(DoingRuntimeError);T;'To;�;[[@*id;F;:MissingFile;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;$@;I"Doing::Errors::MissingFile;F;%I"/MissingFile = Class.new(DoingRuntimeError);T;�I"!Class.new(DoingRuntimeError);T;'To;�;[[@*ie;F;:MissingEditor;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;$@;I"!Doing::Errors::MissingEditor;F;%I"1MissingEditor = Class.new(DoingRuntimeError);T;�I"!Class.new(DoingRuntimeError);T;'To;�;[[@*if;F;:NonInteractive;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;$@;I""Doing::Errors::NonInteractive;F;%I".NonInteractive = Class.new(StandardError);T;�I"Class.new(StandardError);T;'To;�;[[@*ih;F;:NoEntryError;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;$@;I" Doing::Errors::NoEntryError;F;%I"0NoEntryError = Class.new(DoingRuntimeError);T;�I"!Class.new(DoingRuntimeError);T;'To;�;[[@*ij;F;:InvalidTimeExpression;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;$@;I")Doing::Errors::InvalidTimeExpression;F;%I"9InvalidTimeExpression = Class.new(DoingRuntimeError);T;�I"!Class.new(DoingRuntimeError);T;'To;�;[[@*ik;F;:InvalidSection;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;$@;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;U@;VIC;[�;U@;WIC;[�;U@;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@*i ;F;:Errors;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@;4i�;$@;I"Doing::Errors;F;'To; ;IC;[o;;F; ;Z;;;I" Doing::Prompt.force_answer=;F;[[@0;[[I"lib/doing/prompt.rb;Ti;F;:force_answer=;;;[�;{�;IC;"$Sets the attribute force_answer ;T;[o;) ;*I" param;F;+I"4the value to set the attribute force_answer to.;T;I" value;T;,0;!@;[�;I"aSets the attribute force_answer @param value the value to set the attribute force_answer to.;T; 0;!@;"F;#0;$@;%I"9def force_answer=(value) @force_answer = value end;T;&I"def force_answer=(value);T;'To;;F; ;Z;;;I""Doing::Prompt.default_answer=;F;[[@0;[[@i;F;:default_answer=;;;[�;{�;IC;"&Sets the attribute default_answer ;T;[o;) ;*I" param;F;+I"6the value to set the attribute default_answer to.;T;I" value;T;,0;!@*;[�;I"eSets the attribute default_answer @param value the value to set the attribute default_answer to.;T; 0;!@*;"F;#0;$@;%I"=def default_answer=(value) @default_answer = value end;T;&I"def default_answer=(value);T;'To;;F; ;Z;;;I"Doing::Prompt.force_answer;F;[�;[[@i;F;:force_answer;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@<;4i�;$@;:T;%I"1def force_answer @force_answer ||= nil end;T;&I"def force_answer;T;'To;;F; ;Z;;;I"!Doing::Prompt.default_answer;F;[�;[[@i;F;:default_answer;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@H;4i�;$@;:T;%I"7def default_answer @default_answer ||= false end;T;&I"def default_answer;T;'To;;F; ;Z;;;I"Doing::Prompt.yn;F;[[I" question;T0[I"default_response:;TI" false;T;[[@i";T;:yn;;;[�;{�;IC;"-Ask a yes or no question in the terminal;T;[o;) ;*I" param;F;+I"The question to ask;T;I" question;T;,[I"String;T;!@To;) ;*I" param;F;+I"!default response if no input;T;I"default_response;T;,[I" Bool;T;!@To;) ;*I"return;F;+I"yes or no;T;0;,[I" Bool;T;!@T;[�;I" Ask a yes or no question in the terminal @param question [String] The question to ask @param default_response (Bool) default response if no input @return (Bool) yes or no ;T; 0;!@T;4i�;"T;5o;6;7F;8i;9i!;$@;:T;%I"5def yn(question, default_response: false) unless @force_answer.nil? return @force_answer end default = if default_response.is_a?(String) default_response =~ /y/i ? true : false else default_response end # if global --default is set, answer default return default if @default_answer # if this isn't an interactive shell, answer default return default unless $stdout.isatty # clear the buffer if ARGV&.length ARGV.length.times do ARGV.shift end end system 'stty cbreak' cw = white cbw = boldwhite cbg = boldgreen cd = Color.default options = unless default.nil? "#{cw}[#{default ? "#{cbg}Y#{cw}/#{cbw}n" : "#{cbw}y#{cw}/#{cbg}N"}#{cw}]#{cd}" else "#{cw}[#{cbw}y#{cw}/#{cbw}n#{cw}]#{cd}" end $stdout.syswrite "#{cbw}#{question.sub(/\?$/, '')} #{options}#{cbw}?#{cd} " res = $stdin.sysread 1 puts system 'stty cooked' res.chomp! res.downcase! return default if res.empty? res =~ /y/i ? true : false end;T;&I".def yn(question, default_response: false);T;'To;;F; ;Z;;;I"Doing::Prompt.fzf;F;[�;[[@iR;F;:fzf;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@x;4i�;$@;:T;%I"'def fzf @fzf ||= install_fzf end;T;&I"def fzf;T;'To;;F; ;Z;;;I"Doing::Prompt.install_fzf;F;[�;[[@iV;F;:install_fzf;;;[�;{�;IC;"�;T;[o;) ;*I" raise;F;+@ ;0;,[I"RuntimeError;T;!@�;[�;@ ; 0;!@�;4i�;$@;:T;%I"�def install_fzf fzf_dir = File.join(File.dirname(__FILE__), '../helpers/fzf') FileUtils.mkdir_p(fzf_dir) unless File.directory?(fzf_dir) fzf_bin = File.join(fzf_dir, 'bin/fzf') return fzf_bin if File.exist?(fzf_bin) prev_level = Doing.logger.level Doing.logger.adjust_verbosity({ log_level: :info }) Doing.logger.log_now(:warn, 'Compiling and installing fzf -- this will only happen once') Doing.logger.log_now(:warn, 'fzf is copyright Junegunn Choi, MIT License <https://github.com/junegunn/fzf/blob/master/LICENSE>') system("'#{fzf_dir}/install' --bin --no-key-bindings --no-completion --no-update-rc --no-bash --no-zsh --no-fish &> /dev/null") unless File.exist?(fzf_bin) Doing.logger.log_now(:warn, 'Error installing, trying again as root') system("sudo '#{fzf_dir}/install' --bin --no-key-bindings --no-completion --no-update-rc --no-bash --no-zsh --no-fish &> /dev/null") end raise RuntimeError.new('Error installing fzf, please report at https://github.com/ttscoff/doing/issues') unless File.exist?(fzf_bin) Doing.logger.info("fzf installed to #{fzf}") Doing.logger.adjust_verbosity({ log_level: prev_level }) fzf_bin end;T;&I"def install_fzf;T;'To;;F; ;Z;;;I"Doing::Prompt.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;[[@ir;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;8im;9iq;$@;:T;%I"]def choose_from(options, prompt: 'Make a selection: ', multiple: false, sorted: true, fzf_args: []) return nil unless $stdout.isatty # 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; ;Z;;;I"$Doing::Prompt.choose_from_items;F;[[I" items;T0[I" **opt;T0;[[@i�;T;:choose_from_items;;;[�;{�;IC;"=Create an interactive menu to select from a set of Items;T;[o;) ;*I" param;F;+I"list of items;T;I" items;T;,[I" Array;T;!@�o;) ;*I" param;F;+I"options;T;I"opt;T;,[I" Hash;T;!@�o;) ;*I" param;F;+I"include section;T;I"include_section;T;,[I"Boolean;T;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":header;T;,[I"String;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":prompt;T;,[I"String;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":query;T;,[I"String;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":show_if_single;T;,[I"Boolean;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I" :menu;T;,[I"Boolean;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I" :sort;T;,[I"Boolean;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+0;I":multiple;T;,[I"Boolean;T;E0;!@�o;B;*I"option;F;+0;I"opt;T;,0;Co;D ;*I"option;F;+I"�;T;I" :case;T;,[I"Symbol;T;E[I":sensitive;TI":ignore;TI":smart;T;!@�;[�;I"� Create an interactive menu to select from a set of Items @param items [Array] list of items @param opt [Hash] options @param include_section [Boolean] include section @option opt [String] :header @option opt [String] :prompt @option opt [String] :query @option opt [Boolean] :show_if_single @option opt [Boolean] :menu @option opt [Boolean] :sort @option opt [Boolean] :multiple @option opt [Symbol] :case (:sensitive, :ignore, :smart) ;T; 0;!@�;4i�;"T;5o;6;7F;8i|;9i�;$@;:T;%I"�def choose_from_items(items, **opt) return items unless $stdout.isatty return nil unless items.count.positive? case_sensitive = opt.fetch(:case, :smart).normalize_case header = opt.fetch(:header, 'Arrows: navigate, tab: mark for selection, ctrl-a: select all, enter: commit') prompt = opt.fetch(:prompt, 'Select entries to act on > ') query = opt.fetch(:query) { opt.fetch(:search, '') } include_section = opt.fetch(:include_section, false) 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_args = [ %(--header="#{header}"), %(--prompt="#{prompt.sub(/ *$/, ' ')}"), opt.fetch(:multiple) ? '--multi' : '--no-multi', '-0', '--bind ctrl-a:select-all', %(-q "#{query}"), '--info=inline' ] fzf_args.push('-1') unless opt.fetch(:show_if_single) fzf_args << case case_sensitive when :sensitive '+i' when :ignore '-i' end fzf_args << '-e' if opt.fetch(:exact, false) unless opt.fetch(:menu) raise InvalidArgument, "Can't skip menu when no query is provided" unless query && !query.empty? fzf_args.concat([%(--filter="#{query}"), opt.fetch(: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.fetch(:multiple) ? selected : selected[0] end;T;&I"(def choose_from_items(items, **opt);T;'T;U@;VIC;[o;b;c0;d0;e0;;�;$@;g@? ;h;�;U@;WIC;[�;U@;XIC;Y{;ZIC;Y{;IC;Y{;\@<;]@;[T;IC;Y{;\@H;]@*;[T;[T;IC;Y{�;[T;[T;^{�;_[�;[[@i ;T;:Prompt;;;;;[�;{�;IC;"Terminal Prompt methods;T;[�;[�;I"Terminal Prompt methods;T; 0;!@;4i�;"F;5o;6;7F;8i ;9i ;$@;I"Doing::Prompt;F;'To; ;IC;[o;;F; ;;;;I"Doing::Section#original;F;[�;[[I"lib/doing/section.rb;Ti;F;: original;;;[�;{�;IC;"-Returns the value of attribute original. ;T;[�;[�;I"-Returns the value of attribute original.;T; 0;!@4;"F;#0;$@2;%I"!def original @original end;T;&I"def original;T;'To;;F; ;;;;I"Doing::Section#original=;F;[[@0;[[@9i;F;:original=;;;[�;{�;IC;" Sets the attribute original ;T;[o;) ;*I" param;F;+I"0the value to set the attribute original to.;T;I" value;T;,0;!@B;[�;I"YSets the attribute original @param value the value to set the attribute original to.;T; 0;!@B;"F;#0;$@2;%I"1def original=(value) @original = value end;T;&I"def original=(value);T;'To;;F; ;;;;I"Doing::Section#title;F;[�;[[@9i;F;;-;;;[�;{�;IC;"*Returns the value of attribute title. ;T;[�;[�;I"*Returns the value of attribute title.;T; 0;!@T;"F;#0;$@2;%I"def title @title end;T;&I"def title;T;'To;;F; ;;;;I"Doing::Section#title=;F;[[@0;[[@9i;F;;.;;;[�;{�;IC;"Sets the attribute title ;T;[o;) ;*I" param;F;+I"-the value to set the attribute title to.;T;I" value;T;,0;!@a;[�;I"SSets the attribute title @param value the value to set the attribute title to.;T; 0;!@a;"F;#0;$@2;%I"+def title=(value) @title = value end;T;&I"def title=(value);T;'To;;F; ;;;;I"Doing::Section#initialize;F;[[I" title;T0[I"original:;TI"nil;T;[[@9i ;F;;;;;;[�;{�;IC;"�;T;[o;) ;*I"return;F;+I"a new instance of Section;T;0;,[I"Section;F;!@s;[�;@ ; 0;!@s;4i�;$@2;:T;%I"�def initialize(title, original: nil) super() @title = title @original = if original.nil? "#{title}:" else original =~ /:(\s+@\S+(\(.*?\))?)*$/ ? original : "#{original}:" end end;T;&I")def initialize(title, original: nil);T;'To;;F; ;;;;I"Doing::Section#to_s;F;[�;[[@9i;T;;L;;;[�;{�;IC;"Outputs section title;T;[�;[�;I"Outputs section title;T; 0;!@�;4i�;"F;5o;6;7F;8i;9i;$@2;:T;%I"def to_s @title end;T;&I" def to_s;T;'To;;F; ;;;;I"Doing::Section#inspect;F;[�;[[@9i;T;;M;;;[�;{�;IC;"�;T;[o;) ;*I"private;F;+I"�;T;0;,0;!@�;[�;I" @private;T; 0;!@�;4i�;"F;5o;6;7F;8i;9i;$@2;:T;%I"Wdef inspect %(#<Doing::Section @title="#{@title}" @original="#{@original}">) end;T;&I"def inspect;T;'T;U@2;VIC;[�;U@2;WIC;[�;U@2;XIC;Y{;ZIC;Y{�;[T;IC;Y{; IC;Y{;\@4;]@B;[T;-IC;Y{;\@T;]@a;[T;[T;[T;^{�;_[�;[[@9i ;T;:Section;;;;;[�;{�;IC;"Section Object;T;[�;[�;I"Section Object;T; 0;!@2;4i�;"F;5o;6;7F;8i ;9i ;$@;I"Doing::Section;F;ao;b;c0;d0;e0;;f;$@;g0;h;Z;'To;�;[[I"lib/doing/version.rb;Ti;F;:VERSION;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;$@;I"Doing::VERSION;F;%I"VERSION = '2.1.1pre';T;�I"'2.1.1pre';T;'To; ;IC;[o;;F; ;Z;;;I"*Doing::Completion.generate_completion;F;[[I" type:;TI" 'zsh';T[I" file:;TI" 'stdout';T;[[I"lib/doing/completion.rb;Ti;T;:generate_completion;;;[�;{�;IC;">Generate a completion script and output to file or stdout;T;[o;) ;*I" param;F;+I"*shell to generate for (zsh|bash|fish);T;I" type;T;,[I"String;T;!@�o;) ;*I" param;F;+I"!Path to save to, or 'stdout';T;I" file;T;,[I"String;T;!@�;[�;I"�Generate a completion script and output to file or stdout @param type [String] shell to generate for (zsh|bash|fish) @param file [String] Path to save to, or 'stdout' ;T; 0;!@�;4i�;"F;5o;6;7F;8i;9i;$@�;:T;%I"def generate_completion(type: 'zsh', file: 'stdout') generator = case type.to_s when /^f/ FishCompletions.new when /^b/ BashCompletions.new else ZshCompletions.new end result = generator.generate_completions if file =~ /^stdout$/i $stdout.puts result else File.open(File.expand_path(file), 'w') do |f| f.puts result end Doing.logger.warn('File written:', "#{type} completions written to #{file}") end end;T;&I"9def generate_completion(type: 'zsh', file: 'stdout');T;'T;U@�;VIC;[�;U@�;WIC;[�;U@�;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@�i;T;:Completion;;;;;[�;{�;IC;" Completion script generator;T;[�;[�;I" Completion script generator;T; 0;!@�;4i�;"F;5o;6;7F;8i;9i;$@;I"Doing::Completion;F;'To; ;IC;["o;;F; ;;;;I"Doing::LogAdapter#logdev=;F;[[@0;[[I"lib/doing/log_adapter.rb;Ti;T;:logdev=;;;[�;{�;IC;"Sets the log device ;T;[�;[�;I"Sets the log device;T; 0;!@�;"F;5o;6;7F;8i ;9i ;$@�;%I"-def logdev=(value) @logdev = value end;T;&I"def logdev=(value);T;'To;;F; ;;;;I""Doing::LogAdapter#max_length=;F;[[@0;[[@i;T;:max_length=;;;[�;{�;IC;"4Max length of log messages (truncate in middle) ;T;[�;[�;I"4Max length of log messages (truncate in middle);T; 0;!@;"F;5o;6;7F;8i;9i;$@�;%I"5def max_length=(value) @max_length = value end;T;&I"def max_length=(value);T;'To;;F; ;;;;I"Doing::LogAdapter#level;F;[�;[[@i;T;: level;;;[�;{�;IC;"=Returns the current log level (debug, info, warn, error) ;T;[�;[�;I"=Returns the current log level (debug, info, warn, error);T; 0;!@;"F;5o;6;7F;8i;9i;$@�;%I"def level @level end;T;&I"def level;T;'To;;F; ;;;;I"Doing::LogAdapter#messages;F;[�;[[@i;F;: messages;;;[�;{�;IC;"-Returns the value of attribute messages. ;T;[�;[�;I"-Returns the value of attribute messages.;T; 0;!@+;"F;#0;$@�;%I"!def messages @messages end;T;&I"def messages;T;'To;;F; ;;;;I"Doing::LogAdapter#results;F;[�;[[@i;F;:results;;;[�;{�;IC;",Returns the value of attribute results. ;T;[�;[�;I",Returns the value of attribute results.;T; 0;!@8;"F;#0;$@�;%I"def results @results end;T;&I"def results;T;'To;�;[[@i;F;:TOPIC_WIDTH;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@E;$@�;I"#Doing::LogAdapter::TOPIC_WIDTH;F;%I"TOPIC_WIDTH = 12;T;�I"12;T;'To;�;[[@i;F;:LOG_LEVELS;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@P;$@�;I""Doing::LogAdapter::LOG_LEVELS;F;%I"LOG_LEVELS = { debug: ::Logger::DEBUG, info: ::Logger::INFO, warn: ::Logger::WARN, error: ::Logger::ERROR }.freeze;T;�I"r{ debug: ::Logger::DEBUG, info: ::Logger::INFO, warn: ::Logger::WARN, error: ::Logger::ERROR }.freeze;T;'To;�;[[@i!;F;:COUNT_KEYS;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@[;$@�;I""Doing::LogAdapter::COUNT_KEYS;F;%I"�COUNT_KEYS = %i[ added added_tags archived autotag completed completed_archived deleted moved removed_tags rotated skipped updated ].freeze;T;�I"�%i[ added added_tags archived autotag completed completed_archived deleted moved removed_tags rotated skipped updated ].freeze;T;'To;;F; ;;;;I"!Doing::LogAdapter#initialize;F;[[I" level;TI" :info;T;[[@i5;T;;;;;;[�;{�;IC;"*Create a new instance of a log writer;T;[o;) ;*I" param;F;+I"the log level;T;I" level;T;,[I" optional;TI"symbol;T;!@fo;) ;*I"return;F;+I"!a new instance of LogAdapter;T;0;,[I"LogAdapter;F;!@f;[�;I"b Create a new instance of a log writer @param level (optional, symbol) the log level ;T; 0;!@f;4i�;"F;5o;6;7F;8i0;9i4;$@�;:T;%I" def initialize(level = :info) @messages = [] @counters = {} COUNT_KEYS.each { |key| @counters[key] = { tag: [], count: 0 } } @results = [] @logdev = $stderr @max_length = `tput cols`.strip.to_i - 5 || 85 self.log_level = level @prev_level = level end;T;&I""def initialize(level = :info);T;'To;;F; ;;;;I"!Doing::LogAdapter#log_level=;F;[[I" level;TI"'info';T;[[@iG;T;:log_level=;;;[�;{�;IC;"$Set the log level on the writer;T;[o;) ;*I" param;F;+I"the log level;T;I" level;T;,[I"symbol;T;!@�o;) ;*I"return;F;+I"nothing;T;0;,0;!@�;[�;I"f Set the log level on the writer @param level (symbol) the log level @return nothing ;T; 0;!@�;4i�;"F;5o;6;7F;8i@;9iF;$@�;:T;%I"def log_level=(level = 'info') level = level.to_s level = case level when /^[e0]/i :error when /^[w1]/i :warn when /^[d3]/i :debug else :info end @level = level end;T;&I"#def log_level=(level = 'info');T;'To;;F; ;;;;I"!Doing::LogAdapter#temp_level;F;[[I" level;T0;[[@iY;T;:temp_level;;;[�;{�;IC;"Set log level temporarily;T;[�;[�;I"Set log level temporarily;T; 0;!@�;4i�;"F;5o;6;7F;8iX;9iX;$@�;:T;%I"�def temp_level(level) return if level.nil? || level.to_sym == @log_level @prev_level = log_level.dup @log_level = level.to_sym end;T;&I"def temp_level(level);T;'To;;F; ;;;;I"$Doing::LogAdapter#restore_level;F;[�;[[@ia;T;:restore_level;;;[�;{�;IC;"Restore temporary level;T;[�;[�;I"Restore temporary level;T; 0;!@�;4i�;"F;5o;6;7F;8i`;9i`;$@�;:T;%I"�def restore_level return if @prev_level.nil? || @prev_level == @log_level self.log_level = @prev_level @prev_level = nil end;T;&I"def restore_level;T;'To;;F; ;;;;I"'Doing::LogAdapter#adjust_verbosity;F;[[I"options;TI"{};T;[[@ih;F;:adjust_verbosity;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@�;:T;%I"Zdef adjust_verbosity(options = {}) if options[:log_level] self.log_level = options[:log_level].to_sym elsif options[:quiet] self.log_level = :error elsif options[:verbose] || options[:debug] self.log_level = :debug end log_now :debug, 'Logging at level:', @level.to_s # log_now :debug, 'Doing Version:', Doing::VERSION end;T;&I"'def adjust_verbosity(options = {});T;'To;;F; ;;;;I"Doing::LogAdapter#count;F;[ [I"key;T0[I"level:;TI" :info;T[I"count:;TI"1;T[I" tag:;TI"nil;T[I" message:;TI"nil;T;[[@it;F;: count;;;[�;{�;IC;"�;T;[o;) ;*I" raise;F;+@ ;0;,[I"ArgumentError;T;!@�;[�;@ ; 0;!@�;4i�;$@�;:T;%I"9def count(key, level: :info, count: 1, tag: nil, message: nil) raise ArgumentError, 'invalid counter key' unless COUNT_KEYS.include?(key) @counters[key][:count] += count @counters[key][:tag].concat(tag).sort.uniq unless tag.nil? @counters[key][:level] ||= level @counters[key][:message] ||= message end;T;&I"Cdef count(key, level: :info, count: 1, tag: nil, message: nil);T;'To;;F; ;;;;I"Doing::LogAdapter#debug;F;[[I" topic;T0[I"message;TI"nil;T[I"█T0;[[@i�;T;: debug;;;[�;{�;IC;"Print a debug message;T;[o;) ;*I" param;F;+I"the topic of the message;T;I" topic;T;,0;!@�o;) ;*I" param;F;+I"the message detail;T;I"message;T;,0;!@�o;) ;*I"return;F;+I"nothing;T;0;,0;!@�;[�;I"� Print a debug message @param topic the topic of the message @param message the message detail @return nothing ;T; 0;!@�;4i�;"F;5o;6;7F;8i};9i;$@�;:T;%I"Xdef debug(topic, message = nil, &block) write(:debug, topic, message, &block) end;T;&I",def debug(topic, message = nil, &block);T;'To;;F; ;;;;I"Doing::LogAdapter#info;F;[[I" topic;T0[I"message;TI"nil;T[I"█T0;[[@i�;T;: info;;;[�;{�;IC;"Print a message;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"nothing;T;0;,0;!@;[�;I"� Print a message @param topic the topic of the message, e.g. "Configuration file", "Deprecation", etc. @param message the message detail @return nothing ;T; 0;!@;4i�;"F;5o;6;7F;8i�;9i�;$@�;:T;%I"Vdef info(topic, message = nil, &block) write(:info, topic, message, &block) end;T;&I"+def info(topic, message = nil, &block);T;'To;;F; ;;;;I"Doing::LogAdapter#warn;F;[[I" topic;T0[I"message;TI"nil;T[I"█T0;[[@i�;T;: warn;;;[�;{�;IC;"Print a message;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"nothing;T;0;,0;!@(;[�;I"� Print a message @param topic the topic of the message, e.g. "Configuration file", "Deprecation", etc. @param message the message detail @return nothing ;T; 0;!@(;4i�;"F;5o;6;7F;8i�;9i�;$@�;:T;%I"Vdef warn(topic, message = nil, &block) write(:warn, topic, message, &block) end;T;&I"+def warn(topic, message = nil, &block);T;'To;;F; ;;;;I"Doing::LogAdapter#error;F;[[I" topic;T0[I"message;TI"nil;T[I"█T0;[[@i�;T;: error;;;[�;{�;IC;"Print an error message;T;[o;) ;*I" param;F;+I"Mthe topic of the message, e.g. "Configuration file", "Deprecation", etc.;T;I" topic;T;,0;!@Ho;) ;*I" param;F;+I"the message detail;T;I"message;T;,0;!@Ho;) ;*I"return;F;+I"nothing;T;0;,0;!@H;[�;I"� Print an error message @param topic the topic of the message, e.g. "Configuration file", "Deprecation", etc. @param message the message detail @return nothing ;T; 0;!@H;4i�;"F;5o;6;7F;8i�;9i�;$@�;:T;%I"Xdef error(topic, message = nil, &block) write(:error, topic, message, &block) end;T;&I",def error(topic, message = nil, &block);T;'To;;F; ;;;;I"!Doing::LogAdapter#abort_with;F;[[I" topic;T0[I"message;TI"nil;T[I"█T0;[[@i�;T;:abort_with;;;[�;{�;IC;"IPrint an error message and immediately abort the process;T;[o;) ;*I" param;F;+I"Mthe topic of the message, e.g. "Configuration file", "Deprecation", etc.;T;I" topic;T;,0;!@ho;) ;*I" param;F;+I"(the message detail (can be omitted);T;I"message;T;,0;!@ho;) ;*I"return;F;+I"nothing;T;0;,0;!@h;[�;I"2 Print an error message and immediately abort the process @param topic the topic of the message, e.g. "Configuration file", "Deprecation", etc. @param message the message detail (can be omitted) @return nothing ;T; 0;!@h;4i�;"F;5o;6;7F;8i�;9i�;$@�;:T;%I"]def abort_with(topic, message = nil, &block) error(topic, message, &block) abort end;T;&I"1def abort_with(topic, message = nil, &block);T;'To;;F; ;;;;I"&Doing::LogAdapter#formatted_topic;F;[[I" topic;T0[I"colon:;TI" false;T;[[@i�;T;:formatted_topic;;;[�;{�;IC;"Format the topic;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"Separate with a colon?;T;I" colon;T;,0;!@�o;) ;*I"return;F;+I""the formatted topic statement;T;0;,0;!@�;[�;I"� Format the topic @param topic the topic of the message, e.g. "Configuration file", "Deprecation", etc. @param colon Separate with a colon? @return the formatted topic statement ;T; 0;!@�;4i�;"F;5o;6;7F;8i�;9i�;$@�;:T;%I"�def formatted_topic(topic, colon: false) if colon "#{topic}: ".rjust(TOPIC_WIDTH) elsif topic =~ /:$/ "#{topic} ".rjust(TOPIC_WIDTH) else "#{topic} " end end;T;&I"-def formatted_topic(topic, colon: false);T;'To;;F; ;;;;I"Doing::LogAdapter#write;F;[ [I"level_of_message;T0[I" topic;T0[I"message;TI"nil;T[I"█T0;[[@i�;T;;];;;[�;{�;IC;"Log a message.;T;[ o;) ;*I" param;F;+I"Ethe Symbol level of message, one of :debug, :info, :warn, :error;T;I"level_of_message;T;,[I"Symbol;T;!@�o;) ;*I" param;F;+I"%the String topic or full message;T;I" topic;T;,[I"String;T;!@�o;) ;*I" param;F;+I""the String 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;!@�o;) ;*I"return;F;+I")false if the message was not written;T;0;,[I"Boolean;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"█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; ;;;;I"!Doing::LogAdapter#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;[[@i;F;:log_change;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@;4i�;$@�;:T;%I"def log_change(tags_added: [], tags_removed: [], count: 1, item: nil, single: false) if tags_added.empty? && tags_removed.empty? count(:skipped, level: :debug, message: '%count %items with no change', count: count) else if tags_added.empty? count(:skipped, level: :debug, message: 'no tags added to %count %items') elsif single && item added = tags_added.log_tags info('Tagged:', %(added #{tags_added.count == 1 ? 'tag' : 'tags'} #{added} to #{item.title})) else count(:added_tags, level: :info, tag: tags_added, message: '%tags added to %count %items') end if tags_removed.empty? count(:skipped, level: :debug, message: 'no tags removed from %count %items') elsif single && item added = tags_added.log_tags info('Untagged:', %(removed #{tags_removed.count == 1 ? 'tag' : 'tags'} #{added} from #{item.title})) else count(:removed_tags, level: :info, tag: tags_removed, message: '%tags removed from %count %items') end end end;T;&I"Ydef log_change(tags_added: [], tags_removed: [], count: 1, item: nil, single: false);T;'To;;F; ;;;N;I"%Doing::LogAdapter#format_counter;F;[[I"key;T0[I" data;T0;[[@i(;F;:format_counter;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@1;4i�;$@�;:T;%I"def format_counter(key, data) case key when :rotated ['Rotated:', data[:message] || 'rotated %count %items'] when :autotag ['Autotag:', data[:message] || 'autotagged %count %items'] 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; ;;;N;I"%Doing::LogAdapter#total_counters;F;[�;[[@iE;F;:total_counters;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@A;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; ;;;N;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;!@Mo;) ;*I"return;F;+I"+whether the message should be written.;T;0;,[@";!@M;[�;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;!@M;4i�;"F;5o;6;7F;8iT;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; ;;;N;I"Doing::LogAdapter#message;F;[[I" topic;T0[I"message;TI"nil;T;[[@ik;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;!@eo;) ;*I" param;F;+I"the message detail;T;I"message;T;,0;!@eo;) ;*I"return;F;+I"the formatted message;T;0;,0;!@eo;) ;*I" raise;F;+@ ;0;,[I"ArgumentError;T;!@e;[�;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;!@e;4i�;"F;5o;6;7F;8ib;9ij;$@�;: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; ;;;N;I"$Doing::LogAdapter#color_message;F;[ [I" level;T0[I" topic;T0[I"message;TI"nil;T[I"█T0;[[@i{;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;U@�;VIC;[�;U@�;WIC;[�;U@�;XIC;Y{;ZIC;Y{�;[T;IC;Y{ :logdevIC;Y{;\0;]@�;[T:max_lengthIC;Y{;\0;]@;[T;IC;Y{;\@;]0;[T;IC;Y{;\@+;]0;[T;IC;Y{;\@8;]0;[T;[T;[T;^{�;_[�;[[@i;T;:LogAdapter;;;;;[�;{�;IC;"Log adapter;T;[�;[�;I" Log adapter ;T; 0;!@�;4i�;"T;5o;6;7F;8i ;9i;$@;I"Doing::LogAdapter;F;ao;b;c0;d0;e0;;f;$@;g0;h;Z;'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;;;;;;[�;{�;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: {}) @config_file = file.nil? ? default_config_file : File.expand_path(file) @settings = configure(options) end;T;&I",def initialize(file = nil, options: {});T;'To;;F; ;;;;I"%Doing::Configuration#config_file;F;[�;[[@�ig;F;;�;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@;4i�;$@�;:T;%I"?def config_file @config_file ||= default_config_file end;T;&I"def config_file;T;'To;;F; ;;;;I"&Doing::Configuration#config_file=;F;[[I" file;T0;[[@�ik;F;;�;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@;4i�;$@�;:T;%I"5def config_file=(file) @config_file = file end;T;&I"def config_file=(file);T;'To;;F; ;;;;I"$Doing::Configuration#config_dir;F;[�;[[@�io;F;:config_dir;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@;4i�;$@�;:T;%I"zdef config_dir @config_dir ||= File.join(Util.user_home, '.config', 'doing') # @config_dir ||= Util.user_home end;T;&I"def config_dir;T;'To;;F; ;;;;I"-Doing::Configuration#default_config_file;F;[�;[[@�it;F;:default_config_file;;;[�;{�;IC;"�;T;[o;) ;*I" raise;F;+@ ;0;,[I"DoingRuntimeError;T;!@(;[�;@ ; 0;!@(;4i�;$@�;:T;%I"�def default_config_file raise DoingRuntimeError, "#{config_dir} exists but is not a directory" if File.exist?(config_dir) && !File.directory?(config_dir) unless File.exist?(config_dir) FileUtils.mkdir_p(config_dir) Doing.logger.log_now(:warn, "Config directory created at #{config_dir}") end # File.join(config_dir, 'config.yml') File.join(config_dir, 'config.yml') end;T;&I"def default_config_file;T;'To;;F; ;;;;I",Doing::Configuration#additional_configs;F;[�;[[@�i{;F;;�;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@8;4i�;$@�;:T;%I"Kdef additional_configs @additional_configs ||= find_local_config end;T;&I"def additional_configs;T;'To;;F; ;;;;I"'Doing::Configuration#choose_config;F;[�;[[@�i�;T;:choose_config;;;[�;{�;IC;"7Present a menu if there are multiple configs found;T;[o;) ;*I"return;F;+I"file path;T;0;,[I"String;T;!@D;[�;I"Y Present a menu if there are multiple configs found @return [String] file path ;T; 0;!@D;4i�;"T;5o;6;7F;8i;9i�;$@�;:T;%I"ydef choose_config if @additional_configs.count.positive? choices = [@config_file] choices.concat(@additional_configs) res = Doing::Prompt.choose_from(choices.uniq.sort.reverse, sorted: false, prompt: 'Local configs found, select which to update > ') raise UserCancelled, 'Cancelled' unless res res.strip || @config_file else @config_file end end;T;&I"def choose_config;T;'To;;F; ;;;;I"*Doing::Configuration#resolve_key_path;F;[[I"keypath;T0;[[@�i�;T;:resolve_key_path;;;[�;{�;IC;"%Resolve a fuzzy-matched key path;T;[o;) ;*I" param;F;+I"|A dot-separated key path, e.g. "plugins.plugin_path". Will also work with "plug.path" (fuzzy matched, first match wins);T;I"keypath;T;,[I"String;T;!@Wo;) ;*I"return;F;+I"#ordered array of resolved keys;T;0;,[I" Array;T;!@W;[�;I"@ Resolve a fuzzy-matched key path @param keypath [String] A dot-separated key path, e.g. "plugins.plugin_path". Will also work with "plug.path" (fuzzy matched, first match wins) @return [Array] ordered array of resolved keys ;T; 0;!@W;4i�;"T;5o;6;7F;8i�;9i�;$@�;:T;%I"kdef resolve_key_path(keypath) cfg = @settings real_path = [] 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(distance: 4) real_path << key new_cfg = val break end if new_cfg.nil? Doing.logger.error("Key match not found: #{path}") break end cfg = new_cfg end end Doing.logger.debug('Config:', "translated key path #{keypath} to #{real_path.join('.')}") real_path end;T;&I""def resolve_key_path(keypath);T;'To;;F; ;;;;I"'Doing::Configuration#value_for_key;F;[[I"keypath;TI"'';T;[[@�i�;T;:value_for_key;;;[�;{�;IC;"/Get the value for a fuzzy-matched key path;T;[o;) ;*I" param;F;+I"|A dot-separated key path, e.g. "plugins.plugin_path". Will also work with "plug.path" (fuzzy matched, first match wins);T;I"keypath;T;,[I"String;T;!@ro;) ;*I"return;F;+I"Config value;T;0;,[I" Hash;T;!@r;[�;I"7 Get the value for a fuzzy-matched key path @param keypath [String] A dot-separated key path, e.g. "plugins.plugin_path". Will also work with "plug.path" (fuzzy matched, first match wins) @return [Hash] Config value ;T; 0;!@r;4i�;"T;5o;6;7F;8i�;9i�;$@�;:T;%I"def value_for_key(keypath = '') cfg = @settings real_path = ['config'] unless keypath =~ /^[.*]?$/ real_path = resolve_key_path(keypath) return nil unless real_path&.count&.positive? cfg = cfg.dig(*real_path) end cfg.nil? ? nil : { real_path[-1] => cfg } end;T;&I"$def value_for_key(keypath = '');T;'To;;F; ;;;;I"Doing::Configuration#from;F;[[I"user_config;T0;[[@�i�;T;: from;;;[�;{�;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"2Doing::Configuration#update_deprecated_config;F;[�;[[@�i�;T;:update_deprecated_config;;;[�;{�;IC;"KMethod for transitioning from ~/.doingrc to ~/.config/doing/config.yml;T;[�;[�;I"M Method for transitioning from ~/.doingrc to ~/.config/doing/config.yml ;T; 0;!@�;4i�;"T;5o;6;7F;8i�;9i�;$@�;:T;%I"def update_deprecated_config # return # Until further notice return if File.exist?(default_config_file) old_file = File.join(Util.user_home, '.doingrc') return unless File.exist?(old_file) wwid = Doing::WWID.new Doing.logger.log_now(:warn, 'Deprecated:', "main config file location has changed to #{config_file}") res = wwid.yn("Move #{old_file} to new location, preserving settings?", default_response: true) return unless res if File.exist?(default_config_file) res = wwid.yn("#{default_config_file} already exists, overwrite it?", default_response: false) unless res @config_file = old_file return end end FileUtils.mv old_file, default_config_file, force: true Doing.logger.log_now(:warn, 'Config:', "Config file moved to #{default_config_file}") Doing.logger.log_now(:warn, 'Config:', %(If ~/.doingrc exists in the future, it will be considered a local config and its values will override the default configuration.)) Process.exit 0 end;T;&I"!def update_deprecated_config;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"�def configure(opt = {}) update_deprecated_config if config_file == default_config_file @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; ;;;;I"!Doing::Configuration#inspect;F;[�;[[@�i%;T;;M;;;[�;{�;IC;"�;T;[o;) ;*I"private;F;+I"�;T;0;,0;!@�;[�;I" @private;T; 0;!@�;4i�;"F;5o;6;7F;8i$;9i$;$@�;:T;%I"Bdef inspect %(<Doing::Configuration #{@settings.hash}>) end;T;&I"def inspect;T;'To;;F; ;;;;I"Doing::Configuration#to_s;F;[�;[[@�i*;T;;L;;;[�;{�;IC;"�;T;[o;) ;*I"private;F;+I"�;T;0;,0;!@�;[�;I" @private;T; 0;!@�;4i�;"F;5o;6;7F;8i);9i);$@�;:T;%I"(def to_s YAML.dump(@settings) end;T;&I" def to_s;T;'To;;F; ;;;N;I"+Doing::Configuration#find_deprecations;F;[[I"config;T0;[[@�i5;T;:find_deprecations;;;[�;{�;IC;"$Test for deprecated config keys;T;[o;) ;*I" param;F;+I"The configuration;T;I"config;T;,0;!@�;[�;I"M Test for deprecated config keys @param config The configuration ;T; 0;!@�;4i�;"T;5o;6;7F;8i0;9i4;$@�;:T;%I"Qdef 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; ;;;N;I"&Doing::Configuration#local_config;F;[�;[[@�iW;T;:local_config;;;[�;{�;IC;"Read local configurations;T;[o;) ;*I"return;F;+I"Hash of config options;T;0;,0;!@�;[�;I"D Read local configurations @return Hash of config options ;T; 0;!@�;4i�;"T;5o;6;7F;8iR;9iV;$@�;: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; ;;;N;I",Doing::Configuration#read_local_configs;F;[�;[[@�id;F;:read_local_configs;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@ ;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; ;;;N;I"%Doing::Configuration#read_config;F;[�;[[@�iu;T;:read_config;;;[�;{�;IC;"Reads a configuration.;T;[�;[�;I" Reads a configuration. ;T; 0;!@;4i�;"T;5o;6;7F;8ir;9it;$@�;: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; ;;;N;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; ;;;N;I"&Doing::Configuration#load_plugins;F;[[I"add_dir;TI"nil;T;[[@�i�;F;:load_plugins;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@7;4i�;$@�;:T;%I"�def load_plugins(add_dir = nil) FileUtils.mkdir_p(add_dir) if add_dir && !File.exist?(add_dir) Plugins.load_plugins(add_dir) end;T;&I"$def load_plugins(add_dir = nil);T;'T;U@�;VIC;[�;U@�;WIC;[�;U@�;XIC;Y{;ZIC;Y{�;[T;IC;Y{;-IC;Y{;\@�;]0;[T:ignore_localIC;Y{;\0;]@�;[T;[T;[T;^{�;_[�;[[@�i;T;:Configuration;;;;;[�;{�;IC;"Configuration object;T;[�;[�;I" Configuration object ;T; 0;!@�;4i�;"T;5o;6;7F;8i ;9i;$@;I"Doing::Configuration;F;ao;b;c0;d0;e0;;f;$@;g0;h;Z;'To; ;IC;[o;;F; ;Z;;;I"Doing::Plugins.user_home;F;[�;[[I" lib/doing/plugin_manager.rb;Ti;F;;w;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@\;4i�;$@Z;:T;%I"6def user_home @user_home ||= Util.user_home end;T;&I"def user_home;T;'To;;F; ;Z;;;I"Doing::Plugins.plugins;F;[�;[[@ai;F;:plugins;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@i;4i�;$@Z;:T;%I"Hdef plugins @plugins ||= { import: {}, export: {} } end;T;&I"def plugins;T;'To;;F; ;Z;;;I" Doing::Plugins.load_plugins;F;[[I"add_dir;TI"nil;T;[[@ai;T;;>;;;[�;{�;IC;"%Load plugins from plugins folder;T;[�;[�;I"' Load plugins from plugins folder ;T; 0;!@u;4i�;"T;5o;6;7F;8i;9i;$@Z;: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; ;Z;;;I" Doing::Plugins.plugins_path;F;[[I"add_dir;TI"nil;T;[[@ai';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&;$@Z;: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; ;Z;;;I"Doing::Plugins.register;F;[[I" title;T0[I" type;T0[I" klass;T0;[[@ai9;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;$@Z;: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; ;Z;;;I"#Doing::Plugins.validate_plugin;F;[[I" title;T0[I" type;T0[I" klass;T0;[[@aiU;F;:validate_plugin;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@Z;: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; ;Z;;;I"Doing::Plugins.valid_type;F;[[I" type;T0[I" default:;TI"nil;T;[[@aib;F;:valid_type;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@Z;: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; ;Z;;;I" Doing::Plugins.list_plugins;F;[[I"options;TI"{};T;[[@aiw;T;:list_plugins;;;[�;{�;IC;"%List available plugins to stdout;T;[o;) ;*I" param;F;+I"�;T;I"options;T;,[I" type;TI"separator;T;!@�;[�;I"Q List available plugins to stdout @param options { type, separator } ;T; 0;!@�;4i�;"T;5o;6;7F;8ir;9iv;$@Z;: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; ;Z;;;I"%Doing::Plugins.available_plugins;F;[[I" type:;TI":export;T;[[@ai�;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;!@�o;) ;*I"return;F;+I"plugin names;T;0;,[I"Array<String>;T;!@�;[�;I"� Return array of available plugin names @param type Plugin type (:import, :export) @return [Array<String>] plugin names ;T; 0;!@�;4i�;"T;5o;6;7F;8i�;9i�;$@Z;: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; ;Z;;;I" Doing::Plugins.plugin_names;F;[[I" type:;TI":export;T[I"separator:;TI"'|';T;[[@ai�;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;!@�o;) ;*I" param;F;+I"%The separator to join names with;T;I"separator;T;,0;!@�o;) ;*I"return;F;+I"Plugin names;T;0;,[I"String;T;!@�;[�;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;!@�;4i�;"T;5o;6;7F;8i�;9i�;$@Z;: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; ;Z;;;I" Doing::Plugins.plugin_regex;F;[[I" type:;TI":export;T;[[@ai�;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�;$@Z;: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; ;Z;;;I"$Doing::Plugins.plugin_templates;F;[[I" type:;TI":export;T;[[@ai�;F;:plugin_templates;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@6;4i�;$@Z;: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; ;Z;;;I""Doing::Plugins.template_regex;F;[[I" type:;TI":export;T;[[@ai�;F;:template_regex;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@E;4i�;$@Z;: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; ;Z;;;I"(Doing::Plugins.template_for_trigger;F;[[I"trigger;T0[I" type:;TI":export;T;[[@ai�;F;:template_for_trigger;;;[�;{�;IC;"�;T;[o;) ;*I" raise;F;+@ ;0;,[I"Errors::InvalidArgument;T;!@T;[�;@ ; 0;!@T;4i�;$@Z;: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;U@Z;VIC;[�;U@Z;WIC;[�;U@Z;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@ai ;T;:Plugins;;;;;[�;{�;IC;"Plugin handling;T;[�;[�;I"Plugin handling;T; 0;!@Z;4i�;"F;5o;6;7F;8i ;9i ;$@;I"Doing::Plugins;F;'T;U@;VIC;[�;U@;WIC;[�;U@;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@D i [I"lib/doing/hash.rb;Ti[@i[@�i[I"lib/doing/time.rb;Ti[@ri[@�i[@�i[@� i[@{i[@�i [@*i[@i[I"lib/doing/string.rb;Ti[I"lib/doing/symbol.rb;Ti[@9i[@�i[@�i[@i[@�i[@ai[I"!lib/doing/string_chronify.rb;Ti;T;: Doing;;;;;[�;{�;IC;";Cribbed from <https://github.com/flori/term-ansicolor>;T;[�;[�;I";Cribbed from <https://github.com/flori/term-ansicolor>;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;!@�;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;'To;;F; ;;;;I"Hash#deep_set;F;[[I" path;T0[I" value;T0;[[@�i,;T;: deep_set;;;[�;{�;IC;"+Set a nested hash value using an array;T;[ o;) ;*I"example;F;+I"�;F;I"+`{}.deep_set(['one', 'two'], 'value')`;T;,0;!@�o;) ;*I"example;F;+I"�;F;I"*`=> { 'one' => { 'two' => 'value' } };T;,0;!@�o;) ;*I" param;F;+I" key path;T;I" path;T;,[I" Array;T;!@�o;) ;*I" param;F;+I"The value;T;I" value;T;,0;!@�;[�;I"�Set a nested hash value using an array @example `{}.deep_set(['one', 'two'], 'value')` @example `=> { 'one' => { 'two' => 'value' } } @param path [Array] key path @param value The value ;T; 0;!@�;4i�;"F;5o;6;7F;8i$;9i+;$@�;:T;%I"Rdef deep_set(path, value) if path.count == 1 if value self[path[0]] = value else delete(path[0]) end else if value self.default_proc = ->(h, k) { h[k] = Hash.new(&h.default_proc) } dig(*path[0..-2])[path.fetch(-1)] = value else return self unless dig(*path) dig(*path[0..-2]).delete(path.fetch(-1)) path.pop cleaned = self path.each do |key| if cleaned[key].empty? cleaned.delete(key) break end cleaned = cleaned[key] end empty? ? nil : self end end end;T;&I"def deep_set(path, value);T;'T;U@�;VIC;[�;U@�;WIC;[�;U@�;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@�i ;T;: Hash;;;;;[�;{�;IC;"Hash helpers;T;[�;[�;I"Hash helpers;T; 0;!@�;4i�;"F;5o;6;7F;8i ;9i ;$@;I" Hash;F;ao;b;c0;d0;e0;;f;$@;g0;h;Z;'To; ;IC;[o;;F; ;;;;I"Time#relative_date;F;[�;[[@�i;F;:relative_date;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@;4i�;$@;: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;U@;VIC;[�;U@;WIC;[�;U@;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@�i ;T;: Time;;;;;[�;{�;IC;"Date helpers;T;[�;[�;I" Date helpers ;T; 0;!@;4i�;"T;5o;6;7F;8i;9i ;$@;I" Time;F;ao;b;c0;d0;e0;;f;$@;g0;h;Z;'T@�o; ;IC;[-o;;F; ;;;;I"String#is_rx?;F;[�;[[@�i;T;:is_rx?;;;[�;{�;IC;"PDetermines if receiver is surrounded by slashes or starts with single quote;T;[o;) ;*I"return;F;+I"$True if regex, False otherwise.;T;0;,[@";!@:;[�;I" Determines if receiver is surrounded by slashes or starts with single quote @return True if regex, False otherwise. ;T; 0;!@:;4i�;"T;5o;6;7F;8i;9i;$@8;:T;%I".def is_rx? self =~ %r{(^/.*?/$|^')} end;T;&I"def is_rx?;T;'To;;F; ;;;;I"String#to_rx;F;[[I"distance:;TI"3;T[I"case_type:;TI":smart;T;[[@�i$;T;: to_rx;;;[�;{�;IC;"�Convert string to fuzzy regex. Characters in words can be separated by up to *distance* characters in haystack, spaces indicate unlimited distance.;T;[ o;) ;*I"example;F;+I"�;F;I"V`"this word".to_rx(2) => /t.{0,3}h.{0,3}i.{0,3}s.{0,3}.*?w.{0,3}o.{0,3}r.{0,3}d/`;T;,0;!@Lo;) ;*I" param;F;+I"(Allowed distance between characters;T;I" distance;T;,[I"Integer;T;!@Lo;) ;*I" param;F;+I"The case type;T;I"case_type;T;,0;!@Lo;) ;*I"return;F;+I"Regex pattern;T;0;,[I"Regexp;T;!@L;[�;I"� Convert string to fuzzy regex. Characters in words can be separated by up to *distance* characters in haystack, spaces indicate unlimited distance. @example `"this word".to_rx(2) => /t.{0,3}h.{0,3}i.{0,3}s.{0,3}.*?w.{0,3}o.{0,3}r.{0,3}d/` @param distance [Integer] Allowed distance between characters @param case_type The case type @return [Regexp] Regex pattern ;T; 0;!@L;4i�;"T;5o;6;7F;8i;9i#;$@8;:T;%I"bdef to_rx(distance: 3, case_type: :smart) case_sensitive = case case_type when :smart self =~ /[A-Z]/ ? true : false when :sensitive true else false end pattern = case dup.strip when %r{^/.*?/$} sub(%r{/(.*?)/}, '\1') when /^'/ sub(/^'(.*?)'?$/, '\1') else split(/ +/).map { |w| w.split('').join(".{0,#{distance}}") }.join('.*?') end Regexp.new(pattern, !case_sensitive) end;T;&I".def to_rx(distance: 3, case_type: :smart);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;!@s;[�;I"� Test string for truthiness (0, "f", "false", "n", "no" all return false, case insensitive, otherwise true) @return [Boolean] String is truthy ;T; 0;!@s;4i�;"T;5o;6;7F;8i9;9i=;$@8;: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#compress;F;[�;[[@�iG;T;;m;;;[�;{�;IC;"-Compress multiple spaces to single space;T;[�;[�;I"-Compress multiple spaces to single space;T; 0;!@�;4i�;"F;5o;6;7F;8iF;9iF;$@8;:T;%I"-def compress gsub(/ +/, ' ').strip end;T;&I"def compress;T;'To;;F; ;;;;I"String#compress!;F;[�;[[@�iK;F;;l;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@8;:T;%I")def compress! replace compress end;T;&I"def compress!;T;'To;;F; ;;;;I"String#highlight_tags!;F;[[I" color;TI" 'yellow';T;[[@�iP;T;:highlight_tags!;;;[�;{�;IC;"�;T;[�;[o:YARD::Tags::RefTagList;Uo;b;c@8;dI"#highlight_tags;T;eT;;t;$@8;go;;F; ;;;;I"String#highlight_tags;F;[[I" color;TI" 'yellow';T;[[@�i[;T;;t;;;[�;{�;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;8iT;9iZ;$@8;:T;%I"�def highlight_tags(color = 'yellow') escapes = scan(/(\e\[[\d;]+m)[^\e]+@/) color = color.split(' ') unless color.is_a?(Array) tag_color = '' color.each { |c| tag_color += Doing::Color.send(c) } last_color = if !escapes.empty? escapes[-1][0] else Doing::Color.default end gsub(/(\s|m)(@[^ ("']+)/, "\\1#{tag_color}\\2#{Doing::Color.reset}#{last_color}") end;T;&I")def highlight_tags(color = 'yellow');T;'T;h0;*I" param;T;0;I"!@param (see #highlight_tags);T; 0;!@�;4i�;"T;5o;6;7F;8iO;9iO;$@8;: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;[�;[[@�im;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;8ih;9il;$@8;: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;[[@�iw;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;8ir;9iv;$@8;: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�;$@8;: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;[[@�i�;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;8i�;9i�;$@8;: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;[[@�i�;F;:truncmiddle!;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@';4i�;$@8;: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;[�;[[@�i�;T;;�;;;[�;{�;IC;"Remove color escape codes;T;[o;) ;*I"return;F;+I"clean string;T;0;,0;!@8;[�;I": Remove color escape codes @return clean string ;T; 0;!@8;4i�;"T;5o;6;7F;8i�;9i�;$@8;: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;!@I;4i�;$@8;: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"color:;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;!@Uo;) ;*I" param;F;+I"5(Optional) The width to pad each subsequent line;T;I"offset;T;,[I"Integer;T;!@Uo;) ;*I" param;F;+I",(Optional) A prefix to add to each line;T;I"prefix;T;,[I"String;T;!@U;[�;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;!@U;4i�;"T;5o;6;7F;8i�;9i�;$@8;:T;%I"�def wrap(len, pad: 0, indent: ' ', offset: 0, prefix: '', color: '', after: '', reset: '') last_color = color.empty? ? '' : after.last_color note_rx = /(?i-m)(%(?:[io]d|(?:\^[\s\S])?(?:(?:[ _t]|[^a-z0-9])?\d+)?(?:[\s\S][ _t]?)?)?note)/ # Don't break inside of tag values str = gsub(/@\S+\(.*?\)/) { |tag| tag.gsub(/\s/, '%%%%') } words = str.split(/ /).map { |word| word.gsub(/%%%%/, ' ') } out = [] line = [] words.each do |word| if line.join(' ').uncolor.length + word.uncolor.length + 1 > len out.push(line.join(' ')) line.clear end line << word.uncolor end out.push(line.join(' ')) note = '' after.sub!(note_rx) do note = Regexp.last_match(0) '' end out[0] = format("%-#{pad}s%s%s", out[0], last_color, after) left_pad = ' ' * offset left_pad += indent out.map { |l| "#{left_pad}#{color}#{l}#{last_color}" }.join("\n").strip + last_color + " #{note}".chomp end;T;&I"_def wrap(len, pad: 0, indent: ' ', offset: 0, prefix: '', color: '', 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�;$@8;: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�;$@8;: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�;$@8;: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, :ignore;T;0;,0;!@�;[�;I"d Convert a case sensitivity string to a symbol @return Symbol :smart, :sensitive, :ignore ;T; 0;!@�;4i�;"T;5o;6;7F;8i�;9i�;$@8;: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�;$@8;:T;%I"�def normalize_case(default = :smart) case self when /^c/i :sensitive when /^i/i :ignore 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;$@8;: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�;$@8;: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�;$@8;: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�;$@8;:T;%I"Bdef normalize_trigger gsub(/\((?!\?:)/, '(?:').downcase end;T;&I"def normalize_trigger;T;'To;;F; ;;;;I"String#to_tags;F;[�;[[@�i;F;;r;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@;4i�;$@8;: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�;$@8;: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�;$@8;:T;%I"�def add_tags(tags, remove: false) title = self.dup tags = tags.to_tags 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"**options;T0;[[@�i1;T;: tag!;;;[�;{�;IC;"*Add, rename, or remove a tag in place;T;[o;) ;*I"see;F;+0;I" #tag;T;,0;!@K;[�;I"7 Add, rename, or remove a tag in place @see #tag ;T; 0;!@K;4i�;"T;5o;6;7F;8i,;9i0;$@8;:T;%I"?def tag!(tag, **options) replace tag(tag, **options) end;T;&I"def tag!(tag, **options);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"force:;TI" false;T;[[@�iB;T;;A;;;[�;{�;IC;"!Add, rename, or remove a tag;T;[ o;) ;*I" param;F;+I"The tag;T;I"tag;T;,0;!@`o;) ;*I" param;F;+I" Value for tag (@tag(value));T;I" value;T;,[I"String;T;!@`o;) ;*I" param;F;+I"%Remove the tag instead of adding;T;I"remove;T;,[I"Boolean;T;!@`o;) ;*I" param;F;+I"Replace tag with this tag;T;I"rename_to;T;,[I"String;T;!@`o;) ;*I" param;F;+I"Tag is regular expression;T;I" regex;T;,[I"Boolean;T;!@`o;) ;*I" param;F;+I"-Operating on a single item (for logging);T;I"single;T;,[I"Boolean;T;!@`o;) ;*I" param;F;+I"0With rename_to, add tag if it doesn't exist;T;I" force;T;,[I"Boolean;T;!@`o;) ;*I"return;F;+I""The string with modified tags;T;0;,[I"String;T;!@`;[�;I"� Add, rename, or remove a tag @param tag The tag @param value [String] Value for tag (@tag(value)) @param remove [Boolean] Remove the tag instead of adding @param rename_to [String] Replace tag with this tag @param regex [Boolean] Tag is regular expression @param single [Boolean] Operating on a single item (for logging) @param force [Boolean] With rename_to, add tag if it doesn't exist @return [String] The string with modified tags ;T; 0;!@`;4i�;"T;5o;6;7F;8i5;9iA;$@8;:T;%I" def tag(tag, value: nil, remove: false, rename_to: nil, regex: false, single: false, force: 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 rx = Regexp.new("(?<=^| )@#{rx_tag}(?<parens>\\((?<value>[^)]*)\\))?(?= |$)", case_sensitive) m = title.match(rx) if m.nil? && rename_to && force title.tag!(rename_to, value: value, single: single) elsif m title.gsub!(rx) do rename_to ? "@#{rename_to}#{value.nil? ? m['parens'] : "(#{value})"}" : '' 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"gdef tag(tag, value: nil, remove: false, rename_to: nil, regex: false, single: false, force: 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;8iy;9i};$@8;:T;%I"-def dedup_tags! replace dedup_tags end;T;&I"def dedup_tags!;T;'To;;F; ;;;;I"String#dedup_tags;F;[�;[[@�i�;F;:dedup_tags;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@8;:T;%I"*def dedup_tags title = dup tags = title.scan(/(?<=\A| )(@(\S+?)(\([^)]+\))?)(?= |\Z)/).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#last_color;F;[�;[[@�i�;T;:last_color;;;[�;{�;IC;"PReturns the last escape sequence from a string. Actually returns all escape codes, with the assumption that the result of inserting them will generate the same color as was set at the end of the string. Because you can send modifiers like dark and bold separate from color codes, only using the last code may not render the same style.;T;[o;) ;*I"return;F;+I"All escape codes in string;T;0;,[I"String;T;!@�;[�;I"�Returns the last escape sequence from a string. Actually returns all escape codes, with the assumption that the result of inserting them will generate the same color as was set at the end of the string. Because you can send modifiers like dark and bold separate from color codes, only using the last code may not render the same style. @return [String] All escape codes in string ;T; 0;!@�;4i�;"F;5o;6;7F;8i�;9i�;$@8;:T;%I"6def last_color scan(/\e\[[\d;]+m/).join('') end;T;&I"def last_color;T;'To;;F; ;;;;I"String#link_urls!;F;[[I" **opt;T0;[[@�i�;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;8i�;9i�;$@8;:T;%I"adef link_urls!(**opt) fmt = opt.fetch(:format, :html) replace link_urls(format: fmt) end;T;&I"def link_urls!(**opt);T;'To;;F; ;;;;I"String#link_urls;F;[[I" **opt;T0;[[@�i�;F;:link_urls;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@8;:T;%I"�def link_urls(**opt) fmt = opt.fetch(:format, :html) return self unless fmt str = dup str = str.remove_self_links if fmt == :markdown str.replace_qualified_urls(format: fmt).clean_unlinked_urls end;T;&I"def link_urls(**opt);T;'To;;F; ;;;;I"String#remove_self_links;F;[�;[[@�i�;T;:remove_self_links;;;[�;{�;IC;"$Remove <self-linked> formatting;T;[�;[�;I"$Remove <self-linked> formatting;T; 0;!@;4i�;"F;5o;6;7F;8i�;9i�;$@8;:T;%I"�def remove_self_links gsub(/<(.*?)>/) do |match| m = Regexp.last_match if m[1] =~ /^https?:/ m[1] else match end end end;T;&I"def remove_self_links;T;'To;;F; ;;;;I""String#replace_qualified_urls;F;[[I"**options;T0;[[@�i�;T;:replace_qualified_urls;;;[�;{�;IC;"Replace qualified urls;T;[�;[�;I"Replace qualified urls;T; 0;!@;4i�;"F;5o;6;7F;8i�;9i�;$@8;:T;%I"�def replace_qualified_urls(**options) fmt = options.fetch(:format, :html) gsub(%r{(?mi)(?x: (?<!["'\[(\\]) (?<protocol>(?:http|https)://) (?<domain>[\w\-]+(?:\.[\w\-]+)+) (?<path>[\w\-.,@?^=%&;:/~+#]*[\w\-@^=%&;/~+#])? )}) do |_match| m = Regexp.last_match url = "#{m['domain']}#{m['path']}" proto = m['protocol'].nil? ? 'http://' : m['protocol'] case fmt when :terminal TTY::Link.link_to("#{proto}#{url}", "#{proto}#{url}") when :html %(<a href="#{proto}#{url}" title="Link to #{m['domain']}">[#{url}]</a>) when :markdown "[#{url}](#{proto}#{url})" else m[0] end end end;T;&I"*def replace_qualified_urls(**options);T;'To;;F; ;;;;I"String#clean_unlinked_urls;F;[�;[[@�i�;T;:clean_unlinked_urls;;;[�;{�;IC;"Clean up unlinked <urls>;T;[�;[�;I"Clean up unlinked <urls>;T; 0;!@!;4i�;"F;5o;6;7F;8i�;9i�;$@8;:T;%I"�def clean_unlinked_urls gsub(/<(\w+:.*?)>/) do |match| m = Regexp.last_match if m[1] =~ /<a href/ match else %(<a href="#{m[1]}" title="Link to #{m[1]}">[link]</a>) end end end;T;&I"def clean_unlinked_urls;T;'To;;F; ;;;;I"String#set_type;F;[[I" kind;TI"nil;T;[[@�i�;F;: set_type;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@/;4i�;$@8;:T;%I"[def set_type(kind = nil) if kind case kind.to_s when /^a/i gsub(/^\[ *| *\]$/, '').split(/ *, */) when /^i/i to_i when /^f/i to_f when /^sy/i sub(/^:/, '').to_sym when /^b/i self =~ /^(true|yes)$/ ? true : false else to_s end else case self when / *, */ gsub(/^\[ *| *\]$/, '').split(/ *, */) when /^[0-9]+$/ to_i when /^[0-9]+\.[0-9]+$/ to_f when /^:\w+/ sub(/^:/, '').to_sym when /^(true|yes)$/i true when /^(false|no)$/i false else to_s end end end;T;&I"def set_type(kind = nil);T;'To;;F; ;;;;I"String#chronify;F;[[I"**options;T0;[[@�i ;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"Additional options;T;I"options;T;,0;!@>o;B;*I"option;F;+0;I"options;T;,0;Co;D ;*I"option;F;+I"(assume future date (default: false);T;I":future;T;,[I"Boolean;T;E0;!@>o;B;*I"option;F;+0;I"options;T;,0;Co;D ;*I"option;F;+I"F:begin or :end to assume beginning or end of arbitrary time range;T;I":guess;T;,[I"Symbol;T;E0;!@>o;) ;*I"return;F;+I"result;T;0;,[I" DateTime;T;!@>o;) ;*I" raise;F;+@ ;0;,[I"InvalidTimeExpression;T;!@>;[�;I"� 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 options Additional options @option options :future [Boolean] assume future date (default: false) @option options :guess [Symbol] :begin or :end to assume beginning or end of arbitrary time range @return [DateTime] result ;T; 0;!@>;4i�;"T;5o;6;7F;8i;9i;$@8;:T;%I"def chronify(**options) now = Time.now raise InvalidTimeExpression, "Invalid time expression #{inspect}" if to_s.strip == '' secs_ago = if match(/^(\d+)$/) # plain number, assume minutes Regexp.last_match(1).to_i * 60 elsif (m = match(/^(?:(?<day>\d+)d)?(?:(?<hour>\d+)h)?(?:(?<min>\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(self, { guess: options.fetch(:guess, :begin), context: options.fetch(:future, false) ? :future : :past, ambiguous_time_range: 8 }) end end;T;&I"def chronify(**options);T;'To;;F; ;;;;I"String#chronify_qty;F;[�;[[@�i>;T;:chronify_qty;;;[�;{�;IC;"�Converts simple strings into seconds that can be added to a Time object Input string can be HH:MM or XX[dhm][[XXhm][XXm]] (1d2h30m, 45m, 1.5d, 1h20m, etc.);T;[o;) ;*I"return;F;+I"seconds;T;0;,[I"Integer;T;!@m;[�;I"� Converts simple strings into seconds that can be added to a Time object Input string can be HH:MM or XX[dhm][[XXhm][XXm]] (1d2h30m, 45m, 1.5d, 1h20m, etc.) @return [Integer] seconds ;T; 0;!@m;4i�;"T;5o;6;7F;8i5;9i=;$@8;:T;%I"Jdef chronify_qty minutes = 0 case self.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;T;'T;U@8;VIC;[�;U@8;WIC;[o;b;c@8;dI"Doing::Color;T;e0;;�;$@;g@? ;h;�;U@8;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@�i[@�i ;T;:String;;;;;[�;{�;IC;"!Chronify methods for strings;T;[�;[�;I"!Chronify methods for strings;T; 0;!@8;4i�;"F;5o;6;7F;8i ;9i ;$@;I"String;F;ao;b;c0;d0;e0;;f;$@;g0;h;Z;'To; ;IC;[o;;F; ;;;;I"Symbol#normalize_bool;F;[[I"default;TI" :and;T;[[@�i ;F;;h;;;[�;{�;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;;d;;;[�;{�;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;F;;f;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@�;:T;%I""def normalize_case self end;T;&I"def normalize_case;T;'T;U@�;VIC;[�;U@�;WIC;[�;U@�;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@�i;T;:Symbol;;;;;[�;{�;IC;"Symbol helpers;T;[�;[�;I" Symbol helpers ;T; 0;!@�;4i�;"T;5o;6;7F;8i ;9i;$@;I"Symbol;F;ao;b;c0;d0;e0;;f;$@;g0;h;Z;'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;!@�;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"end_char:;TI" "\n";T;[[@�i;F;:status;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@�;4i�;$@�;:T;%I"�def status(msg, reset: true, end_char: "\n") $stderr.print format("#{esc['kill']}#{esc['boldyellow']}> #{esc['whiteboard']}%s#{esc['default']}%s", msg, reset ? "\r" : end_char) end;T;&I"1def status(msg, reset: true, end_char: "\n");T;'To;;F; ;;;;I"Status#msg;F;[ [I"msg;T0[I"reset:;TI" true;T[I"color:;TI"'green';T[I"end_char:;TI" "\n";T;[[@�i;F;:msg;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@;4i�;$@�;:T;%I"�def msg(msg, reset: true, color: 'green', end_char: "\n") $stderr.print format("#{esc['kill']}#{esc[color]}%s#{esc['default']}%s", msg, reset ? "\r" : end_char) end;T;&I">def msg(msg, reset: true, color: 'green', end_char: "\n");T;'To;;F; ;;;;I"Status#clear;F;[�;[[@�i;F;: clear;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@";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;!@.;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;U@�;VIC;[�;U@�;WIC;[�;U@�;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@�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;;;;;;[�;{�;IC;"�;T;[o;) ;*I"return;F;+I"/a new instance of MarkdownDocumentListener;T;0;,[I"MarkdownDocumentListener;F;!@O;[�;@ ; 0;!@O;4i�;$@M;: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;!@i;4i�;$@M;: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;!@u;4i�;"F;5o;6;7F;8i;9i;$@M;: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;$@M;: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�;$@M;: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@;$@M;: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�;$@M;: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;$@M;: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;$@M;: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�;$@M;: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;!@�;4i�;$@M;: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;!@;4i�;"F;5o;6;7F;8iz;9iz;$@M;: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}` <mark>`#{@parent_command.join(' ')}`</mark>#{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�;$@M;: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"<GLI::Commands::MarkdownDocumentListener#default_command;F;[[I" name;T0;[[@\i�;T;:default_command;;;[�;{�;IC;"EGives you the name of the current command in the current context;T;[�;[�;I"EGives you the name of the current command in the current context;T; 0;!@5;4i�;"F;5o;6;7F;8i�;9i�;$@M;:T;%I"_def default_command(name) @io.puts "#### [Default Command] #{name}" unless name.nil? end;T;&I"def default_command(name);T;'To;;F; ;;;;I"9GLI::Commands::MarkdownDocumentListener#end_commands;F;[�;[[@\i�;F;:end_commands;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@E;4i�;$@M;:T;%I"*def end_commands decrement_nest end;T;&I"def end_commands;T;'To;;F; ;;;N;I"7GLI::Commands::MarkdownDocumentListener#add_dashes;F;[[I" name;T0;[[@\i�;F;:add_dashes;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@Q;4i�;$@M;:T;%I"_def add_dashes(name) name = "-#{name}" name = "-#{name}" if name.length > 2 name end;T;&I"def add_dashes(name);T;'To;;F; ;;;N;I"3GLI::Commands::MarkdownDocumentListener#header;F;[[I"content;T0[I"increment;T0;[[@\i�;F;:header;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@_;4i�;$@M;: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; ;;;N;I";GLI::Commands::MarkdownDocumentListener#increment_nest;F;[[I"increment;TI"1;T;[[@\i�;F;:increment_nest;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@o;4i�;$@M;:T;%I"Mdef increment_nest(increment=1) @nest = "#{@nest}#{'#'*increment}" end;T;&I"$def increment_nest(increment=1);T;'To;;F; ;;;N;I";GLI::Commands::MarkdownDocumentListener#decrement_nest;F;[[I"increment;TI"1;T;[[@\i�;F;:decrement_nest;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@~;4i�;$@M;:T;%I"Odef decrement_nest(increment=1) @nest.gsub!(/#{'#'*increment}$/, '') end;T;&I"$def decrement_nest(increment=1);T;'T;U@M;VIC;[�;U@M;WIC;[�;U@M;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@\i ;T;:MarkdownDocumentListener;;;;;[�;{�;IC;";DocumentListener class for GLI documentation generator;T;[�;[�;I";DocumentListener class for GLI documentation generator;T; 0;!@M;4i�;"F;5o;6;7F;8i;9i;$@K;I",GLI::Commands::MarkdownDocumentListener;F;ao;b;c0;d0;e0;;f;$@;g0;h;Z;'T;U@K;VIC;[�;U@K;WIC;[�;U@K;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@\i;F;: Commands;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@K;4i�;$@I;I"GLI::Commands;F;'T;U@I;VIC;[�;U@I;WIC;[�;U@I;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[[@\i ;F;:GLI;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@I;4i�;$@;I"GLI;F;U@;VIC;[�;U@;WIC;[�;U@;XIC;Y{;ZIC;Y{�;[T;IC;Y{�;[T;[T;^{�;_[�;[�;F;;�;;;;;[�;{�;IC;"�;T;[�;[�;@ ; 0;!@;4i�;$0;@ ;M@;S@�:Hash#deep_freeze@�:Hash#deep_freeze!@�:Hash#stringify_keys@�:Hash#symbolize_keys@�:Hash#deep_set@�: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#id@�:Doing::Item#initialize@�:Doing::Item#interval@�:Doing::Item#end_date@�:Doing::Item#equal?@�:Doing::Item#same_time?@:"Doing::Item#overlapping_time?@':Doing::Item#tag@B:Doing::Item#tags@�:Doing::Item#tags?@�:Doing::Item#search@�:Doing::Item#should_finish?@:Doing::Item#should_time?@&:Doing::Item#move_to@5:Doing::Item#to_s@`:Doing::Item#inspect@n:Doing::Item#should?@:Doing::Item#calc_interval@�:Doing::Item#all_tags?@�:Doing::Item#no_tags?@�:Doing::Item#any_tags?@�:Doing::Item#split_tags@�:Doing::Note@�:Doing::Note#initialize@�:Doing::Note#add@:Doing::Note#append@2:Doing::Note#append_string@H:Doing::Note#compress!@^:Doing::Note#compress@j:Doing::Note#strip_lines!@}:Doing::Note#strip_lines@�:Doing::Note#to_s@�:Doing::Note#inspect@�:Doing::Note#equal?@�;U@:Time#relative_date@:Doing::Util@k:Doing::Util#user_home@m:Doing::Util#exec_available@z:%Doing::Util#first_available_exec@�:#Doing::Util#merge_default_proc@�:(Doing::Util#duplicate_frozen_values@�:"Doing::Util#deep_merge_hashes@�:#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@N:Doing::Util#default_editor@\:!Doing::Util#editor_with_args@h: Doing::Util#args_for_editor@t:$Doing::Util#find_default_editor@�:Doing::WWID@�:#Doing::WWID#additional_configs@�: Doing::WWID#current_section@�:Doing::WWID#doing_file@�:Doing::WWID#content@�: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@6: Doing::WWID#default_option=@C:Doing::WWID#initialize@U:Doing::WWID#logger@h: Doing::WWID#init_doing_file@v:Doing::WWID#create@�:Doing::WWID#fork_editor@�:Doing::WWID#format_input@�:Doing::WWID#sections@�:Doing::WWID#guess_section@�:Doing::WWID#guess_view@:Doing::WWID#add_item@2:Doing::WWID#dedup@~:Doing::WWID#import@�:Doing::WWID#last_note@�:Doing::WWID#reset_item@�:Doing::WWID#repeat_item@�:Doing::WWID#repeat_last@<:Doing::WWID#last_entry@S:Doing::WWID#all_tags@j:Doing::WWID#tag_groups@{:#Doing::WWID#fuzzy_filter_items@�:Doing::WWID#filter_items@�:Doing::WWID#interactive@@ :Doing::WWID#act_on@[ :Doing::WWID#tag_last@� :Doing::WWID#next_item@� :Doing::WWID#edit_last@ :Doing::WWID#stop_start@1 :Doing::WWID#write@} :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@D:Doing::WWID#list_date@m:Doing::WWID#yesterday@�:Doing::WWID#recent@�:Doing::WWID#last@�:Doing::WWID#autotag@#:Doing::WWID#tag_times@9:Doing::WWID#get_interval@b:Doing::WWID#format_time@�:!Doing::WWID#combined_content@�:Doing::WWID#output@�:!Doing::WWID#record_tag_times@�:Doing::WWID#do_archive@ :Doing::WWID#run_after@0 ;q@�:Array#to_tags@�:Array#to_tags!@:Array#highlight_tags@:Array#log_tags@.:Array#nested_hash@A:Doing::Hooks@� :#Doing::Hooks::DEFAULT_PRIORITY@� :Doing::Hooks.register@: Doing::Hooks.priority_value@:Doing::Hooks.register_one@):Doing::Hooks.insert_hook@A:Doing::Hooks.trigger@S:Doing::Items@t:Doing::Items#sections@v:Doing::Items#sections=@�:Doing::Items#initialize@�: Doing::Items#section_titles@�:Doing::Items#section?@�:Doing::Items#add_section@�:Doing::Items#in_section@�:Doing::Items#delete_item@:Doing::Items#update_item@):Doing::Items#to_s@G:Doing::Items#inspect@U:Doing::Pager@y:Doing::Pager.paginate@{:Doing::Pager.paginate=@�:Doing::Pager.page@�: Doing::Pager.command_exist?@�:Doing::Pager.git_pager@�:Doing::Pager.pagers@�:!Doing::Pager.find_executable@�:!Doing::Pager.exec_available?@�:Doing::Pager.which_pager@�:Doing::Color@? :Doing::Color::ATTRIBUTES@A :"Doing::Color::ATTRIBUTE_NAMES@O :Doing::Color#support?@Z :Doing::Color.coloring?@m :Doing::Color.coloring=@~ :Doing::Color.coloring@� :!Doing::Color::COLORED_REGEXP@� :Doing::Color#uncolor@� :Doing::Color#attributes@� :Doing::Color.attributes@� :Doing::Errors@:!Doing::Errors::UserCancelled@:,Doing::Errors::UserCancelled#initialize@:Doing::Errors::EmptyInput@G:)Doing::Errors::EmptyInput#initialize@I:&Doing::Errors::DoingStandardError@p:1Doing::Errors::DoingStandardError#initialize@r: Doing::Errors::WrongCommand@�:+Doing::Errors::WrongCommand#initialize@�:%Doing::Errors::DoingRuntimeError@�:0Doing::Errors::DoingRuntimeError#initialize@�: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@L:#Doing::Errors::HookUnavailable@w:%Doing::Errors::InvalidPluginType@�:$Doing::Errors::PluginUncallable@�:#Doing::Errors::InvalidArgument@�:#Doing::Errors::MissingArgument@�:Doing::Errors::MissingFile@�:!Doing::Errors::MissingEditor@�:"Doing::Errors::NonInteractive@�: Doing::Errors::NoEntryError@�:)Doing::Errors::InvalidTimeExpression@�:"Doing::Errors::InvalidSection@�:Doing::Errors::InvalidView@�: Doing::Errors::ItemNotFound@�:Doing::Prompt@: Doing::Prompt.force_answer=@:"Doing::Prompt.default_answer=@*:Doing::Prompt.force_answer@<:!Doing::Prompt.default_answer@H:Doing::Prompt.yn@T:Doing::Prompt.fzf@x:Doing::Prompt.install_fzf@�:Doing::Prompt.choose_from@�:$Doing::Prompt.choose_from_items@�;y@8:String#is_rx?@::String#to_rx@L:String#truthy?@s:String#compress@�:String#compress!@�:String#highlight_tags!@�:String#highlight_tags@�:String#ignore?@�:String#truncate@�:String#truncate!@�:String#truncmiddle@:String#truncmiddle!@':String#uncolor@8:String#uncolor!@I:String#wrap@U: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!@K:String#tag@`:String#dedup_tags!@�:String#dedup_tags@�:String#last_color@�:String#link_urls!@�:String#link_urls@�:String#remove_self_links@:"String#replace_qualified_urls@:String#clean_unlinked_urls@!:String#set_type@/;z@�:Symbol#normalize_bool@�:Symbol#normalize_order@�:Symbol#normalize_case@�:Doing::Section@2:Doing::Section#original@4:Doing::Section#original=@B:Doing::Section#title@T:Doing::Section#title=@a:Doing::Section#initialize@s:Doing::Section#to_s@�:Doing::Section#inspect@�:Doing::VERSION@�;�@�:Status#cols@�:Status#progress@�:Status#status@�:Status#msg@:Status#clear@":Status#esc@.:Doing::Completion@�:*Doing::Completion.generate_completion@�:Doing::LogAdapter@�:Doing::LogAdapter#logdev=@�:"Doing::LogAdapter#max_length=@:Doing::LogAdapter#level@:Doing::LogAdapter#messages@+:Doing::LogAdapter#results@8:#Doing::LogAdapter::TOPIC_WIDTH@E:"Doing::LogAdapter::LOG_LEVELS@P:"Doing::LogAdapter::COUNT_KEYS@[:!Doing::LogAdapter#initialize@f:!Doing::LogAdapter#log_level=@�:!Doing::LogAdapter#temp_level@�:$Doing::LogAdapter#restore_level@�:'Doing::LogAdapter#adjust_verbosity@�:Doing::LogAdapter#count@�:Doing::LogAdapter#debug@�:Doing::LogAdapter#info@:Doing::LogAdapter#warn@(:Doing::LogAdapter#error@H:!Doing::LogAdapter#abort_with@h:&Doing::LogAdapter#formatted_topic@�:Doing::LogAdapter#write@�:Doing::LogAdapter#log_now@�:%Doing::LogAdapter#output_results@:!Doing::LogAdapter#log_change@:%Doing::LogAdapter#format_counter@1:%Doing::LogAdapter#total_counters@A:%Doing::LogAdapter#write_message?@M:Doing::LogAdapter#message@e:$Doing::LogAdapter#color_message@�:Doing::Configuration@�:"Doing::Configuration#settings@�:'Doing::Configuration#ignore_local=@�:,Doing::Configuration::MissingConfigFile@�:#Doing::Configuration::DEFAULTS@�:$Doing::Configuration#initialize@�:%Doing::Configuration#config_file@:&Doing::Configuration#config_file=@:$Doing::Configuration#config_dir@:-Doing::Configuration#default_config_file@(:,Doing::Configuration#additional_configs@8:'Doing::Configuration#choose_config@D:*Doing::Configuration#resolve_key_path@W:'Doing::Configuration#value_for_key@r:Doing::Configuration#from@�:2Doing::Configuration#update_deprecated_config@�:#Doing::Configuration#configure@�:!Doing::Configuration#inspect@�:Doing::Configuration#to_s@�:+Doing::Configuration#find_deprecations@�:&Doing::Configuration#local_config@�:,Doing::Configuration#read_local_configs@ :%Doing::Configuration#read_config@:+Doing::Configuration#find_local_config@$:&Doing::Configuration#load_plugins@7:Doing::Plugins@Z:Doing::Plugins.user_home@\:Doing::Plugins.plugins@i: Doing::Plugins.load_plugins@u: Doing::Plugins.plugins_path@�:Doing::Plugins.register@�:#Doing::Plugins.validate_plugin@�:Doing::Plugins.valid_type@�: Doing::Plugins.list_plugins@�:%Doing::Plugins.available_plugins@�: Doing::Plugins.plugin_names@�: Doing::Plugins.plugin_regex@!:$Doing::Plugins.plugin_templates@6:"Doing::Plugins.template_regex@E:(Doing::Plugins.template_for_trigger@T:String#chronify@>:String#chronify_qty@m;�@I:GLI::Commands@K:,GLI::Commands::MarkdownDocumentListener@M:7GLI::Commands::MarkdownDocumentListener#initialize@O:6GLI::Commands::MarkdownDocumentListener#beginning@i:3GLI::Commands::MarkdownDocumentListener#ending@u: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@�:4GLI::Commands::MarkdownDocumentListener#command@:8GLI::Commands::MarkdownDocumentListener#end_command@%:<GLI::Commands::MarkdownDocumentListener#default_command@5:9GLI::Commands::MarkdownDocumentListener#end_commands@E:7GLI::Commands::MarkdownDocumentListener#add_dashes@Q:3GLI::Commands::MarkdownDocumentListener#header@_:;GLI::Commands::MarkdownDocumentListener#increment_nest@o:;GLI::Commands::MarkdownDocumentListener#decrement_nest@~