{"id":17838001,"url":"https://github.com/sampoder/group-creator","last_synced_at":"2025-09-14T14:32:19.555Z","repository":{"id":55585415,"uuid":"331159332","full_name":"sampoder/group-creator","owner":"sampoder","description":"👩‍👩‍👦‍👦 A CLI tool to generate groups for teachers.","archived":false,"fork":false,"pushed_at":"2021-01-22T14:12:09.000Z","size":21,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-27T23:48:37.365Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sampoder.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-01-20T01:36:39.000Z","updated_at":"2024-05-31T18:41:05.000Z","dependencies_parsed_at":"2022-08-15T03:40:43.128Z","dependency_job_id":null,"html_url":"https://github.com/sampoder/group-creator","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sampoder%2Fgroup-creator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sampoder%2Fgroup-creator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sampoder%2Fgroup-creator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sampoder%2Fgroup-creator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sampoder","download_url":"https://codeload.github.com/sampoder/group-creator/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229788139,"owners_count":18124195,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-27T20:49:44.988Z","updated_at":"2024-12-15T06:21:27.521Z","avatar_url":"https://github.com/sampoder.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Group Creator\n\nThere are two files in here `index.js` \u0026 `clean.js`. \n\n`index.js` contains a program that uses a few external dependencies to make the displays. `cli-table` is used to create a table. `chalk` to display coloured text in the terminal. `inquirer` is used to handle user input. Lastly, `gradient-string` is used to display coloured gradients in the terminal.\n\nBefore running `index.js` please run `yarn` or `npm install` to install the dependencies.\n\nIt can then be ran by running `node index.js [FILEPATH]`\n\n`clean.js` contains a program that uses no external dependencies. It only imports `fs` and `readline` which are built into Node.js.\n\nIt can then be ran by running `node clean.js [FILEPATH] [HEADER TO SORT BY] [ORDER TO SORT BY: asc/desc] [REMINDER HANDLING: redistribute/separate] [AMOUNT OF PEOPLE PER GROUP]`\n\nTo download the project files visit [go.sampoder.com/nlcs](https://go.sampoder.com/nlcs)\n\nNote: in the code comments I refer to headers. Both JSON \u0026 CSV data types name these differently. A header refers to something such as \"Name\" similar to a spreadsheet header.\n\n**`index.js`**\n\n```javascript\n// Import modules used for the program.\n\nconst fs = require(\"fs\"); // Used to read files\nconst gradient = require(\"gradient-string\"); // Used to display gradients in terminal\nconst chalk = require(\"chalk\"); // Used to style output\nconst readline = require(\"readline\"); // Used here to clear the terminal display\nconst inquirer = require(\"inquirer\"); // Used to handle user input\nconst cliTable = require(\"cli-table\"); // Helper module used to display a table in the terminal\n\n// Define critical variables\n\nconst filePath = process.argv[2]; // Reads file path passed through arguments\nlet studentData; // Mutable variable we'll use later\nlet headers; // Mutable variable we'll use later\n\n// Define functions we'll need to use\n\nfunction blankScreen() {\n  // Function that we'll use to show simply a blank screen\n  readline.cursorTo(process.stdout, 0, 0); // Moves the user cursor to 0,0\n  readline.clearScreenDown(process.stdout); // Clears the screen and make it blank\n}\n\nfunction displayHomeHeader() {\n  // Function that will print the header for the program\n  blankScreen(); // Calls out blank screen function\n  console.log(\"\\n\"); // Prints one new line for padding\n  console.log(\n    gradient(\"#338eda\", \"#5bc0de\").multiline(\n      // Creates a multiline gradient based on the string\n      [\n        \"  ██████╗ ██████╗  ██████╗ ██╗   ██╗██████╗\",\n        \" ██╔════╝ ██╔══██╗██╔═══██╗██║   ██║██╔══██╗   \",\n        \" ██║  ███╗██████╔╝██║   ██║██║   ██║██████╔╝  \",\n        \" ██║   ██║██╔══██╗██║   ██║██║   ██║██╔═══╝    \", // ASCII text used to make big text that stands out\n        \" ╚██████╔╝██║  ██║╚██████╔╝╚██████╔╝██║        \",\n        \"  ╚═════╝ ╚═╝  ╚═╝ ╚═════╝  ╚═════╝ ╚═╝        \",\n        \"                                                \",\n        \" ██████╗██████╗ ███████╗ █████╗ ████████╗ ██████╗ ██████╗ \",\n        \"██╔════╝██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗\",\n        \"██║     ██████╔╝█████╗  ███████║   ██║   ██║   ██║██████╔╝\",\n        \"██║     ██╔══██╗██╔══╝  ██╔══██║   ██║   ██║   ██║██╔══██╗\",\n        \"╚██████╗██║  ██║███████╗██║  ██║   ██║   ╚██████╔╝██║  ██║\",\n        \" ╚═════╝╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝\",\n      ].join(\"\\n\") // Joins this array of strings into one massive string\n    )\n  );\n  console.log(\"\\n\"); // Prints one new line for padding\n}\n\nfunction convertCSV(path) {\n  // A function that takes in the path to a CSV file and convert it to JSON\n  let data = fs.readFileSync(path, \"utf8\"); // Uses the fs module to get the text data of the csv in the utf8 format\n\n  let csvData = data.includes(\"\\n\") // Line that checks if the file uses \\n for new lines\n    ? data.replaceAll(\"\\r\", \"\").split(\"\\n\") // Some files use both. It replaces \\r with nothing to fix this. It then splits by \\n as in a CSV a new record is designated by a new line.\n    : data.split(\"\\r\"); // Does the same as the above line, just for files that use \\r\n\n  let headers = csvData[0] // In a csv file the first record are the headers.\n    .trim() // Remove any outer whitespace to clean up the final display\n    .split(\",\") // Split by the comma which is used in a csv to designate separate fields\n    .map((header) =\u003e header.trim()); // This then trims each field name to remove any outer whitespace that a field may have. (The above function only does the line in the file, this does each field)\n\n  csvData.shift(); // Removes the first record in the array. Which is out headers that have now been separated.\n\n  product = csvData.map((record) =\u003e {\n    // This map function then goes through each record in the array and then modifies it based on the below code\n    let jsonData = {}; // Creates an empty JSON object we'll be using below\n    record\n      .split(\",\") // Split by the comma which is used in a csv to designate separate fields\n      .map((dataPoint, index) =\u003e (jsonData[headers[index]] = dataPoint.trim())); // Use map again but this time instead to run the code inside of it for each record of the array. Here it's using the index to find the appropriate header and then set that in the JSON object.\n    return jsonData; // Return that new JSON data, that'll be what the record will now be\n  });\n\n  return [product, headers]; // Return an array with what we have just created\n}\n\nfunction sortData(data, header, order) {\n  // This function will take an array of JSON data, a header to sort by and then the order to sort by\n  let sorted = []; // Create an empty array\n  let unsorted = data; // Set out unsorted array as the incoming data\n\n  for (i in unsorted) {\n    // Look through each record in the unsorted array. This block of code will help as with sorting by adding extra data to our array.\n    unsorted[i].letters = []; // Create an empty array at letters in each JSON object\n    for (x in unsorted[i][header]) {\n      // Loop through every letter in the string attached to the JSON object for the specified header\n      unsorted[i].letters.push(\n        // Push to the array of letters we created\n        unsorted[i][header].toUpperCase().charCodeAt(x) \u003e 64 // This checks if the letter is a part of the alphabet or a number through ASCII codes.\n          ? unsorted[i][header].toUpperCase().charCodeAt(x) - 64 // This turns a letter into it's alphabet position using it's ASCII code. For example A has a code of 065 and now becomes 1.\n          : parseFloat(unsorted[i][header]) // To allow for sorting by custom headers this is a fallback for numbers\n      );\n    }\n  }\n\n  let current; // This variable will hold the JSON object of the current lowest / biggest\n\n  let currentIndex; // This variable holds the index of the current lowest\n\n  while (unsorted.length \u003e 0) {\n    // Loops until no more records are unsorted\n    for (i in unsorted) {\n      // Loops through each record in unsorted\n      if (!current) {\n        // Checks if there isn't currently a smallest / biggest\n        current = unsorted[i]; // If there isn't make this record the smallest / biggest so far\n        currentIndex = i;\n      } else {\n        if (order == \"asc\") {\n          // Check if the order is ascending\n          for (x in unsorted[i].letters) {\n            // Loops through each objects letter code we stored\n            if (!current.letters[x]) {\n              // If current smallest doesn't have this letter break the for loop. This is because Sam comes before Samuel for example.\n              break; // Breaks for loop.\n            } else if (unsorted[i].letters[x] \u003c current.letters[x]) {\n              // If the number value of the unsorted record is smaller run the block\n              current = unsorted[i]; // Switch smallest record so far\n              currentIndex = i;\n              break; // Breaks the for loop as we're now done.\n            } else if (unsorted[i].letters[x] \u003e current.letters[x]) {\n              // If the number value of the current record is smaller run the block\n              break; // Breaks the for loop as we're now done.\n            }\n          }\n        } else if (order == \"desc\") {\n          // Check if the order is descending\n          for (x in unsorted[i].letters) {\n            // Loops through each objects letter code we stored\n            if (!current.letters[x]) {\n              // If current biggest doesn't have this letter break the for loop. This is because Sam comes before Samuel for example.\n              break; // Breaks for loop.\n            } else if (unsorted[i].letters[x] \u003e current.letters[x]) {\n              // If the number value of the unsorted record is larger run the block\n              current = unsorted[i]; // Switch biggest record so far\n              currentIndex = i;\n              break; // Breaks the for loop as we're now done.\n            } else if (unsorted[i].letters[x] \u003c current.letters[x]) {\n              // If the number value of the current record is larger run the block\n              break; // Breaks the for loop as we're now done.\n            }\n          }\n        }\n      }\n    }\n    delete current.letters; // Remove the letters from the smallest / biggest record identified for a clean out put\n    sorted.push(current); // Place the smallest / biggest record in the sorted array\n    unsorted.splice(currentIndex, 1); // Remove the smallest / biggest record from the unsorted array\n    current = null; // Set these two as undefined to support the next run of this while loop\n    currentIndex = null;\n  }\n  return sorted; // Return the final sorted array!\n}\n\nfunction createGroups(data, size, redistributeRemainder) {\n  // Takes in the sorted JSON array, the group size and how to handle remaining students\n  size = data.length \u003e size ? size : data.length; // If the group size is greater then the amount of people provided, make one big group instead\n  const amountOfGroups = (data.length - (data.length % size)) / size; // Calculate the amount of groups to be used.\n  let remainderStudents = data.length \u003e size ? data.length % size : 0; // Calculate the students remaining\n  let offset = 0; // To be used as an offset below\n  let groups = []; // An empty array to store our groups\n  size += redistributeRemainder ? 1 : 0; // If the user wants us to redistribute the remaining students, make the groups bigger by one person. This acts as a potential size in the program.\n  \n  for (let x = 0; x \u003c amountOfGroups; x++) { // Loop for each group that should be made\n    let group = []; // Create an empty array for our group\n    for (let b = 0; b \u003c size \u0026\u0026 data[b + offset]; b++) { // Loop for each group member that should be added + whilst that group member is defined\n      group.push(data[b + offset]); // Push the group member to the array\n    }\n    offset += group.length; // Increase our offset by the size of the group\n    groups.push(group); // Add this group to the array of groups\n  }\n  if (!redistributeRemainder \u0026\u0026 remainderStudents != 0) { // If the user choose for us to create a separate group for remainder students + that there are reminder students, run this code\n    let group = []; // Create an empty array for this group\n    for (let b = 0; b \u003c remainderStudents; b++) { // Loop through each remainder student\n      group.push(data[amountOfGroups * size + b]); // Add these students to the group\n    }\n    groups.push(group); // Add this group to the array of groups\n  } \n  return groups; // Return an array of groups\n}\n\nfunction highlightText(text) {\n  // Function that will highlight text with chalk\n  return chalk.inverse.bold(` ${text} `); // Use chalk to highlight the text in the terminal \n}\n\nfunction displayGroups(groups, query) {\n  // This function will both display groups, but also tailor that display to any specific query provided.\n  blankScreen(); // Print a blank screen\n  console.log(\"\\n\"); // New line provided for padding\n  console.log(\n    gradient(\"#338eda\", \"#5bc0de\").multiline(\n      // Creates a multiline gradient based on the string\n      [\n        \" ██████╗ ██████╗  ██████╗ ██╗   ██╗██████╗ ███████╗\",\n        \"██╔════╝ ██╔══██╗██╔═══██╗██║   ██║██╔══██╗██╔════╝\",\n        \"██║  ███╗██████╔╝██║   ██║██║   ██║██████╔╝███████╗\", // ASCII text used to make big text that stands out\n        \"██║   ██║██╔══██╗██║   ██║██║   ██║██╔═══╝ ╚════██║\",\n        \"╚██████╔╝██║  ██║╚██████╔╝╚██████╔╝██║     ███████║\",\n        \" ╚═════╝ ╚═╝  ╚═╝ ╚═════╝  ╚═════╝ ╚═╝     ╚══════╝\",\n      ].join(\"\\n\") // Joins this array of strings into one massive string\n    )\n  );\n  console.log(\"\\n\"); // New line provided for padding\n  if (\n    !query || // if there is no query\n    query == \"CLEAR\" || // or the query is to CLEAR previous query\n    !(\n      (parseInt(query) \u0026\u0026 groups[parseInt(query) - 1]) ||\n      query.toUpperCase().charCodeAt(0) \u003e 64\n    ) // Or the query target can't be found\n  ) {\n    const table = new cliTable({ // create a table with CLITable\n      head: groups.map((record, index) =\u003e `Group ${index + 1}`), // for each group add a column header\n    });\n    for (i in groups[0]) { // loop through by the amount of members in group 1, which has to have the max amount\n      table.push(\n        groups.map((record, index) =\u003e (record[i] ? record[i].Name : \"\")) // add the person record of i, if it's not defined add a blank cell\n      );\n    }\n    console.log(table.toString()); // print a stringified version of the table\n  } else if (query.toUpperCase().charCodeAt(0) \u003e 64) { // This checks if it's a query consisting of letters and therefore searching for a student\n    for (i in groups) { // loop through each group\n      for (x in groups[i]) { // loop through group member\n        if (\n          groups[i][x].Name.trim().toUpperCase() == query.trim().toUpperCase() // check if this group member is the one searched for\n        ) {\n          console.log(`${groups[i][x].Name} is in Group ${parseInt(i) + 1} \\n`); // if so output which group they're in\n        }\n      }\n    }\n    const table = new cliTable({ // create a table with CLITable\n      head: groups.map((record, index) =\u003e `Group ${index + 1}`), // for each group add a column header\n    });\n    for (i in groups[0]) { // loop through by the amount of members in group 1, which has to have the max amount\n      table.push(\n        groups.map((record, index) =\u003e\n          record[i]\n            ? record[i].Name.trim().toUpperCase() == query.trim().toUpperCase()\n              ? `${highlightText(`${record[i].Name}`)}`\n              : record[i].Name\n            : \"\" // add the person record of i, if it's not defined add a blank cell and if it's the person searched for highlight them\n        )\n      );\n    }\n    console.log(table.toString()); // print a stringified version of the table\n  } else if (parseInt(query) \u0026\u0026 groups[parseInt(query) - 1]) { // check if the query is a number and therefore looking for a group\n    const table = new cliTable({\n      head: [highlightText(`Group ${query}`)].concat( \n        groups.map((record, index) =\u003e `Group ${index + 1}`) // for each group add a column header, at the start though add the column header for our queried group using concat which joins two arrays together\n      ),\n    });\n    for (i in groups[0]) { // loop through by the amount of members in group 1, which has to have the max amount\n      table.push(\n        [\n          groups[parseInt(query) - 1][i] // first add the group member at this number for our queried group\n            ? highlightText(groups[parseInt(query) - 1][i].Name) // highlight them\n            : \"\", // fall back if undefined \n        ].concat(\n          groups.map((record, index) =\u003e (record[i] ? record[i].Name : \"\")) // concentate on the rest of the cells in the row\n        )\n      );\n    }\n    console.log(table.toString()); // print a stringified version of the table\n  }\n  console.log(\"\\n\");\n  inquirer // create an inquirer prompt\n    .prompt([\n      {\n        name: \"query\",\n        message: \"Search (type CLEAR to clear query):\", // ask for a search query\n      },\n    ])\n    .then((answers) =\u003e {\n      displayGroups(groups, answers.query); // on receiving the answers, redisplay the groups with the new query\n    });\n}\n\nfunction askIntroQuestions() {\n  inquirer // Asks multiple inquirer prompts that we'll need\n    .prompt([\n      {\n        type: \"list\",\n        name: \"sortingHeader\",\n        message: `Thank you for importing ${highlightText(\n          filePath\n        )}, what would you like to sort by? \\n`,\n        choices: headers,\n      },\n      {\n        type: \"list\",\n        name: \"ascDesc\",\n        message: `How should we order those? \\n`,\n        choices: [\"Ascending\", \"Descending\"],\n      },\n      {\n        type: \"list\",\n        name: \"reminderHandling\",\n        message: `What would you like me to do with the reminder? \\n`,\n        choices: [\"Redistribute them\", \"Make a separate group\"],\n      },\n      {\n        name: \"number\",\n        message: \"How many students would you like per group?\",\n      },\n    ])\n    .then((answers) =\u003e {\n      if (!parseInt(answers.number)) {\n        console.log(\n          \"\\n Error! We couldn't recognize that number. Let's start again. \\n\"\n        );\n        askIntroQuestions(); // Restarts the process if provided an invalid number\n      } else {\n        studentData = sortData(\n          studentData,\n          answers.sortingHeader,\n          answers.ascDesc == \"Ascending\" ? \"asc\" : \"desc\"\n        ); // Calls our sort data function\n        let groups = createGroups(\n          studentData,\n          parseInt(answers.number),\n          answers.reminderHandling == \"Redistribute them\" ? true : false\n        ); // Creates groups with our now sorted data\n        displayGroups(groups); // Displays the new groups with out any query\n      }\n    });\n}\n\nif (!process.argv[2] || !process.argv[2].includes(\".csv\")) { // Check the required argument has been provided\n  console.log(\"No CSV file passed in arguments. Please pass a .csv file.\");\n} else {\n  [studentData, headers] = convertCSV(filePath); // collect both student data and the headers used\n  displayHomeHeader(); // display our big title\n  askIntroQuestions(); // start the intro questions process\n}\n\n// We're done! Thanks for checking this program out!\n```\n\n**`clean.js`**\n\n```javascript\n// Import modules used for the program.\n\nconst fs = require(\"fs\"); // Used to read files\nconst readline = require(\"readline\"); // Used to clear the display and handle user input\n\n// Define critical variables\n\nconst filePath = process.argv[2]; // Reads file path passed through arguments\nconst sortingHeader = process.argv[3]; // Reads header to sort by passed through arguments\nconst ascDesc = process.argv[4]; // Defines whether we should use ascending or descending order through arguments\nconst reminderHandling = process.argv[5]; // Defines method of reminder handling through arguments\nconst number = process.argv[6]; // Amount of people per group passed through arguments\nlet studentData; // Mutable variable we'll use later\nlet headers; // Mutable variable we'll use later\n\nlet rl = readline.createInterface({\n  input: process.stdin,\n  output: process.stdout,\n}); // Creates a readline instance for us to use later on\n\nfunction blankScreen() {\n  // Function that we'll use to show simply a blank screen\n  readline.cursorTo(process.stdout, 0, 0); // Moves the user cursor to 0,0\n  readline.clearScreenDown(process.stdout); // Clears the screen and make it blank\n}\n\nfunction displayHomeHeader() {\n  // Function that will print the header for the program\n  blankScreen(); // Calls out blank screen function\n  console.log(\"\\n\"); // Prints one new line for padding\n  console.log(\n    gradient(\"#338eda\", \"#5bc0de\").multiline(\n      // Creates a multiline gradient based on the string\n      [\n        \"  ██████╗ ██████╗  ██████╗ ██╗   ██╗██████╗\",\n        \" ██╔════╝ ██╔══██╗██╔═══██╗██║   ██║██╔══██╗   \",\n        \" ██║  ███╗██████╔╝██║   ██║██║   ██║██████╔╝  \",\n        \" ██║   ██║██╔══██╗██║   ██║██║   ██║██╔═══╝    \", // ASCII text used to make big text that stands out\n        \" ╚██████╔╝██║  ██║╚██████╔╝╚██████╔╝██║        \",\n        \"  ╚═════╝ ╚═╝  ╚═╝ ╚═════╝  ╚═════╝ ╚═╝        \",\n        \"                                                \",\n        \" ██████╗██████╗ ███████╗ █████╗ ████████╗ ██████╗ ██████╗ \",\n        \"██╔════╝██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗\",\n        \"██║     ██████╔╝█████╗  ███████║   ██║   ██║   ██║██████╔╝\",\n        \"██║     ██╔══██╗██╔══╝  ██╔══██║   ██║   ██║   ██║██╔══██╗\",\n        \"╚██████╗██║  ██║███████╗██║  ██║   ██║   ╚██████╔╝██║  ██║\",\n        \" ╚═════╝╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝\",\n      ].join(\"\\n\") // Joins this array of strings into one massive string\n    )\n  );\n  console.log(\"\\n\"); // Prints one new line for padding\n}\n\nfunction convertCSV(path) {\n  // A function that takes in the path to a CSV file and convert it to JSON\n  let data = fs.readFileSync(path, \"utf8\"); // Uses the fs module to get the text data of the csv in the utf8 format\n\n  let csvData = data.includes(\"\\n\") // Line that checks if the file uses \\n for new lines\n    ? data.replaceAll(\"\\r\", \"\").split(\"\\n\") // Some files use both. It replaces \\r with nothing to fix this. It then splits by \\n as in a CSV a new record is designated by a new line.\n    : data.split(\"\\r\"); // Does the same as the above line, just for files that use \\r\n\n  let headers = csvData[0] // In a csv file the first record are the headers.\n    .trim() // Remove any outer whitespace to clean up the final display\n    .split(\",\") // Split by the comma which is used in a csv to designate separate fields\n    .map((header) =\u003e header.trim()); // This then trims each field name to remove any outer whitespace that a field may have. (The above function only does the line in the file, this does each field)\n\n  csvData.shift(); // Removes the first record in the array. Which is out headers that have now been separated.\n\n  product = csvData.map((record) =\u003e {\n    // This map function then goes through each record in the array and then modifies it based on the below code\n    let jsonData = {}; // Creates an empty JSON object we'll be using below\n    record\n      .split(\",\") // Split by the comma which is used in a csv to designate separate fields\n      .map((dataPoint, index) =\u003e (jsonData[headers[index]] = dataPoint.trim())); // Use map again but this time instead to run the code inside of it for each record of the array. Here it's using the index to find the appropriate header and then set that in the JSON object.\n    return jsonData; // Return that new JSON data, that'll be what the record will now be\n  });\n\n  return [product, headers]; // Return an array with what we have just created\n}\n\nfunction sortData(data, header, order) {\n  // This function will take an array of JSON data, a header to sort by and then the order to sort by\n  let sorted = []; // Create an empty array\n  let unsorted = data; // Set out unsorted array as the incoming data\n\n  for (i in unsorted) {\n    // Look through each record in the unsorted array. This block of code will help as with sorting by adding extra data to our array.\n    unsorted[i].letters = []; // Create an empty array at letters in each JSON object\n    for (x in unsorted[i][header]) {\n      // Loop through every letter in the string attached to the JSON object for the specified header\n      unsorted[i].letters.push(\n        // Push to the array of letters we created\n        unsorted[i][header].toUpperCase().charCodeAt(x) \u003e 64 // This checks if the letter is a part of the alphabet or a number through ASCII codes.\n          ? unsorted[i][header].toUpperCase().charCodeAt(x) - 64 // This turns a letter into it's alphabet position using it's ASCII code. For example A has a code of 065 and now becomes 1.\n          : parseFloat(unsorted[i][header]) // To allow for sorting by custom headers this is a fallback for numbers\n      );\n    }\n  }\n\n  let current; // This variable will hold the JSON object of the current lowest / biggest\n\n  let currentIndex; // This variable holds the index of the current lowest\n\n  while (unsorted.length \u003e 0) {\n    // Loops until no more records are unsorted\n    for (i in unsorted) {\n      // Loops through each record in unsorted\n      if (!current) {\n        // Checks if there isn't currently a smallest / biggest\n        current = unsorted[i]; // If there isn't make this record the smallest / biggest so far\n        currentIndex = i;\n      } else {\n        if (order == \"asc\") {\n          // Check if the order is ascending\n          for (x in unsorted[i].letters) {\n            // Loops through each objects letter code we stored\n            if (!current.letters[x]) {\n              // If current smallest doesn't have this letter break the for loop. This is because Sam comes before Samuel for example.\n              break; // Breaks for loop.\n            } else if (unsorted[i].letters[x] \u003c current.letters[x]) {\n              // If the number value of the unsorted record is smaller run the block\n              current = unsorted[i]; // Switch smallest record so far\n              currentIndex = i;\n              break; // Breaks the for loop as we're now done.\n            } else if (unsorted[i].letters[x] \u003e current.letters[x]) {\n              // If the number value of the current record is smaller run the block\n              break; // Breaks the for loop as we're now done.\n            }\n          }\n        } else if (order == \"desc\") {\n          // Check if the order is descending\n          for (x in unsorted[i].letters) {\n            // Loops through each objects letter code we stored\n            if (!current.letters[x]) {\n              // If current biggest doesn't have this letter break the for loop. This is because Sam comes before Samuel for example.\n              break; // Breaks for loop.\n            } else if (unsorted[i].letters[x] \u003e current.letters[x]) {\n              // If the number value of the unsorted record is larger run the block\n              current = unsorted[i]; // Switch biggest record so far\n              currentIndex = i;\n              break; // Breaks the for loop as we're now done.\n            } else if (unsorted[i].letters[x] \u003c current.letters[x]) {\n              // If the number value of the current record is larger run the block\n              break; // Breaks the for loop as we're now done.\n            }\n          }\n        }\n      }\n    }\n    delete current.letters; // Remove the letters from the smallest / biggest record identified for a clean out put\n    sorted.push(current); // Place the smallest / biggest record in the sorted array\n    unsorted.splice(currentIndex, 1); // Remove the smallest / biggest record from the unsorted array\n    current = null; // Set these two as undefined to support the next run of this while loop\n    currentIndex = null;\n  }\n  return sorted; // Return the final sorted array!\n}\n\nfunction createGroups(data, size, redistributeRemainder) {\n  // Takes in the sorted JSON array, the group size and how to handle remaining students\n  size = data.length \u003e size ? size : data.length; // If the group size is greater then the amount of people provided, make one big group instead\n  const amountOfGroups = (data.length - (data.length % size)) / size; // Calculate the amount of groups to be used.\n  let remainderStudents = data.length \u003e size ? data.length % size : 0; // Calculate the students remaining\n  let offset = 0; // To be used as an offset below\n  let groups = []; // An empty array to store our groups\n  size += redistributeRemainder ? 1 : 0; // If the user wants us to redistribute the remaining students, make the groups bigger by one person. This acts as a potential size in the program.\n\n  for (let x = 0; x \u003c amountOfGroups; x++) {\n    // Loop for each group that should be made\n    let group = []; // Create an empty array for our group\n    for (let b = 0; b \u003c size \u0026\u0026 data[b + offset]; b++) {\n      // Loop for each group member that should be added + whilst that group member is defined\n      group.push(data[b + offset]); // Push the group member to the array\n    }\n    offset += group.length; // Increase our offset by the size of the group\n    groups.push(group); // Add this group to the array of groups\n  }\n  if (!redistributeRemainder \u0026\u0026 remainderStudents != 0) {\n    // If the user choose for us to create a separate group for remainder students + that there are reminder students, run this code\n    let group = []; // Create an empty array for this group\n    for (let b = 0; b \u003c remainderStudents; b++) {\n      // Loop through each remainder student\n      group.push(data[amountOfGroups * size + b]); // Add these students to the group\n    }\n    groups.push(group); // Add this group to the array of groups\n  }\n  return groups; // Return an array of groups\n}\n\nfunction displayGroups(groups, query) {\n  // This function will both display groups, but also tailor that display to any specific query provided.\n  blankScreen(); // Print a blank screen\n  console.log(\"\\n\"); // New line provided for padding\n  console.log(\n    [\n      \" ██████╗ ██████╗  ██████╗ ██╗   ██╗██████╗ ███████╗\",\n      \"██╔════╝ ██╔══██╗██╔═══██╗██║   ██║██╔══██╗██╔════╝\",\n      \"██║  ███╗██████╔╝██║   ██║██║   ██║██████╔╝███████╗\",\n      \"██║   ██║██╔══██╗██║   ██║██║   ██║██╔═══╝ ╚════██║\",\n      \"╚██████╔╝██║  ██║╚██████╔╝╚██████╔╝██║     ███████║\",\n      \" ╚═════╝ ╚═╝  ╚═╝ ╚═════╝  ╚═════╝ ╚═╝     ╚══════╝\",\n    ].join(\"\\n\") // Joins this array of strings into one massive string\n  );\n  console.log(\"\\n\"); // Print a new line for padding\n  if (\n    !query || // if there is no query\n    query == \"CLEAR\" || // or the query is to CLEAR previous query\n    !(\n      (parseInt(query) \u0026\u0026 groups[parseInt(query) - 1]) ||\n      query.toUpperCase().charCodeAt(0) \u003e 64\n    ) // Or the query target can't be found\n  ) {\n    console.log(\n      groups\n        .map(\n          (record, index) =\u003e\n            `Group ${index + 1}${Array(16 - `Group ${index + 1}`.length).join(\n              // using the length of the string make sure all headers are uniform size\n              \" \"\n            )}` // for each group print out a column header\n        )\n        .join(\"\") // join together all these strings for printing\n    );\n\n    for (i in groups[0]) {\n      // loop through by the amount of members in group 1, which has to have the max amount\n      let string = groups\n        .map(\n          (record, index) =\u003e\n            record[i]\n              ? `${record[i].Name}${Array(16 - record[i].Name.length).join(\n                  \" \"\n                )}`\n              : \"\" // add the person record of i, if it's not defined add a blank cell\n        )\n        .join(\"\"); // join all the strings together\n      console.log(string);\n    }\n  } else if (query.toUpperCase().charCodeAt(0) \u003e 64) {\n    // This checks if it's a query consisting of letters and therefore searching for a student\n    for (i in groups) {\n      // loop through each group\n      for (x in groups[i]) {\n        // loop through group member\n        if (\n          groups[i][x].Name.trim().toUpperCase() == query.trim().toUpperCase() // check if this group member is the one searched for\n        ) {\n          console.log(`${groups[i][x].Name} is in Group ${parseInt(i) + 1} \\n`); // if so output which group they're in\n        }\n      }\n    }\n  } else if (parseInt(query) \u0026\u0026 groups[parseInt(query) - 1]) {\n    // check if the query is a number and therefore looking for a group\n    console.log(`Group ${query}:`);\n    for (i in groups[parseInt(query) - 1]) {\n      // loop through each member of that group\n      console.log(groups[parseInt(query) - 1][i].Name); // print their name\n    }\n  }\n  console.log(\"\\n\");\n  rl.question(\"Search (type CLEAR to clear query): \", function (answer) {\n    // allow user input of a search query\n    displayGroups(groups, answer); // display groups based on the answer\n  });\n}\n\nfunction beginSort() {\n  if (\n    !parseInt(number) ||\n    !filePath ||\n    !filePath.includes(\".csv\") ||\n    !headers.includes(sortingHeader)\n  ) {\n    console.log(\n      \"\\n Error! Invalid arguments. Let's start again. \\n\" // Check for invalid arguments and alert the user\n    );\n  } else {\n    studentData = sortData(studentData, sortingHeader, ascDesc); // sort the data using our function\n    let groups = createGroups(\n      // create our groups using our function\n      studentData,\n      parseInt(number),\n      reminderHandling == \"redistribute\" ? true : false\n    );\n    displayGroups(groups); // display our groups\n  }\n}\n\nif (!process.argv[2] || !process.argv[2].includes(\".csv\")) {\n  // Check the required argument has been provided\n  console.log(\"No CSV file passed in arguments. Please pass a .csv file.\");\n} else {\n  [studentData, headers] = convertCSV(filePath); // collect both student data and the headers used\n  beginSort();\n}\n\n```\n\n---\n\nCreated by Sam Poder for the NLCS Scholarship Program's Computer Science test.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsampoder%2Fgroup-creator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsampoder%2Fgroup-creator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsampoder%2Fgroup-creator/lists"}