{: rooto:"YARD::CodeObjects::RootObject:@childrenIC:&YARD::CodeObjects::CodeObjectList[o:$YARD::CodeObjects::ModuleObject;IC;[o:#YARD::CodeObjects::ClassObject;IC;[,o:$YARD::CodeObjects::MethodObject:@module_functionF: @scope: instance:@visibility: public: @pathI"Doing::Item#date:EF:@parameters[: @files[[I"lib/doing/item.rb;Ti :@current_file_has_commentsF: @name: date:@source_type: ruby: @tags[:@docstrings{:@docstringIC:YARD::Docstring")Returns the value of attribute date. ;T;[:@ref_tags[: @allI")Returns the value of attribute date.;T:@unresolved_reference0: @object@ :@hash_flagF: @summary0:@namespace@ : @sourceI"def date @date end;T:@signatureI" def date;T: @dynamicTo; ; F; ;;;;I"Doing::Item#date=;F;[[I" value;T0;[[@i ;F;: date=;;;[;{;IC;"Sets the attribute date ;T;[o:YARD::Tags::Tag :@tag_nameI" param;F: @textI",the value to set the attribute date to.;T;I" value;T: @types0;!@;[;I"QSets the attribute date @param value the value to set the attribute date to.;T; 0;!@;"F;#0;$@ ;%I")def date=(value) @date = value end;T;&I"def date=(value);T;'To; ; F; ;;;;I"Doing::Item#title;F;[;[[@i ;F;: title;;;[;{;IC;"*Returns the value of attribute title. ;T;[;[;I"*Returns the value of attribute title.;T; 0;!@-;"F;#0;$@ ;%I"def title @title end;T;&I"def title;T;'To; ; F; ;;;;I"Doing::Item#title=;F;[[@0;[[@i ;F;: title=;;;[;{;IC;"Sets the attribute title ;T;[o;) ;*I" param;F;+I"-the value to set the attribute title to.;T;I" value;T;,0;!@:;[;I"SSets the attribute title @param value the value to set the attribute title to.;T; 0;!@:;"F;#0;$@ ;%I"+def title=(value) @title = value end;T;&I"def title=(value);T;'To; ; F; ;;;;I"Doing::Item#section;F;[;[[@i ;F;: section;;;[;{;IC;",Returns the value of attribute section. ;T;[;[;I",Returns the value of attribute section.;T; 0;!@L;"F;#0;$@ ;%I"def section @section end;T;&I"def section;T;'To; ; F; ;;;;I"Doing::Item#section=;F;[[@0;[[@i ;F;: section=;;;[;{;IC;"Sets the attribute section ;T;[o;) ;*I" param;F;+I"/the value to set the attribute section to.;T;I" value;T;,0;!@Y;[;I"WSets the attribute section @param value the value to set the attribute section to.;T; 0;!@Y;"F;#0;$@ ;%I"/def section=(value) @section = value end;T;&I"def section=(value);T;'To; ; F; ;;;;I"Doing::Item#note;F;[;[[@i ;F;: note;;;[;{;IC;")Returns the value of attribute note. ;T;[;[;I")Returns the value of attribute note.;T; 0;!@k;"F;#0;$@ ;%I"def note @note end;T;&I" def note;T;'To; ; F; ;;;;I"Doing::Item#note=;F;[[@0;[[@i ;F;: note=;;;[;{;IC;"Sets the attribute note ;T;[o;) ;*I" param;F;+I",the value to set the attribute note to.;T;I" value;T;,0;!@x;[;I"QSets the attribute note @param value the value to set the attribute note to.;T; 0;!@x;"F;#0;$@ ;%I")def note=(value) @note = value end;T;&I"def note=(value);T;'To; ; F; ;;;;I"Doing::Item#initialize;F;[ [I" date;T0[I" title;T0[I" section;T0[I" note;TI"nil;T;[[@i;T;:initialize;;;[;{;IC;"DInitialize an item with date, title, section, and optional note;T;[ o;) ;*I" param;F;+I"The item's start date;T;I" date;T;,[I" Time;T;!@o;) ;*I" param;F;+I"The title;T;I" title;T;,[I" String;T;!@o;) ;*I" param;F;+I"*The section to which the item belongs;T;I" section;T;,[I" String;T;!@o;) ;*I" param;F;+I"The note (optional);T;I" note;T;,[I"Array or String;T;!@o;) ;*I" return;F;+I"a new instance of Item;T;0;,[I" Item;F;!@;[;I"E Initialize an item with date, title, section, and optional note @param date [Time] The item's start date @param title [String] The title @param section [String] The section to which the item belongs @param note [Array or String] The note (optional) ;T; 0;!@:@ref_tag_recurse_counti;"T:@line_rangeo: Range: exclF: begini:endi;$@ :@explicitT;%I"def initialize(date, title, section, note = nil) @date = date.is_a?(Time) ? date : Time.parse(date) @title = title @section = section @note = Note.new(note) end;T;&I"5def initialize(date, title, section, note = nil);T;'To; ; F; ;;;;I"Doing::Item#duration;F;[;[[@i(;T;: duration;;;[;{;IC;"DIf the entry doesn't have a @done date, return the elapsed time;T;[;[;I"DIf the entry doesn't have a @done date, return the elapsed time;T; 0;!@;4i;"T;5o;6;7F;8i';9i';$@ ;:T;%I"\def duration return nil if @title =~ /(?<=^| )@done\b/ return Time.now - @date end;T;&I"def duration;T;'To; ; F; ;;;;I"Doing::Item#interval;F;[;[[@i4;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.;9i3;$@ ;:T;%I"3def interval @interval ||= calc_interval end;T;&I"def interval;T;'To; ; F; ;;;;I"Doing::Item#end_date;F;[;[[@i=;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;8i8;9i<;$@ ;: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#id;F;[;[[@iD;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;!@;4i;"F;5o;6;7F;8iA;9iC;$@ ;:T;%I"?def id @id ||= (@date.to_s + @title + @section).hash end;T;&I" def id;T;'To; ; F; ;;;;I"Doing::Item#equal?;F;[[I" other;T0;[[@iO;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;8iH;9iN;$@ ;: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;[[@i`;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;8iY;9i_;$@ ;: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;[[@il;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;!@4o;) ;*I" return;F;+I"overlaps?;T;0;,[I" Boolean;T;!@4;[;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;!@4;4i;"T;5o;6;7F;8id;9ik;$@ ;: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;!@Oo;) ;*I" param;F;+I"Additional options;T;I" options;T;,0;!@Oo: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;!@Oo;C ;*I" option;F;+0;I" options;T;,0;Do;E ;*I" option;F;+I"Log as a single change?;T;I" :single;T;,[I" Boolean;T;F0;!@Oo;C ;*I" option;F;+0;I" options;T;,0;Do;E ;*I" option;F;+I"&A value to include as @tag(value);T;I" :value;T;,[I" String;T;F0;!@Oo;C ;*I" option;F;+0;I" options;T;,0;Do;E ;*I" option;F;+I"%if true remove instead of adding;T;I" :remove;T;,[I" Boolean;T;F0;!@Oo;C ;*I" option;F;+0;I" options;T;,0;Do;E ;*I" option;F;+I"3if not nil, rename target tag to this tag name;T;I":rename_to;T;,[I" String;T;F0;!@Oo;C ;*I" option;F;+0;I" options;T;,0;Do;E ;*I" option;F;+I"-treat target tag string as regex pattern;T;I" :regex;T;,[I" Boolean;T;F0;!@Oo;C ;*I" option;F;+0;I" options;T;,0;Do;E ;*I" option;F;+I"0with rename_to, add tag if it doesn't exist;T;I" :force;T;,[I" Boolean;T;F0;!@O;[;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;!@O;4i;"T;5o;6;7F;8ix;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#tag_array;F;[;[[@i;F;:tag_array;;;[;{;IC;" ;T;[;[;I";T; 0;!@;4i;$@ ;:T;%I"+def tag_array tags.tags_to_array end;T;&I"def tag_array;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"def tags?(tags, bool = :and, negate: false) if bool == :pattern tags = tags.join(' ') if tags.is_a?(Array) matches = tag_pattern?(tags.gsub(/ *, */, ' ')) return negate ? !matches : matches end 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#ignore_case;F;[[I" search;T0[I"case_type;T0;[[@i;F;:ignore_case;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@ ;:T;%I"pdef ignore_case(search, case_type) (case_type == :smart && search !~ /[A-Z]/) || case_type == :ignore end;T;&I"'def ignore_case(search, case_type);T;'To; ; F; ;;;;I"Doing::Item#search;F;[ [I" search;T0[I"distance:;TI"nil;T[I" negate:;TI" false;T[I"case_type:;TI"nil;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" 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"Edef search(search, distance: nil, negate: false, case_type: nil);T;'To; ; F; ;;;;I"Doing::Item#should_finish?;F;[;[[@i;F;:should_finish?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[I" Boolean;T;!@7;[;@; 0;!@7;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;,[@C;!@G;[;@; 0;!@G;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;!@Vo;) ;*I" param;F;+I"$add @from(original section) tag;T;I" label;T;,[I" Boolean;T;!@Vo;) ;*I" param;F;+I"log this action;T;I"log;T;,[I" Boolean;T;!@Vo;) ;*I" return;F;+I" nothing;T;0;,0;!@V;[;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;!@V;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;!@;[;I" @private;T; 0;!@;4i;"F;5o;6;7F;8i';9i';$@ ;:T;%I"def inspect # %() %() 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;,[@C;!@;[;@; 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; ;;;Q;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.positive? ? t : nil end;T;&I"def calc_interval;T;'To; ; F; ;;;Q;I"Doing::Item#all_searches?;F;[[I" searches;T0[I"case_type:;TI" :smart;T;[[@iH;F;:all_searches?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@C;!@;[;@; 0;!@;4i;$@ ;:T;%I" def all_searches?(searches, case_type: :smart) return true if searches.nil? || searches.empty? text = @title + @note.to_s searches.each do |s| rx = Regexp.new(s.wildcard_to_rx, ignore_case(s, case_type)) return false unless text =~ rx end true end;T;&I"3def all_searches?(searches, case_type: :smart);T;'To; ; F; ;;;Q;I"Doing::Item#no_searches?;F;[[I" searches;T0[I"case_type:;TI" :smart;T;[[@iS;F;:no_searches?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@C;!@;[;@; 0;!@;4i;$@ ;:T;%I"def no_searches?(searches, case_type: :smart) return true if searches.nil? || searches.empty? text = @title + @note.to_s searches.each do |s| rx = Regexp.new(s.wildcard_to_rx, ignore_case(s, case_type)) return false if text =~ rx end true end;T;&I"2def no_searches?(searches, case_type: :smart);T;'To; ; F; ;;;Q;I"Doing::Item#any_searches?;F;[[I" searches;T0[I"case_type:;TI" :smart;T;[[@i^;F;:any_searches?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@C;!@;[;@; 0;!@;4i;$@ ;:T;%I"def any_searches?(searches, case_type: :smart) return true if searches.nil? || searches.empty? text = @title + @note.to_s searches.each do |s| rx = Regexp.new(s.wildcard_to_rx, ignore_case(s, case_type)) return true if text =~ rx end false end;T;&I"3def any_searches?(searches, case_type: :smart);T;'To; ; F; ;;;Q;I"Doing::Item#all_tags?;F;[[I" tags;T0;[[@ii;F;:all_tags?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@C;!@;[;@; 0;!@;4i;$@ ;:T;%I"def all_tags?(tags) return true if tags.nil? || tags.empty? tags.each do |tag| return false unless @title =~ /@#{tag.wildcard_to_rx}(?= |\(|\Z)/i end true end;T;&I"def all_tags?(tags);T;'To; ; F; ;;;Q;I"Doing::Item#no_tags?;F;[[I" tags;T0;[[@ir;F;: no_tags?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@C;!@ ;[;@; 0;!@ ;4i;$@ ;:T;%I"def no_tags?(tags) return true if tags.nil? || tags.empty? tags.each do |tag| return false if @title =~ /@#{tag.wildcard_to_rx}(?= |\(|\Z)/i end true end;T;&I"def no_tags?(tags);T;'To; ; F; ;;;Q;I"Doing::Item#any_tags?;F;[[I" tags;T0;[[@i{;F;:any_tags?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@C;!@;[;@; 0;!@;4i;$@ ;:T;%I"def any_tags?(tags) return true if tags.nil? || tags.empty? tags.each do |tag| return true if @title =~ /@#{tag.wildcard_to_rx}(?= |\(|\Z)/i end false end;T;&I"def any_tags?(tags);T;'To; ; F; ;;;Q;I"Doing::Item#to_query;F;[[I" query;T0;[[@i;F;: to_query;;;[;{;IC;" ;T;[;[;@; 0;!@,;4i;$@ ;:T;%I"def to_query(query) parser = BooleanTermParser::QueryParser.new transformer = BooleanTermParser::QueryTransformer.new parse_tree = parser.parse(query) transformer.apply(parse_tree).to_elasticsearch end;T;&I"def to_query(query);T;'To; ; F; ;;;Q;I" Doing::Item#to_phrase_query;F;[[I" query;T0;[[@i;F;:to_phrase_query;;;[;{;IC;" ;T;[;[;@; 0;!@:;4i;$@ ;:T;%I"def to_phrase_query(query) parser = PhraseParser::QueryParser.new transformer = PhraseParser::QueryTransformer.new parse_tree = parser.parse(query) transformer.apply(parse_tree).to_elasticsearch end;T;&I"def to_phrase_query(query);T;'To; ; F; ;;;Q;I"Doing::Item#tag_pattern?;F;[[I" tags;T0;[[@i;F;:tag_pattern?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@C;!@H;[;@; 0;!@H;4i;$@ ;:T;%I"def tag_pattern?(tags) query = to_query(tags) no_tags?(query[:must_not]) && all_tags?(query[:must]) && any_tags?(query[:should]) end;T;&I"def tag_pattern?(tags);T;'To; ; F; ;;;Q;I"Doing::Item#split_tags;F;[[I" tags;T0;[[@i;F;:split_tags;;;[;{;IC;" ;T;[;[;@; 0;!@Y;4i;$@ ;:T;%I"qdef split_tags(tags) tags = tags.split(/ *, */) if tags.is_a? String tags.map { |t| t.strip.add_at } end;T;&I"def split_tags(tags);T;'T: @owner@ :@class_mixinsIC;[;^@ :@instance_mixinsIC;[;^@ :@attributesIC:SymbolHash{: classIC;b{:@symbolize_valueT;IC;b{ ;IC;b{: read@ : write@;dT;-IC;b{;e@-;f@:;dT;/IC;b{;e@L;f@Y;dT;1IC;b{;e@k;f@x;dT;dT;dT: @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;c;'To; ;IC;[o; ; F; ;;;;I"Doing::Note#initialize;F;[[I" note;TI"[];T;[[I"lib/doing/note.rb;Ti;T;;3;;;[;{;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, Array, or 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, Array, or 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#compress;F;[;[[@i0;T;: compress;;;[;{;IC;"(Remove blank lines and comments (#);T;[o;) ;*I" return;F;+I"compressed array;T;0;,[I" Array;T;!@;[;I"P Remove blank lines and comments (#) @return [Array] compressed array ;T; 0;!@;4i;"T;5o;6;7F;8i+;9i/;$@};:T;%I"Cdef compress delete_if { |l| l =~ /^\s*$/ || l =~ /^#/ } end;T;&I"def compress;T;'To; ; F; ;;;;I"Doing::Note#compress!;F;[;[[@i4;F;:compress!;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@};:T;%I")def compress! replace compress end;T;&I"def compress!;T;'To; ; F; ;;;;I"Doing::Note#strip_lines;F;[;[[@i>;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;8i8;9i=;$@};:T;%I"'def strip_lines map(&:strip) end;T;&I"def strip_lines;T;'To; ; F; ;;;;I"Doing::Note#strip_lines!;F;[;[[@iB;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#to_s;F;[;[[@iH;T;;O;;;[;{;IC;"Note as multi-line string;T;[;[;I" Note as multi-line string;T; 0;!@;4i;"T;5o;6;7F;8iF;9iG;$@};: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;[;[[@iM;T;;P;;;[;{;IC;";T;[o;) ;*I" private;F;+I";T;0;,0;!@;[;I" @private;T; 0;!@;4i;"F;5o;6;7F;8iL;9iL;$@};:T;%I"kdef inspect "" end;T;&I"def inspect;T;'To; ; F; ;;;;I"Doing::Note#equal?;F;[[I" other;T0;[[@iX;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;8iQ;9iW;$@};:T;%I"Xdef equal?(other) return false unless other.is_a?(Note) to_s == other.to_s end;T;&I"def equal?(other);T;'To; ; F; ;;;Q;I"Doing::Note#append;F;[[I" lines;T0;[[@ie;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;!@3;[;I"V Append an array of strings to note @param lines [Array] Array of strings ;T; 0;!@3;4i;"T;5o;6;7F;8i`;9id;$@};:T;%I"=def append(lines) concat(lines) replace compress end;T;&I"def append(lines);T;'To; ; F; ;;;Q;I"Doing::Note#append_string;F;[[I" input;T0;[[@ip;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;!@I;[;I"~ Append a string to the note content @param input [String] The input string, newlines will be split ;T; 0;!@I;4i;"T;5o;6;7F;8ij;9io;$@};:T;%I"]def append_string(input) concat(input.split(/\n/).map(&:strip)) replace compress end;T;&I"def append_string(input);T;'T;^@};_IC;[;^@};`IC;[;^@};aIC;b{;cIC;b{;dT;IC;b{;dT;dT;g{;h[;[[@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;jo;k ;l0;m0;n0;: Array;$@;po; ;IC;[ o; ; F; ;;;;I"Array#tags_to_array;F;[;[[I"lib/doing/array.rb;Ti;T;:tags_to_array;;;[;{;IC;"&Convert an @tags to plain strings;T;[o;) ;*I" return;F;+I"array of strings;T;0;,[I" Array;T;!@s;[;I"N Convert an @tags to plain strings @return [Array] array of strings ;T; 0;!@s;4i;"T;5o;6;7F;8i ;9i;$@q;:T;%I"8def tags_to_array map { |t| t.sub(/^@/, '') } end;T;&I"def tags_to_array;T;'To; ; F; ;;;;I"Array#to_tags;F;[;[[@xi;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;$@q;:T;%I"4def to_tags map { |t| t.sub(/^@?/, '@') } end;T;&I"def to_tags;T;'To; ; F; ;;;;I"Array#to_tags!;F;[;[[@xi ;F;: to_tags!;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@q;: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;[[@xi,;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+;$@q;: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;[;[[@xi6;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;8i1;9i5;$@q;: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;[[@xi?;T;:nested_hash;;;[;{;IC;";$@q;: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;^@q;_IC;[;^@q;`IC;[;^@q;aIC;b{;cIC;b{;dT;IC;b{;dT;dT;g{;h[;[[@xi ;T;;z;;;;;[;{;IC;"Array helpers;T;[;[;I" Array helpers ;T; 0;!@q;4i;"T;5o;6;7F;8i ;9i ;$@;I" Array;F;jo;k ;l0;m0;n0;;o;$@;p0;q;c;'T;q;c;'To; ;IC;[o; ; F; ;;;;I"Doing::Util#user_home;F;[;[[I"lib/doing/util.rb;Ti ;F;:user_home;;;[;{;IC;" ;T;[;[;@; 0;!@ ;4i;$@;:T;%I"gdef user_home if Dir.respond_to?('home') Dir.home else File.expand_path('~') end end;T;&I"def user_home;T;'To; ; F; ;;;;I"Doing::Util#exec_available;F;[[I"cli;T0;[[@i;T;:exec_available;;;[;{;IC;"+Test if command line tool is available;T;[o;) ;*I" param;F;+I" The name or path of the cli;T;I"cli;T;,[I" String;T;!@;[;I"e Test if command line tool is available @param cli [String] The name or path of the cli ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@;: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;[[@i%;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$;$@;: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;[[@i*;F;:merge_default_proc;;;[;{;IC;" ;T;[;[;@; 0;!@@;4i;$@;:T;%I"def merge_default_proc(target, overwrite) return unless target.is_a?(Hash) && overwrite.is_a?(Hash) && target.default_proc.nil? target.default_proc = overwrite.default_proc end;T;&I".def merge_default_proc(target, overwrite);T;'To; ; F; ;;;;I"(Doing::Util#duplicate_frozen_values;F;[[I" target;T0;[[@i0;F;:duplicate_frozen_values;;;[;{;IC;" ;T;[;[;@; 0;!@P;4i;$@;:T;%I"def duplicate_frozen_values(target) target.each do |key, val| target[key] = val.dup if val.frozen? && duplicable?(val) end end;T;&I"(def duplicate_frozen_values(target);T;'To; ; F; ;;;;I""Doing::Util#deep_merge_hashes;F;[[I"master_hash;T0[I"other_hash;T0;[[@i>;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=;$@;: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;[[@iK;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;$@;: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;[[@iS;F;:duplicable?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@C;!@;[;@; 0;!@;4i;$@;:T;%I"qdef duplicable?(obj) case obj when nil, false, true, Symbol, Numeric false else true end end;T;&I"def duplicable?(obj);T;'To; ; F; ;;;;I"Doing::Util#mergable?;F;[[I" value;T0;[[@i\;F;:mergable?;;;[;{;IC;" ;T;[o;) ;*I" return;F;+@;0;,[@C;!@;[;@; 0;!@;4i;$@;:T;%I"1def mergable?(value) value.is_a?(Hash) end;T;&I"def mergable?(value);T;'To; ; F; ;;;;I"Doing::Util#merge_values;F;[[I" target;T0[I"overwrite;T0;[[@i`;F;:merge_values;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"def merge_values(target, overwrite) target.merge!(overwrite) do |_key, old_val, new_val| if new_val.nil? old_val elsif mergable?(old_val) && mergable?(new_val) deep_merge_hashes(old_val, new_val) else new_val end end end;T;&I"(def merge_values(target, overwrite);T;'To; ; F; ;;;;I"Doing::Util#write_to_file;F;[[I" file;T0[I" content;T0[I" backup:;TI" true;T;[[@is;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;$@;:T;%I"def write_to_file(file, content, backup: true) unless file puts content return end Doing.logger.benchmark(:write_file, :start) file = File.expand_path(file) Backup.write_backup(file) if backup File.open(file, 'w+') do |f| f.puts content Doing.logger.debug('Write:', "File written: #{file}") end Doing.logger.benchmark(:_post_write_hook, :start) Hooks.trigger :post_write, file Doing.logger.benchmark(:_post_write_hook, :finish) Doing.logger.benchmark(:write_file, :finish) end;T;&I"3def write_to_file(file, content, backup: true);T;'To; ; F; ;;;;I"Doing::Util#safe_load_file;F;[[I" filename;T0;[[@i;F;:safe_load_file;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"Jdef safe_load_file(filename) SafeYAML.load_file(filename) || {} end;T;&I"!def safe_load_file(filename);T;'To; ; F; ;;;;I"Doing::Util#default_editor;F;[;[[@i;F;:default_editor;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"Edef default_editor @default_editor ||= find_default_editor end;T;&I"def default_editor;T;'To; ; F; ;;;;I"!Doing::Util#editor_with_args;F;[;[[@i;F;:editor_with_args;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"?def editor_with_args args_for_editor(default_editor) end;T;&I"def editor_with_args;T;'To; ; F; ;;;;I" Doing::Util#args_for_editor;F;[[I" editor;T0;[[@i;F;:args_for_editor;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"def args_for_editor(editor) return editor if editor =~ /-\S/ args = case editor when /^(subl|code|mate)$/ ['-w'] when /^(vim|mvim)$/ ['-f'] else [] end "#{editor} #{args.join(' ')}" end;T;&I" def args_for_editor(editor);T;'To; ; F; ;;;;I"$Doing::Util#find_default_editor;F;[[I"editor_for;TI"'default';T;[[@i;F;:find_default_editor;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@;:T;%I"def find_default_editor(editor_for = 'default') # return nil unless $stdout.isatty || ENV['DOING_EDITOR_TEST'] if ENV['DOING_EDITOR_TEST'] return ENV['EDITOR'] end editor_config = Doing.config.settings['editors'] if editor_config.is_a?(String) Doing.logger.warn('Deprecated:', "Please update your configuration, 'editors' should be a mapping. Delete the key and run `doing config --update`.") return editor_config end if editor_config[editor_for] editor = editor_config[editor_for] # Doing.logger.debug('Editor:', "Using #{editor} from config 'editors->#{editor_for}'") return editor unless editor.nil? || editor.empty? end if editor_for != 'editor' && editor_config['default'] editor = editor_config['default'] # Doing.logger.debug('Editor:', "Using #{editor} from config: 'editors->default'") return editor unless editor.nil? || editor.empty? end editor ||= ENV['DOING_EDITOR'] || ENV['GIT_EDITOR'] || ENV['EDITOR'] unless editor.nil? || editor.empty? # Doing.logger.debug('Editor:', "Found editor in environment variables: #{editor}") return editor end Doing.logger.debug('ENV:', 'No EDITOR environment variable, testing available editors') editors = %w[vim vi code subl mate mvim nano emacs] editors.each do |ed| return 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;'To; ;IC;[o; ; F; ;;;;I"&Doing::Util::Backup#prune_backups;F;[[I" filename;T0[I" limit;TI"10;T;[[I"lib/doing/util_backup.rb;Ti;T;:prune_backups;;;[;{;IC;")Delete all but most recent 5 backups;T;[o;) ;*I" param;F;+I"(Maximum number of backups to retain;T;I" limit;T;,0;!@/;[;I"c Delete all but most recent 5 backups @param limit Maximum number of backups to retain ;T; 0;!@/;4i;"T;5o;6;7F;8i;9i;$@-;:T;%I"def prune_backups(filename, limit = 10) backups = get_backups(filename) return unless backups.count > limit backups[limit..-1].each do |file| FileUtils.rm(File.join(backup_dir, file)) end clear_redo(filename) end;T;&I",def prune_backups(filename, limit = 10);T;'To; ; F; ;;;;I"#Doing::Util::Backup#clear_redo;F;[[I" filename;T0;[[@9i%;T;:clear_redo;;;[;{;IC;"Delete all redo files;T;[o;) ;*I" param;F;+I"(Maximum number of backups to retain;T;I" limit;T;,0;!@G;[;I"T Delete all redo files @param limit Maximum number of backups to retain ;T; 0;!@G;4i;"T;5o;6;7F;8i ;9i$;$@-;:T;%I"def clear_redo(filename) filename ||= Doing.config.settings['doing_file'] backups = Dir.glob("undone*___#{File.basename(filename)}", base: backup_dir).sort.reverse backups.each do |file| FileUtils.rm(File.join(backup_dir, file)) end end;T;&I"def clear_redo(filename);T;'To; ; F; ;;;;I",Doing::Util::Backup#restore_last_backup;F;[[I" filename;TI"nil;T[I" count:;TI"1;T;[[@9i4;T;:restore_last_backup;;;[;{;IC;"kRestore the most recent backup. If a filename is provided, only backups of that filename will be used.;T;[o;) ;*I" param;F;+I"7The filename to restore, if different from default;T;I" filename;T;,0;!@[o;) ;*I" raise;F;+@;0;,[I"DoingRuntimeError;T;!@[;[;I" Restore the most recent backup. If a filename is provided, only backups of that filename will be used. @param filename The filename to restore, if different from default ;T; 0;!@[;4i;"T;5o;6;7F;8i-;9i3;$@-;:T;%I"def restore_last_backup(filename = nil, count: 1) Doing.logger.benchmark(:restore_backup, :start) filename ||= Doing.config.settings['doing_file'] result = get_backups(filename).slice(count - 1) raise DoingRuntimeError, 'End of undo history' if result.nil? backup_file = File.join(backup_dir, result) save_undone(filename) FileUtils.mv(backup_file, filename) prune_backups_after(File.basename(backup_file)) Doing.logger.warn('File update:', "restored from #{result}") Doing.logger.benchmark(:restore_backup, :finish) end;T;&I"6def restore_last_backup(filename = nil, count: 1);T;'To; ; F; ;;;;I"$Doing::Util::Backup#redo_backup;F;[[I" filename;TI"nil;T[I" count:;TI"1;T;[[@9iI;T;:redo_backup;;;[;{;IC;"Undo last undo;T;[o;) ;*I" param;F;+I"The filename;T;I" filename;T;,0;!@wo;) ;*I" raise;F;+@;0;,[I"DoingRuntimeError;T;!@w;[;I"9 Undo last undo @param filename The filename ;T; 0;!@w;4i;"T;5o;6;7F;8iD;9iH;$@-;:T;%I"def redo_backup(filename = nil, count: 1) filename ||= Doing.config.settings['doing_file'] # redo_file = File.join(backup_dir, "undone___#{File.basename(filename)}") undones = Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).sort total = undones.count count = total if count > total skipped = undones.slice!(0, count) undone = skipped.pop raise DoingRuntimeError, 'End of redo history' if undone.nil? redo_file = File.join(backup_dir, undone) FileUtils.move(redo_file, filename) skipped.each do |f| FileUtils.mv(File.join(backup_dir, f), File.join(backup_dir, f.sub(/^undone/, ''))) end Doing.logger.warn('File update:', "restored undo step #{count}/#{total}") Doing.logger.debug('Backup:', "#{total - skipped.count - 1} redos remaining") end;T;&I".def redo_backup(filename = nil, count: 1);T;'To; ; F; ;;;;I"%Doing::Util::Backup#clear_undone;F;[[I" filename;TI"nil;T;[[@9ia;F;:clear_undone;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@-;:T;%I"#def clear_undone(filename = nil) filename ||= Doing.config.settings['doing_file'] # redo_file = File.join(backup_dir, "undone___#{File.basename(filename)}") Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).each do |f| FileUtils.rm(File.join(backup_dir, f)) end end;T;&I"%def clear_undone(filename = nil);T;'To; ; F; ;;;;I"$Doing::Util::Backup#select_redo;F;[[I" filename;TI"nil;T;[[@9io;T;:select_redo;;;[;{;IC;"eSelect from recent undos. If a filename is provided, only backups of that filename will be used.;T;[o;) ;*I" param;F;+I"The filename to restore;T;I" filename;T;,0;!@o;) ;*I" raise;F;+@;0;,[I"DoingRuntimeError;T;!@;[;I" Select from recent undos. If a filename is provided, only backups of that filename will be used. @param filename The filename to restore ;T; 0;!@;4i;"T;5o;6;7F;8ii;9in;$@-;:T;%I"def select_redo(filename = nil) filename ||= Doing.config.settings['doing_file'] undones = Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).sort raise DoingRuntimeError, 'End of redo history' if undones.empty? total = undones.count options = undones.each_with_object([]) do |file, arr| d, _base = date_of_backup(file) next if d.nil? arr.push("#{d.time_ago}\t#{File.join(backup_dir, file)}") end raise DoingRuntimeError, 'No backup files to load' if options.empty? backup_file = show_menu(options, filename) idx = undones.index(File.basename(backup_file)) skipped = undones.slice!(idx, undones.count - idx) undone = skipped.shift redo_file = File.join(backup_dir, undone) FileUtils.move(redo_file, filename) skipped.each do |f| FileUtils.mv(File.join(backup_dir, f), File.join(backup_dir, f.sub(/^undone/, ''))) end Doing.logger.warn('File update:', "restored undo step #{idx}/#{total}") Doing.logger.debug('Backup:', "#{total - skipped.count - 1} redos remaining") end;T;&I"$def select_redo(filename = nil);T;'To; ; F; ;;;;I"&Doing::Util::Backup#select_backup;F;[[I" filename;TI"nil;T;[[@9i;T;:select_backup;;;[;{;IC;"gSelect from recent backups. If a filename is provided, only backups of that filename will be used.;T;[o;) ;*I" param;F;+I"The filename to restore;T;I" filename;T;,0;!@o;) ;*I" raise;F;+@;0;,[I"DoingRuntimeError;T;!@;[;I" Select from recent backups. If a filename is provided, only backups of that filename will be used. @param filename The filename to restore ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@-;:T;%I"def select_backup(filename = nil) filename ||= Doing.config.settings['doing_file'] options = get_backups(filename).each_with_object([]) do |file, arr| d, _base = date_of_backup(file) next if d.nil? arr.push("#{d.time_ago}\t#{File.join(backup_dir, file)}") end raise DoingRuntimeError, 'No backup files to load' if options.empty? backup_file = show_menu(options, filename) Util.write_to_file(File.join(backup_dir, "undone___#{File.basename(filename)}"), IO.read(filename), backup: false) FileUtils.mv(backup_file, filename) prune_backups_after(File.basename(backup_file)) Doing.logger.warn('File update:', "restored from #{backup_file}") end;T;&I"&def select_backup(filename = nil);T;'To; ; F; ;;;;I""Doing::Util::Backup#show_menu;F;[[I" options;T0[I" filename;T0;[[@9i;F;:show_menu;;;[;{;IC;" ;T;[o;) ;*I" raise;F;+@;0;,[I"UserCancelled;T;!@;[;@; 0;!@;4i;$@-;:T;%I"odef show_menu(options, filename) if TTY::Which.which('colordiff') preview = 'colordiff -U 1' pipe = '| awk "(NR>2)"' elsif TTY::Which.which('git') preview = 'git --no-pager diff -U1 --color=always --minimal --word-diff' pipe = ' | awk "(NR>4)"' else preview = 'diff -U 1' pipe = if TTY::Which.which('delta') ' | delta --no-gitconfig --syntax-theme=1337' elsif TTY::Which.which('diff-so-fancy') ' | diff-so-fancy' elsif TTY::Which.which('ydiff') ' | ydiff -c always --wrap < /dev/tty' else cmd = 'sed -e "s/^-/`echo -e "\033[31m"`-/;s/^+/`echo -e "\033[32m"`+/;s/^@/`echo -e "\033[34m"`@/;s/\$/`echo -e "\033[0m"`/"' "| bash -c #{Shellwords.escape(cmd)}" end pipe += ' | awk "(NR>2)"' end result = Doing::Prompt.choose_from(options, prompt: 'Select a backup to restore', sorted: false, fzf_args: [ '--delimiter="\t"', '--with-nth=1', %(--preview='#{preview} "#{filename}" {2} #{pipe}'), '--disabled', '--height=10', '--preview-window="right,70%,nowrap,follow"', '--header="Select a revision to restore"' ]) raise UserCancelled unless result result.strip.split(/\t/).last end;T;&I"%def show_menu(options, filename);T;'To; ; F; ;;;;I"%Doing::Util::Backup#write_backup;F;[[I" filename;TI"nil;T;[[@9i;T;:write_backup;;;[;{;IC;"NWrites a copy of the content to a dated backup file in a hidden directory;T;[o;) ;*I" param;F;+I"The data to back up;T;I" content;T;,0;!@;[;I"z Writes a copy of the content to a dated backup file in a hidden directory @param content The data to back up ;T; 0;!@;4i;"T;5o;6;7F;8i;9i;$@-;:T;%I"def write_backup(filename = nil) Doing.logger.benchmark(:_write_backup, :start) filename ||= Doing.config.settings['doing_file'] unless File.exist?(filename) Doing.logger.debug('Backup:', "original file doesn't exist (#{filename})") return end backup_file = File.join(backup_dir, "#{timestamp_filename}___#{File.basename(filename)}") # compressed = Zlib::Deflate.deflate(content) # Zlib::GzipWriter.open(backup_file + '.gz') do |gz| # gz.write(IO.read(filename)) # end FileUtils.cp(filename, backup_file) prune_backups(filename, Doing.config.settings['history_size'].to_i) clear_undone(filename) Doing.logger.benchmark(:_write_backup, :finish) end;T;&I"%def write_backup(filename = nil);T;'To; ; F; ;;;Q;I"+Doing::Util::Backup#timestamp_filename;F;[;[[@9i;F;:timestamp_filename;;;[;{;IC;" ;T;[;[;@; 0;!@;4i;$@-;:T;%I"Hdef timestamp_filename Time.now.strftime('%Y-%m-%d_%H.%M.%S') end;T;&I"def timestamp_filename;T;'To; ; F; ;;;Q;I"$Doing::Util::Backup#get_backups;F;[[I" filename;TI"nil;T[I"include_forward:;TI" false;T;[[@9i;F;:get_backups;;;[;{;IC;" ;T;[;[;@; 0;!@ ;4i;$@-;:T;%I"def get_backups(filename = nil, include_forward: false) filename ||= Doing.config.settings['doing_file'] backups = Dir.glob("*___#{File.basename(filename)}", base: backup_dir).sort.reverse backups.delete_if { |f| f =~ /^undone/ } unless include_forward end;T;&I"\d{4}-\d{2}-\d{2})_(?