软工作业——wc.exe(node.js)

GItHub地址 https://github.com/a130888599/WordCount

注意

  1. 请将所有测试文件放进test文件
  2. 为了规避各种各样的bug和控制文件大小,没有使用各种框架
  3. 由于原生node.js写GUI过于复杂,改用网页显示

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 20
· Estimate · 估计这个任务需要多少时间 10 20
Development 开发 42460(4天) 32460(3天)
· Analysis · 需求分析 (包括学习新技术) 2*60 6*60
· Design Spec · 生成设计文档 4*60 3*60
· Design Review · 设计复审 (和同事审核设计文档) 3*60 2*60
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 5*60 6*60
· Design · 具体设计 7*60 7*60
· Coding · 具体编码 4*60 6*60
· Code Review · 代码复审 2*60 3*60
· Test · 测试(自我测试,修改代码,提交修改) 2*60 7*60
Reporting 报告 3*60 3*60
· Test Report · 测试报告 2*60 2*60
· Size Measurement · 计算工作量 15 30
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 60
合计 3 * 24 * 60(3天) 4 * 24 * 60(4天)

题目要求

• 实现一个简单完整的软件工具
• 进行单元测试,回归测试,效能测试
• 记录自己在每个环节花费的时间

软件功能

能够统计文本文件的字符数,单词数,行数等,最好具备其他扩展功能,能够快速处理多个文件
命令行格式:wc.exe [parameter] [file_name]
功能:
• -c:返回字符数
• -w:返回词数
• -l:返回行数
• -s:递归处理目录下符合条件的所有文件
• -a:返回代码行数,空行数,注释行数
• -x:打开图形化界面

解题思路

分析了一下需求和自己的技术栈,决定使用node.js进行开发

难点

项目的难点主要有

  1. 如何支持通配符(*,?)
  2. 多参数之间的关系,比如 wc.exe -s -a -l test.c
  3. 读取文件是异步操作,怎么保证程序的顺序执行
  4. GUI如何实现

方法

  1. 由于通配符只有两个,可以在获取到文件名时,先判断是否含有通配符,因此就有三种处理情况:*,?,普通文件,分别设置判断函数就行。
  2. 多参数之间,基本是没有相互影响的,比如-c, -l,但有一种情况是相互影响的,就是含有-s的,因此需要先判断是否有-s参数。
  3. 异步操作,可以使用Promise保证其顺序执行
  4. 改用网页呈现

代码实现

index.js

// 判断参数中是否有-x,有则打开server.js
function isOpenGUI(params) {
  for (let param of params) {
    if (param == '-x') {
      c.fork(__dirname + '/server.js')
      console.log(__dirname + '/server.js');
    }
  }
}
isOpenGUI(params)

// 判断是不是全选文件
function isSearchAllFiles(fileName) {
  // 如果选择的是全部文件
  if (fileName.includes('*')) {
    const fileType = fileName.substr(fileName.indexOf('.'))
    const files = fs.readdirSync('./test')
    return files.filter(item => item.includes(fileType))
  } else {
    return [fileName]
  }
}

const isIncludeS = includeS(params)
newParams = params.filter(item => item != '-s')
const fileArr = isIncludeS ? fs.readdirSync('./test') : isSearchAllFiles(fileName)

// 遍历每一个符合要求的文件,分别传入参数
fileArr.map((file) => {
  let filePath = `${__dirname}/test/${file}`
  newParams.map(async (item) => {
    await fileDetail(item, filePath, file)
  })
})

各功能实现

// -c
function getFileCharsNum(data) {
  const str = removeEscapeChar(data).join('') // 获得处理掉转义符后的字符串
  console.log(`${fileName} 的字符数为:${str.length}`);
  return {
    res: str.length
  }
}

// -l
function getFileLinesNum(data) {
  const arr = removeEscapeChar(data)
  // console.log(arr);
  console.log(`${fileName} 的行数为:${arr.length}`);
  return {
    res: arr.length
  }
}

// -w
function getFileWordsNum(data) {
  const arr = removeEscapeChar(data) // 获取处理掉转义符后的数组,每个元素代表一行
  let res = 0;
  arr.map((item) => { // 通过空格判断单词
    let isSpace = true
    for (let i = 0; i < item.length; i++) {
      if (item[i] == ' ')
        isSpace = true
      else {
        if (isSpace)
          res++
        isSpace = false
      }
    }
  })
  console.log(`${fileName} 的单词数为:${res}`);
  return {
    res
  }
}

// -a
function getFileAllNum(data) {
  let [spaceLines, commentLines, codeLines] = [0, 0, 0]
  const arr = removeEscapeChar(data)
  arr.map((item) => {
    if (isNullLine(item))
      spaceLines++
    else if (isCommentLine(item))
      commentLines++
    else
      codeLines++
  })
  console.log(`${fileName} 的空行数为:${spaceLines}`);
  console.log(`${fileName} 的注释行数为:${commentLines}`);
  console.log(`${fileName} 的代码行数为:${codeLines}`);
  return {
    res: {
      spaceLines: spaceLines,
      commentLines: commentLines,
      codeLines: codeLines
    }
  }
}

// 判断是否为空行
function isNullLine(str) {
  if (str == '')
    return true
  return false
}

// 处理字符串,把转义符去除,返回每行的数据
function removeEscapeChar(str) {
  let [arr, newArr] = [str.split('\r'), []] // 将\r去除
  arr.map((item) => {
    newArr.push(item.trim()) // 将\n去除
  })
  return newArr
}

// 判断是否为代码行,只能判断//,不能判断/**/,/** */和某些编程语言的特定语法
function isCommentLine(str) {
  if (str.includes('//')) // 未找到
    return true
  return false
}

// 设置文件名
function setFileName(name) {
  fileName = name
}

// 获取文件内容
async function getFileDetail(parameters, filePath, file) {
  try {
    await fs.readFile(filePath, 'utf-8', async (error, data) => {
      if (error) {
        console.log(error);
        return
      }
      setFileName(file)
      switch (parameters) {
        case '-c': {
          getFileCharsNum(data)
          return
        }
        case '-w': {
          getFileWordsNum(data)
          return
        }
        case '-l': {
          getFileLinesNum(data)
          return
        }
        case '-a': {
          getFileAllNum(data)
          return
        }
        default:
          console.log('无此命令,请重新输入');
      }
    })
    return new Promise(res => {
      res(true)
    })
  } catch (error) {
    console.log("ERROR: ", error);
  }
}

module.exports = {
  getFileCharsNum,
  getFileLinesNum,
  getFileWordsNum,
  getFileAllNum,
  setFileName
}

服务器代码

const server = http.createServer((req, res) => {
  let url_obj = url.parse(req.url, true);
  //进入路由判断并返回相应文件
  route(url_obj, req, res);
})

server.listen(8081, () => {
  console.log('服务器已开启8081')
  c.exec('start http://localhost:8081/');
})

function route(url_obj, req, res) {
  let pathname = url_obj.pathname;
  switch (pathname) {
    case '/': {
      openHTML(pathname, res)
      return 
    }
    case '/getAllFile': {
      console.log('hhhhhh');
      getAllFileDetail(res)
      return
    }
    default: {
      let data = fs.readFileSync(`./web${req.url}`);
      res.end(data);
      return 
    }
  }
}

// 读取html文件
function openHTML(filmName, res) {
  if (filmName == '/') { //如果是主页
    data = fs.readFileSync(`./web/index.html`, 'utf-8');
  } else {
    data = fs.readFileSync(`./web${filmName}`, 'utf-8'); //异步读取会导致undefined
  }
  res.end(data);
}

// web返回:全部数据
function getAllFileDetail(res) {
  const filesArr = fs.readdirSync('./test')
  let resArr = []
  filesArr.map(item => {
    console.log('item :', item);
    setFileName(item)
    let data = fs.readFileSync(`./test/${item}`, 'utf-8')
    let resValue = {
      fileName: item,
      value: {
        charsNum: getFileCharsNum(data),
        LinesNum: getFileLinesNum(data),
        WordsNum: getFileWordsNum(data),
        AllNum: getFileAllNum(data)
      }
    }
    resArr.push(resValue)
  })
  res.end(JSON.stringify(resArr))
}

测试运行

样例:file.c

  • 测试1:-c -l -w -a

  • 测试2:-s -c

  • 测试3:通配符

  • 测试4:-x

  • 测试5:不按规则输入

项目小结

  1. 字符串识别和处理的时候,使用正则表达式更合适,可惜还没学会
  2. 代码有冗余重复,没有整理完成,可以再优化
  3. PSP预计时间不准确,有待锻炼
  4. 没有设计好文件分类,难以扩展功能,用户体验较差,页面设计过于简单
posted @ 2020-03-23 22:02  Caines  阅读(186)  评论(0编辑  收藏  举报