packages feed

opt-env-conf 0.15.0.0 → 0.15.0.1

raw patch · 3 files changed

+79/−23 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,5 +1,12 @@ # Changelog +## [0.15.0.1] - 2026-03-18++### Changed++* Fixed autocompletion for paths containing `..` components.+  Paths like `../`, `../foo`, and `bar/../` now complete correctly.+ ## [0.15.0.0] - 2026-03-17  ### Added
opt-env-conf.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           opt-env-conf-version:        0.15.0.0+version:        0.15.0.1 synopsis:       Settings parsing for Haskell: command-line arguments, environment variables, and configuration values. homepage:       https://github.com/NorfairKing/opt-env-conf#readme bug-reports:    https://github.com/NorfairKing/opt-env-conf/issues
src/OptEnvConf/Completer.hs view
@@ -63,18 +63,17 @@ filePath :: Completer filePath = Completer $ \fp' -> do   here <- getCurrentDir+  let (prefix, fp, baseDir) = splitDotDot here fp'+  filePathFromDir baseDir prefix fp fp' -  -- An empty string is not a valid relative file or dir, but it is the most-  -- common option so we special case it here-  let (prefix, fp) = stripCurDir fp'+filePathFromDir :: Path Abs Dir -> String -> FilePath -> FilePath -> IO [CompletionResult]+filePathFromDir baseDir prefix fp fp' = do   fmap (filterPrefix fp' . map (addPrefix prefix)) $ do     let listDirForgiving d = fromMaybe ([], []) <$> forgivingAbsence (listDirRel d)     (dirsFromParentListing, filesFromParentListing) <- case parseSomeDir fp of       Nothing -> case fp of         [] -> do-          -- This is not a valid rel dir but still a prefix of a valid rel dir:-          -- the current dir-          (ds, fs) <- listDirRel here+          (ds, fs) <- listDirRel baseDir           pure             ( map fromRelDir $ filter (not . hiddenRel) ds,               map fromRelFile $ filter (not . hiddenRel) fs@@ -87,7 +86,8 @@             map (fromAbsFile . (ad </>)) $ filter (not . hiddenRel) fs           )       Just (Rel rd) -> do-        (ds, fs) <- listDirForgiving rd+        let ad = baseDir </> rd+        (ds, fs) <- listDirForgiving ad         pure           ( map (fromRelDir . (rd </>)) $ filter (not . hiddenRel) ds,             map (fromRelFile . (rd </>)) $ filter (not . hiddenRel) fs@@ -95,11 +95,9 @@      (dirsFromPartialListing, filesFromPartialListing) <- case parseSomeFile fp of       Nothing ->-        -- This is not a valid rel file but still a prefix of a valid-        -- (hidden) rel file.         if fp == "."           then do-            (ds, fs) <- listDirRel here+            (ds, fs) <- listDirRel baseDir             pure               ( map fromRelDir ds,                 map fromRelFile fs@@ -115,8 +113,9 @@           )       Just (Rel rf) -> do         let dir = parent rf+        let ad = baseDir </> dir         let filterHidden = if hiddenRel rf then id else filter (not . hiddenRel)-        (ds, fs) <- listDirForgiving dir+        (ds, fs) <- listDirForgiving ad         pure           ( map (fromRelDir . (dir </>)) $ filterHidden ds,             map (fromRelFile . (dir </>)) $ filterHidden fs@@ -148,25 +147,25 @@ directoryPath :: Completer directoryPath = Completer $ \fp' -> do   here <- getCurrentDir+  let (prefix, fp, baseDir) = splitDotDot here fp'+  directoryPathFromDir baseDir prefix fp fp' -  -- An empty string is not a valid relative file or dir, but it is the most-  -- common option so we special case it here-  let (prefix, fp) = stripCurDir fp'+directoryPathFromDir :: Path Abs Dir -> String -> FilePath -> FilePath -> IO [CompletionResult]+directoryPathFromDir baseDir prefix fp fp' = do   fmap (filterPrefix fp' . map (addPrefix prefix . dirResult)) $ do     let listDirForgiving d = fromMaybe ([], []) <$> forgivingAbsence (listDirRel d)     dirsFromParentListing <- case parseSomeDir fp of       Nothing -> case fp of         [] -> do-          -- This is not a valid rel dir but still a prefix of a valid rel dir:-          -- the current dir-          (ds, _) <- listDirRel here+          (ds, _) <- listDirRel baseDir           pure (map fromRelDir $ filter (not . hiddenRel) ds)         _ -> pure []       Just (Abs ad) -> do         (ds, _) <- listDirForgiving ad         pure (map (fromAbsDir . (ad </>)) $ filter (not . hiddenRel) ds)       Just (Rel rd) -> do-        (ds, _) <- listDirForgiving rd+        let ad = baseDir </> rd+        (ds, _) <- listDirForgiving ad         pure (map (fromRelDir . (rd </>)) $ filter (not . hiddenRel) ds)      dirsFromPartialListing <- case parseSomeDir fp of@@ -177,16 +176,15 @@         (ds, _) <- listDirForgiving dir         pure (map (fromAbsDir . (dir </>)) $ filterHidden ds)       Just (Rel rf) ->-        -- This is not a valid rel dir but still a prefix of a valid-        -- (hidden) rel dir.         if fp == "."           then do-            (ds, _) <- listDirRel here+            (ds, _) <- listDirRel baseDir             pure (map fromRelDir ds)           else do             let dir = parent rf+            let ad = baseDir </> dir             let filterHidden = if hiddenRel rf then id else filter (not . hiddenRel)-            (ds, _) <- listDirForgiving dir+            (ds, _) <- listDirForgiving ad             pure (map (fromRelDir . (dir </>)) $ filterHidden ds)      pure $@@ -223,6 +221,57 @@     let (pf, rest) = stripCurDir rest'      in ("./" <> pf, rest)   p -> ("", p)++-- | Split a path at @..@ components.+--+-- Returns @(prefix, remainder, baseDir)@ where:+--+--  * @prefix@ is the literal text up to and including the last @..\/@ (to+--    prepend to completion results)+--  * @remainder@ is the part after that last @..\/@ (to pass to+--    @parseSomeDir@ \/ @parseSomeFile@, which reject @..@)+--  * @baseDir@ is the absolute directory that @remainder@ should be resolved+--    relative to+--+-- If there are no @..@ components, @prefix@ is just the stripped @.\/@ prefix,+-- @remainder@ is the rest, and @baseDir@ is the current directory.+splitDotDot :: Path Abs Dir -> FilePath -> (FilePath, FilePath, Path Abs Dir)+splitDotDot here fp' =+  let (curDirPrefix, afterCurDir) = stripCurDir fp'+      components = splitOnSlash afterCurDir+      -- Find the index after the last "../" component+      lastDotDotIdx = case [i | (i, c) <- zip [1 ..] components, c == ".."] of+        [] -> 0+        ixs -> maximum ixs+   in if lastDotDotIdx == 0+        then (curDirPrefix, afterCurDir, here)+        else+          let prefixComponents = take lastDotDotIdx components+              remainderComponents = drop lastDotDotIdx components+              prefix = curDirPrefix <> concatMap (<> "/") prefixComponents+              -- Preserve trailing slash from the original input+              trailingSlash+                | "/" `isSuffixOf` afterCurDir, not (null remainderComponents) = "/"+                | otherwise = ""+              remainder = intercalate "/" remainderComponents <> trailingSlash+              baseDir = foldl' applyComponent here prefixComponents+           in (prefix, remainder, baseDir)++-- | Split a filepath on @\/@ separators, dropping empty segments.+splitOnSlash :: FilePath -> [String]+splitOnSlash [] = []+splitOnSlash s =+  let (seg, rest) = break (== '/') s+   in case rest of+        [] -> [seg | not (null seg)]+        _ : rest' -> [seg | not (null seg)] <> splitOnSlash rest'++-- | Apply a single path component to an absolute directory.+applyComponent :: Path Abs Dir -> String -> Path Abs Dir+applyComponent d ".." = parent d+applyComponent d c = case parseRelDir c of+  Nothing -> d+  Just rd -> d </> rd  filterPrefix :: String -> [CompletionResult] -> [CompletionResult] filterPrefix s = filter ((s `isPrefixOf`) . completionResultValue)