Skip to content

Commit

Permalink
#3 Added functionality for national election
Browse files Browse the repository at this point in the history
Added functionality for national election
  • Loading branch information
aabishkaryal authored Nov 23, 2022
2 parents bcad711 + cafea04 commit 849913a
Show file tree
Hide file tree
Showing 13 changed files with 321 additions and 106 deletions.
6 changes: 3 additions & 3 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
builds:
- main: .
binary: npelection
id: npelection
id: npelection

goos: [darwin,linux,windows]
goos: [darwin, linux, windows]
goarch:
- "386"
- amd64
- arm
- arm64
- arm64
23 changes: 23 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
default: release

release:
GOOS=linux GOARCH=amd64 go build -o ./build/linux-amd64/npelection ./main.go
GOOS=linux GOARCH=arm go build -o ./build/linux-arm/npelection ./main.go
GOOS=windows GOARCH=amd64 go build -o ./build/windows-amd64/npelection.exe ./main.go
GOOS=windows GOARCH=arm64 go build -o ./build/windows-386/npelection.exe ./main.go
GOOS=darwin GOARCH=amd64 go build -o ./build/darwin-amd64/npelection ./main.go

lint:
golangci-lint run
go mod tidy -v && git --no-pager diff --quiet go.mod

clean:
rm -rf build

install-linter:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

install-fumpt:
go install mvdan.cc/gofumpt@latest

install-tools: install-linter install-fumpt
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ NpElection displays the election data of Nepal right from the comfort of your te

- Download the latest release as per your system from the [Releases](https://github.com/askbuddie/npelection/releases) page.


# How to use

Once you have downloaded a release file, extract the zip and run from the relative directory:
Expand All @@ -15,7 +14,6 @@ Once you have downloaded a release file, extract the zip and run from the relati
./npelection
```


## Add to Path

```bash
Expand All @@ -24,24 +22,31 @@ Once you have downloaded a release file, extract the zip and run from the relati

Now, you will be able to use **`npelection`** as a command.


## List candidates

```bash
./npelection list
```

> **Note**, you can simply use: **`npelection list`** after doing **`./npelection init`** as mentioned earlier.
List all districts.

## Get Election Result for District

```bash
./npelection get -d <district>
```

> **Note**, you can simply use: **`npelection get -d <district>`** after doing **`./npelection init`**.
# Prerequisites (Build only)

- [Go](https://golang.org/doc/install)


# Build

- Clone the repository with `git clone [email protected]:askbuddie/npelection.git`
- Download the dependencies with `go mod download`
- Run with `go run main.go` or `go build`
- Run `sh build.sh` to build for all platforms (Windows, Linux, MacOS)
- Run `make release` to build for all platforms (Windows, Linux, MacOS)

**Disclaimer: This tool is made entirely for educational purposes. Any legal responsibility belongs to the person or organization that uses it.**
16 changes: 16 additions & 0 deletions app/levenshtein.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package app

import (
levenshtein "github.com/ka-weihe/fast-levenshtein"
)

func GetClosestDistrict(targetDistrict string, districtMap map[string]string) string {
closestDistrict, closestDistance := "", 100
for district := range districtMap {
distance := levenshtein.Distance(targetDistrict, district)
if distance < closestDistance {
closestDistance, closestDistrict = distance, district
}
}
return closestDistrict
}
7 changes: 0 additions & 7 deletions build.sh

This file was deleted.

134 changes: 134 additions & 0 deletions cmd/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
"fmt"
"log"
"net/http"
"npelection/app"
"os"
"strings"

"github.com/PuerkitoBio/goquery"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
)

var (
district string
numParties int
numCandidates int
)

// getCmd represents the get command
var getCmd = &cobra.Command{
Use: "get",
Short: "Get election data for certain district",
RunE: func(cmd *cobra.Command, _ []string) error {
// Get the link for the district to scrape
link, ok := districtLinkMap[district]
// If the district is not found, return an error
if !ok {
closestDistrict := app.GetClosestDistrict(district, districtLinkMap)
fmt.Printf("Incorrect District Name. Did you mean %s?\n", closestDistrict)
fmt.Printf("Use `npelection list` to get a list of all districts.\n\n")
cmd.Help()
return nil
}
// Get the HTML document
response, err := http.Get(link)
if err != nil {
e := fmt.Errorf("error getting response: %v", err)
return e
}
defer response.Body.Close()

if response.StatusCode != 200 {
e := fmt.Errorf("status code error- Got: %v Wanted: %v", response.StatusCode, http.StatusOK)
return e
}
// Load the HTML document
doc, err := goquery.NewDocumentFromReader(response.Body)
if err != nil {
e := fmt.Errorf("error parsing response body: %v", err)
return e
}

// Find the table with constituency wise data
constituencyTableHTML := doc.Find("body > section > section:nth-child(5) > div > div > div.gy-4 > div.election-2079 > div > div > div")
constituencyTable := tablewriter.NewWriter(os.Stdout)
constituencyTable.SetHeader([]string{"Constituency", "Candidate", "Party", "Vote", "Won"})
constituencyTable.SetAutoMergeCellsByColumnIndex([]int{0})
constituencyTable.SetAlignment(tablewriter.ALIGN_LEFT)
constituencyTable.SetRowLine(true)

// For each constituency, scrape the candidate name, party name, vote count and if their win condition.
constituencyTableHTML.Each(func(_ int, constituencySelection *goquery.Selection) {
constituencyName := constituencySelection.Find(".card-header > .card-title").Text()
candidatesListHTML := constituencySelection.Find(".candidate-list__item")
candidatesListHTML.EachWithBreak(func(i int, candidateSelection *goquery.Selection) bool {
if i >= numCandidates {
return false
}
candidateName := candidateSelection.Find(".nominee-name > a").Text()
candidatePartyName := candidateSelection.Find(".candidate-party-name > a").Text()
voteCount := candidateSelection.Find(".vote-count").Text()
won := ""
if candidateSelection.HasClass("elected") {
won = "X"
}
constituencyTable.Append([]string{
strings.TrimSpace(constituencyName),
strings.TrimSpace(candidateName),
strings.TrimSpace(candidatePartyName),
strings.TrimSpace(voteCount),
won,
})
return true
})
})
constituencyTable.Render()

// Find the table with overall party wise data
partyTableHTML := doc.Find("body > section > section:nth-child(5) > div > div > div.row > div.col-xl-8 > div > div.col-xl-6.parties.mb-xl-0.mb-4 > div")
partiesRow := partyTableHTML.Find(".card-body > .row")
// Prepare table output in display
partyTable := tablewriter.NewWriter(os.Stdout)
partyTable.SetHeader([]string{"Party", "Win", "Lead"})
partyTable.SetRowLine(true)
// For each party scrape the partyName, wins and leads in the given district
partiesRow.EachWithBreak(func(i int, partySelection *goquery.Selection) bool {
if i >= numParties {
return false
}
partyName := partySelection.Find(".party-name > a").Text()
numbers := partySelection.Find(".number-display")
if numbers.Length() != 2 {
log.Fatal(fmt.Errorf("unexpected html for wins and leads: %v", numbers.Nodes))
return false
}
wins := numbers.First().Text()
leads := numbers.Last().Text()
partyTable.Append([]string{
strings.TrimSpace(partyName),
strings.TrimSpace(wins),
strings.TrimSpace(leads),
})
return true
})
partyTable.Render()

return nil
},
}

func init() {
rootCmd.AddCommand(getCmd)

getCmd.Flags().StringVarP(&district, "district", "d", "", "District for which the election data is to be shown")
getCmd.Flags().IntVarP(&numParties, "parties", "p", 3, "Number of parties to show in the overall result")
getCmd.Flags().IntVarP(&numCandidates, "candidates", "c", 3, "Number of candidates to show for each constituency")
getCmd.MarkFlagRequired("district")
}
25 changes: 12 additions & 13 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,22 @@ import (
func copy(src, dst string) (int64, error) {
sourceFileStat, err := os.Stat(src)
if err != nil {
return 0, err
return 0, err
}

if !sourceFileStat.Mode().IsRegular() {
return 0, fmt.Errorf("%s is not a regular file", src)
return 0, fmt.Errorf("%s is not a regular file", src)
}

source, err := os.Open(src)
if err != nil {
return 0, err
return 0, err
}
defer source.Close()

destination, err := os.Create(dst)
if err != nil {
return 0, err
return 0, err
}
defer destination.Close()
nBytes, err := io.Copy(destination, source)
Expand All @@ -40,18 +40,17 @@ func copy(src, dst string) (int64, error) {
var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize npelection",
Long: `Sends npelection to the preferred path for respective platforms`,
Run: func(cmd *cobra.Command, args []string) {
Long: `Sends npelection to the preferred path for respective platforms`,
Run: func(_ *cobra.Command, _ []string) {
ext, err := os.Executable()
if err != nil {
panic(err)
}
if err != nil {
panic(err)
}
fn := filepath.Base(ext)
fp := filepath.Clean(ext)
fp := filepath.Clean(ext)
fd := filepath.Dir(ext)

r := runtime.GOOS
switch r {
switch runtime.GOOS {
case "windows":
cmd := exec.Command("setx", "path", "%path%;"+fd)
err := cmd.Run()
Expand All @@ -69,4 +68,4 @@ var initCmd = &cobra.Command{

func init() {
rootCmd.AddCommand(initCmd)
}
}
Loading

0 comments on commit 849913a

Please sign in to comment.