Skip to content

Reimplement bindings to Stats object #76

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ Breaking changes:

New features:
- Integrate `node-fs-aff` into library (#75 by @JordanMartinez)
- Remove `StatsObj` and reimplement bindings to `Stats` object (#76 by @JordanMartinez)

Previously, one could write the following to get a value on the `Stats` object:
```purs
getGid :: Stats -> Number
getGid (Stats obj) = obj.gid
```

This record interface was removed as the underlying value is not a record
that can be copied and modified as such. Now, one must call the corresponding function:
```purs
getGid :: Stats -> Number
getGid s = Stats.gid s
```

Bugfixes:

Expand Down
10 changes: 5 additions & 5 deletions src/Node/FS/Async.purs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import Node.Encoding (Encoding(..), encodingToNode)
import Node.FS (FileDescriptor, ByteCount, FilePosition, BufferLength, BufferOffset, FileMode, SymlinkType, symlinkTypeToNode)
import Node.FS.Constants (FileFlags, fileFlagsToNode, AccessMode, CopyMode, defaultAccessMode, defaultCopyMode)
import Node.FS.Perms (Perms, permsToString, all, mkPerms)
import Node.FS.Stats (StatsObj, Stats(..))
import Node.FS.Stats (Stats)
import Node.Path (FilePath)

type JSCallback a = EffectFn2 (Nullable Error) a Unit
Expand Down Expand Up @@ -99,8 +99,8 @@ foreign import renameImpl :: EffectFn3 FilePath FilePath (JSCallback Unit) Unit
foreign import truncateImpl :: EffectFn3 FilePath Int (JSCallback Unit) Unit
foreign import chownImpl :: EffectFn4 FilePath Int Int (JSCallback Unit) Unit
foreign import chmodImpl :: EffectFn3 FilePath String (JSCallback Unit) Unit
foreign import statImpl :: EffectFn2 FilePath (JSCallback StatsObj) Unit
foreign import lstatImpl :: EffectFn2 FilePath (JSCallback StatsObj) Unit
foreign import statImpl :: EffectFn2 FilePath (JSCallback Stats) Unit
foreign import lstatImpl :: EffectFn2 FilePath (JSCallback Stats) Unit
foreign import linkImpl :: EffectFn3 FilePath FilePath (JSCallback Unit) Unit
foreign import symlinkImpl :: EffectFn4 FilePath FilePath String (JSCallback Unit) Unit
foreign import readlinkImpl :: EffectFn2 FilePath (JSCallback FilePath) Unit
Expand Down Expand Up @@ -157,7 +157,7 @@ stat
:: FilePath
-> Callback Stats
-> Effect Unit
stat file cb = runEffectFn2 statImpl file (handleCallback $ cb <<< map Stats)
stat file cb = runEffectFn2 statImpl file (handleCallback $ cb)

-- | Gets file or symlink statistics. `lstat` is identical to `stat`, except
-- | that if the `FilePath` is a symbolic link, then the link itself is stat-ed,
Expand All @@ -166,7 +166,7 @@ lstat
:: FilePath
-> Callback Stats
-> Effect Unit
lstat file cb = runEffectFn2 lstatImpl file (handleCallback $ cb <<< map Stats)
lstat file cb = runEffectFn2 lstatImpl file (handleCallback $ cb)

-- | Creates a link to an existing file.
link
Expand Down
28 changes: 25 additions & 3 deletions src/Node/FS/Stats.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
export { inspect as showStatsObj } from "util";

export function statsMethod(m, s) {
return s[m]();
}
export const isBlockDeviceImpl = (s) => s.isBlockDevice();
export const isCharacterDeviceImpl = (s) => s.isCharacterDevice();
export const isDirectoryImpl = (s) => s.isDirectory();
export const isFIFOImpl = (s) => s.isFIFO();
export const isFileImpl = (s) => s.isFile();
export const isSocketImpl = (s) => s.isSocket();
export const isSymbolicLinkImpl = (s) => s.isSymbolicLink();
export const devImpl = (s) => s.dev;
export const inodeImpl = (s) => s.ino;
export const modeImpl = (s) => s.mode;
export const nlinkImpl = (s) => s.nlink;
export const uidImpl = (s) => s.uid;
export const gidImpl = (s) => s.gid;
export const rdevImpl = (s) => s.rdev;
export const sizeImpl = (s) => s.size;
export const blkSizeImpl = (s) => s.blkSize;
export const blocksImpl = (s) => s.blocks;
export const accessedTimeMsImpl = (s) => s.atimeMs;
export const modifiedTimeMsImpl = (s) => s.mtimeMs;
export const statusChangedTimeMsImpl = (s) => s.ctimeMs;
export const birthtimeMsImpl = (s) => s.birthtimeMs;
export const accessedTimeImpl = (s) => s.atime;
export const modifiedTimeImpl = (s) => s.mtime;
export const statusChangedTimeImpl = (s) => s.ctime;
export const birthTimeImpl = (s) => s.birthtime;
202 changes: 156 additions & 46 deletions src/Node/FS/Stats.purs
Original file line number Diff line number Diff line change
@@ -1,82 +1,192 @@
module Node.FS.Stats
( Stats(..)
, StatsObj
( Stats
, isFile
, isDirectory
, isBlockDevice
, isCharacterDevice
, isFIFO
, isSocket
, isSymbolicLink
, dev
, inode
, mode
, nlink
, uid
, gid
, rdev
, size
, blkSize
, blocks
, accessedTimeMs
, modifiedTimeMs
, statusChangedTimeMs
, birthtimeMs
, accessedTime
, modifiedTime
, statusChangedTime
, birthTime
) where

import Prelude

import Data.DateTime (DateTime)
import Data.Function.Uncurried (Fn2, Fn0, runFn2)
import Data.Function.Uncurried (Fn1, runFn1)
import Data.JSDate (JSDate, toDateTime)
import Data.Maybe (fromJust)
import Partial.Unsafe (unsafePartial)

type StatsObj =
{ dev :: Number
, mode :: Number
, nlink :: Number
, uid :: Number
, gid :: Number
, rdev :: Number
, ino :: Number
, size :: Number
, atime :: JSDate
, mtime :: JSDate
, ctime :: JSDate
, isFile :: Fn0 Boolean
, isDirectory :: Fn0 Boolean
, isBlockDevice :: Fn0 Boolean
, isCharacterDevice :: Fn0 Boolean
, isFIFO :: Fn0 Boolean
, isSocket :: Fn0 Boolean
}

-- | Stats wrapper to provide a usable interface to the underlying properties and methods.
data Stats = Stats StatsObj

foreign import showStatsObj :: StatsObj -> String
import Data.Maybe (Maybe(..))
import Data.Time.Duration (Milliseconds)
import Partial.Unsafe (unsafeCrashWith)

instance showStats :: Show Stats where
show (Stats o) = "Stats " <> showStatsObj o

foreign import statsMethod :: Fn2 String StatsObj Boolean
foreign import data Stats :: Type

isFile :: Stats -> Boolean
isFile (Stats s) = runFn2 statsMethod "isFile" s
foreign import showStatsObj :: Stats -> String

isDirectory :: Stats -> Boolean
isDirectory (Stats s) = runFn2 statsMethod "isDirectory" s
instance showStats :: Show Stats where
show s = "Stats " <> showStatsObj s

isBlockDevice :: Stats -> Boolean
isBlockDevice (Stats s) = runFn2 statsMethod "isBlockDevice" s
isBlockDevice s = runFn1 isBlockDeviceImpl s

foreign import isBlockDeviceImpl :: Fn1 (Stats) (Boolean)

isCharacterDevice :: Stats -> Boolean
isCharacterDevice (Stats s) = runFn2 statsMethod "isCharacterDevice" s
isCharacterDevice s = runFn1 isCharacterDeviceImpl s

foreign import isCharacterDeviceImpl :: Fn1 (Stats) (Boolean)

-- | Returns true if the <fs.Stats> object describes a file system directory.
-- | If the `fs.Stats`> object was obtained from `fs.lstat()`,
-- | this method will always return `false``. This is because `fs.lstat()`
-- | returns information about a symbolic link itself and not the path to which it resolves.
isDirectory :: Stats -> Boolean
isDirectory s = runFn1 isDirectoryImpl s

foreign import isDirectoryImpl :: Fn1 (Stats) (Boolean)

isFIFO :: Stats -> Boolean
isFIFO (Stats s) = runFn2 statsMethod "isFIFO" s
isFIFO s = runFn1 isFIFOImpl s

foreign import isFIFOImpl :: Fn1 (Stats) (Boolean)

isFile :: Stats -> Boolean
isFile s = runFn1 isFileImpl s

foreign import isFileImpl :: Fn1 (Stats) (Boolean)

isSocket :: Stats -> Boolean
isSocket (Stats s) = runFn2 statsMethod "isSocket" s
isSocket s = runFn1 isSocketImpl s

foreign import isSocketImpl :: Fn1 (Stats) (Boolean)

isSymbolicLink :: Stats -> Boolean
isSymbolicLink (Stats s) = runFn2 statsMethod "isSymbolicLink" s
isSymbolicLink s = runFn1 isSymbolicLinkImpl s

foreign import isSymbolicLinkImpl :: Fn1 (Stats) (Boolean)

-- | The numeric identifier of the device containing the file.
dev :: Stats -> Number
dev s = runFn1 devImpl s

foreign import devImpl :: Fn1 (Stats) (Number)

-- | The file system specific "Inode" number for the file.
inode :: Stats -> Number
inode s = runFn1 inodeImpl s

foreign import inodeImpl :: Fn1 (Stats) (Number)

-- | A bit-field describing the file type and mode.
mode :: Stats -> Number
mode s = runFn1 modeImpl s

foreign import modeImpl :: Fn1 (Stats) (Number)

-- | The number of hard-links that exist for the file.
nlink :: Stats -> Number
nlink s = runFn1 nlinkImpl s

foreign import nlinkImpl :: Fn1 (Stats) (Number)

-- | The numeric user identifier of the user that owns the file (POSIX).
uid :: Stats -> Number
uid s = runFn1 uidImpl s

foreign import uidImpl :: Fn1 (Stats) (Number)

-- | The numeric group identifier of the group that owns the file (POSIX).
gid :: Stats -> Number
gid s = runFn1 gidImpl s

foreign import gidImpl :: Fn1 (Stats) (Number)

-- | A numeric device identifier if the file represents a device.
rdev :: Stats -> Number
rdev s = runFn1 rdevImpl s

foreign import rdevImpl :: Fn1 (Stats) (Number)

-- | The size of the file in bytes.
-- | If the underlying file system does not support getting the size of the file, this will be 0.
size :: Stats -> Number
size s = runFn1 sizeImpl s

foreign import sizeImpl :: Fn1 (Stats) (Number)

-- | The file system block size for i/o operations.
blkSize :: Stats -> Number
blkSize s = runFn1 blkSizeImpl s

foreign import blkSizeImpl :: Fn1 (Stats) (Number)

-- | The number of blocks allocated for this file.
blocks :: Stats -> Number
blocks s = runFn1 blocksImpl s

foreign import blocksImpl :: Fn1 (Stats) (Number)

accessedTimeMs :: Stats -> Milliseconds
accessedTimeMs s = runFn1 accessedTimeMsImpl s

foreign import accessedTimeMsImpl :: Fn1 (Stats) (Milliseconds)

modifiedTimeMs :: Stats -> Milliseconds
modifiedTimeMs s = runFn1 modifiedTimeMsImpl s

foreign import modifiedTimeMsImpl :: Fn1 (Stats) (Milliseconds)

statusChangedTimeMs :: Stats -> Milliseconds
statusChangedTimeMs s = runFn1 statusChangedTimeMsImpl s

foreign import statusChangedTimeMsImpl :: Fn1 (Stats) (Milliseconds)

birthtimeMs :: Stats -> Milliseconds
birthtimeMs s = runFn1 birthtimeMsImpl s

foreign import birthtimeMsImpl :: Fn1 (Stats) (Milliseconds)

accessedTime :: Stats -> DateTime
accessedTime (Stats s) = unsafePartial $ fromJust (toDateTime s.atime)
accessedTime s = case toDateTime $ runFn1 accessedTimeImpl s of
Just d -> d
Nothing -> unsafeCrashWith $ "Impossible: `accessedTime` returned invalid DateTime value."

foreign import accessedTimeImpl :: Fn1 (Stats) (JSDate)

modifiedTime :: Stats -> DateTime
modifiedTime (Stats s) = unsafePartial $ fromJust (toDateTime s.mtime)
modifiedTime s = case toDateTime $ runFn1 modifiedTimeImpl s of
Just d -> d
Nothing -> unsafeCrashWith $ "Impossible: `modifiedTime` returned invalid DateTime value."

foreign import modifiedTimeImpl :: Fn1 (Stats) (JSDate)

statusChangedTime :: Stats -> DateTime
statusChangedTime (Stats s) = unsafePartial $ fromJust (toDateTime s.ctime)
statusChangedTime s = case toDateTime $ runFn1 statusChangedTimeImpl s of
Just d -> d
Nothing -> unsafeCrashWith $ "Impossible: `statusChangedTime` returned invalid DateTime value."

foreign import statusChangedTimeImpl :: Fn1 (Stats) (JSDate)

birthTime :: Stats -> DateTime
birthTime s = case toDateTime $ runFn1 birthTimeImpl s of
Just d -> d
Nothing -> unsafeCrashWith $ "Impossible: `birthTime` returned invalid DateTime value."

foreign import birthTimeImpl :: Fn1 (Stats) (JSDate)
10 changes: 5 additions & 5 deletions src/Node/FS/Sync.purs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import Node.Encoding (Encoding(..), encodingToNode)
import Node.FS (FileDescriptor, ByteCount, FilePosition, BufferLength, BufferOffset, FileMode, SymlinkType, symlinkTypeToNode)
import Node.FS.Constants (AccessMode, CopyMode, FileFlags, defaultAccessMode, defaultCopyMode, fileFlagsToNode)
import Node.FS.Perms (Perms, permsToString, all, mkPerms)
import Node.FS.Stats (StatsObj, Stats(..))
import Node.FS.Stats (Stats)
import Node.Path (FilePath)

access :: FilePath -> Effect (Maybe Error)
Expand Down Expand Up @@ -90,8 +90,8 @@ foreign import renameSyncImpl :: EffectFn2 FilePath FilePath Unit
foreign import truncateSyncImpl :: EffectFn2 FilePath Int Unit
foreign import chownSyncImpl :: EffectFn3 FilePath Int Int Unit
foreign import chmodSyncImpl :: EffectFn2 FilePath String Unit
foreign import statSyncImpl :: EffectFn1 FilePath StatsObj
foreign import lstatSyncImpl :: EffectFn1 FilePath StatsObj
foreign import statSyncImpl :: EffectFn1 FilePath Stats
foreign import lstatSyncImpl :: EffectFn1 FilePath Stats
foreign import linkSyncImpl :: EffectFn2 FilePath FilePath Unit
foreign import symlinkSyncImpl :: EffectFn3 FilePath FilePath String Unit
foreign import readlinkSyncImpl :: EffectFn1 FilePath FilePath
Expand Down Expand Up @@ -142,15 +142,15 @@ chmod file perms = runEffectFn2 chmodSyncImpl file (permsToString perms)
stat
:: FilePath
-> Effect Stats
stat file = map Stats $ runEffectFn1 statSyncImpl file
stat file = runEffectFn1 statSyncImpl file

-- | Gets file or symlink statistics. `lstat` is identical to `stat`, except
-- | that if the `FilePath` is a symbolic link, then the link itself is stat-ed,
-- | not the file that it refers to.
lstat
:: FilePath
-> Effect Stats
lstat file = map Stats $ runEffectFn1 lstatSyncImpl file
lstat file = runEffectFn1 lstatSyncImpl file

-- | Creates a link to an existing file.
link
Expand Down
2 changes: 1 addition & 1 deletion test/Test.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Test where

import Prelude

import Data.Either (Either(..), either, isRight)
import Data.Either (Either(..), either)
import Data.Maybe (Maybe(..), isNothing)
import Data.Traversable (for_, traverse)
import Effect (Effect)
Expand Down