Full Code of mactat/framed for AI

master 4f18db334ce4 cached
33 files
54.8 KB
16.2k tokens
46 symbols
1 requests
Download .txt
Repository: mactat/framed
Branch: master
Commit: 4f18db334ce4
Files: 33
Total size: 54.8 KB

Directory structure:
gitextract__ybk0m0_/

├── .dockerignore
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       ├── pr.yaml
│       └── release.yaml
├── .gitignore
├── .goreleaser.yaml
├── LICENSE
├── Makefile
├── README.md
├── cmd/
│   ├── capture.go
│   ├── create.go
│   ├── import.go
│   ├── root.go
│   ├── verify.go
│   └── visualize.go
├── dockerfiles/
│   ├── dockerfile
│   ├── goreleaser.dockerfile
│   └── test.dockerfile
├── docs/
│   └── framed.tape
├── examples/
│   ├── golang.yaml
│   ├── python.yaml
│   └── structure.yaml
├── framed.go
├── framed.yaml
├── go.mod
├── go.sum
├── pkg/
│   └── ext/
│       ├── decoder.go
│       ├── encoder.go
│       ├── network.go
│       ├── printer.go
│       └── system.go
└── test/
    ├── test.bats
    └── test.yaml

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
README.md
pipelines/
dockerfiles/
docs/
build/
LICENSE
Makefile

================================================
FILE: .github/FUNDING.yml
================================================
github: [mactat]


================================================
FILE: .github/workflows/pr.yaml
================================================
name: pr
on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]
  workflow_dispatch: {}
jobs:
  pr:
    name: "Pull Request Checks"
    runs-on: "ubuntu-latest"
    steps:
      - uses: actions/checkout@v3
      - name: "Framed verify"
        uses: 'mactat/framed-action@v0.0.7'
      - name: "Functional tests"
        run: make test BUILD=true EXPORT=true
        shell: bash
      - name: Test Summary
        uses: test-summary/action@v2
        with:
          paths: "results/test.xml"
        if: always()
      - name: golangci-lint
        uses: golangci/golangci-lint-action@v3
        with:
          version: v1.53

================================================
FILE: .github/workflows/release.yaml
================================================
name: release
on:
  push:
    tags:
      - 'v[0-9]+.[0-9]+.[0-9]+'

permissions:
  contents: write

jobs:

  goreleaser:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - run: git fetch --force --tags
      - name: Log in to Docker Hub
        uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_TOKEN }}
      - uses: actions/setup-go@v4
        with:
          go-version: stable
      - uses: goreleaser/goreleaser-action@v5
        with:
          distribution: goreleaser
          version: latest
          args: release --clean
        env:
          GITHUB_TOKEN: ${{ secrets.GORELEASER_GH_TOKEN }}

================================================
FILE: .gitignore
================================================
build/
.vscode/

================================================
FILE: .goreleaser.yaml
================================================
builds:
  - binary: framed
    goos:
      - darwin
      - linux
      - windows
    goarch:
      - amd64
      - arm64
    env:
      - CGO_ENABLED=0

release:
  prerelease: auto

dockers:
  - dockerfile: "dockerfiles/goreleaser.dockerfile"
    image_templates:
    - "mactat/framed:latest"
    - "mactat/framed:{{ .Tag }}"

universal_binaries:
  - replace: true

brews:
    - name: framed
      homepage: "https://github.com/mactat/framed"
      repository:
        owner: mactat
        name: homebrew-mactat
      commit_author:
        name: mactat
        email: maciektatarsk@gmail.com

checksum:
  name_template: 'checksums.txt'

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 Maciej Tatarski

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: Makefile
================================================
# Definitions
ROOT                    := $(PWD)
GO_VERSION              := 1.20.4
ALPINE_VERSION          := 3.18
OS					  	:= linux
ARCH                    := amd64
BUILD                   := true

.PHONY: version
version:
	$(eval VERSION := $(shell git describe --tags --abbrev=0 2> /dev/null || git rev-parse --short HEAD))
	@echo "Version: $(VERSION)"

.PHONY: build-docker
build-docker:
	docker build \
	-t mactat/framed \
	-f ./dockerfiles/dockerfile \
	--build-arg GO_VERSION=$(GO_VERSION) \
	--build-arg ALPINE_VERSION=$(ALPINE_VERSION) \
	.

.PHONY: release-docker
release-docker: version build-docker
	docker tag mactat/framed mactat/framed:$(VERSION)
	docker tag mactat/framed mactat/framed:alpine-$(ALPINE_VERSION)-$(VERSION)
	docker push mactat/framed:$(VERSION)
	docker push mactat/framed:alpine-$(ALPINE_VERSION)-$(VERSION)
	docker push mactat/framed:latest

.PHONY: build-in-docker
build-in-docker: clean version
	docker run \
		--rm \
		-v $(ROOT):/app \
		--env GOOS=$(OS) \
		--env GOARCH=$(ARCH) \
		golang:$(GO_VERSION)-alpine$(ALPINE_VERSION) \
		/bin/sh -c "cd /app && go build -o ./build/ ./framed.go"
	sudo chown -R $(USER):$(USER) ./build
	cd ./build && \
	tar -zcvf \
	  ./framed-$(OS)-$(ARCH)-$(VERSION).tar.gz \
	  ./framed$(if $(filter $(OS),windows),.exe)

.PHONY: release-lin
release-lin:
	$(MAKE) build-in-docker OS=linux ARCH=amd64

.PHONY: release-win
release-win:
	$(MAKE) build-in-docker OS=windows ARCH=amd64

.PHONY: release-mac
release-mac:
	$(MAKE) build-in-docker OS=darwin ARCH=amd64

.PHONY: build
build:
	go build -o ./build/ ./framed.go

.PHONY: test
test:
	@if [ "$(BUILD)" = "true" ]; then\
        $(MAKE) build-docker;\
    fi
	docker build -f ./dockerfiles/test.dockerfile -t framed-test .
	@if [ "$(EXPORT)" = "true" ]; then\
		mkdir -p ./results;\
		docker run --rm framed-test /bin/sh -c "/test/bats/bin/bats -F junit /test/" > ./results/test.xml;\
	else\
		docker run --rm framed-test /bin/sh -c "/test/bats/bin/bats --pretty /test/";\
	fi
.PHONY: format
format:
	go fmt ./...

.PHONY: lint
lint:
	docker run -t --rm -v $(PWD):/app -w /app golangci/golangci-lint:v1.53.3 golangci-lint run -v

.PHONY: clean
clean:
	rm -f ./build/framed
	rm -f ./build/framed.exe


================================================
FILE: README.md
================================================
<img src="./docs/static/logo-wide.png" style="object-fit: cover; width: 100%;">

[![onTag](https://github.com/mactat/framed/actions/workflows/release.yaml/badge.svg)](https://github.com/mactat/framed/actions/workflows/release.yaml) [![pr](https://github.com/mactat/framed/actions/workflows/pr.yaml/badge.svg?branch=master)](https://github.com/mactat/framed/actions/workflows/pr.yaml) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/mactat/framed) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/mactat/framed) ![Docker Pulls](https://img.shields.io/docker/pulls/mactat/framed) ![Docker Image Version (latest by date)](https://img.shields.io/docker/v/mactat/framed?label=docker-version)  ![GitHub](https://img.shields.io/github/license/mactat/framed) ![GitHub Repo stars](https://img.shields.io/github/stars/mactat/framed?style=social)

# Framed - Files and Directories Reusability, Architecture, and Management

Framed is a CLI tool that simplifies the organization and management of files and directories in a reusable and architectural manner. It provides YAML templates for defining project structures and enables workflows based on those.

To always be in sync with the YAML template, Framed provides a built-in test command that can be used in CI/CD pipelines to verify the project structure.

## Demo

![Demo](./docs/static/demo.gif)

## Features

- **YAML Templates**: Framed uses YAML templates to define the entire project structure.

- **Always in Sync**: Framed provides a built-in test command that can be used in CI/CD pipelines to verify the project structure and ensure that it is always in sync with the YAML template.

- **Consistency Across Projects**: Framed offers a consistent way of organizing files and directories across different projects.

## Example configuration

To get started with Framed, you can use the following example:

```yaml
# Framed Configuration
name: framed

structure:
  name: root
  maxDepth: 5 # Disallow dirs deeper than 5
  files:
    - README.md
    - framed.yaml
    - main.go
    - go.mod
    - go.sum
    - .gitignore
  dirs:
    - name: cmd
      allowedPatterns:
        - ".go"
      forbiddenPatterns:
        - "_test.go" # Disallow tests in /src
    - name: pipelines
      maxCount: 2
      allowedPatterns:
        - ".yml"
        - ".yaml" # only yaml files allowed
      files:
        - pr.yaml
    - name: dockerfiles
      minCount: 1 # At least one file has to be there
      allowChildren: false # Allow subdirectories creation, default true
      allowedPatterns:
        - ".dockerfile"
    - name: docs
      maxCount: 10 # No more than 10 files per dir
      allowedPatterns:
        - ".md"
        - ".txt"
      dirs:
        - name: design
    - name: examples
```

### Project Structure Definition

Framed allows you to define the desired structure of your project using a YAML-based configuration file. The configuration specifies the required files and directories that should exist in the project.

### Root-level Requirements

The structure section defines the files that are required at the root level of the project. These files must be present for the project to be considered valid.

### Nested Structure

The dirs section allows you to define nested directories within the project structure. Each subdirectory can have its own set of required files and directories.

### File Requirements

You can specify file requirements using the files property. It ensures that specific files are present within the designated directory.

### File Patterns

The allowedPatterns property enables you to define file patterns using glob syntax. This allows for more flexible matching of files based on their extensions or naming conventions.

### Forbidden Files

The forbiddenPatterns property lets you specify file patterns that are not allowed within a directory. This can be useful for enforcing certain naming conventions or excluding specific types of files.

### Minimum File Count

The minCount property allows you to set a minimum count for files within a directory. It ensures that a certain number of files must be present in the directory.

### Maximum File Count

The maxCount property allows you to set a maximum count for files within a directory. It limits the number of files that can exist within the directory.

### Allowing Children

The allowChildren property, when set to true, permits the presence of additional directories within a specified directory. This provides flexibility for organizing files and directories within the project.

## Installation

### Brew Installation

1. Open a terminal and run the following command:

   ```shell
   brew tap mactat/mactat
   brew install framed
   ```

### Darwin (macOS) Installation

1. Download the `framed-darwin-amd64-<version>.tar.gz` package from release page.

2. Extract the package by double-clicking on the downloaded file or using a tool like `tar` in your terminal:

   ```shell
   tar -xzf framed-darwin-amd64-<version>.tar.gz
   ```

3. This will extract the `framed` binary.

4. Open a terminal and navigate to the extracted directory:

   ```shell
   cd framed-darwin-amd64-<version>
   ```

5. Make the binary executable by running the following command:

   ```shell
   chmod +x framed
   ```

6. Move the `framed` binary to a directory in your system's `PATH` so that it can be accessed from anywhere. For example:

   ```shell
   sudo mv framed /usr/local/bin/
   ```

7. You can now use the `framed` command to execute the application.

### Linux Installation

1. Download the `framed-linux-amd64-<version>.tar.gz` package from release page.

2. Extract the package using the following command in your terminal:

   ```shell
   tar -xzf framed-linux-amd64-<version>.tar.gz
   ```

3. This will extract the `framed` binary.

4. Open a terminal and navigate to the extracted directory:

   ```shell
   cd framed-linux-amd64-<version>
   ```

5. Make the binary executable by running the following command:

   ```shell
   chmod +x framed
   ```

6. Move the `framed` binary to a directory in your system's `PATH` so that it can be accessed from anywhere. For example:

   ```shell
   sudo mv framed /usr/local/bin/
   ```

7. You can now use the `framed` command to execute the application.

### Windows Installation

1. Download the `framed-windows-amd64-<version>.tar.gz` package from release page.

2. Extract the package using a file extraction tool like 7-Zip or WinRAR.

3. This will extract the `framed.exe` binary.

4. Move the `framed.exe` binary to a directory that is included in your system's `PATH`, such as `C:\Windows` or `C:\Windows\System32`.

5. You can now use the `framed` command to execute the application from the Command Prompt or PowerShell.

**Please note that the exact steps may vary depending on your system configuration.**

## Usage

**Note**: The following commands assume that you have already installed Framed and added it to your system's PATH environment variable.

**Note**: By default template file is `framed.yaml`. You can specify a different template file using the `--template` flag f.e `--template path/to/my-template.yaml`.

### 1. Creating a Project Structure

To create a new project structure using a YAML template, run the following command:

```bash
framed create
```

If you also want to create required files, run the following command:

```bash
framed create --files
```

### 2. Capturing current project structure

To capture the current project structure as a YAML template, run the following command:

```bash
framed capture --output <template-file>
```

### 3. Test Project Structure (CI/CD)

To test the project structure for consistency and compliance with the YAML template, run the following command:

```bash
framed verify
```

For a complete list of available commands and usage examples, refer to the [documentation](link-to-full-docs).

### 4. Visualize Project Structure

To visualize the project structure, run the following command:

```bash
framed visualize
```

### 5. Importing Project Structure

To import the project structure from url, run the following command:

```bash
framed import --url <url>
```

url has to be pointing to a yaml file with valid structure.

To import an example project structure, run the following command:

```bash
framed import --example <example-name>
```

Currently available examples:

- python
- golang

See [examples](./examples) for more details.

## Running from docker

To run framed from docker, run the following command:

```bash
docker run --rm -v $(pwd):/app --user $(id -u):$(id -g) mactat/framed framed <command>
```

example:

```bash
docker run --rm -v $(pwd):/app --user $(id -u):$(id -g) mactat/framed framed import --example python
```

Images can be found on [dockerhub](https://hub.docker.com/r/mactat/framed).


## Github Action

You can use framed as a github action to verify your project structure. Minimal example:

  ```yaml
  name: Verify Project Structure
  on: [push, pull_request]
  jobs:
    verify:
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v2
        - name: Verify Project Structure
          uses: mactat/framed@0.0.7
          with:
            template: './framed.yaml' # Optional, default is framed.yaml
            version: 'v0.0.8'         # Optional, default is v0.0.8
  ```

## TODO

- [ ] Add support from importing part of the structure from url or file like:

  ```yaml
  name: framed

  structure:
    name: root
    dirs:
      - name: other
        template: other.yaml # Use another template for this dir
      - name: another
        template: https://yourfile.com/framed.yaml # Share templates between projects
  ```

- [x] Add some tests
- [ ] Add contributing guidelines
- [ ] Add more examples
- [x] Create a github action for running tests
- [ ] Move remaining business logic to a separate package


================================================
FILE: cmd/capture.go
================================================
/*
Copyright © 2023 Maciej Tatarski maciektatarski@gmail.com
*/

// Package cmd represents the command line interface of the application
package cmd

import (
	"fmt"

	"framed/pkg/ext"
	"os"
	"strconv"

	"github.com/spf13/cobra"
)

// captureCmd represents the capture command
var captureCmd = &cobra.Command{
	Use:   "capture",
	Short: "Capture the current project structure as a YAML template",
	Long: `This command is capturing the current project structure as a YAML template.

Example:
framed capture --output ./framed.yaml --name my-project
`,
	Run: func(cmd *cobra.Command, args []string) {
		output := cmd.Flag("output").Value.String()
		name := cmd.Flag("name").Value.String()
		depthStr := cmd.Flag("depth").Value.String()
		depth, err := strconv.Atoi(depthStr)
		if err != nil {
			ext.PrintOut("🚨 Invalid depth value: ", depthStr)
			os.Exit(1)
		}
		ext.PrintOut("📝 Name:", name+"\n")

		// capture subdirectories
		subdirs := ext.CaptureSubDirs(".", depth)
		ext.PrintOut("📂 Directories:", fmt.Sprintf("%v", len(subdirs)))

		// capture files
		files := ext.CaptureAllFiles(".", depth)
		ext.PrintOut("📄 Files:", fmt.Sprintf("%v", len(files)))

		// capture patterns
		patterns := ext.CaptureRequiredPatterns(".", depth)
		ext.PrintOut("🔁 Patterns:", fmt.Sprintf("%v", len(patterns)))

		// export config
		ext.ExportConfig(name, output, subdirs, files, patterns)
		ext.PrintOut("\n✅ Exported to file: ", output)
	},
}

func init() {
	rootCmd.AddCommand(captureCmd)

	captureCmd.PersistentFlags().String("output", "./framed.yaml", "path to output file")

	captureCmd.PersistentFlags().String("name", "default", "name of the project")

	captureCmd.PersistentFlags().String("depth", "-1", "depth of the directory tree to capture")
}


================================================
FILE: cmd/create.go
================================================
/*
Copyright © 2023 Maciej Tatarski maciektatarski@gmail.com
*/

// Package cmd represents the command line interface of the application
package cmd

import (
	"framed/pkg/ext"

	"github.com/spf13/cobra"
)

// createCmd represents the create command
var createCmd = &cobra.Command{
	Use:   "create",
	Short: "Create a new project structure using a YAML template",
	Long: `This command is creating a new project structure from a YAML template.

Example:
framed create --template ./framed.yaml --files true
	`,
	Run: func(cmd *cobra.Command, args []string) {
		path := cmd.Flag("template").Value.String()
		createFiles := cmd.Flag("files").Value.String() == "true"

		// read config
		_, dirList := ext.ReadConfig(path)

		// create directories
		ext.CreateAllDirs(dirList)

		// create files
		if createFiles {
			ext.CreateAllFiles(dirList)
		}
	},
}

func init() {
	rootCmd.AddCommand(createCmd)

	createCmd.PersistentFlags().String("template", "./framed.yaml", "path to template file default")

	createCmd.PersistentFlags().Bool("files", false, "create required files")
}


================================================
FILE: cmd/import.go
================================================
/*
Copyright © 2023 Maciej Tatarski maciektatarski@gmail.com
*/

// Package cmd represents the command line interface of the application
package cmd

import (
	"framed/pkg/ext"
	"os"

	"github.com/spf13/cobra"
)

// importCmd represents the import command
var importCmd = &cobra.Command{
	Use:   "import",
	Short: "Import the project structure",
	Long: `This command is importing the project structure from a YAML template. It can be imported from a template or from a remote URL.
Example:
framed import https://raw.githubusercontent.com/username/repo/master/framed.yaml
or
framed import --example python --output ./python.yaml
`,
	Run: func(cmd *cobra.Command, args []string) {
		url := cmd.Flag("url").Value.String()
		example := cmd.Flag("example").Value.String()
		output := cmd.Flag("output").Value.String()

		if url != "" {
			err := ext.ImportFromUrl(output, url)
			if err != nil {
				ext.PrintOut("☠️ Failed to import from url ==>", url)
				os.Exit(1)
			}
		}

		if example != "" {
			err := ext.ImportFromUrl(output, ext.ExampleToUrl(example))
			if err != nil {
				ext.PrintOut("☠️ Failed to import from example ==>", example)
				os.Exit(1)
			}
		}

		ext.PrintOut("✅ Saved to ==>", output)

		// try to load
		configTree, _ := ext.ReadConfig(output)
		ext.PrintOut("✅ Imported successfully ==>", configTree.Name)

	},
}

func init() {
	rootCmd.AddCommand(importCmd)

	// Here you will define your flags and configuration settings.
	importCmd.PersistentFlags().String("url", "", "url to template file")
	importCmd.PersistentFlags().String("example", "", "example template file from github")
	importCmd.MarkFlagsMutuallyExclusive("url", "example")

	// path to file
	importCmd.PersistentFlags().String("output", "./framed.yaml", "path to template file")
}


================================================
FILE: cmd/root.go
================================================
/*
Copyright © 2023 Maciej Tatarski maciektatarski@gmail.com
*/

// Package cmd represents the command line interface of the application
package cmd

import (
	"os"

	"github.com/spf13/cobra"
)

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "framed",
	Short: "CLI tool for managing folder and files structures",
	Long: `FRAMED (Files and Directories Reusability, Architecture, and Management)
is a powerful CLI tool written in Go that simplifies the organization and management
of files and directories in a reusable and architectural manner. It provides YAML
templates for defining project structures and ensures that your projects adhere to 
the defined structure, enabling consistency and reusability.

	`,
}

func Execute() {
	err := rootCmd.Execute()
	if err != nil {
		os.Exit(1)
	}
}

func init() {}


================================================
FILE: cmd/verify.go
================================================
/*
Copyright © 2023 Maciej Tatarski maciektatarski@gmail.com
*/

// Package cmd represents the command line interface of the application
package cmd

import (
	"fmt"
	"framed/pkg/ext"
	"os"

	"github.com/spf13/cobra"
)

// testCmd represents the test command
var testCmd = &cobra.Command{
	Use:   "verify",
	Short: "Verify the project rules and structure",
	Long: `This command is verifying the project structure for consistency and compliance with the YAML template.
	
Example:
framed verify --template ./framed.yaml
`,
	Run: func(cmd *cobra.Command, args []string) {
		path := cmd.Flag("template").Value.String()
		// read config
		_, dirList := ext.ReadConfig(path)

		allGood := true
		// verify directories
		for _, dir := range dirList {
			if !ext.DirExists(dir.Path) {
				ext.PrintOut("❌ Directory not found ==>", dir.Path)
				allGood = false
			}

			// verify files
			if dir.Files != nil {
				ext.VerifyFiles(dir, &allGood)
			}

			// verify minCount
			numFiles := ext.CountFiles(dir.Path)
			if numFiles < dir.MinCount {
				ext.PrintOut("❌ Min count ("+fmt.Sprint(dir.MinCount)+") not met ==>", dir.Path)
				allGood = false
			}

			// verify maxCount
			if numFiles > dir.MaxCount {
				ext.PrintOut("❌ Max count ("+fmt.Sprint(dir.MaxCount)+") exceeded ==>", dir.Path)
				allGood = false
			}

			// verify childrenAllowed
			if !dir.AllowChildren {
				if ext.HasDirs(dir.Path) {
					ext.PrintOut("❌ Children not allowed ==>", dir.Path)
					allGood = false
				}
			}

			// verify maxDepth
			if ext.CheckDepth(dir.Path) > dir.MaxDepth {
				ext.PrintOut("❌ Max depth exceeded ("+fmt.Sprint(dir.MaxDepth)+") ==>", dir.Path)
				allGood = false
			}

			// Verify forbidden
			if dir.ForbiddenPatterns != nil {
				ext.VerifyForbiddenPatterns(dir, &allGood)
			}

			// Verify allowed patterns
			if dir.AllowedPatterns != nil {
				ext.VerifyAllowedPatterns(dir, &allGood)
			}
		}

		if allGood {
			fmt.Println("✅ Verified successfully!")
		} else {
			os.Exit(1)
		}
	},
}

func init() {
	rootCmd.AddCommand(testCmd)

	testCmd.PersistentFlags().String("template", "./framed.yaml", "path to template file")
}


================================================
FILE: cmd/visualize.go
================================================
/*
Copyright © 2023 Maciej Tatarski maciektatarski@gmail.com
*/

// Package cmd represents the command line interface of the application
package cmd

import (
	"framed/pkg/ext"

	"github.com/spf13/cobra"
)

// visualizeCmd represents the visualize command
var visualizeCmd = &cobra.Command{
	Use:   "visualize",
	Short: "Visualize the project structure",
	Long: `This command is visualizing the project structure from a YAML template.

Example:
framed visualize --template ./framed.yaml`,
	Run: func(cmd *cobra.Command, args []string) {
		path := cmd.Flag("template").Value.String()

		// read config
		_, dirList := ext.ReadConfig(path)

		// visualize template
		ext.VisualizeTemplate(dirList)
	},
}

func init() {
	rootCmd.AddCommand(visualizeCmd)

	visualizeCmd.PersistentFlags().String("template", "./framed.yaml", "Path to template file default is ./framed.yaml")
}


================================================
FILE: dockerfiles/dockerfile
================================================
ARG GO_VERSION=1.20.4
ARG ALPINE_VERSION=3.18

FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} as builder

WORKDIR /app
COPY go.mod go.sum /app/
RUN go mod download
COPY framed.go /app/
COPY cmd /app/cmd
COPY pkg /app/pkg
RUN go build -o framed /app/framed.go

FROM alpine:${ALPINE_VERSION} as release

COPY --from=builder /app/framed /bin/framed

WORKDIR /app
CMD ["/bin/sh"]

================================================
FILE: dockerfiles/goreleaser.dockerfile
================================================
ARG ALPINE_VERSION=3.18

FROM alpine:${ALPINE_VERSION} as release
COPY framed /bin/framed
CMD [ "/bin/sh" ]

================================================
FILE: dockerfiles/test.dockerfile
================================================
FROM mactat/framed:latest as builder

RUN apk add --no-cache git bash sudo yq ncurses

ENV TERM=linux

# clone bats
RUN git clone https://github.com/bats-core/bats-core.git /test/bats
RUN git clone https://github.com/bats-core/bats-support.git /test/test_helper/bats-support
RUN git clone https://github.com/bats-core/bats-assert.git /test/test_helper/bats-assert
RUN git clone https://github.com/bats-core/bats-file.git /test/test_helper/bats-file

COPY /test/test.bats /test/test.yaml /test/




================================================
FILE: docs/framed.tape
================================================
Output ./docs/static/demo.gif

# Settings

Set FontSize 20
Set Width 1200
Set Height 600

# Requirements

Require framed

# Setup

Hide
Type "mkdir -p /tmp/framed-demo"
Enter
Type "cd /tmp/framed-demo"
Enter
Ctrl+L
Show

# Flow

Sleep 1s
Type "echo 'Welcome to Framed!'"
Enter
Sleep 4s
Ctrl+L

Sleep 1s
Type "tree"
Enter
Sleep 4s
Ctrl+L

Sleep 1s
Type "framed import --example python"
Enter
Sleep 4s
Ctrl+L

Sleep 1s
Type "tree"
Enter
Sleep 4s
Ctrl+L

Sleep 1s
Type "cat framed.yaml"
Enter
Sleep 6s
Ctrl+L

Sleep 1s
Type "framed verify"
Enter
Sleep 6s
Ctrl+L

Sleep 1s
Type "framed create --files"
Enter
Sleep 6s
Ctrl+L

Sleep 1s
Type "framed verify"
Enter
Sleep 4s
Ctrl+L

Sleep 1s
Type "tree"
Enter
Sleep 5s
Ctrl+L

Sleep 1s
Type "rm setup.py"
Enter
Sleep 1s
Type "framed verify"
Enter
Sleep 6s
Ctrl+L

# Clean
Hide
Type "cd -"
Enter
Type "rm -r /tmp/framed-demo"
Enter

================================================
FILE: examples/golang.yaml
================================================
# FRAMED Configuration
name: golang_example

structure:
    name: root
    files:
        - README.md
        - framed.yaml
        - go.mod
        - go.sum
        - LICENSE.md
        - .gitignore
        - Makefile
    dirs:
        - name: cmd
          minCount: 1
          maxCount: 10
          allowedPatterns:
              - ".go"
        - name: .github
          dirs:
              - name: workflows
                maxCount: 4
                allowedPatterns:
                    - ".yml"
                    - ".yaml"
                files:
                    - pr.yaml
                    - release.yaml
        - name: dockerfiles
          minCount: 1
          allowChildren: false
          allowedPatterns:
              - "dockerfile"
              - "Dockerfile"
        - name: docs
          maxCount: 10 # No more than 10 files per dir
          allowedPatterns:
              - ".md"
              - ".txt"
        - name: pkg
          allowedPatterns:
              - ".go"
        - name: examples
        - name: build


================================================
FILE: examples/python.yaml
================================================
# FRAMED Configuration
name: python_example

structure:
  name: root
  files:
    - README.md
    - setup.py
    - .gitignore
  dirs:
    - name: docs
    - name: tests
      files:
        - __init__.py
    - name: package_name
      files:
        - __init__.py
      allowedPatterns:
        - .py


================================================
FILE: examples/structure.yaml
================================================
# FRAMED Configuration
name: structure

structure:
  name: root
  dirs:
    - name: src
      dirs:
        - name: this
          dirs:
            - name: is
              files:
                - test.yaml
            - name: test
    - name: test
    - name: docs
    - name: dockerfiles
    - name: examples


================================================
FILE: framed.go
================================================
/*
Copyright © 2023 Maciej Tatarski maciektatarski@gmail.com
*/
package main

import "framed/cmd"

func main() {
	cmd.Execute()
}


================================================
FILE: framed.yaml
================================================
# FRAMED Configuration
name: framed

structure:
    name: root
    maxDepth: 5 # Disallow dirs deeper than 5
    files:
        - README.md
        - framed.yaml
        - framed.go
        - go.mod
        - go.sum
        - .gitignore
    dirs:
        - name: cmd
          allowedPatterns:
              - ".go"
          forbiddenPatterns:
              - "_test.go" # Disallow tests in /cmd
        - name: pkg
          dirs:
              - name: ext
                allowedPatterns:
                    - ".go"
        - name: .github
          dirs:
              - name: workflows
                maxCount: 4
                allowedPatterns:
                    - ".yml"
                    - ".yaml" # only yaml files allowed
                files:
                    - pr.yaml
                    - release.yaml
        - name: dockerfiles
          minCount: 1 # At least one file has to be there
          allowChildren: false # Allow subdirectories creation, default true
          allowedPatterns:
              - "dockerfile"
        - name: docs
          maxCount: 10 # No more than 10 files per dir
          allowedPatterns:
              - ".md"
              - ".txt"
        - name: examples


================================================
FILE: go.mod
================================================
module framed

go 1.20

require (
	github.com/TwiN/go-color v1.4.0
	github.com/creasty/defaults v1.7.0
	github.com/spf13/cobra v1.7.0
	gopkg.in/yaml.v3 v3.0.1
)

require (
	github.com/inconshreveable/mousetrap v1.1.0 // indirect
	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
	github.com/spf13/pflag v1.0.5 // indirect
	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
)


================================================
FILE: go.sum
================================================
github.com/TwiN/go-color v1.4.0 h1:fNbOwOrvup5oj934UragnW0B1WKaAkkB85q19Y7h4ng=
github.com/TwiN/go-color v1.4.0/go.mod h1:0QTVEPlu+AoCyTrho7bXbVkrCkVpdQr7YF7PYWEtSxM=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: pkg/ext/decoder.go
================================================
package ext

import (
	"fmt"
	"os"

	"github.com/creasty/defaults"
	"gopkg.in/yaml.v3"
)

// SingleDir struct
type SingleDir struct {
	Name              string       `yaml:"name"`
	Path              string       `yaml:"path"`
	Files             *[]string    `yaml:"files"`
	Dirs              *[]SingleDir `yaml:"dirs"`
	AllowedPatterns   *[]string    `yaml:"allowedPatterns"`
	ForbiddenPatterns *[]string    `yaml:"forbiddenPatterns"`
	MinCount          int          `default:"0" yaml:"minCount"`
	MaxCount          int          `default:"1000" yaml:"maxCount"`
	MaxDepth          int          `default:"1000" yaml:"maxDepth"`
	AllowChildren     bool         `default:"true" yaml:"allowChildren"`
}

// UnmarshalYAML implements yaml.Unmarshaler interface
// Meant for initializing default values
func (s *SingleDir) UnmarshalYAML(unmarshal func(interface{}) error) error {
	err := defaults.Set(s)
	if err != nil {
		fmt.Println("Cannot set defaults!")
		os.Exit(1)
	}

	type plain SingleDir
	if err := unmarshal((*plain)(s)); err != nil {
		return err
	}

	return nil
}

type config struct {
	Name      string     `yaml:"name"`
	Structure *SingleDir `yaml:"structure"`
}

func ReadConfig(path string) (config, []SingleDir) {
	yamlFile, err := os.ReadFile(path)
	if err != nil {
		// add emoji
		PrintOut("☠️ Can not read file ==>", path)
		os.Exit(1)
	}
	PrintOut("✅ Loaded template from  ==>", path)
	// Map to store the parsed YAML data
	var curConfig config

	// Unmarshal the YAML string into the data map
	err = yaml.Unmarshal([]byte(yamlFile), &curConfig)
	if err != nil {
		PrintOut("☠️ Can not decode file ==>", path)
		os.Exit(1)
	}

	if curConfig.Structure == nil {
		PrintOut("☠️ Can not find correct structure in ==>", path)
		os.Exit(1)
	} else {
		PrintOut("✅ Read structure for ==>", curConfig.Name)
	}

	dirList := []SingleDir{}
	traverseStructure(curConfig.Structure, ".", &dirList)
	return curConfig, dirList
}

func traverseStructure(dir *SingleDir, path string, dirsList *[]SingleDir) {
	// Change path
	if dir == nil {
		PrintOut("☠️  Can't traverse nil dir ==>", path)
		os.Exit(1)
	}
	dir.Path = path

	// add current dir to dirsList
	*dirsList = append(*dirsList, *dir)

	if dir.Dirs == nil {
		return
	}
	// traverse children
	for _, child := range *dir.Dirs {
		traverseStructure(&child, path+"/"+child.Name, dirsList)
	}
}


================================================
FILE: pkg/ext/encoder.go
================================================
package ext

import (
	"fmt"
	"os"
	"strings"

	"gopkg.in/yaml.v3"
)

// Consider implementing a custom Unmarshaler for SingleDirOut
type SingleDirOut struct {
	Name              string          `yaml:"name"`
	Path              string          `yaml:"-"`
	Files             *[]string       `yaml:"files,omitempty"`
	Dirs              *[]SingleDirOut `yaml:"dirs,omitempty"`
	AllowedPatterns   *[]string       `yaml:"allowedPatterns,omitempty"`
	ForbiddenPatterns *[]string       `yaml:"forbiddenPatterns,omitempty"`
	MinCount          int             `yaml:"minCount,omitempty"`
	MaxCount          int             `yaml:"maxCount,omitempty"`
	MaxDepth          int             `yaml:"maxDepth,omitempty"`
	AllowChildren     bool            `yaml:"allowChildren,omitempty"`
}

type configOut struct {
	Name      string        `yaml:"name"`
	Structure *SingleDirOut `yaml:"structure"`
}

func ExportConfig(name string, path string, subdirs []string, files []string, patterns map[string]string) {
	// create config, files and dirs are empty
	config := configOut{
		Name: name,
		Structure: &SingleDirOut{
			Name:  "root",
			Files: &[]string{},
			Dirs:  &[]SingleDirOut{},
		},
	}
	// add subdirs
	insertSubdirs(config.Structure.Dirs, subdirs)

	// add files
	insertFiles(config.Structure, files)

	// add patterns
	insertPatterns(config.Structure, patterns)

	// export config
	yamlFile, err := yaml.Marshal(config)
	if err != nil {
		fmt.Printf("Error while Marshaling. %v", err)
	}

	// Save to file
	err = os.WriteFile(path, yamlFile, 0644)
	if err != nil {
		fmt.Printf("Error while writing file. %v", err)
	}
}

func insertSubdirs(dirs *[]SingleDirOut, subdirs []string) {
	for subdir := range subdirs {
		insertSingleDir(dirs, subdirs[subdir])
	}
}

func insertSingleDir(dirs *[]SingleDirOut, dir string) {
	subdirPath := strings.Split(dir, "/")
	curDirs := dirs
	for i := range subdirPath {
		if !containsDir(*curDirs, subdirPath[i]) {
			*curDirs = append(*curDirs, SingleDirOut{
				Name:            subdirPath[i],
				Files:           &[]string{},
				Dirs:            &[]SingleDirOut{},
				AllowedPatterns: &[]string{},
			})
		}
		// go deeper
		curDirs = getDir(*curDirs, subdirPath[i]).Dirs
	}
}

func containsDir(dirs []SingleDirOut, name string) bool {
	for _, dir := range dirs {
		if dir.Name == name {
			return true
		}
	}
	return false
}

func getDir(dirs []SingleDirOut, name string) *SingleDirOut {
	for _, dir := range dirs {
		if dir.Name == name {
			return &dir
		}
	}
	return nil
}

func insertFiles(root *SingleDirOut, files []string) {
	for file := range files {
		insertSingleFile(root, files[file])
	}
}

func insertSingleFile(root *SingleDirOut, file string) {
	subdirPath := strings.Split(file, "/")

	if len(subdirPath) == 1 {
		*root.Files = append(*root.Files, subdirPath[0])
		return
	}
	curDirs := root.Dirs
	curDir := root
	for i := 0; i < len(subdirPath)-1; i++ {
		curDir = getDir(*curDirs, subdirPath[i])
		curDirs = curDir.Dirs
	}
	*curDir.Files = append(*curDir.Files, subdirPath[len(subdirPath)-1])

}

func insertPatterns(root *SingleDirOut, patterns map[string]string) {
	for dir, pattern := range patterns {
		insertSinglePattern(root, dir, pattern)
	}
}

func insertSinglePattern(root *SingleDirOut, dir string, pattern string) {
	subdirPath := strings.Split(dir, "/")
	curDirs := root.Dirs
	curDir := root
	for i := range subdirPath {
		// go deeper
		curDir = getDir(*curDirs, subdirPath[i])
		curDirs = curDir.Dirs
	}
	// insert pattern
	*curDir.AllowedPatterns = append(*curDir.AllowedPatterns, pattern)
}


================================================
FILE: pkg/ext/network.go
================================================
package ext

import (
	"io"
	"net/http"
	"os"
)

func ExampleToUrl(example string) string {
	return "https://raw.githubusercontent.com/mactat/framed/master/examples/" + example + ".yaml"
}

func ImportFromUrl(path string, url string) error {
	// Get the data
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// Create the file
	out, err := os.Create(path)
	if err != nil {
		return err
	}
	defer out.Close()

	// Write the body to file
	_, err = io.Copy(out, resp.Body)
	return err
}


================================================
FILE: pkg/ext/printer.go
================================================
package ext

import (
	"fmt"
	"os"
	"strings"

	"github.com/TwiN/go-color"
)

func PrintOut(prompt string, text string) {
	fmt.Printf("%-35s %-35s\n", prompt, text)
}

// This is ugly but it works, it needs to be refactored. It also has some bugs in case of out of order directories.
func VisualizeTemplate(template []SingleDir) {
	for dirNum, dir := range template {
		connectorDir := "├──"
		initString := ""
		depth := strings.Count(dir.Path, string(os.PathSeparator)) + 1
		name := strings.Split(dir.Path, string(os.PathSeparator))[depth-1]

		dirDepth := depth
		if depth <= 2 {
			dirDepth = 1
		} else {
			initString = "│"
		}

		if dirNum == len(template)-1 {
			connectorDir = "└──"
		}

		printDirectory(initString, dirDepth, connectorDir, name)

		if dir.Files == nil {
			continue
		}

		for num, file := range *dir.Files {
			connector := "├──"
			if depth > 1 {
				initString = "│"
			}

			if num == len(*dir.Files)-1 {
				connector = "└──"
			}

			printFile(initString, depth, connector, file)
		}
	}
}

func printDirectory(initString string, dirDepth int, connectorDir string, name string) {
	output := initString + strings.Repeat("    ", dirDepth-1) + connectorDir + " 📂 " + color.Ize(color.Blue, name)
	println(output)
}

func printFile(initString string, depth int, connector string, file string) {
	output := initString + strings.Repeat("    ", depth-1) + connector + " 📄 " + color.Ize(color.Green, file)
	println(output)
}


================================================
FILE: pkg/ext/system.go
================================================
package ext

import (
	"errors"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

func CreateDir(path string) {
	// Create directory
	if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
		err := os.Mkdir(path, os.ModePerm)
		fmt.Printf("%-35s %-35s\n", "📂 Creating directory ==> ", path)
		if err != nil {
			log.Println(err)
		}
	}

}

func CreateAllDirs(dirList []SingleDir) {
	for _, dir := range dirList {
		CreateDir(dir.Path)
	}
}

func CreateFile(path string, name string) {
	// Check if file exists
	if _, err := os.Stat(path + "/" + name); errors.Is(err, os.ErrNotExist) {
		// Create file
		fmt.Printf("%-35s %-35s\n", "📄 Creating file      ==> ", path+"/"+name)
		file, err := os.Create(path + "/" + name)
		if err != nil {
			log.Println(err)
		}
		defer file.Close()
	}
}

func CreateAllFiles(dirList []SingleDir) {
	for _, dir := range dirList {
		if dir.Files == nil {
			continue
		}
		for _, file := range *dir.Files {
			CreateFile(dir.Path, file)
		}
	}
}

// Check if directory exists on given path and is type dir
func DirExists(path string) bool {
	if path == "." {
		return true
	}
	info, err := os.Stat(path)
	if os.IsNotExist(err) {
		return false
	}
	return info.IsDir()
}

// Check if file exists on given path and is type file
func FileExists(path string) bool {
	info, err := os.Stat(path)
	if os.IsNotExist(err) {
		return false
	}
	return !info.IsDir()
}

// Count files in given directory
func CountFiles(path string) int {
	files, err := os.ReadDir(path)
	if err != nil {
		log.Fatal(err)
	}
	filesCount := 0
	for _, file := range files {
		if !file.IsDir() {
			filesCount++
		}
	}
	return filesCount
}

func HasDirs(path string) bool {
	files, err := os.ReadDir(path)
	if err != nil {
		log.Fatal(err)
	}
	for _, file := range files {
		if file.IsDir() {
			return true
		}
	}
	return false
}

// Check depth of folder tree, exclude .git folder
func CheckDepth(path string) int {
	maxDepth := 0
	var depth int
	err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if info.IsDir() && info.Name() == ".git" {
			return filepath.SkipDir
		} else if info.IsDir() {
			depth = strings.Count(path, string(os.PathSeparator)) + 1
			if depth > maxDepth {
				maxDepth = depth
			}
		}
		return nil
	})
	if err != nil {
		log.Println(err)
	}
	return maxDepth
}

func MatchPatternInDir(path string, pattern string) []string {
	if pattern == "" {
		pattern = ".*"
	}
	// List all files in directory
	files, err := os.ReadDir(path)
	if err != nil {
		log.Fatal(err)
	}
	matched := []string{}
	for _, file := range files {
		if !file.IsDir() {
			match, err := regexp.MatchString(pattern, file.Name())
			if err != nil {
				log.Fatal(err)
			}
			if match {
				matched = append(matched, file.Name())
			}
		}
	}
	return matched
}

// Capture all subdirectories in given directory
func CaptureSubDirs(path string, depth int) []string {
	var dirs []string
	err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if info.IsDir() && info.Name() == ".git" {
			return filepath.SkipDir
		} else if info.IsDir() && depth > 0 && strings.Count(path, string(os.PathSeparator)) >= depth {
			return filepath.SkipDir
		} else if info.IsDir() && info.Name() != "." {
			dirs = append(dirs, path)
		}
		return nil
	})
	if err != nil {
		fmt.Printf("Cannot traverse dirs!")
		os.Exit(1)
	}
	return dirs
}

// Capture all files in given directory
func CaptureAllFiles(path string, depth int) []string {
	var files []string
	err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if info.IsDir() && info.Name() == ".git" {
			return filepath.SkipDir
		} else if !info.IsDir() && depth > 0 && strings.Count(path, string(os.PathSeparator)) >= depth {
			return filepath.SkipDir
		} else if !info.IsDir() {
			files = append(files, path)
		}
		return nil
	})
	if err != nil {
		fmt.Printf("Cannot traverse dirs!")
		os.Exit(1)
	}
	return files
}

// Capture rules for files with same extension in given directory. If all files in subdirectory have the same extension, save the extension to map with directory path as key.
// It should return map path -> extension
func CaptureRequiredPatterns(path string, depth int) map[string]string {
	var rules = make(map[string]string)
	var dirs []string
	err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if info.IsDir() && info.Name() == ".git" {
			return filepath.SkipDir
		} else if info.IsDir() && depth > 0 && strings.Count(path, string(os.PathSeparator)) >= depth {
			return filepath.SkipDir
		} else if info.IsDir() && info.Name() != "." {
			dirs = append(dirs, path)
		}
		return nil
	})
	if err != nil {
		fmt.Printf("Cannot traverse dirs!")
		os.Exit(1)
	}
	// Check files in dir, if all extensions are the same, save extension to map with dir path as key
	for _, dir := range dirs {
		files, err := os.ReadDir(dir)
		if err != nil {
			log.Fatal(err)
		}
		ext := ""
		for _, file := range files {
			if !file.IsDir() {
				extension := filepath.Ext(file.Name())
				if ext == "" {
					ext = extension
				} else if ext != extension {
					ext = ""
					break
				}
			}
		}
		if ext != "" {
			rules[dir] = ext
		}
	}
	return rules
}

func VerifyFiles(dir SingleDir, allGood *bool) {
	for _, file := range *dir.Files {
		if !FileExists(dir.Path + "/" + file) {
			PrintOut("❌ File not found      ==>", dir.Path+"/"+file)
			*allGood = false
		}
	}
}
func VerifyForbiddenPatterns(dir SingleDir, allGood *bool) {
	for _, pattern := range *dir.ForbiddenPatterns {
		matched := MatchPatternInDir(dir.Path, pattern)
		for _, match := range matched {
			PrintOut("❌ Forbidden pattern ("+pattern+") matched under ==>", dir.Path+"/"+match)
			*allGood = false
		}
	}
}

func VerifyAllowedPatterns(dir SingleDir, allGood *bool) {
	matchedCount := 0
	for _, pattern := range *dir.AllowedPatterns {
		matched := MatchPatternInDir(dir.Path, pattern)
		matchedCount += len(matched)
	}
	if matchedCount != CountFiles(dir.Path) && len(*dir.AllowedPatterns) > 0 {
		patternsString := strings.Join(*dir.AllowedPatterns, " ")
		PrintOut("❌ Not all files match required pattern ("+patternsString+") under ==>", dir.Path)
		*allGood = false
	}
}


================================================
FILE: test/test.bats
================================================
setup() {
    # Load
    load 'test_helper/bats-support/load'
    load 'test_helper/bats-assert/load'
    load 'test_helper/bats-file/load'

    # Vars
    TMP_DIR="/tmp/framed-test"
    VALID_URL="https://raw.githubusercontent.com/mactat/framed/master/examples/python.yaml"
    INVALID_URL="https://raw.githubusercontent.com/mactat/framed/master/examples/invalid.yaml"
    DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )"

    # Commands
    mkdir -p $TMP_DIR
    cd $TMP_DIR
    all_commands=(import visualize create capture verify)
    commands_with_template=(visualize create verify)
}

teardown() {
    sudo rm -R $TMP_DIR
}

### Basic

@test "Run framed" {
    run framed --help
    assert_success
    assert_output --partial 'FRAMED (Files and Directories Reusability, Architecture, and Management)'
}


### Import

@test "Import from valid example" {

    run framed import --example python
    assert_success
    assert_output --partial '✅ Imported successfully'
    assert_file_exists "$TMP_DIR/framed.yaml"

    # assert that content is correct
    run cat $TMP_DIR/framed.yaml
    assert_output --partial 'name: python_example'
}

@test "Import from invalid example" {
    run framed import --example invalid
    assert_failure
    assert_output --partial '☠️ Can not find correct structure'
}

@test "Import from valid url" {
    run framed import --url $VALID_URL
    assert_success
    assert_output --partial '✅ Imported successfully'
    assert_file_exists "$TMP_DIR/framed.yaml"

    # assert that content is correct
    run cat $TMP_DIR/framed.yaml
    assert_output --partial 'name: python_example'
}

@test "Import from invalid url" {
    run framed import --url $INVALID_URL
    assert_failure
    assert_output --partial '☠️ Can not find correct structure'
}

@test "Import with specified output" {
    run framed import --example python --output $TMP_DIR/test.yaml
    assert_success
    assert_output --partial '✅ Imported successfully'
    assert_file_exists "$TMP_DIR/test.yaml"

    # assert that content is correct
    run cat $TMP_DIR/test.yaml
    assert_output --partial 'name: python_example'
}


### Visualize

@test "Visualize show correct structure" {
    # setup
    cp $DIR/test.yaml $TMP_DIR/framed.yaml
    assert_file_exists "$(pwd)/framed.yaml"

    # test
    run framed visualize
    assert_success
    assert_output --partial '✅ Read structure'
    assert_output --partial 'test1'
    assert_output --partial 'test2'
    assert_output --partial 'test3'
    assert_output --partial 'test4'
}


### Create

@test "Create creates correct dirs" {
    # setup
    cp $DIR/test.yaml $TMP_DIR/framed.yaml
    assert_file_exists "$(pwd)/framed.yaml"

    # test
    run framed create
    assert_success
    assert_output --partial '✅ Read structure'
    assert_output --partial '📂 Creating directory'
    assert_dir_exists "$(pwd)/test2"
    assert_dir_exists "$(pwd)/test3"
}

@test "Create creates correct files in dirs" {
    # setup
    cp $DIR/test.yaml $TMP_DIR/framed.yaml
    assert_file_exists "$(pwd)/framed.yaml"

    # test
    run framed create --files
    assert_success
    assert_output --partial '✅ Read structure'
    assert_output --partial '📂 Creating directory'
    assert_output --partial '📄 Creating file'
    assert_dir_exists "$(pwd)/test2"
    assert_dir_exists "$(pwd)/test3"
    assert_file_exists "$(pwd)/test1.md"
    assert_file_exists "$(pwd)/test3/test4.md"
}

@test "Validate validates correct structure" {
    # setup
    cp $DIR/test.yaml $TMP_DIR/framed.yaml
    assert_file_exists "$(pwd)/framed.yaml"
    run framed create --files
    assert_success

    # test
    run framed verify
    assert_success
    assert_output --partial '✅ Read structure'
    assert_output --partial '✅ Verified successfully!'
}


### Validate

@test "Verify should spot missing file" {
    # setup
    cp $DIR/test.yaml $TMP_DIR/framed.yaml
    assert_file_exists "$(pwd)/framed.yaml"
    run framed create --files
    assert_success
    rm "$(pwd)/test1.md"

    # test
    run framed verify
    assert_failure
    assert_output --partial '❌ File not found'
    assert_output --partial 'test1.md'
}

@test "Verify should spot missing dir" {
    # setup
    cp $DIR/test.yaml $TMP_DIR/framed.yaml
    assert_file_exists "$(pwd)/framed.yaml"
    run framed create --files
    assert_success
    rm -R "$(pwd)/test2"

    # test
    run framed verify
    assert_failure
    assert_output --partial '❌ Directory not found'
    assert_output --partial 'test2'
}

@test "Verify should spot missing file and dir" {
    # setup
    cp $DIR/test.yaml $TMP_DIR/framed.yaml
    assert_file_exists "$(pwd)/framed.yaml"
    run framed create --files
    assert_success
    rm "$(pwd)/test1.md"
    rm -R "$(pwd)/test2"

    # test
    run framed verify
    assert_failure
    assert_output --partial '❌ File not found'
    assert_output --partial 'test1.md'
    assert_output --partial '❌ Directory not found'
    assert_output --partial 'test2'
}

@test "Verify should spot wrong pattern if allowedPatterns is set" {
    # setup
    cp $DIR/test.yaml $TMP_DIR/framed.yaml
    assert_file_exists "$(pwd)/framed.yaml"
    run framed create --files
    assert_success

    yq -i '.structure.dirs[0].allowedPatterns[0] = "md"' "$(pwd)/framed.yaml"
    touch "$(pwd)/test2/test3.txt"

    # test
    run framed verify
    assert_failure
    assert_output --partial '❌ Not all files match required pattern'
    assert_output --partial 'test2'
    assert_output --partial 'md'
}

@test "Verify should spot wrong pattern if forbiddenPatterns is set" {
    # setup
    cp $DIR/test.yaml $TMP_DIR/framed.yaml
    assert_file_exists "$(pwd)/framed.yaml"
    run framed create --files
    assert_success

    yq -i '.structure.dirs[0].forbiddenPatterns[0] = "md"' "$(pwd)/framed.yaml"
    touch "$(pwd)/test2/test3.md"

    # test
    run framed verify
    assert_failure
    assert_output --partial '❌ Forbidden pattern (md) matched'
    assert_output --partial 'test2'
}

@test "Verify should spot if there is less files than minCount" {
    # setup
    cp $DIR/test.yaml $TMP_DIR/framed.yaml
    assert_file_exists "$(pwd)/framed.yaml"
    run framed create --files
    assert_success

    yq -i '.structure.dirs[0].minCount = 2' "$(pwd)/framed.yaml"

    # test
    run framed verify
    assert_failure
    assert_output --partial '❌ Min count (2) not met'
    assert_output --partial 'test2'
}

@test "Verify should spot if there is more files than maxCount" {
    # setup
    cp $DIR/test.yaml $TMP_DIR/framed.yaml
    assert_file_exists "$(pwd)/framed.yaml"
    run framed create --files
    assert_success

    yq -i '.structure.dirs[0].maxCount = 1' "$(pwd)/framed.yaml"
    touch "$(pwd)/test2/test3.md"
    touch "$(pwd)/test2/test4.md"

    # test
    run framed verify
    assert_failure
    assert_output --partial '❌ Max count (1) exceeded'
    assert_output --partial 'test2'
}

@test "Verify should spot when maxDepth is exceeded" {
    # setup
    cp $DIR/test.yaml $TMP_DIR/framed.yaml
    assert_file_exists "$(pwd)/framed.yaml"
    run framed create --files
    assert_success

    yq -i '.structure.dirs[0].maxDepth = 1' "$(pwd)/framed.yaml"
    mkdir "$(pwd)/test2/test3"

    # test
    run framed verify
    assert_failure
    assert_output --partial '❌ Max depth exceeded (1)'
    assert_output --partial 'test2'
}

@test "Verify should spot when there are subdirs with children not allowed" {
    # setup
    cp $DIR/test.yaml $TMP_DIR/framed.yaml
    assert_file_exists "$(pwd)/framed.yaml"
    run framed create --files
    assert_success

    yq -i '.structure.dirs[0].allowChildren = false' "$(pwd)/framed.yaml"
    mkdir "$(pwd)/test2/test3"

    # test
    run framed verify
    assert_failure
    assert_output --partial '❌ Children not allowed'
    assert_output --partial 'test2'
}


### Capture

@test "Capture should fail if depth flag is not integer" {
    # test
    run framed capture --depth invalid_value
    assert_failure
    assert_output --partial '🚨 Invalid depth value'
    assert_output --partial 'invalid_value'
}

@test "Capture should export correct name when name flag is set" {
    # test
    run framed capture --name test_name
    assert_success
    assert_output --partial '✅ Exported to file'

    # verify
    assert_file_exists "$(pwd)/framed.yaml"

    run yq eval '.name' "$(pwd)/framed.yaml"
    assert_success
    assert_output 'test_name'
}

@test "Capture should save file to correct path when output flag is set" {
    # test
    run framed capture --output "$(pwd)/test.yaml"
    assert_success
    assert_output --partial '✅ Exported to file'

    # verify
    assert_file_exists "$(pwd)/test.yaml"
}

@test "Capture should capture correct structure without depth flag" {
    # setup
    mkdir "$(pwd)/test1"
    touch "$(pwd)/test1/test2.md"

    # test
    run framed capture
    assert_success
    assert_output --partial '📝 Name:                             default'
    assert_output --partial '📂 Directories:                      1'
    assert_output --partial '📄 Files:                            1'
    assert_output --partial '🔁 Patterns:                         1'
    assert_output --partial '✅ Exported to file'

    # verify
    assert_file_exists "$(pwd)/framed.yaml"

    run yq eval '.name' "$(pwd)/framed.yaml"
    assert_success
    assert_output 'default'

    run yq eval '.structure.dirs.[0].name' "$(pwd)/framed.yaml"
    assert_success
    assert_output 'test1'

    run yq eval '.structure.dirs.[0].files.[0]' "$(pwd)/framed.yaml"
    assert_success
    assert_output 'test2.md'

    run yq eval '.structure.dirs.[0].allowedPatterns.[0]' "$(pwd)/framed.yaml"
    assert_success
    assert_output '.md'
}

@test "Capture should capture correct depth when flag is provided" {
    # setup
    mkdir "$(pwd)/test1"
    mkdir "$(pwd)/test1/test2"
    mkdir "$(pwd)/test1/test2/test3"
    touch "$(pwd)/test1/test2/test3/test4.md"
    touch "$(pwd)/test6.md"

    # test
    run framed capture --depth 1
    assert_success
    assert_output --partial '📂 Directories:                      1'
    assert_output --partial '📄 Files:                            1'
    run yq eval '.structure.dirs' "$(pwd)/framed.yaml"
    assert_success
    assert_output --partial 'test1'
    refute_output --partial 'test2'
    refute_output --partial 'test3'
    refute_output --partial 'test4.md'

    run yq eval '.structure.files' "$(pwd)/framed.yaml"
    assert_success
    assert_output --partial 'test6.md'

}


### All

@test "All commands fails when file not exist" {
    for command in "${commands_with_template[@]}"
    do
        run framed $command
        assert_failure
        assert_output --partial '☠️ Can not read file'
    done
}

================================================
FILE: test/test.yaml
================================================
# FRAMED Configuration
name: example

structure:
  name: root
  files:
    - test1.md
  dirs:
    - name: test2
    - name: test3
      files:
        - test4.md
Download .txt
gitextract__ybk0m0_/

├── .dockerignore
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       ├── pr.yaml
│       └── release.yaml
├── .gitignore
├── .goreleaser.yaml
├── LICENSE
├── Makefile
├── README.md
├── cmd/
│   ├── capture.go
│   ├── create.go
│   ├── import.go
│   ├── root.go
│   ├── verify.go
│   └── visualize.go
├── dockerfiles/
│   ├── dockerfile
│   ├── goreleaser.dockerfile
│   └── test.dockerfile
├── docs/
│   └── framed.tape
├── examples/
│   ├── golang.yaml
│   ├── python.yaml
│   └── structure.yaml
├── framed.go
├── framed.yaml
├── go.mod
├── go.sum
├── pkg/
│   └── ext/
│       ├── decoder.go
│       ├── encoder.go
│       ├── network.go
│       ├── printer.go
│       └── system.go
└── test/
    ├── test.bats
    └── test.yaml
Download .txt
SYMBOL INDEX (46 symbols across 12 files)

FILE: cmd/capture.go
  function init (line 56) | func init() {

FILE: cmd/create.go
  function init (line 40) | func init() {

FILE: cmd/import.go
  function init (line 55) | func init() {

FILE: cmd/root.go
  function Execute (line 27) | func Execute() {
  function init (line 34) | func init() {}

FILE: cmd/verify.go
  function init (line 89) | func init() {

FILE: cmd/visualize.go
  function init (line 33) | func init() {

FILE: framed.go
  function main (line 8) | func main() {

FILE: pkg/ext/decoder.go
  type SingleDir (line 12) | type SingleDir struct
    method UnmarshalYAML (line 27) | func (s *SingleDir) UnmarshalYAML(unmarshal func(interface{}) error) e...
  type config (line 42) | type config struct
  function ReadConfig (line 47) | func ReadConfig(path string) (config, []SingleDir) {
  function traverseStructure (line 77) | func traverseStructure(dir *SingleDir, path string, dirsList *[]SingleDi...

FILE: pkg/ext/encoder.go
  type SingleDirOut (line 12) | type SingleDirOut struct
  type configOut (line 25) | type configOut struct
  function ExportConfig (line 30) | func ExportConfig(name string, path string, subdirs []string, files []st...
  function insertSubdirs (line 62) | func insertSubdirs(dirs *[]SingleDirOut, subdirs []string) {
  function insertSingleDir (line 68) | func insertSingleDir(dirs *[]SingleDirOut, dir string) {
  function containsDir (line 85) | func containsDir(dirs []SingleDirOut, name string) bool {
  function getDir (line 94) | func getDir(dirs []SingleDirOut, name string) *SingleDirOut {
  function insertFiles (line 103) | func insertFiles(root *SingleDirOut, files []string) {
  function insertSingleFile (line 109) | func insertSingleFile(root *SingleDirOut, file string) {
  function insertPatterns (line 126) | func insertPatterns(root *SingleDirOut, patterns map[string]string) {
  function insertSinglePattern (line 132) | func insertSinglePattern(root *SingleDirOut, dir string, pattern string) {

FILE: pkg/ext/network.go
  function ExampleToUrl (line 9) | func ExampleToUrl(example string) string {
  function ImportFromUrl (line 13) | func ImportFromUrl(path string, url string) error {

FILE: pkg/ext/printer.go
  function PrintOut (line 11) | func PrintOut(prompt string, text string) {
  function VisualizeTemplate (line 16) | func VisualizeTemplate(template []SingleDir) {
  function printDirectory (line 55) | func printDirectory(initString string, dirDepth int, connectorDir string...
  function printFile (line 60) | func printFile(initString string, depth int, connector string, file stri...

FILE: pkg/ext/system.go
  function CreateDir (line 13) | func CreateDir(path string) {
  function CreateAllDirs (line 25) | func CreateAllDirs(dirList []SingleDir) {
  function CreateFile (line 31) | func CreateFile(path string, name string) {
  function CreateAllFiles (line 44) | func CreateAllFiles(dirList []SingleDir) {
  function DirExists (line 56) | func DirExists(path string) bool {
  function FileExists (line 68) | func FileExists(path string) bool {
  function CountFiles (line 77) | func CountFiles(path string) int {
  function HasDirs (line 91) | func HasDirs(path string) bool {
  function CheckDepth (line 105) | func CheckDepth(path string) int {
  function MatchPatternInDir (line 125) | func MatchPatternInDir(path string, pattern string) []string {
  function CaptureSubDirs (line 150) | func CaptureSubDirs(path string, depth int) []string {
  function CaptureAllFiles (line 170) | func CaptureAllFiles(path string, depth int) []string {
  function CaptureRequiredPatterns (line 191) | func CaptureRequiredPatterns(path string, depth int) map[string]string {
  function VerifyFiles (line 233) | func VerifyFiles(dir SingleDir, allGood *bool) {
  function VerifyForbiddenPatterns (line 241) | func VerifyForbiddenPatterns(dir SingleDir, allGood *bool) {
  function VerifyAllowedPatterns (line 251) | func VerifyAllowedPatterns(dir SingleDir, allGood *bool) {
Condensed preview — 33 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (62K chars).
[
  {
    "path": ".dockerignore",
    "chars": 63,
    "preview": "README.md\npipelines/\ndockerfiles/\ndocs/\nbuild/\nLICENSE\nMakefile"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 17,
    "preview": "github: [mactat]\n"
  },
  {
    "path": ".github/workflows/pr.yaml",
    "chars": 653,
    "preview": "name: pr\non:\n  pull_request:\n    types: [opened, synchronize, reopened, ready_for_review]\n  workflow_dispatch: {}\njobs:\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "chars": 795,
    "preview": "name: release\non:\n  push:\n    tags:\n      - 'v[0-9]+.[0-9]+.[0-9]+'\n\npermissions:\n  contents: write\n\njobs:\n\n  goreleaser"
  },
  {
    "path": ".gitignore",
    "chars": 15,
    "preview": "build/\n.vscode/"
  },
  {
    "path": ".goreleaser.yaml",
    "chars": 638,
    "preview": "builds:\n  - binary: framed\n    goos:\n      - darwin\n      - linux\n      - windows\n    goarch:\n      - amd64\n      - arm6"
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2023 Maciej Tatarski\n\nPermission is hereby granted, free of charge, to any person obtaining a"
  },
  {
    "path": "Makefile",
    "chars": 2217,
    "preview": "# Definitions\nROOT                    := $(PWD)\nGO_VERSION              := 1.20.4\nALPINE_VERSION          := 3.18\nOS\t\t\t\t"
  },
  {
    "path": "README.md",
    "chars": 9900,
    "preview": "<img src=\"./docs/static/logo-wide.png\" style=\"object-fit: cover; width: 100%;\">\n\n[![onTag](https://github.com/mactat/fra"
  },
  {
    "path": "cmd/capture.go",
    "chars": 1745,
    "preview": "/*\nCopyright © 2023 Maciej Tatarski maciektatarski@gmail.com\n*/\n\n// Package cmd represents the command line interface of"
  },
  {
    "path": "cmd/create.go",
    "chars": 1074,
    "preview": "/*\nCopyright © 2023 Maciej Tatarski maciektatarski@gmail.com\n*/\n\n// Package cmd represents the command line interface of"
  },
  {
    "path": "cmd/import.go",
    "chars": 1773,
    "preview": "/*\nCopyright © 2023 Maciej Tatarski maciektatarski@gmail.com\n*/\n\n// Package cmd represents the command line interface of"
  },
  {
    "path": "cmd/root.go",
    "chars": 874,
    "preview": "/*\nCopyright © 2023 Maciej Tatarski maciektatarski@gmail.com\n*/\n\n// Package cmd represents the command line interface of"
  },
  {
    "path": "cmd/verify.go",
    "chars": 2136,
    "preview": "/*\nCopyright © 2023 Maciej Tatarski maciektatarski@gmail.com\n*/\n\n// Package cmd represents the command line interface of"
  },
  {
    "path": "cmd/visualize.go",
    "chars": 872,
    "preview": "/*\nCopyright © 2023 Maciej Tatarski maciektatarski@gmail.com\n*/\n\n// Package cmd represents the command line interface of"
  },
  {
    "path": "dockerfiles/dockerfile",
    "chars": 378,
    "preview": "ARG GO_VERSION=1.20.4\nARG ALPINE_VERSION=3.18\n\nFROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} as builder\n\nWORKDIR /ap"
  },
  {
    "path": "dockerfiles/goreleaser.dockerfile",
    "chars": 107,
    "preview": "ARG ALPINE_VERSION=3.18\n\nFROM alpine:${ALPINE_VERSION} as release\nCOPY framed /bin/framed\nCMD [ \"/bin/sh\" ]"
  },
  {
    "path": "dockerfiles/test.dockerfile",
    "chars": 496,
    "preview": "FROM mactat/framed:latest as builder\n\nRUN apk add --no-cache git bash sudo yq ncurses\n\nENV TERM=linux\n\n# clone bats\nRUN "
  },
  {
    "path": "docs/framed.tape",
    "chars": 871,
    "preview": "Output ./docs/static/demo.gif\n\n# Settings\n\nSet FontSize 20\nSet Width 1200\nSet Height 600\n\n# Requirements\n\nRequire framed"
  },
  {
    "path": "examples/golang.yaml",
    "chars": 1053,
    "preview": "# FRAMED Configuration\nname: golang_example\n\nstructure:\n    name: root\n    files:\n        - README.md\n        - framed.y"
  },
  {
    "path": "examples/python.yaml",
    "chars": 301,
    "preview": "# FRAMED Configuration\nname: python_example\n\nstructure:\n  name: root\n  files:\n    - README.md\n    - setup.py\n    - .giti"
  },
  {
    "path": "examples/structure.yaml",
    "chars": 313,
    "preview": "# FRAMED Configuration\nname: structure\n\nstructure:\n  name: root\n  dirs:\n    - name: src\n      dirs:\n        - name: this"
  },
  {
    "path": "framed.go",
    "chars": 130,
    "preview": "/*\nCopyright © 2023 Maciej Tatarski maciektatarski@gmail.com\n*/\npackage main\n\nimport \"framed/cmd\"\n\nfunc main() {\n\tcmd.Ex"
  },
  {
    "path": "framed.yaml",
    "chars": 1218,
    "preview": "# FRAMED Configuration\nname: framed\n\nstructure:\n    name: root\n    maxDepth: 5 # Disallow dirs deeper than 5\n    files:\n"
  },
  {
    "path": "go.mod",
    "chars": 415,
    "preview": "module framed\n\ngo 1.20\n\nrequire (\n\tgithub.com/TwiN/go-color v1.4.0\n\tgithub.com/creasty/defaults v1.7.0\n\tgithub.com/spf13"
  },
  {
    "path": "go.sum",
    "chars": 1979,
    "preview": "github.com/TwiN/go-color v1.4.0 h1:fNbOwOrvup5oj934UragnW0B1WKaAkkB85q19Y7h4ng=\ngithub.com/TwiN/go-color v1.4.0/go.mod h"
  },
  {
    "path": "pkg/ext/decoder.go",
    "chars": 2350,
    "preview": "package ext\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/creasty/defaults\"\n\t\"gopkg.in/yaml.v3\"\n)\n\n// SingleDir struct\ntype Singl"
  },
  {
    "path": "pkg/ext/encoder.go",
    "chars": 3561,
    "preview": "package ext\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"gopkg.in/yaml.v3\"\n)\n\n// Consider implementing a custom Unmarshaler for "
  },
  {
    "path": "pkg/ext/network.go",
    "chars": 523,
    "preview": "package ext\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc ExampleToUrl(example string) string {\n\treturn \"https://raw.githubu"
  },
  {
    "path": "pkg/ext/printer.go",
    "chars": 1449,
    "preview": "package ext\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/TwiN/go-color\"\n)\n\nfunc PrintOut(prompt string, text string) "
  },
  {
    "path": "pkg/ext/system.go",
    "chars": 6228,
    "preview": "package ext\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nfunc CreateDir(path string)"
  },
  {
    "path": "test/test.bats",
    "chars": 10776,
    "preview": "setup() {\n    # Load\n    load 'test_helper/bats-support/load'\n    load 'test_helper/bats-assert/load'\n    load 'test_hel"
  },
  {
    "path": "test/test.yaml",
    "chars": 162,
    "preview": "# FRAMED Configuration\nname: example\n\nstructure:\n  name: root\n  files:\n    - test1.md\n  dirs:\n    - name: test2\n    - na"
  }
]

About this extraction

This page contains the full source code of the mactat/framed GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 33 files (54.8 KB), approximately 16.2k tokens, and a symbol index with 46 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!