From da03176b5fada796c7bbb403814c762c2abb1b60 Mon Sep 17 00:00:00 2001 From: itohara Date: Mon, 24 Feb 2025 17:41:05 +0530 Subject: [PATCH 1/2] solution --- README.md | 125 ++++++++++++++++++++++++++++++++--------- app.go | 147 +++++++++++++++++++++++++++++++++++++++++++++++++ distributor.go | 68 +++++++++++++++++++++++ go.mod | 3 + main.go | 101 +++++++++++++++++++++++++++++++++ places.go | 130 +++++++++++++++++++++++++++++++++++++++++++ uitls.go | 23 ++++++++ 7 files changed, 571 insertions(+), 26 deletions(-) create mode 100644 app.go create mode 100644 distributor.go create mode 100644 go.mod create mode 100644 main.go create mode 100644 places.go create mode 100644 uitls.go diff --git a/README.md b/README.md index f1c342f65..8d8262fc1 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,114 @@ -# Real Image Challenge 2016 +# Distributor Permission Tool - Instructions -In the cinema business, a feature film is usually provided to a regional distributor based on a contract for exhibition in a particular geographical territory. +This tool allows you to manage distributors and their regional permissions via a command-line interface. -Each authorization is specified by a combination of included and excluded regions. For example, a distributor might be authorzied in the following manner: +## Commands and Their Functionality + +### 1. Creating a New Distributor +**Command:** +```sh +make ``` -Permissions for DISTRIBUTOR1 -INCLUDE: INDIA -INCLUDE: UNITEDSTATES -EXCLUDE: KARNATAKA-INDIA -EXCLUDE: CHENNAI-TAMILNADU-INDIA +**Example:** +```sh +make a ``` -This allows `DISTRIBUTOR1` to distribute in any city inside the United States and India, *except* cities in the state of Karnataka (in India) and the city of Chennai (in Tamil Nadu, India). - -At this point, asking your program if `DISTRIBUTOR1` has permission to distribute in `CHICAGO-ILLINOIS-UNITEDSTATES` should get `YES` as the answer, and asking if distribution can happen in `CHENNAI-TAMILNADU-INDIA` should of course be `NO`. Asking if distribution is possible in `BANGALORE-KARNATAKA-INDIA` should also be `NO`, because the whole state of Karnataka has been excluded. - -Sometimes, a distributor might split the work of distribution amount smaller sub-distiributors inside their authorized geographies. For instance, `DISTRIBUTOR1` might assign the following permissions to `DISTRIBUTOR2`: +**Functionality:** +- Creates a new distributor named `a`. +- If the distributor already exists, an error message is displayed. +### 2. Creating a Distributor with a Parent +**Command:** +```sh +make < parent_distributor ``` -Permissions for DISTRIBUTOR2 < DISTRIBUTOR1 -INCLUDE: INDIA -EXCLUDE: TAMILNADU-INDIA +**Example:** +```sh +make b < a ``` -Now, `DISTRIBUTOR2` can distribute the movie anywhere in `INDIA`, except inside `TAMILNADU-INDIA` and `KARNATAKA-INDIA` - `DISTRIBUTOR2`'s permissions are always a subset of `DISTRIBUTOR1`'s permissions. It's impossible/invalid for `DISTRIBUTOR2` to have `INCLUDE: CHINA`, for example, because `DISTRIBUTOR1` isn't authorized to do that in the first place. +**Functionality:** +- Creates a new distributor `b` and assigns `a` as its parent. +- If the parent does not exist, an error message is displayed. +- If the child distributor already exists, an error message is displayed. -If `DISTRIBUTOR2` authorizes `DISTRIBUTOR3` to handle just the city of Hubli, Karnataka, India, for example: +### 3. Adding an Include Permission +**Command:** +```sh +for include ,... ``` -Permissions for DISTRIBUTOR3 < DISTRIBUTOR2 < DISTRIBUTOR1 -INCLUDE: HUBLI-KARNATAKA-INDIA +**Example:** +```sh +for a include IN +for a include IN,US ``` -Again, `DISTRIBUTOR2` cannot authorize `DISTRIBUTOR3` with a region that they themselves do not have access to. +**Functionality:** +- Grants `a` access to `IN` (India) in first example. +- Use , for multiple code +- If the distributor does not exist, an error message is displayed. -We've provided a CSV with the list of all countries, states and cities in the world that we know of - please use the data mentioned there for this program. *The codes you see there may be different from what you see here, so please always use the codes in the CSV*. This Readme is only an example. - -Write a program in any language you want (If you're here from Gophercon, use Go :D) that does this. Feel free to make your own input and output format / command line tool / GUI / Webservice / whatever you want. Feel free to hold the dataset in whatever structure you want, but try not to use external databases - as far as possible stick to your langauage without bringing in MySQL/Postgres/MongoDB/Redis/Etc. +### 4. Adding an Exclude Permission +**Command:** +```sh +for exclude ,... +``` +**Example:** +```sh +for a exclude AR-IN +for a exclude AR-IN,US +``` +**Functionality:** +- Revokes access to `AR-IN` (Arunachal Pradesh, India) for `a` in first example. +- Use , for multiple code +- If the distributor does not exist, an error message is displayed. -To submit a solution, fork this repo and send a Pull Request on Github. +### 5. Listing Permissions +**Command:** +```sh +for list +``` +**Example:** +```sh +for a list +``` +**Functionality:** +- Displays the include and exclude permissions for `a`. +- If the distributor does not exist, an error message is displayed. -For any questions or clarifications, raise an issue on this repo and we'll answer your questions as fast as we can. +### 6. Exiting the CLI +**Command:** +```sh +exit +``` +**Functionality:** +- Terminates the CLI tool. +## Notes +- `place_code` can be: + - `CountryCode` (e.g., `IN` for India) + - `ProvinceCode-CountryCode` (e.g., `UP-IN` for Uttar Pradesh, India) + - `CityCode-ProvinceCode-CountryCode` (e.g., `ZAIDR-UP-IN` for a specific city in Uttar Pradesh, India) +## Example Workflow +```sh +make a +for a include IN +for a exclude AR-IN +make b < a +for a list +for b list +exit +``` +### Expected Output: +``` +Created distributor: a +a now includes IN +a now excludes AR-IN +Created distributor: b with parent: a +Permissions for a: +Includes: map[IN:true] +Excludes: map[AR-IN:true] +Permissions for b: +Includes: map[] +Excludes: map[] +Exiting CLI... +``` diff --git a/app.go b/app.go new file mode 100644 index 000000000..81ad0fd61 --- /dev/null +++ b/app.go @@ -0,0 +1,147 @@ +package main + +import "fmt" + +func (app *App) New_Distributor(name string) error { + _, distrubutor_exist := app.Distributors[name] + if distrubutor_exist { + err := fmt.Errorf("distributor %v already exits", name) + return err + } + + app.Distributors[name] = Distributor{ + Name: name, + IncludeRegions: make(map[string]bool), + ExcludeRegions: make(map[string]bool), + Parent: nil, + } + fmt.Printf("%v addedd successfull\n", name) + return nil +} + +func (app *App) Include_Permissions(name string, place_code string) error { + distributor, exist := app.Distributors[name] + if !exist { + err := fmt.Errorf("no distrubutor with name %v", name) + return err + } + + is_place := app.Places.Place_Exist(place_code) + if !is_place { + err := fmt.Errorf("invalid place code: %v", place_code) + return err + } + + if distributor.Parent != nil { + if !distributor.Parent.Has_Permission(place_code) { + err := fmt.Errorf("parent distrubutor has not the permissions") + return err + } + _, exist := distributor.Parent.IncludeRegions[place_code] + if exist { + fmt.Println("added permissions to ", name) + return nil + } + } + + _, exist = distributor.ExcludeRegions[place_code] + if exist { + delete(distributor.ExcludeRegions, place_code) + } + + distributor.IncludeRegions[place_code] = true + fmt.Println("added permissions to ", name) + return nil +} + +func (app *App) Exclude_Permissions(name, place_code string) error { + d, exist := app.Distributors[name] + if !exist { + err := fmt.Errorf("no distrubutor with name %v", name) + return err + } + + if !app.Places.Place_Exist(place_code) { + err := fmt.Errorf("invalid place code: %v", place_code) + return err + } + + if d.Parent != nil { + _, exist = d.Parent.ExcludeRegions[place_code] + if exist { + fmt.Println("added exclude permissions to ", name) + return nil + } + } + + _, exist = d.IncludeRegions[place_code] + if exist { + delete(d.IncludeRegions, place_code) + } + + _, exist = d.ExcludeRegions[place_code] + if !exist { + d.ExcludeRegions[place_code] = true + } + fmt.Println("added exlcude permissions to ", name) + return nil +} + +func (app *App) New_Dist_With_Parent(parent_name, name string) error { + parent_dist, parent_exist := app.Distributors[parent_name] + if !parent_exist { + err := fmt.Errorf("distributor %v does not exits", parent_name) + return err + } + + dist, dist_exist := app.Distributors[name] + if dist_exist { + if dist.Parent != nil { + err := fmt.Errorf("%v already has a parent", name) + return err + } + dist.Parent = &parent_dist + fmt.Printf("%v addedd successfull", name) + return nil + } + + app.Distributors[name] = Distributor{ + Name: name, + IncludeRegions: make(map[string]bool), + ExcludeRegions: make(map[string]bool), + Parent: &parent_dist, + } + fmt.Printf("%v addedd successfull\n", name) + return nil +} + +func (app *App) Get_Permissions(name string) error { + dist, e := app.Distributors[name] + if !e { + err := fmt.Errorf("no distributor with name: %v", name) + return err + } + + has_parent := dist.Parent != nil + + fmt.Println("INCLUDE") + for k := range dist.IncludeRegions { + app.Places.Get_Name_From_Codes(k) + } + if has_parent { + for k := range dist.Parent.IncludeRegions { + app.Places.Get_Name_From_Codes(k) + } + } + fmt.Println("==============================") + fmt.Println("EXCLUDE") + for k := range dist.ExcludeRegions { + app.Places.Get_Name_From_Codes(k) + } + if has_parent { + for k := range dist.Parent.ExcludeRegions { + app.Places.Get_Name_From_Codes(k) + } + } + return nil +} diff --git a/distributor.go b/distributor.go new file mode 100644 index 000000000..fa4e946ac --- /dev/null +++ b/distributor.go @@ -0,0 +1,68 @@ +package main + +import ( + "strings" +) + +type Distributor struct { + Name string + IncludeRegions map[string]bool + ExcludeRegions map[string]bool + Parent *Distributor +} + +func (d *Distributor) Has_Permission(place_code string) bool { + // exact code in exlude map + _, exclude_exist := d.ExcludeRegions[place_code] + if exclude_exist { + return false + } + + codes := strings.Split(place_code, "-") + + switch len(codes) { + case 1: + _, include_exist := d.IncludeRegions[codes[0]] + // _, e_exist := d.ExcludeRegions[codes[0]] // this case redundant when providing only country code + return include_exist + + case 2: + province_code := codes[0] + "-" + codes[1] + county_code := codes[1] + _, inlude_exist := d.IncludeRegions[province_code] + if inlude_exist { + return true + } + _, include_country_exist := d.IncludeRegions[county_code] + _, exclude_province_exist := d.ExcludeRegions[province_code] + + if include_country_exist { + return !exclude_province_exist + } + return false + + case 3: + _, include_city_exist := d.IncludeRegions[place_code] + if include_city_exist { + return true + } + + province_code := codes[1] + "-" + codes[2] + _, include_province_exist := d.IncludeRegions[province_code] + _, exclude_city_exist := d.ExcludeRegions[place_code] + if include_province_exist { + return !exclude_city_exist + } + + country_code := codes[2] + _, include_country_exist := d.IncludeRegions[country_code] + _, exclude_province_exist := d.ExcludeRegions[province_code] + if include_country_exist { + return !exclude_province_exist + } + + default: + return false + } + return false +} diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..e7ba487e4 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module rajma + +go 1.23.4 diff --git a/main.go b/main.go new file mode 100644 index 000000000..d8d960f90 --- /dev/null +++ b/main.go @@ -0,0 +1,101 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +type App struct { + Distributors map[string]Distributor + Places *Places +} + +func main() { + data := Read_file("cities.csv") + p := LoadPlace(data) + app := App{ + Distributors: make(map[string]Distributor), + } + + app.Places = p + + r := bufio.NewReader(os.Stdin) + fmt.Println("====================Distribution System====================") + fmt.Println(" Please consult README.md for commands") + for { + fmt.Print("> ") + input, _ := r.ReadString(byte('\n')) + input, _ = strings.CutSuffix(input, "\n") + if input == "exit" { + break + } + + cmds := strings.Fields(input) + if cmds[0] == "make" { + if len(cmds) == 2 { + dist_name := strings.TrimSpace(cmds[1]) + if dist_name == "" { + fmt.Println("This command needs a name for distributor") + continue + } + err := app.New_Distributor(dist_name) + if err != nil { + fmt.Println(err.Error()) + } + continue + } + if len(cmds) == 4 && cmds[2] == "<" { + err := app.New_Dist_With_Parent(cmds[3], cmds[1]) + if err != nil { + fmt.Println(err.Error()) + } + continue + } + fmt.Println("invalid command") + continue + } + + if cmds[0] == "for" { + if len(cmds) < 3 { + fmt.Println("Invalid command") + continue + } + dist := cmds[1] + action := cmds[2] + + if action == "list" { + err := app.Get_Permissions(dist) + if err != nil { + fmt.Println(err.Error()) + } + continue + } + + place_codes := strings.Split(cmds[3], ",") + if action == "include" { + for _, v := range place_codes { + err := app.Include_Permissions(dist, v) + if err != nil { + fmt.Println(err.Error()) + } + continue + } + } else if action == "exclude" { + for _, v := range place_codes { + err := app.Exclude_Permissions(dist, v) + if err != nil { + fmt.Println(err.Error()) + } + continue + } + } else { + fmt.Println("invalid command") + continue + } + continue + } + fmt.Println("invalid command") + } +} diff --git a/places.go b/places.go new file mode 100644 index 000000000..80ce8300b --- /dev/null +++ b/places.go @@ -0,0 +1,130 @@ +package main + +import ( + "fmt" + "strings" +) + +type ( + City struct { + CityName string + } + Province struct { + Cities map[string]City + ProvinceName string + } + Country struct { + Provinces map[string]Province + CountyName string + } +) + +type Places struct { + Countries map[string]Country +} + +func LoadPlace(data [][]string) *Places { + p := &Places{Countries: make(map[string]Country)} + + for _, v := range data { + county_name, province_name, city_name := v[5], v[4], v[3] + county_code, province_code, city_code := v[2], v[1], v[0] + + _, county_exist := p.Countries[county_code] + if !county_exist { + p.Countries[county_code] = Country{ + // CountryCode: county_code, + CountyName: county_name, + Provinces: make(map[string]Province), + } + } + country := p.Countries[county_code] + + _, province_exist := country.Provinces[province_code] + if !province_exist { + country.Provinces[province_code] = Province{ + // ProvinceCode: province_code, + ProvinceName: province_name, + Cities: make(map[string]City), + } + } + province := country.Provinces[province_code] + + province.Cities[city_code] = City{ + CityName: city_name, + // CityCode: city_code, + } + } + + return p +} + +func (p *Places) Place_Exist(place_code string) bool { + codes := strings.Split(place_code, "-") + + switch len(codes) { + case 1: + _, e := p.Countries[codes[0]] + return e + + case 2: + c, e := p.Countries[codes[1]] + if !e { + return false + } + _, e = c.Provinces[codes[0]] + return e + + case 3: + c, e := p.Countries[codes[2]] + if !e { + return false + } + p, e := c.Provinces[codes[1]] + if !e { + return false + } + _, e = p.Cities[codes[0]] + return e + + default: + return false + } +} + +func (p *Places) Get_Name_From_Codes(code string) { + codes := strings.Split(code, "-") + + switch len(codes) { + case 1: + c := p.Countries[codes[0]] + fmt.Println(c.CountyName) + + case 2: + c := p.Countries[codes[1]] + p := c.Provinces[codes[0]] + + fmt.Println(p.ProvinceName, ",", c.CountyName) + + case 3: + c := p.Countries[codes[2]] + p := c.Provinces[codes[1]] + ci := p.Cities[codes[0]] + fmt.Println(ci.CityName, ",", p.ProvinceName, ",", c.CountyName) + + default: + fmt.Println("Invalid code") + } +} + +func (p *Places) Print() { + for _, country := range p.Countries { + fmt.Println("county:", country.CountyName) + for _, province := range country.Provinces { + fmt.Println(" province:", province.ProvinceName) + for _, city := range province.Cities { + fmt.Println(" city:", city.CityName) + } + } + } +} diff --git a/uitls.go b/uitls.go new file mode 100644 index 000000000..da835b4ed --- /dev/null +++ b/uitls.go @@ -0,0 +1,23 @@ +package main + +import ( + "encoding/csv" + "log" + "os" +) + +func Read_file(filename string) [][]string { + f, err := os.Open(filename) + if err != nil { + log.Panic(err.Error()) + } + + reader_csv := csv.NewReader(f) + data, err := reader_csv.ReadAll() + if err != nil { + log.Panic(err.Error()) + } + + data_wihtout_header := data[1:] + return data_wihtout_header +} From 94cecdc0c4752053f46b1b04844c4129a0dd72ca Mon Sep 17 00:00:00 2001 From: itohara Date: Tue, 25 Feb 2025 08:36:12 +0530 Subject: [PATCH 2/2] cleanup --- app.go | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/app.go b/app.go index 81ad0fd61..beb552305 100644 --- a/app.go +++ b/app.go @@ -37,11 +37,6 @@ func (app *App) Include_Permissions(name string, place_code string) error { err := fmt.Errorf("parent distrubutor has not the permissions") return err } - _, exist := distributor.Parent.IncludeRegions[place_code] - if exist { - fmt.Println("added permissions to ", name) - return nil - } } _, exist = distributor.ExcludeRegions[place_code] @@ -66,14 +61,6 @@ func (app *App) Exclude_Permissions(name, place_code string) error { return err } - if d.Parent != nil { - _, exist = d.Parent.ExcludeRegions[place_code] - if exist { - fmt.Println("added exclude permissions to ", name) - return nil - } - } - _, exist = d.IncludeRegions[place_code] if exist { delete(d.IncludeRegions, place_code) @@ -122,26 +109,14 @@ func (app *App) Get_Permissions(name string) error { return err } - has_parent := dist.Parent != nil - fmt.Println("INCLUDE") for k := range dist.IncludeRegions { app.Places.Get_Name_From_Codes(k) } - if has_parent { - for k := range dist.Parent.IncludeRegions { - app.Places.Get_Name_From_Codes(k) - } - } fmt.Println("==============================") fmt.Println("EXCLUDE") for k := range dist.ExcludeRegions { app.Places.Get_Name_From_Codes(k) } - if has_parent { - for k := range dist.Parent.ExcludeRegions { - app.Places.Get_Name_From_Codes(k) - } - } return nil }