luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

ramdisk2.lua (12067B)


      1 local ramdisk = {}
      2 
      3 -- Node metatable for directories
      4 local dirNode = {}
      5 dirNode.__index = dirNode
      6 
      7 -- Node metatable for files
      8 local fileNode = {}
      9 fileNode.__index = fileNode
     10 
     11 -- Helper function to split path into components
     12 local function splitPath(path)
     13     local components = {}
     14     -- Remove leading slash if present
     15     path = path:gsub("^/+", "")
     16     -- Split by slash
     17     for component in path:gmatch("[^/]+") do
     18         table.insert(components, component)
     19     end
     20     return components
     21 end
     22 
     23 -- Helper function to find root node
     24 local function findRoot(node)
     25     while node.parent ~= nil do
     26         node = node.parent
     27     end
     28     return node
     29 end
     30 
     31 -- Traverse method for both file and directory nodes
     32 local function traverse(self, path)
     33     -- Start from root
     34     local root = findRoot(self)
     35     local current = root
     36 
     37     -- Split path into components
     38     local components = splitPath(path)
     39 
     40     -- Traverse through each component
     41     for i, component in ipairs(components) do
     42         if current.type ~= "directory" then
     43             return nil, "not a directory: " .. current.name
     44         end
     45 
     46         -- Check if this is the last component
     47         local isLast = (i == #components)
     48 
     49         -- Search in directories first
     50         local found = false
     51         for _, dir in ipairs(current.dirs) do
     52             if dir.name == component then
     53                 current = dir
     54                 found = true
     55                 break
     56             end
     57         end
     58 
     59         -- If not found in dirs and this is the last component, check files
     60         if not found and isLast then
     61             for _, file in ipairs(current.files) do
     62                 if file.name == component then
     63                     current = file
     64                     found = true
     65                     break
     66                 end
     67             end
     68         end
     69 
     70         -- If still not found, check in dirs for last component (could be a directory)
     71         if not found then
     72             return nil, "path not found: " .. component
     73         end
     74     end
     75 
     76     return current
     77 end
     78 
     79 -- Method to create a new directory
     80 function dirNode:newDir(name)
     81     local dir = {
     82         name = name,
     83         type = "directory",
     84         parent = self,
     85         dirs = {},
     86         files = {}
     87     }
     88     setmetatable(dir, dirNode)
     89     table.insert(self.dirs, dir)
     90     return dir
     91 end
     92 
     93 -- Method to create a new file
     94 function dirNode:newFile(name, contents)
     95     local file = {
     96         name = name,
     97         type = "file",
     98         parent = self,
     99         contents = contents or ""
    100     }
    101     setmetatable(file, fileNode)
    102     table.insert(self.files, file)
    103     return file
    104 end
    105 
    106 -- Read method for files
    107 function fileNode:read()
    108     return self.contents
    109 end
    110 
    111 -- Write method for files
    112 function fileNode:write(contents)
    113     self.contents = contents
    114 end
    115 
    116 -- Delete method for files
    117 function fileNode:delete()
    118     if self.parent then
    119         -- Remove from parent's files array
    120         for i, file in ipairs(self.parent.files) do
    121             if file == self then
    122                 table.remove(self.parent.files, i)
    123                 break
    124             end
    125         end
    126     end
    127     -- Clear file data
    128     self.name = nil
    129     self.parent = nil
    130     self.contents = nil
    131 end
    132 
    133 -- Delete method for directories
    134 function dirNode:delete()
    135     if self.parent then
    136         -- Remove from parent's dirs array
    137         for i, dir in ipairs(self.parent.dirs) do
    138             if dir == self then
    139                 table.remove(self.parent.dirs, i)
    140                 break
    141             end
    142         end
    143     end
    144     -- Clear directory data
    145     self.name = nil
    146     self.parent = nil
    147     -- Note: dirs and files arrays are left for garbage collection
    148 end
    149 
    150 -- Add traverse method to both metatables
    151 dirNode.traverse = traverse
    152 fileNode.traverse = traverse
    153 
    154 -- Function to create a root directory node
    155 function ramdisk.createRoot()
    156     local root = {
    157         name = "",
    158         type = "directory",
    159         parent = nil,
    160         dirs = {},
    161         files = {}
    162     }
    163     setmetatable(root, dirNode)
    164     return root
    165 end
    166 
    167 -- Helper function to get full path of a node
    168 local function getFullPath(node)
    169     local parts = {}
    170     local current = node
    171 
    172     while current.parent ~= nil do
    173         table.insert(parts, 1, current.name)
    174         current = current.parent
    175     end
    176 
    177     return table.concat(parts, "/")
    178 end
    179 
    180 -- Helper function to check if content is binary (contains non-printable chars)
    181 local function isBinary(content)
    182     if type(content) ~= "string" then
    183         return false
    184     end
    185 
    186     for i = 1, #content do
    187         local byte = content:byte(i)
    188         -- Check for control characters except tab, newline, carriage return
    189         if byte < 32 and byte ~= 9 and byte ~= 10 and byte ~= 13 then
    190             return true
    191         end
    192         -- Check for extended ASCII
    193         if byte > 127 then
    194             return true
    195         end
    196     end
    197     return false
    198 end
    199 
    200 -- Helper function to encode binary data to base64
    201 local function base64Encode(data)
    202     local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    203     return ((data:gsub('.', function(x)
    204         local r,b='',x:byte()
    205         for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
    206         return r;
    207     end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
    208         if (#x < 6) then return '' end
    209         local c=0
    210         for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
    211         return b:sub(c+1,c+1)
    212     end)..({ '', '==', '=' })[#data%3+1])
    213 end
    214 
    215 -- Helper function to decode base64 to binary data
    216 local function base64Decode(data)
    217     local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    218     data = string.gsub(data, '[^'..b..'=]', '')
    219     return (data:gsub('.', function(x)
    220         if (x == '=') then return '' end
    221         local r,f='',(b:find(x)-1)
    222         for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
    223         return r;
    224     end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
    225         if (#x ~= 8) then return '' end
    226         local c=0
    227         for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
    228         return string.char(c)
    229     end))
    230 end
    231 
    232 -- Helper function to escape special characters in text content
    233 local function escapeText(text)
    234     text = text:gsub("\\", "\\\\")  -- Backslash must be first
    235     text = text:gsub("\n", "\\n")   -- Newline
    236     text = text:gsub("\t", "\\t")   -- Tab
    237     text = text:gsub("\r", "\\r")   -- Carriage return
    238     return text
    239 end
    240 
    241 -- Helper function to unescape special characters in text content
    242 local function unescapeText(text)
    243     text = text:gsub("\\r", "\r")   -- Carriage return
    244     text = text:gsub("\\t", "\t")   -- Tab
    245     text = text:gsub("\\n", "\n")   -- Newline
    246     text = text:gsub("\\\\", "\\")  -- Backslash must be last
    247     return text
    248 end
    249 
    250 -- Function to save the filesystem to serialized format
    251 function ramdisk.saveFS(rootdir)
    252     local dirs = {}
    253     local files = {}
    254 
    255     -- Recursive function to traverse directories
    256     local function traverseDir(dir, currentPath)
    257         -- Add directory path (with trailing slash)
    258         if currentPath ~= "" then
    259             table.insert(dirs, currentPath .. "/")
    260         end
    261 
    262         -- Process subdirectories
    263         for _, subdir in ipairs(dir.dirs) do
    264             local subdirPath = currentPath == "" and subdir.name or currentPath .. "/" .. subdir.name
    265             traverseDir(subdir, subdirPath)
    266         end
    267 
    268         -- Process files
    269         for _, file in ipairs(dir.files) do
    270             local filePath = currentPath == "" and file.name or currentPath .. "/" .. file.name
    271             local contents = file.contents or ""
    272 
    273             if isBinary(contents) then
    274                 table.insert(files, filePath .. "~~~~BIN:" .. base64Encode(contents))
    275             else
    276                 table.insert(files, filePath .. "~~~~" .. escapeText(contents))
    277             end
    278         end
    279     end
    280 
    281     -- Start traversal from root
    282     traverseDir(rootdir, "")
    283 
    284     -- Build the final format: [[dirs||||files]]
    285     local dirSection = table.concat(dirs, "||||")
    286     local fileSection = table.concat(files, "||||")
    287 
    288     return "[[" .. dirSection .. "||||" .. fileSection .. "]]"
    289 end
    290 
    291 -- Function to load a filesystem from serialized format
    292 function ramdisk.loadFS(serialized)
    293     -- Create new root
    294     local root = ramdisk.createRoot()
    295 
    296     -- Remove the [[ and ]] wrapper
    297     if not serialized:match("^%[%[.*%]%]$") then
    298         error("Invalid serialized format: missing [[ ]] wrapper")
    299     end
    300 
    301     local content = serialized:sub(3, -3)  -- Remove [[ and ]]
    302 
    303     -- Split by |||| to separate entries
    304     local entries = {}
    305     local currentEntry = ""
    306     local i = 1
    307     while i <= #content do
    308         -- Check if we're at a |||| separator
    309         if content:sub(i, i+3) == "||||" then
    310             if currentEntry ~= "" then
    311                 table.insert(entries, currentEntry)
    312                 currentEntry = ""
    313             end
    314             i = i + 4  -- Skip the ||||
    315         else
    316             currentEntry = currentEntry .. content:sub(i, i)
    317             i = i + 1
    318         end
    319     end
    320     -- Don't forget the last entry
    321     if currentEntry ~= "" then
    322         table.insert(entries, currentEntry)
    323     end
    324 
    325     -- Track created directories
    326     local createdDirs = {}
    327     createdDirs[""] = root  -- Root is always available
    328 
    329     -- Helper to ensure directory exists
    330     local function ensureDir(path)
    331         if createdDirs[path] then
    332             return createdDirs[path]
    333         end
    334 
    335         -- Split path into components
    336         local components = splitPath(path)
    337         local current = root
    338         local currentPath = ""
    339 
    340         for _, component in ipairs(components) do
    341             local nextPath = currentPath == "" and component or currentPath .. "/" .. component
    342 
    343             if not createdDirs[nextPath] then
    344                 -- Check if directory already exists
    345                 local found = false
    346                 for _, dir in ipairs(current.dirs) do
    347                     if dir.name == component then
    348                         current = dir
    349                         found = true
    350                         break
    351                     end
    352                 end
    353 
    354                 if not found then
    355                     current = current:newDir(component)
    356                 end
    357 
    358                 createdDirs[nextPath] = current
    359             else
    360                 current = createdDirs[nextPath]
    361             end
    362 
    363             currentPath = nextPath
    364         end
    365 
    366         return current
    367     end
    368 
    369     -- First pass: create all directories
    370     for _, entry in ipairs(entries) do
    371         if entry:match("/$") then  -- It's a directory
    372             local dirPath = entry:sub(1, -2)  -- Remove trailing slash
    373             ensureDir(dirPath)
    374         end
    375     end
    376 
    377     -- Second pass: create all files
    378     for _, entry in ipairs(entries) do
    379         if entry:match("~~~~") then  -- It's a file
    380             local separatorPos = entry:find("~~~~")
    381             if separatorPos then
    382                 local filePath = entry:sub(1, separatorPos - 1)
    383                 local fileContent = entry:sub(separatorPos + 4)
    384 
    385                 -- Determine parent directory and filename
    386                 local lastSlash = filePath:match("^.*()/")
    387                 local parentPath, fileName
    388 
    389                 if lastSlash then
    390                     parentPath = filePath:sub(1, lastSlash - 1)
    391                     fileName = filePath:sub(lastSlash + 1)
    392                 else
    393                     parentPath = ""
    394                     fileName = filePath
    395                 end
    396 
    397                 -- Get or create parent directory
    398                 local parentDir = ensureDir(parentPath)
    399 
    400                 -- Check if it's binary
    401                 if fileContent:match("^BIN:") then
    402                     local base64Data = fileContent:sub(5)  -- Remove "BIN:" prefix
    403                     local binaryContent = base64Decode(base64Data)
    404                     parentDir:newFile(fileName, binaryContent)
    405                 else
    406                     local textContent = unescapeText(fileContent)
    407                     parentDir:newFile(fileName, textContent)
    408                 end
    409             end
    410         end
    411     end
    412 
    413     return root
    414 end
    415 
    416 return ramdisk