Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(x/gov): constitution amendments can update the constitution #25

Merged
merged 11 commits into from
Sep 25, 2024
1 change: 0 additions & 1 deletion x/gov/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ func GetQueryCmd() *cobra.Command {
GetCmdQueryDeposits(),
GetCmdQueryTally(),
GetCmdConstitution(),
GetCmdGenerateConstitutionAmendment(),
)

return govQueryCmd
Expand Down
19 changes: 15 additions & 4 deletions x/gov/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/cosmos/cosmos-sdk/version"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"

"github.com/atomone-hub/atomone/x/gov/client/utils"
govutils "github.com/atomone-hub/atomone/x/gov/client/utils"
"github.com/atomone-hub/atomone/x/gov/types"
v1 "github.com/atomone-hub/atomone/x/gov/types/v1"
Expand Down Expand Up @@ -73,6 +74,7 @@ func NewTxCmd(legacyPropCmds []*cobra.Command) *cobra.Command {
NewCmdWeightedVote(),
NewCmdSubmitProposal(),
NewCmdDraftProposal(),
NewCmdGenerateConstitutionAmendment(),

// Deprecated
cmdSubmitLegacyProp,
Expand Down Expand Up @@ -377,11 +379,11 @@ $ %s tx gov weighted-vote 1 yes=0.6,no=0.3,abstain=0.1 --from mykey
return cmd
}

// GetCmdConstitutionAmendmentMsg returns the command to generate the sdk.Msg
// NewCmdConstitutionAmendmentMsg returns the command to generate the sdk.Msg
// required for a constitution amendment proposal generating the unified diff
// between the current constitution (queried) and the updated constitution
// from the provided markdown file.
func GetCmdGenerateConstitutionAmendment() *cobra.Command {
func NewCmdGenerateConstitutionAmendment() *cobra.Command {
cmd := &cobra.Command{
Use: "generate-constitution-amendment [path/to/updated/constitution.md]",
Args: cobra.ExactArgs(1),
Expand All @@ -394,6 +396,10 @@ valid constitution amendment proposal message containing the unified diff
between the current constitution and the updated constitution provided
in a markdown file.

NOTE: this is just a utility command, it is not able to generate or submit a valid Tx
to submit on-chain. Use the 'tx gov submit-proposal' command in conjunction with the
result of this one to submit the proposal.

Example:
$ %s tx gov generate-constitution-amendment path/to/updated/constitution.md
`,
Expand All @@ -419,7 +425,7 @@ $ %s tx gov generate-constitution-amendment path/to/updated/constitution.md
}

// Generate the unified diff between the current and updated constitutions
diff, err := types.GenerateUnifiedDiff(resp.Constitution, updatedConstitution)
diff, err := utils.GenerateUnifiedDiff(resp.Constitution, updatedConstitution)
if err != nil {
return err
}
Expand All @@ -430,7 +436,12 @@ $ %s tx gov generate-constitution-amendment path/to/updated/constitution.md
},
}

flags.AddTxFlagsToCmd(cmd)
// This is not a tx command (but a utility for the proposal tx), so we don't need to add tx flags.
// It might actually be confusing, so we just add the query flags.
flags.AddQueryFlagsToCmd(cmd)
tbruyelle marked this conversation as resolved.
Show resolved Hide resolved
// query commands have the FlagOutput default to "text", but we want to override it to "json"
// in this case.
cmd.Flags().Set(flags.FlagOutput, "json")

return cmd
}
2 changes: 1 addition & 1 deletion x/gov/client/cli/tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ func (s *CLITestSuite) TestCmdGenerateConstitutionAmendment() {
tc := tc

s.Run(tc.name, func() {
cmd := cli.GetCmdGenerateConstitutionAmendment()
cmd := cli.NewCmdGenerateConstitutionAmendment()
out, err := clitestutil.ExecTestCLICmd(s.clientCtx, cmd, tc.args)
s.Require().NoError(err)
s.Require().Contains(out.String(), tc.expCmdOutput)
Expand Down
35 changes: 35 additions & 0 deletions x/gov/client/utils/unified_diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package utils

import (
"fmt"

"github.com/hexops/gotextdiff"
"github.com/hexops/gotextdiff/myers"
"github.com/hexops/gotextdiff/span"
)

// GenerateUnifiedDiff generates a unified diff from src and dst strings using gotextdiff.
// This is the only function that uses the gotextdiff library as its primary use is for
// clients.
func GenerateUnifiedDiff(src, dst string) (string, error) {
// Create spans for the source and destination texts
srcURI := span.URIFromPath("src")

if src == "" || src[len(src)-1] != '\n' {
src += "\n" // Add an EOL to src if it's empty or newline is missing
}
if dst == "" || dst[len(dst)-1] != '\n' {
dst += "\n" // Add an EOL to dst if it's empty or newline is missing
}

// Compute the edits using the Myers diff algorithm
eds := myers.ComputeEdits(srcURI, src, dst)

// Generate the unified diff string
diff := gotextdiff.ToUnified("src", "dst", src, eds)

// Convert the diff to a string
diffStr := fmt.Sprintf("%v", diff)

return diffStr, nil
}
93 changes: 93 additions & 0 deletions x/gov/client/utils/unified_diff_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package utils_test

import (
"strings"
"testing"

"github.com/atomone-hub/atomone/x/gov/client/utils"
"github.com/atomone-hub/atomone/x/gov/types"
"github.com/stretchr/testify/require"
)

func TestGenerateUnifiedDiff(t *testing.T) {
tests := []struct {
name string
src string
dst string
expected string
}{
{
name: "No changes",
src: "Line one\nLine two\nLine three",
dst: "Line one\nLine two\nLine three",
expected: ``,
},
{
name: "Line added",
src: "Line one\nLine two",
dst: "Line one\nLine two\nLine three",
expected: `@@ -1,2 +1,3 @@
Line one
Line two
+Line three
`,
},
{
name: "Line deleted",
src: "Line one\nLine two\nLine three",
dst: "Line one\nLine three",
expected: `@@ -1,3 +1,2 @@
Line one
-Line two
Line three
`,
},
{
name: "Line modified",
src: "Line one\nLine two\nLine three",
dst: "Line one\nLine two modified\nLine three",
expected: `@@ -1,3 +1,3 @@
Line one
-Line two
+Line two modified
Line three
`,
},
{
name: "Multiple changes",
src: "Line one\nLine two\nLine three",
dst: "Line zero\nLine one\nLine three\nLine four",
expected: `@@ -1,3 +1,4 @@
+Line zero
Line one
-Line two
Line three
+Line four
`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diff, err := utils.GenerateUnifiedDiff(tt.src, tt.dst)
require.NoError(t, err)

diffContent := strings.TrimPrefix(diff, "--- src\n+++ dst\n")
expectedContent := strings.TrimPrefix(tt.expected, "--- src\n+++ dst\n")

require.Equal(t, expectedContent, diffContent)
})
}
}

func TestUnifiedDiffIntegration(t *testing.T) {
src := "Line one\nLine two\nLine three"
dst := "Line zero\nLine one\nLine three\nLine four"

diffStr, err := utils.GenerateUnifiedDiff(src, dst)
require.NoError(t, err)

result, err := types.ApplyUnifiedDiff(src, diffStr)
require.NoError(t, err)
require.Equal(t, dst, result)
}
30 changes: 0 additions & 30 deletions x/gov/types/unified_diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ import (
"fmt"
"strconv"
"strings"

"github.com/hexops/gotextdiff"
"github.com/hexops/gotextdiff/myers"
"github.com/hexops/gotextdiff/span"
)

// Hunk represents a single change hunk in a unified diff.
Expand Down Expand Up @@ -271,29 +267,3 @@ func ApplyUnifiedDiff(src, diffStr string) (string, error) {

return strings.Join(resultLines, "\n"), nil
}

// GenerateUnifiedDiff generates a unified diff from src and dst strings using gotextdiff.
// This is the only function that uses the gotextdiff library as its primary use is for
// clients.
func GenerateUnifiedDiff(src, dst string) (string, error) {
// Create spans for the source and destination texts
srcURI := span.URIFromPath("src")

if src == "" || src[len(src)-1] != '\n' {
src += "\n" // Add an EOL to src if it's empty or newline is missing
}
if dst == "" || dst[len(dst)-1] != '\n' {
dst += "\n" // Add an EOL to dst if it's empty or newline is missing
}

// Compute the edits using the Myers diff algorithm
eds := myers.ComputeEdits(srcURI, src, dst)

// Generate the unified diff string
diff := gotextdiff.ToUnified("src", "dst", src, eds)

// Convert the diff to a string
diffStr := fmt.Sprintf("%v", diff)

return diffStr, nil
}
84 changes: 0 additions & 84 deletions x/gov/types/unified_diff_test.go
Original file line number Diff line number Diff line change
@@ -1,83 +1,11 @@
package types

import (
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestGenerateUnifiedDiff(t *testing.T) {
tests := []struct {
name string
src string
dst string
expected string
}{
{
name: "No changes",
src: "Line one\nLine two\nLine three",
dst: "Line one\nLine two\nLine three",
expected: ``,
},
{
name: "Line added",
src: "Line one\nLine two",
dst: "Line one\nLine two\nLine three",
expected: `@@ -1,2 +1,3 @@
Line one
Line two
+Line three
`,
},
{
name: "Line deleted",
src: "Line one\nLine two\nLine three",
dst: "Line one\nLine three",
expected: `@@ -1,3 +1,2 @@
Line one
-Line two
Line three
`,
},
{
name: "Line modified",
src: "Line one\nLine two\nLine three",
dst: "Line one\nLine two modified\nLine three",
expected: `@@ -1,3 +1,3 @@
Line one
-Line two
+Line two modified
Line three
`,
},
{
name: "Multiple changes",
src: "Line one\nLine two\nLine three",
dst: "Line zero\nLine one\nLine three\nLine four",
expected: `@@ -1,3 +1,4 @@
+Line zero
Line one
-Line two
Line three
+Line four
`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diff, err := GenerateUnifiedDiff(tt.src, tt.dst)
require.NoError(t, err)

diffContent := strings.TrimPrefix(diff, "--- src\n+++ dst\n")
expectedContent := strings.TrimPrefix(tt.expected, "--- src\n+++ dst\n")

require.Equal(t, expectedContent, diffContent)
})
}
}

func TestApplyUnifiedDiff(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -221,18 +149,6 @@ func TestApplyUnifiedDiff(t *testing.T) {
}
}

func TestUnifiedDiffIntegration(t *testing.T) {
src := "Line one\nLine two\nLine three"
dst := "Line zero\nLine one\nLine three\nLine four"

diffStr, err := GenerateUnifiedDiff(src, dst)
require.NoError(t, err)

result, err := ApplyUnifiedDiff(src, diffStr)
require.NoError(t, err)
require.Equal(t, dst, result)
}

func TestParseUnifiedDiff(t *testing.T) {
diffStr := `@@ -1,3 +1,4 @@
+Line zero
Expand Down