require'term/ansicolor'# A class for searching and matching text patterns within files.## This class provides functionality to search through file systems for content# matching specified patterns, with support for various output formats and# filtering options. It handles directory pruning, file skipping, and different# types of pattern matching including regular expressions and fuzzy matching.## @example# grepper = Utils::Grepper.new(args: { l: true }, roots: ['.'])# grepper.searchclassUtils::GrepperincludeTins::FindincludeUtils::PatternsincludeTerm::ANSIColor# A queue implementation with size limitation.## @example queue = Utils::Grepper::Queue.new(5) queue << "item1" queue <<# "item2" # ... queue.data # => [ "item1", "item2", ... ]## The Queue class provides a fixed-size buffer for storing objects. When the# maximum size is exceeded, the oldest item is automatically removed.classQueue# The initialize method sets up a new instance with the specified maximum# size and empty data array.## @param max_size [ Integer ] the maximum size limit for the data storagedefinitialize(max_size)@max_size,@data=max_size,[]end# The max_size reader method provides access to the maximum size value.## @return [ Integer ] the maximum size value stored in the instanceattr_reader:max_size# The data method returns a duplicate of the internal data array.## This method provides access to the internal @data instance variable by# returning a shallow copy of the array, ensuring that external# modifications do not affect the original data structure.## @return [ Array ] a duplicate of the internal data arraydefdata@data.dupend# The push method adds an element to the queue and removes the oldest# element if the maximum size is exceeded.## @param x [ Object ] the element to be added to the queue## @return [ Queue ] returns self to allow for method chainingdefpush(x)@data.shiftif@data.size>@max_size@data<<xselfendalias<<pushend# The initialize method sets up the grepper instance with the provided# options.## This method configures the grepper by processing the input options, setting up# the root directories for searching, initializing the configuration, and# preparing pattern matchers for filename and skip patterns. It also handles# queue initialization for buffering output when specified.## @param opts [ Hash ] the options hash containing configuration settings# @option opts [ Hash ] :args the command-line arguments# @option opts [ Array ] :roots the root directories to search# @option opts [ Utils::ConfigFile ] :config the configuration file object# @option opts [ Hash ] :pattern the pattern-related options## @return [ Utils::Grepper ] a new grepper instance configured with the# provided optionsdefinitialize(opts={})@args=opts[:args]||{}@roots=discover_roots(opts[:roots])@config=opts[:config]||Utils::ConfigFile.newifn=@args.values_at(*%w[A B C]).compact.firstifn.to_s=~/\A\d+\Z/and(n=n.to_i)>=1@queue=Queue.newnelseraiseArgumentError,"needs to be an integer number >= 1"endend@paths=[]pattern_opts=opts.subhash(:pattern)|{:cset=>@args[?a],:icase=>@args[?i]!=?n,}@pattern=choose(@args[?p],pattern_opts,default: ?r)@name_pattern=ifname_pattern=@args[?N]RegexpPattern.new(:pattern=>name_pattern)elsifname_pattern=@args[?n]FuzzyPattern.new(:pattern=>name_pattern)end@skip_pattern=ifskip_pattern=@args[?S]RegexpPattern.new(:pattern=>skip_pattern)elsifskip_pattern=@args[?s]FuzzyPattern.new(:pattern=>skip_pattern)endend# The paths reader method provides access to the paths instance variable.## @return [ Array ] the array of paths stored in the instance variableattr_reader:paths# The pattern reader method provides access to the pattern matcher object.## This method returns the internal pattern matcher that was initialized# during object creation, allowing external code to interact with the pattern# matching functionality directly.## @return [ Utils::Patterns::Pattern ] the pattern matcher object used for# matching operationsattr_reader:pattern# The match method processes a file to find matching content based on# configured patterns.# It handles directory pruning, file skipping, and various output formats# depending on the configuration.# The method opens files for reading, applies pattern matching, and manages# output through different code paths.# It supports features like line-based searching, git blame integration, and# multiple output modes.# The method returns the instance itself to allow for method chaining.## @return [ Utils::Grepper ] returns self to allow for method chainingdefmatch(filename)@filename=filename@output=[]bn,s=File.basename(filename),File.stat(filename)if!s||s.directory?&&@config.search.prune?(bn)@args[?v]andwarn"Pruning #{filename.inspect}."pruneendifs.file?&&!@config.search.skip?(bn)&&(!@name_pattern||@name_pattern.match(bn))thenFile.open(filename,'rb',encoding: Encoding::UTF_8)do|file|@args[?v]andwarn"Matching #{filename.inspect}."if@args[?f]@output<<filenameelsematch_linesfileendendelse@args[?v]andwarn"Skipping #{filename.inspect}."endunless@output.empty?casewhen@args[?g]@output.uniq!@output.eachdo|l|blamer=LineBlamer.for_line(l)ifblame=blamer.performauthor=nilblame.sub!(/^[0-9a-f^]+/){Term::ANSIColor.yellow($&)}blame.sub!(/\(([^)]+)\)/){author=$1;"(#{Term::ANSIColor.red($1)})"}if!@args[?G]||author&.downcase&.match?(@args[?G].downcase)puts"#{blame.chomp}#{Term::ANSIColor.blue(l)}"endendendwhen@args[?l],@args[?e],@args[?E],@args[?r]@output.uniq!@paths.concat@outputelseSTDOUT.puts@outputend@output.clearendselfend# The match_lines method processes each line from a file using pattern# matching.## This method iterates through lines in the provided file, applying pattern# matching to identify relevant content. It handles various output options# based on command-line arguments and manages queuing of lines for context# display.## @param file [IO] the file object to be processed line by linedefmatch_lines(file)forlineinfileifm=@pattern.match(line)@skip_patternand@skip_pattern=~lineandnextline[m.begin(0)...m.end(0)]=blackon_whitem[0]@queueand@queue<<linecasewhen@args[?l]@output<<@filenamewhen@args[?L],@args[?r],@args[?g]@output<<"#{@filename}:#{file.lineno}"when@args[?e],@args[?E]@output<<"#{@filename}:#{file.lineno}"breakelse@output<<red("#{@filename}:#{file.lineno}")if@args[?B]or@args[?C]@output.concat@queue.dataelse@output<<lineendif@args[?A]or@args[?C]where=file.telllineno=file.lineno@queue.max_size.timesdofile.eof?andbreakline=file.readline@queue<<line@output<<lineendfile.seekwherefile.lineno=linenoendendelse@queueand@queue<<lineendendend# The search method performs a file search operation within specified roots,# filtering results based on various criteria including file extensions,# pruning directories, and skipping specific files.## It utilizes a visit lambda to determine whether each file or directory# should be processed or skipped based on configuration settings and# command-line arguments. The method employs the find utility to traverse# the filesystem, executing match operations on qualifying files.## @return [ Utils::Grepper ] returns self to allow for method chainingdefsearchsuffixes=Array(@args[?I])visit=->filename{s=filename.lstatbn=filename.pathname.basenameif!s||s.directory?&&@config.search.prune?(bn)||(s.file?||s.symlink?)&&@config.search.skip?(bn)||@args[?F]&&s.symlink?then@args[?v]andwarn"Pruning #{filename.inspect}."pruneelsifsuffixes.empty?trueelsesuffixes.include?(filename.suffix)end}find(*@roots,visit: visit)do|filename|match(filename)end@paths=@paths.sort_by(&:source_location)selfendprivate# The discover_roots method processes an array of root patterns and expands# them into actual directory paths.## This method takes an array of root patterns, which may include glob# patterns, and uses Dir[r] to expand each pattern into matching directory# paths.# It handles the case where the input roots array is nil by defaulting to an# empty array. The expanded paths are then concatenated into a single result# array.## @param roots [ Array<String>, nil ] an array of root patterns or nil## @return [ Array<String> ] an array of expanded directory paths matching the input patternsdefdiscover_roots(roots)roots||=[]roots.inject([]){|rs,r|rs.concatDir[r]}endend