This document describes the internal API architecture and data structures used by GitCells.
GitCells follows a clean architecture pattern with clear separation of concerns:
cmd/gitcells/ # CLI commands and entry points
├── main.go # Application entry point
├── init.go # Initialize command
├── convert.go # Convert command
├── watch.go # Watch command
├── sync.go # Sync command
└── status.go # Status command
internal/ # Private application code
├── config/ # Configuration management
├── converter/ # Excel ↔ JSON conversion
├── git/ # Git operations
├── watcher/ # File system monitoring
└── utils/ # Utilities and helpers
pkg/ # Public API packages
└── models/ # Data models and types
The ExcelDocument represents the complete structure of an Excel file in JSON format:
type ExcelDocument struct {
Version string `json:"version"`
Metadata DocumentMetadata `json:"metadata"`
Sheets []Sheet `json:"sheets"`
DefinedNames map[string]string `json:"defined_names,omitempty"`
Properties DocumentProperties `json:"properties,omitempty"`
}Fields:
Version: GitCells format version (e.g., "1.0")Metadata: File metadata including checksums and timestampsSheets: Array of worksheet dataDefinedNames: Excel named ranges and definitionsProperties: Document properties (title, author, etc.)
Contains metadata about the Excel file:
type DocumentMetadata struct {
Created time.Time `json:"created"`
Modified time.Time `json:"modified"`
AppVersion string `json:"app_version"`
OriginalFile string `json:"original_file"`
FileSize int64 `json:"file_size"`
Checksum string `json:"checksum"`
}Fields:
Created: When the JSON was first createdModified: Last modification time of the Excel fileAppVersion: Version of GitCells that created this JSONOriginalFile: Path to the original Excel fileFileSize: Size of the original Excel file in bytesChecksum: SHA256 hash of the original Excel file
Represents a single worksheet within an Excel file:
type Sheet struct {
Name string `json:"name"`
Index int `json:"index"`
Cells map[string]Cell `json:"cells"`
MergedCells []MergedCell `json:"merged_cells,omitempty"`
RowHeights map[int]float64 `json:"row_heights,omitempty"`
ColumnWidths map[string]float64 `json:"column_widths,omitempty"`
Hidden bool `json:"hidden"`
Protection *SheetProtection `json:"protection,omitempty"`
ConditionalFormats []ConditionalFormat `json:"conditional_formats,omitempty"`
}Fields:
Name: Worksheet name as it appears in ExcelIndex: Zero-based position of the sheet in the workbookCells: Map of cell references (e.g., "A1") to cell dataMergedCells: Array of merged cell rangesRowHeights: Custom row heights (if different from default)ColumnWidths: Custom column widths (if different from default)Hidden: Whether the sheet is hidden in ExcelProtection: Sheet protection settingsConditionalFormats: Conditional formatting rules
Represents a single cell's data and formatting:
type Cell struct {
Value interface{} `json:"value"`
Formula string `json:"formula,omitempty"`
Style *CellStyle `json:"style,omitempty"`
Type CellType `json:"type"`
Comment *Comment `json:"comment,omitempty"`
Hyperlink string `json:"hyperlink,omitempty"`
DataValidation *DataValidation `json:"data_validation,omitempty"`
}Fields:
Value: The computed value of the cell (string, number, boolean, etc.)Formula: Excel formula if the cell contains one (includes the = prefix)Style: Formatting information (fonts, colors, borders, etc.)Type: The type of cell content (string, number, formula, etc.)Comment: Cell comment/annotationHyperlink: URL or cell reference if cell contains a hyperlinkDataValidation: Data validation rules applied to the cell
Enumeration of possible cell types:
type CellType string
const (
CellTypeString CellType = "string"
CellTypeNumber CellType = "number"
CellTypeBoolean CellType = "boolean"
CellTypeDate CellType = "date"
CellTypeError CellType = "error"
CellTypeFormula CellType = "formula"
)The converter package provides the core Excel ↔ JSON conversion functionality:
type Converter interface {
ExcelToJSON(filePath string, options ConvertOptions) (*models.ExcelDocument, error)
JSONToExcel(doc *models.ExcelDocument, outputPath string, options ConvertOptions) error
}Configuration options for conversion operations:
type ConvertOptions struct {
PreserveFormulas bool // Keep Excel formulas in JSON
PreserveStyles bool // Keep cell formatting
PreserveComments bool // Keep cell comments
CompactJSON bool // Generate compact vs. pretty JSON
IgnoreEmptyCells bool // Skip empty cells in JSON
MaxCellsPerSheet int // Memory protection limit
}package main
import (
"github.com/sirupsen/logrus"
"github.com/Classic-Homes/gitcells/internal/converter"
)
func main() {
logger := logrus.New()
conv := converter.NewConverter(logger)
options := converter.ConvertOptions{
PreserveFormulas: true,
PreserveStyles: true,
PreserveComments: true,
IgnoreEmptyCells: true,
}
doc, err := conv.ExcelToJSON("data.xlsx", options)
if err != nil {
logger.Fatal(err)
}
// Use the document...
}err = conv.JSONToExcel(doc, "output.xlsx", options)
if err != nil {
logger.Fatal(err)
}The watcher package provides file system monitoring capabilities:
type FileWatcher struct {
// Internal fields...
}
type EventHandler func(event FileEvent) error
type FileEvent struct {
Path string
Type EventType
Timestamp time.Time
}
type EventType int
const (
EventTypeCreate EventType = iota
EventTypeModify
EventTypeDelete
)type Config struct {
IgnorePatterns []string // File patterns to ignore
DebounceDelay time.Duration // Delay before processing events
FileExtensions []string // Extensions to watch (.xlsx, .xls, etc.)
}package main
import (
"time"
"github.com/sirupsen/logrus"
"github.com/Classic-Homes/gitcells/internal/watcher"
)
func main() {
config := &watcher.Config{
IgnorePatterns: []string{"~$*", "*.tmp"},
DebounceDelay: 2 * time.Second,
FileExtensions: []string{".xlsx", ".xls", ".xlsm"},
}
handler := func(event watcher.FileEvent) error {
logger.Infof("File %s was %s", event.Path, event.Type)
// Process the file change...
return nil
}
logger := logrus.New()
fw, err := watcher.NewFileWatcher(config, handler, logger)
if err != nil {
logger.Fatal(err)
}
// Add directories to watch
err = fw.AddDirectory("./data")
if err != nil {
logger.Fatal(err)
}
// Start watching
err = fw.Start()
if err != nil {
logger.Fatal(err)
}
// ... wait for events ...
// Stop watching
fw.Stop()
}The git package provides version control operations:
type Client struct {
// Internal fields...
}
type Config struct {
UserName string
UserEmail string
CommitTemplate string
AutoPush bool
AutoPull bool
Branch string
}func NewClient(repoPath string, config *Config, logger *logrus.Logger) (*Client, error)
func (c *Client) AutoCommit(files []string, metadata map[string]string) error
func (c *Client) Push() error
func (c *Client) Pull() error
func (c *Client) GetStatus() (*Status, error)gitConfig := &git.Config{
UserName: "GitCells",
UserEmail: "gitcells@example.com",
CommitTemplate: "GitCells: {action} {filename} at {timestamp}",
AutoPush: false,
AutoPull: true,
Branch: "main",
}
client, err := git.NewClient(".", gitConfig, logger)
if err != nil {
logger.Fatal(err)
}
// Commit changes
metadata := map[string]string{
"filename": "data.xlsx",
"action": "modify",
}
err = client.AutoCommit([]string{"data.xlsx.json"}, metadata)
if err != nil {
logger.Error(err)
}The config package handles application configuration:
type Config struct {
Version string `yaml:"version"`
Git GitConfig `yaml:"git"`
Watcher WatcherConfig `yaml:"watcher"`
Converter ConverterConfig `yaml:"converter"`
}
func Load(configPath string) (*Config, error)version: 1.0
git:
branch: main
auto_push: false
auto_pull: true
user_name: "GitCells"
user_email: "gitcells@example.com"
commit_template: "GitCells: {action} {filename} at {timestamp}"
watcher:
directories: ["./data"]
ignore_patterns: ["~$*", "*.tmp"]
debounce_delay: 2s
file_extensions: [".xlsx", ".xls", ".xlsm"]
converter:
preserve_formulas: true
preserve_styles: true
preserve_comments: true
compact_json: false
ignore_empty_cells: true
max_cells_per_sheet: 1000000The models package includes diff functionality for comparing Excel documents:
type ExcelDiff struct {
Timestamp time.Time `json:"timestamp"`
Summary DiffSummary `json:"summary"`
SheetDiffs []SheetDiff `json:"sheet_diffs"`
}
type DiffSummary struct {
TotalChanges int `json:"total_changes"`
AddedSheets int `json:"added_sheets"`
ModifiedSheets int `json:"modified_sheets"`
DeletedSheets int `json:"deleted_sheets"`
}
func ComputeDiff(oldDoc, newDoc *ExcelDocument) *ExcelDiff// Load two versions of a document
oldDoc, _ := conv.ExcelToJSON("data_v1.xlsx", options)
newDoc, _ := conv.ExcelToJSON("data_v2.xlsx", options)
// Compute the diff
diff := models.ComputeDiff(oldDoc, newDoc)
// Display the changes
fmt.Printf("Total changes: %d\n", diff.Summary.TotalChanges)
for _, sheetDiff := range diff.SheetDiffs {
fmt.Printf("Sheet %s has %d changes\n", sheetDiff.SheetName, len(sheetDiff.Changes))
}GitCells uses structured error handling with custom error types:
type GitCellsError struct {
Type ErrorType
Operation string
File string
Cause error
Message string
Retryable bool
}
func (e *GitCellsError) Error() string
func (e *GitCellsError) Unwrap() error
func (e *GitCellsError) IsRecoverable() booltype ErrorType string
const (
ErrorTypeValidation ErrorType = "validation"
ErrorTypeIO ErrorType = "io"
ErrorTypeConversion ErrorType = "conversion"
ErrorTypeGit ErrorType = "git"
ErrorTypeConfig ErrorType = "config"
ErrorTypeNetwork ErrorType = "network"
)import "github.com/Classic-Homes/gitcells/internal/utils"
// Create a structured error
err := utils.NewError(
utils.ErrorTypeConversion,
"excel_to_json",
"data.xlsx",
"failed to parse formula in cell B2",
)
// Wrap an existing error
wrappedErr := utils.WrapError(originalErr, utils.ErrorTypeIO, "file_read", "data.xlsx")
// Check if error is recoverable
if utils.IsRecoverableError(err) {
// Retry the operation
}GitCells includes comprehensive testing utilities:
Test Excel files are located in test/testdata/sample_files/:
simple.xlsx: Basic spreadsheet with text, numbers, and formulascomplex.xlsx: Multi-sheet workbook with merged cells, comments, and formulasempty.xlsx: Empty Excel file for testing edge cases
// Create test converter
logger := logrus.New()
logger.SetLevel(logrus.WarnLevel)
conv := converter.NewConverter(logger)
// Test conversion
doc, err := conv.ExcelToJSON("test/testdata/sample_files/simple.xlsx", options)
require.NoError(t, err)
assert.NotNil(t, doc)# Run all tests
go test ./...
# Run with coverage
go test -coverprofile=coverage.txt ./...
# View coverage report
go tool cover -html=coverage.txt- Use
MaxCellsPerSheetto limit memory usage with large files - Enable
IgnoreEmptyCellsfor sparse spreadsheets - Process files in batches for bulk operations
- Configure appropriate
DebounceDelayto avoid excessive processing - Use specific file extension filters to reduce monitoring overhead
- Exclude temporary files and directories from watching
- Batch multiple file changes into single commits
- Use shallow clones for better performance with large repositories
- Configure appropriate Git user credentials to avoid authentication prompts
GitCells is designed to be extensible:
Implement the Converter interface to add support for other formats:
type CustomConverter struct {
logger *logrus.Logger
}
func (c *CustomConverter) ExcelToJSON(filePath string, options ConvertOptions) (*models.ExcelDocument, error) {
// Custom implementation
}
func (c *CustomConverter) JSONToExcel(doc *models.ExcelDocument, outputPath string, options ConvertOptions) error {
// Custom implementation
}Create specialized file event handlers:
func DatabaseSyncHandler(event watcher.FileEvent) error {
// Sync changes to database
return nil
}
func NotificationHandler(event watcher.FileEvent) error {
// Send notifications about changes
return nil
}While not yet implemented, GitCells is designed to support plugins for:
- Custom file format support
- Additional version control systems
- Integration with external services
- Custom conflict resolution strategies
This API documentation provides a comprehensive overview of GitCells's internal architecture and public interfaces. For more specific implementation details, refer to the source code and inline documentation.