anchor.diff:      let condition (line:string) = 
         line.IndexOf(value = searchString, comparisonType = System.StringComparison.OrdinalIgnoreCase) >= 0
       let foundLinesOfFile = lines codepage >> Seq.filter condition
  -    // TODO: Показывать контекст поиска: +-1 строка выше и ниже найденного вхождения.               
  +    // TODO: Показывать контекст поиска: +-1 строка выше и ниже найденного вхождения.
       filePaths
       |> Seq.map (fun path -> path, foundLinesOfFile path )
       |> Seq.filter (fun (_, foundLines) -> foundLines |> Seq.length > 0)
  
positive.code: 
  [<Experimental("Эксперементальные функции модуля Scripting")>]
  module Scripting =
    open System
    open System.IO
    open DmitriyVlasov
    open DmitriyVlasov.Collections
    open DmitriyVlasov.Scripting
  
    /// Посчитать количество файлов в каталоге в разрезе в разрезе раширений.
    /// Если количество файлов меньше порогового показать пути к файлам.
    let filterGroupCountShowFiles rareFiles searchPattern dirPath = 
      let count = Seq.sumBy (fun _ -> 1)
      let showFiles = 
        Seq.map (String.replace dirPath "..")
        >> Seq.map (sprintf "\n\"%s\"")
        >> String.concat ""
      let rare files = 
        if count files <= rareFiles
        then showFiles files
        else ""
      find SearchOption.AllDirectories searchPattern dirPath
      |> Seq.groupBy extension
      |> Seq.map (fun (ext, files) -> ext, count files, files)
      |> Seq.sortBy (fun (_, count, _) -> count)
      |> Seq.iter (fun (ext, count, files) -> 
        printfn "%10s; %3d;%s" ext count (rare files))
  
    /// Статистика слов в файлах.
    let wordStatistics codepage delimiters searchPattern dirPath =
      find SearchOption.AllDirectories searchPattern dirPath
      |> Seq.map (fun filePath -> 
        filePath
        |> readText codepage
        |> String.splitMany delimiters)
      |> Seq.concat
      |> Seq.countBy id
      |> Seq.sortBy ( fun (_, number) -> - number )
  
negative.code:       groupFiles
        |> List.iter (fun filePath -> mv filePath <| newDirPath ++ filename filePath) )
  
    /// Время создания пары файлов меньше заданного времени минут.
    let timeSpanLess minutes file1 file2 = 
      let creationTime filePath = FileInfo(filePath).CreationTime
      creationTime file2 - creationTime file1 <= TimeSpan.FromMinutes(minutes)
  
    /// Удалить дубликаты файлов. Дубликаты ищутся с использованием хеширующей функции 
    let deleteDuplicateFilesBy hashFunction path =
      find SearchOption.AllDirectories "*.*" path
      |> Seq.map FileInfo
      |> Seq.groupBy hashFunction
      |> Seq.filter (snd >> Seq.length >> ((>) 1))
      |> Seq.map (snd >> Seq.tail)
      |> Seq.concat
      |> Seq.iter (fun fi -> 
        File.Delete fi.FullName)
  
    /// Удалить дубрикаты файлов у которых время создания и размер одинаковые
    let deleteDuplicateFiles path =
      deleteDuplicateFilesBy (fun fi -> 
        sprintf "#%s%d" (fi.CreationTime.ToString()) fi.Length) path
  
    let incBasename delim (basename:string) = 
      let inc = int >> (+) 1 >> string
      match String.split delim basename with
      | [|x|]      -> String.concat delim [x; "1"]
      | [|x;iter|] -> String.concat delim [x; (inc iter)]
      | _ -> basename
  
    let nextFileName (path:string) =
      let newFileName = 
        path 
        |> basename 
        |> incBasename "~"
        |> Seq.singleton
        |> Seq.append [extension path]
        |> String.concat "" 
      (dirname path) ++ newFileName
  
    let distributeFilesByCreationTime sourcePath destinationPath = 
      ls sourcePath
      |> Seq.map (fun path ->
        let fi = FileInfo path 
        fi.FullName,
        destinationPath 
        ++ string fi.CreationTime.Year
        ++ sprintf "%02d" fi.CreationTime.Month
        ++ sprintf "%02d" fi.CreationTime.Day
        ++ fi.Name)
  
    let moveDistributeFilesByCreationTime sourcePath destinationPath = 
      distributeFilesByCreationTime sourcePath destinationPath
      |> Seq.iter (fun (sourceFileName, destFileName) -> 
        try
          let newDir = dirname destFileName
          if not <| Directory.Exists(newDir) then
            mk newDir |> ignore
  
          if fileExists destFileName then
            mv sourceFileName (nextFileName destFileName)
          else 
            mv sourceFileName destFileName
               
          // TODO: 
          // Если имена файлов одинаковые:
          // Проверяем одинаковый ли размер файла:
          // Если размер одинаковый считаем, что файл один и тот-же и из источника удаляем.
          // Если размер разный, файл переименовываем 
  
        with ex ->
          eprintfn "Source: '%s'; Destination: '%s'; %s:'%s'" sourceFileName destFileName (ex.GetType().ToString()) ex.Message
      )
  
    type MediaType =
      | Photo of string
      | Video of string
      | Text  of string 
      | Excel of string
      | Web   of string
      | Book  of string
      | Slide of string
      | Other of string
  
    let mediaType ext =
      match ext with
      | "jpeg" | "jpg" | "bmp"
      | "png"
        -> Photo ext
      | "wmv" | "nef" | "avi"
      | "mov" | "mp4" | "mts"
      | "3gp" | "mod" | "vob"
        -> Video ext
      | "doc" | "docx" | "rtf"
      | "txt" | "odt"
        -> Text ext
      | "xls" | "xlsx" | "xlsm"
        -> Excel ext
      | "ppt" | "pptx"
        -> Slide ext
      | "css" | "gif" | "htm" 
      | "html" | "js" | "mht"
        -> Web ext
      | "pdf" | "tiff"
        -> Book ext
      | _ -> Other ext
  
    let showMediaType =
       function
       | Photo _   -> "#photo"
       | Video _   -> "#video"
       | Text  _   -> "#text"
       | Excel _   -> "#excel"
       | Web   _   -> "#web"
       | Slide _   -> "#slide"
       | Book  _   -> "#book"
       | Other ext -> ext
  
    type FileInfo with
      member this.MediaType = 
       this.Extension.ToLowerInvariant().Replace(".","") |> mediaType
  