Posted to tcl by hypnotoad at Fri Sep 20 13:58:56 GMT 2019view raw
- #! /usr/bin/env tclsh
- namespace eval ::practcl::xvfs {}
- # Functions
- proc ::practcl::xvfs::printHelp {channel {errors ""}} {
- if {[llength $errors] != 0} {
- foreach error $errors {
- puts $channel "error: $error"
- }
- puts $channel ""
- }
- puts $channel "Usage: dir2c \[--help\] --directory <rootDirectory> --name <fsName>"
- flush $channel
- }
- proc ::practcl::xvfs::sanitizeCString {string} {
- set output [join [lmap char [split $string ""] {
- if {![regexp {[A-Za-z0-9./-]} $char]} {
- binary scan $char H* char
- set char "\\[format %03o 0x$char]"
- }
- set char
- }] ""]
- return $output
- }
- proc ::practcl::xvfs::sanitizeCStringList {list {prefix ""} {width 80}} {
- set lines [list]
- set row [list]
- foreach item $list {
- lappend row "\"[sanitizeCString $item]\""
- set rowString [join $row {, }]
- set rowString "${prefix}${rowString}"
- if {[string length $rowString] > $width} {
- set row [list]
- lappend lines $rowString
- unset rowString
- }
- }
- if {[info exists rowString]} {
- lappend lines $rowString
- }
- return [join $lines "\n"]
- }
- proc ::practcl::xvfs::binaryToCHex {binary {prefix ""} {width 10}} {
- set binary [binary encode hex $binary]
- set output [list]
- set width [expr {$width * 2}]
- set stopAt [expr {$width - 1}]
- set offset 0
- while 1 {
- set row [string range $binary $offset [expr {$offset + $stopAt}]]
- if {[string length $row] == 0} {
- break
- }
- incr offset [string length $row]
- set rowOutput [list]
- while {$row ne ""} {
- set value [string range $row 0 1]
- set row [string range $row 2 end]
- lappend rowOutput "\\x$value"
- }
- set rowOutput [join $rowOutput {}]
- set rowOutput "${prefix}\"${rowOutput}\""
- lappend output $rowOutput
- }
- if {[llength $output] == 0} {
- return "${prefix}\"\""
- }
- set output [join $output "\n"]
- }
- proc ::practcl::xvfs::processFile {resultVar fsName inputFile outputFile fileInfoDict} {
- upvar 1 $resultVar result
- array set fileInfo $fileInfoDict
- switch -exact -- $fileInfo(type) {
- "file" {
- set type "XVFS_FILE_TYPE_REG"
- set fd [open $inputFile]
- fconfigure $fd -encoding binary -translation binary -blocking true
- set data [read $fd]
- set size [string length $data]
- set data [string trimleft [binaryToCHex $data "\t\t\t"]]
- close $fd
- }
- "directory" {
- set type "XVFS_FILE_TYPE_DIR"
- set children $fileInfo(children)
- set size [llength $children]
- if {$size == 0} {
- set children "NULL"
- } else {
- set children [string trimleft [sanitizeCStringList $children "\t\t\t"]]
- # This initializes it using a C99 compound literal, C99 is required
- set children "(const char *\[\]) \{$children\}"
- }
- }
- default {
- return -code error "Unable to process $inputFile, unknown type: $fileInfo(type)"
- }
- }
- ::clay::putb result "\t\{"
- ::clay::putb result "\t\t.name = \"[sanitizeCString $outputFile]\","
- ::clay::putb result "\t\t.type = $type,"
- ::clay::putb result "\t\t.size = $size,"
- switch -exact -- $fileInfo(type) {
- "file" {
- ::clay::putb result "\t\t.data.fileContents = (const unsigned char *) $data"
- }
- "directory" {
- ::clay::putb result "\t\t.data.dirChildren = $children"
- }
- }
- ::clay::putb result "\t\},"
- }
- proc ::practcl::xvfs::processDirectory {resultVar fsName directory {subDirectory ""}} {
- upvar 1 $resultVar result
- set subDirectories [list]
- set outputFiles [list]
- set workingDirectory [file join $directory $subDirectory]
- set outputDirectory $subDirectory
- if {$subDirectory eq ""} {
- set isTopLevel true
- } else {
- set isTopLevel false
- }
- if {$isTopLevel} {
- ::clay::putb result "static const struct xvfs_file_data xvfs_${fsName}_data\[\] = \{"
- }
- # XXX:TODO: Include hidden files ?
- set children [list]
- foreach file [glob -nocomplain -tails -directory $workingDirectory *] {
- if {$file in {. ..}} {
- continue
- }
- set inputFile [file join $workingDirectory $file]
- set outputFile [file join $outputDirectory [encoding convertto utf-8 $file]]
- unset -nocomplain fileInfo
- catch {
- file lstat $inputFile fileInfo
- }
- if {![info exists fileInfo]} {
- puts stderr "warning: Unable to access $inputFile, skipping"
- }
- lappend children [file tail $file]
- if {$fileInfo(type) eq "directory"} {
- lappend subDirectories $outputFile
- continue
- }
- processFile result $fsName $inputFile $outputFile [array get fileInfo]
- lappend outputFiles $outputFile
- }
- foreach subDirectory $subDirectories {
- lappend outputFiles {*}[processDirectory result $fsName $directory $subDirectory]
- }
- set inputFile $directory
- set outputFile $outputDirectory
- unset -nocomplain fileInfo
- file stat $inputFile fileInfo
- set fileInfo(children) $children
- processFile result $fsName $inputFile $outputFile [array get fileInfo]
- lappend outputFiles $outputFile
- if {$isTopLevel} {
- ::clay::putb result "\};"
- }
- return $outputFiles
- }
- proc ::practcl::xvfs::main {argv} {
- # Main entry point
- ## 1. Parse arguments
- if {[llength $argv] % 2 != 0} {
- lappend argv ""
- }
- foreach {arg val} $argv {
- switch -exact -- $arg {
- "--help" {
- printHelp stdout
- exit 0
- }
- "--directory" {
- set rootDirectory $val
- }
- "--name" {
- set fsName $val
- }
- default {
- printHelp stderr [list "Invalid option: $arg $val"]
- exit 1
- }
- }
- }
- ## 2. Validate arguments
- set errors [list]
- if {![info exists rootDirectory]} {
- lappend errors "--directory must be specified"
- }
- if {![info exists fsName]} {
- lappend errors "--name must be specified"
- }
- if {[llength $errors] != 0} {
- printHelp stderr $errors
- exit 1
- }
- ## 3. Start processing directory and producing initial output
- set ::practcl::xvfs::outputFiles [processDirectory result $fsName $rootDirectory]
- set ::practcl::xvfs::fsName $fsName
- set ::practcl::xvfs::rootDirectory $rootDirectory
- }
- package provide xvfs 1