Skip to content

Commit

Permalink
Adding the diffr base logic
Browse files Browse the repository at this point in the history
Signed-off-by: Raj Babu Das <[email protected]>
  • Loading branch information
imrajdas committed Aug 26, 2023
1 parent 4cd1e2e commit 4484eb8
Show file tree
Hide file tree
Showing 12 changed files with 390 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
13 changes: 13 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/imrajdas/diffr

go 1.20

require (
github.com/pmezard/go-difflib v1.0.0
github.com/spf13/cobra v1.7.0
)

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
)
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
9 changes: 9 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import (
"github.com/imrajdas/diffr/pkg/cmd/root"
)

func main() {
root.Execute()
}
28 changes: 28 additions & 0 deletions pkg/cmd/root/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package root

import (
"github.com/imrajdas/diffr/pkg/cmd/version"
"github.com/imrajdas/diffr/pkg/diffr"
"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "diffr [dir1] [dir2]",
Example: "diffr /path/to/dir1 /path/to/dir2",
Short: "A web-based content difference analyzer",
Long: `A web-based tool to compare content differences between two directories ` + "\n" + `Find more information at: https://github.com/imrajdas/diffr`,
Args: cobra.ExactArgs(2),
Run: diffr.RunWebServer,
}

func Execute() {
cobra.CheckErr(rootCmd.Execute())
}

func init() {
rootCmd.Flags().IntVarP(&diffr.Port, "port", "p", 8080, "Set the port for the web server to listen on, default is 8080")
rootCmd.Flags().StringVarP(&diffr.Address, "address", "a", "http://localhost", "Set the address for the web server to listen on, default is http://localhost")

rootCmd.CompletionOptions.DisableDefaultCmd = true
rootCmd.AddCommand(version.VersionCmd)
}
16 changes: 16 additions & 0 deletions pkg/cmd/version/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package version

import (
"os"

"github.com/spf13/cobra"
)

var VersionCmd = &cobra.Command{
Use: "version",
Short: "Displays the version of diffr",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
cmd.Printf("Diffr Version: %s", os.Getenv("VERSION"))
},
}
6 changes: 6 additions & 0 deletions pkg/diffr/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package diffr

var (
Port int
Address string
)
87 changes: 87 additions & 0 deletions pkg/diffr/diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package diffr

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"

"github.com/pmezard/go-difflib/difflib"
)

func compareFiles(file1, file2 string) (string, error) {
content1, err := ioutil.ReadFile(file1)
if err != nil {
return "", err
}

content2, err := ioutil.ReadFile(file2)
if err != nil {
return "", err
}

diff := difflib.UnifiedDiff{
A: difflib.SplitLines(string(content1)),
B: difflib.SplitLines(string(content2)),
FromFile: file1,
ToFile: file2,
Context: 3,
}

diffs, err := difflib.GetUnifiedDiffString(diff)
if err != nil {
return "", err
}

return diffs, nil
}

func CompareDirectories(dir1, dir2 string, diffChan chan<- string, errorChan chan<- error, wg *sync.WaitGroup) {
defer wg.Done()

filepath.Walk(dir1, func(path1 string, info os.FileInfo, err error) error {
if err != nil {
errorChan <- fmt.Errorf("error accessing %s: %s", path1, err)
return nil
}

relPath, err := filepath.Rel(dir1, path1)
if err != nil {
errorChan <- fmt.Errorf("error getting relative path of %s: %s", path1, err)
return nil
}

path2 := filepath.Join(dir2, relPath)

if info.IsDir() {
return nil
}

if _, err := os.Stat(path2); err == nil {
diff, err := compareFiles(path1, path2)
if err != nil {
errorChan <- fmt.Errorf("error comparing files %s and %s: %s", path1, path2, err)
return nil
}

if diff != "" {
diffChan <- fmt.Sprintf("Differences in file: %s\n%s", relPath, diff)
}
} else if os.IsNotExist(err) {
diff, err := compareFiles(path1, "/dev/null")
if err != nil {
errorChan <- fmt.Errorf("error comparing files %s and /dev/null: %s", path1, err)
return nil
}

if diff != "" {
diffChan <- fmt.Sprintf("Differences in file: %s (present in %s but not in %s)\n%s", relPath, dir1, dir2, diff)
}
} else {
errorChan <- fmt.Errorf("error accessing %s: %s", path2, err)
}

return nil
})
}
134 changes: 134 additions & 0 deletions pkg/diffr/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package diffr

import (
"fmt"
"html/template"
"net/http"
"os"
"os/exec"
"os/signal"
"runtime"
"sync"
"syscall"

"github.com/spf13/cobra"
)

var (
dir1 string
dir2 string
)

func openBrowser(url string) error {
var cmd *exec.Cmd

switch runtime.GOOS {
case "darwin":
cmd = exec.Command("open", url)
case "linux":
cmd = exec.Command("xdg-open", url)
case "windows":
cmd = exec.Command("cmd", "/c", "start", url)
default:
return fmt.Errorf("unsupported platform")
}

return cmd.Start()
}

func RunWebServer(cmd *cobra.Command, args []string) {
if len(args) != 2 {
fmt.Errorf("Error: Usage: \n diffr /path/to/dir1 /path/to/dir2")
return
}

dir1 = args[0]
dir2 = args[1]

serverURL := fmt.Sprintf("%s:%d", Address, Port)

http.HandleFunc("/", handler)
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))

server := &http.Server{Addr: fmt.Sprintf(":%d", Port)}

// Channel to receive signals (e.g., interrupt or termination)
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)

go func() {
fmt.Printf("Server started at %s\n", serverURL)
err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
fmt.Println("Error starting server:", err)
os.Exit(1)
}
}()

fmt.Println("Opening browser...")
err := openBrowser(serverURL)
if err != nil {
fmt.Println("Error opening browser:", err)
}

// Wait for a termination signal
<-signalCh

fmt.Println("Shutting down server...")
err = server.Shutdown(nil)
if err != nil {
fmt.Println("Error shutting down server:", err)
}
}

type PageData struct {
Title string
Diff string
}

func handler(w http.ResponseWriter, r *http.Request) {
var (
wg sync.WaitGroup
finalStr = ""
diffChan = make(chan string)
errorChan = make(chan error)
)

go func() {
for diff := range diffChan {
finalStr += diff
}
}()

go func() {
for err := range errorChan {
fmt.Printf("error: %v", err)
}
}()

wg.Add(1)
go CompareDirectories(dir1, dir2, diffChan, errorChan, &wg)
wg.Wait()

close(diffChan)
close(errorChan)

tmpl, err := template.ParseFiles("static/templates/template.html")
if err != nil {
fmt.Printf("error: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

data := PageData{
Title: "Diffr - A web-based content difference analyzer",
Diff: finalStr,
}

// Execute the template with the data and write the output to the response writer
err = tmpl.Execute(w, data)
if err != nil {
fmt.Printf("error: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
17 changes: 17 additions & 0 deletions static/css/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.navbar {
background-color: #010b18; /* Dark blue color */
}
.navbar-brand {
color: #fff;
font-weight: bold;
}
.github-star {
font-size: 24px;
color: #fff;
margin-right: 20px;
}

#myDiffElement {
margin-left: 10%;
margin-right: 10%;
}
27 changes: 27 additions & 0 deletions static/js/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
var diffString = document.getElementById("diff-data").getAttribute("data-diff");
document.addEventListener('DOMContentLoaded', function () {
var targetElement = document.getElementById('myDiffElement');
var configuration = {
drawFileList: true,
fileListToggle: true,
fileListStartVisible: true,
fileContentToggle: true,
matching: 'lines',
outputFormat: 'side-by-side',
synchronisedScroll: true,
highlight: true,
highlightLanguages: true,
renderNothingWhenEmpty: false,
};
var diff2htmlUi = new Diff2HtmlUI(targetElement, diffString, configuration);
diff2htmlUi.draw();
diff2htmlUi.highlightCode();

// Dark mode toggle functionality
const darkModeToggle = document.getElementById('darkModeToggle');
const body = document.body;

darkModeToggle.addEventListener('click', () => {
body.classList.toggle('dark-mode');
});
});
Loading

0 comments on commit 4484eb8

Please sign in to comment.