diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..f45d177 Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md index 7575abd..4152526 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ -# Getting Started +# A simple Chinese chatbot with weather request functionality using RASA framework -![](asset/xbot.jpg) +## Getting Started +Command line demo: +![](asset/example_1) ## Usage 1. Install require packages ``` curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - -poetry install python -m pip install -U pip pip3 install rasa-x --extra-index-url https://pypi.rasa.com/simple ``` @@ -22,13 +23,13 @@ refer to: [poetry documentation](https://python-poetry.org/docs/) and newest [of Otherwise, your model will be saved at /models/default ``` -rasa train --num-threads 4 +rasa train --num-threads 4 # multi-threads for efficient computation ``` -5. Run the raas action server: +5. Run the raas action server and interact with the bot on the command line interfact: ``` -rasa run actions +rasa run actions & rasa shell ``` diff --git a/actions/.DS_Store b/actions/.DS_Store new file mode 100644 index 0000000..38734ca Binary files /dev/null and b/actions/.DS_Store differ diff --git a/actions/action.py b/actions/action.py index 77d093d..527d670 100644 --- a/actions/action.py +++ b/actions/action.py @@ -6,7 +6,8 @@ from rasa_sdk.forms import FormAction from actions import third_chat -from actions.third_weather import get_weather_by_day +# from actions.third_weather import get_weather_by_day +from actions.qweather import get_weather_by_day from requests import ( ConnectionError, HTTPError, @@ -105,17 +106,30 @@ def get_text_weather_date(address, date_time, date_time_number): except (ConnectionError, HTTPError, TooManyRedirects, Timeout) as e: text_message = "{}".format(e) else: + # text_message_tpl = """ + # {} {} ({}) 的天气情况为:白天:{};夜晚:{};气温:{}-{} °C + # """ + # text_message = text_message_tpl.format( + # result['location']['name'], + # date_time, + # result['result']['date'], + # result['result']['text_day'], + # result['result']['text_night'], + # result['result']["high"], + # result['result']["low"], + # ) text_message_tpl = """ - {} {} ({}) 的天气情况为:白天:{};夜晚:{};气温:{}-{} °C + 我掐指一算,{} {} ({}) 的天气情况为:白天:{};夜晚:{};气温:{}-{} °C;湿度:{} """ text_message = text_message_tpl.format( - result['location']['name'], + result['location'], date_time, - result['result']['date'], - result['result']['text_day'], - result['result']['text_night'], - result['result']["high"], - result['result']["low"], + result['fxDate'], + result['textDay'], + result['textNight'], + result["tempMin"], + result["tempMax"], + result["humidity"] ) return text_message diff --git a/actions/qweather.py b/actions/qweather.py new file mode 100644 index 0000000..7f4e972 --- /dev/null +++ b/actions/qweather.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +""" + qweather.py + ~~~~~~~~~ + 使用和风天气数据查询天气 +""" + +import requests +import json + +KEY = '882e721338b4441dbf1b7e514029aaa8' # API key(私钥) +UID = "" # 用户ID, @todo: 当前并没有使用这个值,签名验证方式将使用到这个值 + +# LOCATION = 'beijing' # 所查询的位置,可以使用城市拼音、v3 ID、经纬度等 +API_loc_id = 'https://geoapi.qweather.com/v2/city/lookup?' # 城市id查询 +API = 'https://devapi.qweather.com/v7/weather/3d?' # API URL,可替换为其他 URL +# UNIT = 'c' # 单位 +# LANGUAGE = 'zh-Hans' # 查询结果的返回语言 + +def fetch_location_id(location): + result = requests.get(API_loc_id, params={ + 'key': KEY, + 'location': location, + }) + return result.json() + +def fetch_weather(location_id): + result = requests.get(API, params={ + 'key': KEY, + 'location': location_id, + }) + return result.json() + +def get_weather_by_day(location, day=1): + location_id = fetch_location_id(location)["location"][0]["id"] + result = fetch_weather(location_id) + normal_result = { + "location": location, + "fxDate": result["daily"][day]["fxDate"], + "tempMax": result["daily"][day]["tempMax"], + "tempMin": result["daily"][day]["tempMin"], + "textDay": result["daily"][day]["textDay"], + "textNight": result["daily"][day]["textNight"], + "humidity": result["daily"][day]["humidity"] + } + + return normal_result + +if __name__ == "__main__": + print(get_weather_by_day('上海', 1)) + print(json.dumps(get_weather_by_day('上海', 1), ensure_ascii=False)) \ No newline at end of file diff --git a/asset/.DS_Store b/asset/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/asset/.DS_Store differ diff --git a/asset/example.png b/asset/example.png new file mode 100644 index 0000000..88c69f7 Binary files /dev/null and b/asset/example.png differ diff --git a/asset/example_1.png b/asset/example_1.png new file mode 100644 index 0000000..8928184 Binary files /dev/null and b/asset/example_1.png differ diff --git a/data/.DS_Store b/data/.DS_Store new file mode 100644 index 0000000..88ab1cc Binary files /dev/null and b/data/.DS_Store differ diff --git a/data/nlu/.DS_Store b/data/nlu/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/data/nlu/.DS_Store differ diff --git a/data/nlu/number.yml b/data/nlu/number.yml deleted file mode 100644 index 4fac157..0000000 --- a/data/nlu/number.yml +++ /dev/null @@ -1,163 +0,0 @@ -version: "2.0" -nlu: -- intent: request_number - examples: | - - 帮我查一个[电话号码](type) - - 帮我查询[手机号](type) - - 查询[手机号码](type) - - 查询[电话号码](type) - - [手机号](type) - - [电话号](type) - - 查一个[手机](type) - - [13548501905](number) - - 号码是[19820618425](number) - - 查一下[19862618425](number) - - 查下[19862618425](number) - - 我要查的是[19860612425](number) - - 查询的号码是[11160222425](number) - - 我想查的号码是[12260222425](number) - - 帮我查询[电话号码](type),号码是[19820618425](number) - - 帮我查一个号码[19862618425](number) - - [12260222425](number)这[手机号](type) - - 查询[电话](type)[19862618425](number) - - 查询[电话号码](type)[19860612425](number) - - [电话号码](type)为[19860218425](number) - - [电话号](type)为[12860618425](number) - - 查[电话](type)[19820618425](number) - - 我想知道[电话号码](type)为[19860612425](number) - - 我想查[电话号码](type)[19860618422](number) - - 我要查下[电话号](type)[19822618425](number) - - 你好!请帮我查询一下[电话](type)[12260618425](number) - - 查一下[手机号码](type)[19862228425](number) - - 帮我查个[电话](type)[19860612222](number) - - 请告诉我[电话号码](type)为[19860222425](number) - - 查[电话](type)[11160222425](number) - - 查[电话号码](type)[19800222425](number) - - 查[手机号码](type)[12260222425](number) - - 查询[手机](type)[12260222425](number) - - [手机号](type)[12260222425](number) - - 我想查[身份证](type) - - 我要查下[身份证](type) - - 你好!请帮我查询一下[身份证](type) - - 帮我查一个[身份证号码](type) - - 帮我查询[身份证](type) - - 查询[身份证](type) - - 查询[身份证号码](type) - - [身份证](type) - - [身份证号码](type) - - [查身份证](type) - - [330781198509071514](number) - - [44010319790115345X](number) - - 请告诉我[33078119850907853X](number)这个号码是谁的 - - [110101199909073876](number)谁的号码 - - 查下[320102198708085599](number) - - 查[433335196712305681](number) - - 号码[430102199711012075](number) - - 帮我查下[110101199909073876](number) - - [110101199909073876](number)的个人信息 - - [433335196712305681](number)是谁 - - 身份证[433335196712305681](number) - - 查下号码[433335196712305681](number) - - 帮我查询身份证[441235196712305622](number) - - 查询身份证[441235192212305681](number) - - 我想查身份证[441222296712305681](number) - - 身份证号码为[441235196712302222](number) - - 查个身份证,号码是[33078119850907853X](number) -- intent: inform_business - examples: | - - [开房信息](business) - - [开房](business) - - [住宿信息](business) - - [住宿记录](business) - - 查一查[开房记录](business) - - 查[开房信息](business) - - [住宿信息](business)怎么样 - - 查下[住宿信息](business) - - 我想了解[开房记录](business) - - [违章记录](business) - - [违章](business) - - [违章信息](business) - - 查下[违章记录](business) - - 我想知道这个号码的[违章信息](business) - - 能告诉我他的[违章记录](business)吗 - - 最近的[违章记录](business) - - [犯罪记录](business) - - [犯罪信息](business) - - [违法记录](business) - - [犯罪](business) - - [违法](business) - - 我要查询这个人的[犯罪记录](business) - - 帮我查[犯罪记录](business) - - 查查[犯罪信息](business) - - 这个人有没有[违法](business) - - 告诉我[犯罪记录](business) - - 查[违法信息](business) - - 我要知道这个人是否有[违法犯罪](business) - - [教育经历](business) - - [教育情况](business) - - [教育](business) - - [教育信息](business) - - 查一查[教育经历](business) - - 这个人的[教育经历](business) - - 看下[教育情况](business) - - 查下[教育信息](business) - - 帮我查一查[教育径路](business) - - [出行轨迹](business) - - [出行信息](business) - - [出行路线](business) - - [出行](business) - - 查下[出行轨迹](business) - - 我想知道这个人的[出行信息](business) -- synonym: 身份证号码 - examples: | - - 身份证 - - 身份证号码 -- synonym: 电话号码 - examples: | - - 电话 - - 电话号 - - 电话号码 - - 手机 - - 手机号 - - 手机号码 -- synonym: 违章记录 - examples: | - - 违章 - - 违章信息 - - 违章记录 - - 违章处罚 - - 违章情况 -- synonym: 教育经历 - examples: | - - 教育 - - 教育经历 - - 教育信息 - - 教育情况 -- synonym: 出行轨迹 - examples: | - - 出行 - - 出行路线 - - 出行信息 - - 出行轨迹 - - 出行记录 -- synonym: 开房信息 - examples: | - - 开房记录 - - 开房 - - 住宿记录 - - 住宿信息 - - 开房信息 -- synonym: 犯罪记录 - examples: | - - 犯罪 - - 犯罪信息 - - 犯罪记录 - - 违法记录 - - 违法 - - 违法犯罪 -- regex: number - examples: | - - ([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx]) -- regex: phone_number - examples: | - - ((\d{3,4}-)?\d{7,8})|(((\+86)|(86))?(1)\d{10}) diff --git a/data/story/.DS_Store b/data/story/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/data/story/.DS_Store differ diff --git a/data/story/number.yml b/data/story/number.yml deleted file mode 100644 index 8e00610..0000000 --- a/data/story/number.yml +++ /dev/null @@ -1,470 +0,0 @@ -version: "2.0" -stories: -- story: happy path - steps: - - intent: greet - - action: utter_answer_greet - - intent: request_number - - action: number_form - - active_loop: number_form - - active_loop: null - - intent: thanks - - action: utter_noworries -- story: happy path - steps: - - intent: greet - - action: utter_answer_greet - - intent: request_number - - action: number_form - - active_loop: number_form - - slot_was_set: - - requested_slot: business - - slot_was_set: - - business: 开房记录 - - slot_was_set: - - business: 开房记录 - - intent: thanks - - action: utter_noworries -- story: happy path:item+number+business+business+business - steps: - - intent: request_number - entities: - - type: 身份证号码 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 身份证号码 - - slot_was_set: - - requested_slot: number - - slot_was_set: - - number: '431124199720139720' - - slot_was_set: - - type: 身份证号码 - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: inform_business - entities: - - business: 违章记录 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 身份证号码 - - slot_was_set: - - number: '431124199720139720' - - slot_was_set: - - business: 违章记录 - - slot_was_set: - - business: null - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: inform_business - entities: - - business: 开房信息 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 身份证号码 - - slot_was_set: - - number: '431124199720139720' - - slot_was_set: - - business: 开房信息 - - slot_was_set: - - business: null - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: inform_business - entities: - - business: 出行轨迹 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 身份证号码 - - slot_was_set: - - number: '431124199720139720' - - slot_was_set: - - business: 出行轨迹 - - slot_was_set: - - business: null - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: affirm - - action: utter_answer_affirm -- story: happy path:number+business+business+business - steps: - - intent: request_number - entities: - - number: '18902346721' - - action: number_form - - active_loop: number_form - - slot_was_set: - - number: '18902346721' - - slot_was_set: - - type: 电话号码 - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: inform_business - entities: - - business: 违章记录 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 电话号码 - - slot_was_set: - - number: '18902346721' - - slot_was_set: - - business: 违章记录 - - slot_was_set: - - business: null - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: inform_business - entities: - - business: 犯罪记录 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 电话号码 - - slot_was_set: - - number: '18902346721' - - slot_was_set: - - business: 犯罪记录 - - slot_was_set: - - business: null - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: inform_business - entities: - - business: 开房信息 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 电话号码 - - slot_was_set: - - number: '18902346721' - - slot_was_set: - - business: 开房信息 - - slot_was_set: - - business: null - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: affirm - - action: utter_answer_affirm -- story: unhappy:chit+answer - steps: - - intent: greet - - action: utter_answer_greet - - intent: request_number - entities: - - type: 身份证号码 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 身份证号码 - - slot_was_set: - - number: null - - slot_was_set: - - business: null - - slot_was_set: - - requested_slot: number - - intent: chitchat - entities: - - date_time: 今天 - - action: utter_chitchat - - action: number_form - - slot_was_set: - - requested_slot: number - - slot_was_set: - - number: '440123199087233467' - - slot_was_set: - - type: 身份证号码 - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: thanks - - action: utter_noworries -- story: very unhappy:chit + chit + answer - steps: - - intent: greet - - action: utter_answer_greet - - intent: request_number - entities: - - type: 电话号码 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 电话号码 - - slot_was_set: - - number: null - - slot_was_set: - - business: null - - slot_was_set: - - requested_slot: number - - intent: chitchat - - action: utter_chitchat - - action: number_form - - slot_was_set: - - requested_slot: number - - intent: chitchat - - action: utter_chitchat - - action: number_form - - slot_was_set: - - requested_slot: number - - slot_was_set: - - number: '18902346721' - - slot_was_set: - - type: 电话号码 - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: thanks - - action: utter_noworries -- story: very very unhappy:chitchat+chitchat+chitchat+answer - steps: - - intent: greet - - action: utter_answer_greet - - intent: request_number - entities: - - type: 电话号码 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 电话号码 - - slot_was_set: - - number: null - - slot_was_set: - - business: null - - slot_was_set: - - requested_slot: number - - intent: chitchat - - action: utter_chitchat - - action: number_form - - slot_was_set: - - requested_slot: number - - intent: chitchat - - action: utter_chitchat - - action: number_form - - slot_was_set: - - requested_slot: number - - intent: chitchat - - action: utter_chitchat - - action: number_form - - slot_was_set: - - requested_slot: number - - slot_was_set: - - number: '18902346721' - - slot_was_set: - - type: 电话号码 - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: thanks - - action: utter_noworries -- story: unhappy path:stop and really stop path - steps: - - intent: greet - - action: utter_answer_greet - - intent: request_number - entities: - - type: 身份证号码 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 身份证号码 - - slot_was_set: - - number: null - - slot_was_set: - - business: null - - slot_was_set: - - requested_slot: number - - intent: stop - - action: utter_ask_continue - - intent: deny - - action: action_deactivate_loop - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: thanks - - action: utter_noworries -- story: unhappy path:stop but continue path - steps: - - intent: greet - - action: utter_answer_greet - - intent: request_number - entities: - - type: 电话号码 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 电话号码 - - slot_was_set: - - number: null - - slot_was_set: - - business: null - - slot_was_set: - - requested_slot: number - - intent: stop - - action: utter_ask_continue - - intent: affirm - - action: number_form - - slot_was_set: - - requested_slot: number - - slot_was_set: - - number: '18902346721' - - slot_was_set: - - type: 电话号码 - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: thanks - - action: utter_noworries -- story: unhappy path:chitchat stop but continue path - steps: - - intent: greet - - action: utter_answer_greet - - intent: request_number - entities: - - type: 身份证号码 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 身份证号码 - - slot_was_set: - - number: null - - slot_was_set: - - business: null - - slot_was_set: - - requested_slot: number - - intent: chitchat - - action: utter_chitchat - - action: number_form - - slot_was_set: - - requested_slot: number - - intent: stop - - action: utter_ask_continue - - intent: affirm - - action: number_form - - slot_was_set: - - requested_slot: number - - slot_was_set: - - number: '440123199087233467' - - slot_was_set: - - type: 身份证号码 - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: thanks - - action: utter_noworries -- story: 'unhappy path: stop but continue and chitchat path' - steps: - - intent: greet - - action: utter_answer_greet - - intent: request_number - entities: - - type: 电话号码 - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 电话号码 - - slot_was_set: - - number: null - - slot_was_set: - - business: null - - slot_was_set: - - requested_slot: number - - intent: stop - - action: utter_ask_continue - - intent: affirm - - action: number_form - - slot_was_set: - - requested_slot: number - - intent: chitchat - - action: utter_chitchat - - action: number_form - - slot_was_set: - - requested_slot: number - - slot_was_set: - - number: '18902346721' - - slot_was_set: - - type: 电话号码 - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: thanks - - action: utter_noworries -- story: 'unhappy path: chitchat stop but continue and chitchat path' - steps: - - intent: greet - - action: utter_answer_greet - - intent: request_number - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 电话号码 - - slot_was_set: - - number: null - - slot_was_set: - - business: null - - slot_was_set: - - requested_slot: number - - intent: chitchat - - action: utter_chitchat - - action: number_form - - slot_was_set: - - requested_slot: number - - intent: stop - - action: utter_ask_continue - - intent: affirm - - action: number_form - - slot_was_set: - - requested_slot: number - - intent: chitchat - - action: utter_chitchat - - action: number_form - - slot_was_set: - - requested_slot: number - - slot_was_set: - - number: '18902346721' - - slot_was_set: - - type: 电话号码 - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: thanks - - action: utter_noworries -- story: 'unhappy path: chitchat, stop and really stop path' - steps: - - intent: greet - - action: utter_answer_greet - - intent: request_number - - action: number_form - - active_loop: number_form - - slot_was_set: - - type: 电话号码 - - slot_was_set: - - number: null - - slot_was_set: - - business: null - - slot_was_set: - - requested_slot: number - - intent: chitchat - - action: utter_chitchat - - action: number_form - - slot_was_set: - - requested_slot: number - - intent: stop - - action: utter_ask_continue - - intent: deny - - action: action_deactivate_loop - - active_loop: null - - slot_was_set: - - requested_slot: null - - intent: thanks - - action: utter_noworries diff --git a/domain.yml b/domain.yml index 082d781..a054748 100644 --- a/domain.yml +++ b/domain.yml @@ -1,49 +1,50 @@ +version: '2.0' +config: + store_entities_as_slots: true session_config: session_expiration_time: 60 carry_over_slots_to_new_session: true intents: -- affirm -- deny -- greet -- goodbye -- thanks -- whoareyou -- whattodo -- request_weather -- request_number -- inform -- inform_business -- stop -- chitchat +- affirm: + use_entities: true +- deny: + use_entities: true +- greet: + use_entities: true +- goodbye: + use_entities: true +- thanks: + use_entities: true +- whoareyou: + use_entities: true +- whattodo: + use_entities: true +- request_weather: + use_entities: true +- inform: + use_entities: true +- stop: + use_entities: true +- chitchat: + use_entities: true entities: - date_time - address -- type -- number -- business slots: - date_time: - type: unfeaturized - auto_fill: false - influence_conversation: false address: - type: unfeaturized - auto_fill: false - influence_conversation: false - type: - type: unfeaturized - auto_fill: false - influence_conversation: false - number: - type: unfeaturized + type: rasa.shared.core.slots.UnfeaturizedSlot + initial_value: null auto_fill: false influence_conversation: false - business: - type: unfeaturized + date_time: + type: rasa.shared.core.slots.UnfeaturizedSlot + initial_value: null auto_fill: false influence_conversation: false requested_slot: - type: unfeaturized + type: rasa.shared.core.slots.UnfeaturizedSlot + initial_value: null + auto_fill: true influence_conversation: false responses: utter_answer_affirm: @@ -51,32 +52,24 @@ responses: - text: 嗯嗯,很开心能够帮您解决问题~ - text: 嗯嗯,还需要什么我能够帮助您的呢? utter_answer_greet: - - text: 您好!请问我可以帮到您吗? - - text: 您好!很高兴为您服务。请说出您要查询的功能? + - text: 您好!请问您要咨询的问题是什么呢? + - text: 您好!很高兴为您服务。 utter_answer_goodbye: - - text: 再见 - - text: 拜拜 - - text: 虽然我有万般舍不得,但是天下没有不散的宴席~祝您安好! - text: 期待下次再见! - text: 嗯嗯,下次需要时随时记得我哟~ - - text: see you! utter_answer_deny: - - text: 主人,您不开心吗?不要离开我哦 - - text: 怎么了,主人? + - text: 哦哦,那您觉得该如何呢? utter_answer_thanks: - text: 嗯呢。不用客气~ - - text: 这是我应该做的,主人~ - - text: 嗯嗯,合作愉快! + - text: 不用客气~ utter_answer_whoareyou: - - text: 您好!我是小蒋呀,您的AI智能助理 + - text: 您好!我是小刚,您的AI智能咨询顾问。更多可咨询内容尽请期待! utter_answer_whattodo: - - text: 您好!很高兴为您服务,我目前只支持查询天气哦。 + - text: 您好!很高兴为您服务。我目前只支持查询天气哦。 utter_ask_date_time: - text: 请问您要查询哪一天的天气? utter_ask_address: - text: 请问您要查下哪里的天气? - utter_ask_number: - - text: 请问您要查的{type}号码是多少? utter_ask_business: - text: 请问您要查询什么业务呢? utter_default: @@ -85,17 +78,12 @@ responses: - text: 请问您还要继续吗? utter_noworries: - text: 不用客气 :) - - text: 没事啦 - text: 不客气哈,都是老朋友了 :) utter_wrong_business: - text: 当前还不支持{business}业务,请重新输入。 - utter_wrong_type: - - text: 当前还不支持查询{type}。 - utter_wrong_number: - - text: 您输入的{number}有误,请重新输入。 utter_chitchat: - - text: 呃呃呃呃呃 - - text: 您这是在尬聊吗? + - text: 啊啊啊,您在说什么呀~ + - text: 哈哈哈,您好有趣~ actions: - utter_answer_affirm - utter_answer_deny @@ -106,12 +94,14 @@ actions: - utter_answer_whattodo - utter_ask_date_time - utter_ask_address -- utter_ask_number - utter_ask_business -- utter_ask_type +- utter_default +- utter_ask_continue +- utter_noworries +- utter_wrong_business +- utter_chitchat - action_default_fallback - weather_form -- number_form forms: weather_form: {} - number_form: {} +e2e_actions: [] diff --git a/get-poetry.py b/get-poetry.py new file mode 100644 index 0000000..e45a167 --- /dev/null +++ b/get-poetry.py @@ -0,0 +1,1086 @@ +""" +This script will install Poetry and its dependencies +in isolation from the rest of the system. + +It does, in order: + + - Downloads the latest stable (or pre-release) version of poetry. + - Downloads all its dependencies in the poetry/_vendor directory. + - Copies it and all extra files in $POETRY_HOME. + - Updates the PATH in a system-specific way. + +There will be a `poetry` script that will be installed in $POETRY_HOME/bin +which will act as the poetry command but is slightly different in the sense +that it will use the current Python installation. + +What this means is that one Poetry installation can serve for multiple +Python versions. +""" +import argparse +import hashlib +import json +import os +import platform +import re +import shutil +import stat +import subprocess +import sys +import tarfile +import tempfile + +from contextlib import closing +from contextlib import contextmanager +from functools import cmp_to_key +from gzip import GzipFile +from io import UnsupportedOperation +from io import open + + +try: + from urllib.error import HTTPError + from urllib.request import Request + from urllib.request import urlopen +except ImportError: + from urllib2 import HTTPError + from urllib2 import Request + from urllib2 import urlopen + +try: + input = raw_input +except NameError: + pass + + +try: + try: + import winreg + except ImportError: + import _winreg as winreg +except ImportError: + winreg = None + +try: + u = unicode +except NameError: + u = str + +SHELL = os.getenv("SHELL", "") +WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") + + +FOREGROUND_COLORS = { + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "white": 37, +} + +BACKGROUND_COLORS = { + "black": 40, + "red": 41, + "green": 42, + "yellow": 43, + "blue": 44, + "magenta": 45, + "cyan": 46, + "white": 47, +} + +OPTIONS = {"bold": 1, "underscore": 4, "blink": 5, "reverse": 7, "conceal": 8} + + +def style(fg, bg, options): + codes = [] + + if fg: + codes.append(FOREGROUND_COLORS[fg]) + + if bg: + codes.append(BACKGROUND_COLORS[bg]) + + if options: + if not isinstance(options, (list, tuple)): + options = [options] + + for option in options: + codes.append(OPTIONS[option]) + + return "\033[{}m".format(";".join(map(str, codes))) + + +STYLES = { + "info": style("green", None, None), + "comment": style("yellow", None, None), + "error": style("red", None, None), + "warning": style("yellow", None, None), +} + + +def is_decorated(): + if platform.system().lower() == "windows": + return ( + os.getenv("ANSICON") is not None + or "ON" == os.getenv("ConEmuANSI") + or "xterm" == os.getenv("Term") + ) + + if not hasattr(sys.stdout, "fileno"): + return False + + try: + return os.isatty(sys.stdout.fileno()) + except UnsupportedOperation: + return False + + +def is_interactive(): + if not hasattr(sys.stdin, "fileno"): + return False + + try: + return os.isatty(sys.stdin.fileno()) + except UnsupportedOperation: + return False + + +def colorize(style, text): + if not is_decorated(): + return text + + return "{}{}\033[0m".format(STYLES[style], text) + + +@contextmanager +def temporary_directory(*args, **kwargs): + try: + from tempfile import TemporaryDirectory + except ImportError: + name = tempfile.mkdtemp(*args, **kwargs) + + yield name + + shutil.rmtree(name) + else: + with TemporaryDirectory(*args, **kwargs) as name: + yield name + + +def string_to_bool(value): + value = value.lower() + + return value in {"true", "1", "y", "yes"} + + +def expanduser(path): + """ + Expand ~ and ~user constructions. + + Includes a workaround for http://bugs.python.org/issue14768 + """ + expanded = os.path.expanduser(path) + if path.startswith("~/") and expanded.startswith("//"): + expanded = expanded[1:] + + return expanded + + +HOME = expanduser("~") +POETRY_HOME = os.environ.get("POETRY_HOME") or os.path.join(HOME, ".poetry") +POETRY_BIN = os.path.join(POETRY_HOME, "bin") +POETRY_ENV = os.path.join(POETRY_HOME, "env") +POETRY_LIB = os.path.join(POETRY_HOME, "lib") +POETRY_LIB_BACKUP = os.path.join(POETRY_HOME, "lib-backup") + + +BIN = """# -*- coding: utf-8 -*- +import glob +import sys +import os + +lib = os.path.normpath(os.path.join(os.path.realpath(__file__), "../..", "lib")) +vendors = os.path.join(lib, "poetry", "_vendor") +current_vendors = os.path.join( + vendors, "py{}".format(".".join(str(v) for v in sys.version_info[:2])) +) + +sys.path.insert(0, lib) +sys.path.insert(0, current_vendors) + +if __name__ == "__main__": + from poetry.console import main + + main() +""" + +BAT = u('@echo off\r\n{python_executable} "{poetry_bin}" %*\r\n') + + +PRE_MESSAGE = """# Welcome to {poetry}! + +This will download and install the latest version of {poetry}, +a dependency and package manager for Python. + +It will add the `poetry` command to {poetry}'s bin directory, located at: + +{poetry_home_bin} + +{platform_msg} + +You can uninstall at any time by executing this script with the --uninstall option, +and these changes will be reverted. +""" + +PRE_UNINSTALL_MESSAGE = """# We are sorry to see you go! + +This will uninstall {poetry}. + +It will remove the `poetry` command from {poetry}'s bin directory, located at: + +{poetry_home_bin} + +This will also remove {poetry} from your system's PATH. +""" + + +PRE_MESSAGE_UNIX = """This path will then be added to your `PATH` environment variable by +modifying the profile file{plural} located at: + +{rcfiles}""" + + +PRE_MESSAGE_FISH = """This path will then be added to your `PATH` environment variable by +modifying the `fish_user_paths` universal variable.""" + +PRE_MESSAGE_WINDOWS = """This path will then be added to your `PATH` environment variable by +modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key.""" + +PRE_MESSAGE_NO_MODIFY_PATH = """This path needs to be in your `PATH` environment variable, +but will not be added automatically.""" + +POST_MESSAGE_UNIX = """{poetry} ({version}) is installed now. Great! + +To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH` +environment variable. Next time you log in this will be done +automatically. + +To configure your current shell run `source {poetry_home_env}` +""" + +POST_MESSAGE_FISH = """{poetry} ({version}) is installed now. Great! + +{poetry}'s bin directory ({poetry_home_bin}) has been added to your `PATH` +environment variable by modifying the `fish_user_paths` universal variable. +""" + +POST_MESSAGE_WINDOWS = """{poetry} ({version}) is installed now. Great! + +To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` +environment variable. Future applications will automatically have the +correct environment, but you may need to restart your current shell. +""" + +POST_MESSAGE_UNIX_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! + +To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH` +environment variable. + +To configure your current shell run `source {poetry_home_env}` +""" + +POST_MESSAGE_FISH_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! + +To get started you need {poetry}'s bin directory ({poetry_home_bin}) +in your `PATH` environment variable, which you can add by running +the following command: + + set -U fish_user_paths {poetry_home_bin} $fish_user_paths +""" + +POST_MESSAGE_WINDOWS_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! + +To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` +environment variable. This has not been done automatically. +""" + + +class Installer: + + CURRENT_PYTHON = sys.executable + CURRENT_PYTHON_VERSION = sys.version_info[:2] + METADATA_URL = "https://pypi.org/pypi/poetry/json" + VERSION_REGEX = re.compile( + r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?" + "(" + "[._-]?" + r"(?:(stable|beta|b|rc|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*)?)?" + "([.-]?dev)?" + ")?" + r"(?:\+[^\s]+)?" + ) + + REPOSITORY_URL = "https://github.com/python-poetry/poetry" + BASE_URL = REPOSITORY_URL + "/releases/download/" + FALLBACK_BASE_URL = "https://github.com/sdispater/poetry/releases/download/" + + def __init__( + self, + version=None, + preview=False, + force=False, + modify_path=True, + accept_all=False, + file=None, + base_url=BASE_URL, + ): + self._version = version + self._preview = preview + self._force = force + self._modify_path = modify_path + self._accept_all = accept_all + self._offline_file = file + self._base_url = base_url + + def allows_prereleases(self): + return self._preview + + def run(self): + version, current_version = self.get_version() + + if version is None: + return 0 + + self.customize_install() + self.display_pre_message() + self.ensure_home() + + try: + self.install( + version, upgrade=current_version is not None, file=self._offline_file + ) + except subprocess.CalledProcessError as e: + print(colorize("error", "An error has occured: {}".format(str(e)))) + print(e.output.decode()) + + return e.returncode + + self.display_post_message(version) + + return 0 + + def uninstall(self): + self.display_pre_uninstall_message() + + if not self.customize_uninstall(): + return + + self.remove_home() + self.remove_from_path() + + def get_version(self): + current_version = None + if os.path.exists(POETRY_LIB): + with open( + os.path.join(POETRY_LIB, "poetry", "__version__.py"), encoding="utf-8" + ) as f: + version_content = f.read() + + current_version_re = re.match( + '(?ms).*__version__ = "(.+)".*', version_content + ) + if not current_version_re: + print( + colorize( + "warning", + "Unable to get the current Poetry version. Assuming None", + ) + ) + else: + current_version = current_version_re.group(1) + + # Skip retrieving online release versions if install file is specified + if self._offline_file is not None: + if current_version is not None and not self._force: + print("There is a version of Poetry already installed.") + return None, current_version + + return "from an offline file", current_version + + print(colorize("info", "Retrieving Poetry metadata")) + + metadata = json.loads(self._get(self.METADATA_URL).decode()) + + def _compare_versions(x, y): + mx = self.VERSION_REGEX.match(x) + my = self.VERSION_REGEX.match(y) + + vx = tuple(int(p) for p in mx.groups()[:3]) + (mx.group(5),) + vy = tuple(int(p) for p in my.groups()[:3]) + (my.group(5),) + + if vx < vy: + return -1 + elif vx > vy: + return 1 + + return 0 + + print("") + releases = sorted( + metadata["releases"].keys(), key=cmp_to_key(_compare_versions) + ) + + if self._version and self._version not in releases: + print(colorize("error", "Version {} does not exist.".format(self._version))) + + return None, None + + version = self._version + if not version: + for release in reversed(releases): + m = self.VERSION_REGEX.match(release) + if m.group(5) and not self.allows_prereleases(): + continue + + version = release + + break + + current_version = None + if os.path.exists(POETRY_LIB): + with open( + os.path.join(POETRY_LIB, "poetry", "__version__.py"), encoding="utf-8" + ) as f: + version_content = f.read() + + current_version_re = re.match( + '(?ms).*__version__ = "(.+)".*', version_content + ) + if not current_version_re: + print( + colorize( + "warning", + "Unable to get the current Poetry version. Assuming None", + ) + ) + else: + current_version = current_version_re.group(1) + + if current_version == version and not self._force: + print("Latest version already installed.") + return None, current_version + + return version, current_version + + def customize_install(self): + if not self._accept_all: + print("Before we start, please answer the following questions.") + print("You may simply press the Enter key to leave unchanged.") + + modify_path = input("Modify PATH variable? ([y]/n) ") or "y" + if modify_path.lower() in {"n", "no"}: + self._modify_path = False + + print("") + + def customize_uninstall(self): + if not self._accept_all: + print() + + uninstall = ( + input("Are you sure you want to uninstall Poetry? (y/[n]) ") or "n" + ) + if uninstall.lower() not in {"y", "yes"}: + return False + + print("") + + return True + + def ensure_home(self): + """ + Ensures that $POETRY_HOME exists or create it. + """ + if not os.path.exists(POETRY_HOME): + os.mkdir(POETRY_HOME, 0o755) + + def remove_home(self): + """ + Removes $POETRY_HOME. + """ + if not os.path.exists(POETRY_HOME): + return + + shutil.rmtree(POETRY_HOME) + + def install(self, version, upgrade=False, file=None): + """ + Installs Poetry in $POETRY_HOME. + """ + if file is not None: + print("Attempting to install from file: " + colorize("info", file)) + else: + print("Installing version: " + colorize("info", version)) + + self.make_lib(version) + self.make_bin() + self.make_env() + self.update_path() + + return 0 + + def make_lib(self, version): + """ + Packs everything into a single lib/ directory. + """ + if os.path.exists(POETRY_LIB_BACKUP): + shutil.rmtree(POETRY_LIB_BACKUP) + + # Backup the current installation + if os.path.exists(POETRY_LIB): + shutil.copytree(POETRY_LIB, POETRY_LIB_BACKUP) + shutil.rmtree(POETRY_LIB) + + try: + self._make_lib(version) + except Exception: + if not os.path.exists(POETRY_LIB_BACKUP): + raise + + shutil.copytree(POETRY_LIB_BACKUP, POETRY_LIB) + shutil.rmtree(POETRY_LIB_BACKUP) + + raise + finally: + if os.path.exists(POETRY_LIB_BACKUP): + shutil.rmtree(POETRY_LIB_BACKUP) + + def _make_lib(self, version): + # Check if an offline installer file has been specified + if self._offline_file is not None: + try: + self.extract_lib(self._offline_file) + return + except Exception: + raise RuntimeError("Could not install from offline file.") + + # We get the payload from the remote host + platform = sys.platform + if platform == "linux2": + platform = "linux" + + url = self._base_url + "{}/".format(version) + name = "poetry-{}-{}.tar.gz".format(version, platform) + checksum = "poetry-{}-{}.sha256sum".format(version, platform) + + try: + r = urlopen(url + "{}".format(checksum)) + except HTTPError as e: + if e.code == 404: + raise RuntimeError("Could not find {} file".format(checksum)) + + raise + + checksum = r.read().decode() + + try: + r = urlopen(url + "{}".format(name)) + except HTTPError as e: + if e.code == 404: + raise RuntimeError("Could not find {} file".format(name)) + + raise + + meta = r.info() + size = int(meta["Content-Length"]) + current = 0 + block_size = 8192 + + print( + " - Downloading {} ({:.2f}MB)".format( + colorize("comment", name), size / 1024 / 1024 + ) + ) + + sha = hashlib.sha256() + with temporary_directory(prefix="poetry-installer-") as dir_: + tar = os.path.join(dir_, name) + with open(tar, "wb") as f: + while True: + buffer = r.read(block_size) + if not buffer: + break + + current += len(buffer) + f.write(buffer) + sha.update(buffer) + + # Checking hashes + if checksum != sha.hexdigest(): + raise RuntimeError( + "Hashes for {} do not match: {} != {}".format( + name, checksum, sha.hexdigest() + ) + ) + + self.extract_lib(tar) + + def extract_lib(self, filename): + gz = GzipFile(filename, mode="rb") + try: + with tarfile.TarFile(filename, fileobj=gz, format=tarfile.PAX_FORMAT) as f: + f.extractall(POETRY_LIB) + finally: + gz.close() + + def _which_python(self): + """Decides which python executable we'll embed in the launcher script.""" + allowed_executables = ["python", "python3"] + if WINDOWS: + allowed_executables += ["py.exe -3", "py.exe -2"] + + # \d in regex ensures we can convert to int later + version_matcher = re.compile(r"^Python (?P\d+)\.(?P\d+)\..+$") + fallback = None + for executable in allowed_executables: + try: + raw_version = subprocess.check_output( + executable + " --version", stderr=subprocess.STDOUT, shell=True + ).decode("utf-8") + except subprocess.CalledProcessError: + continue + + match = version_matcher.match(raw_version.strip()) + if match: + return executable + + if fallback is None: + # keep this one as the fallback; it was the first valid executable we found. + fallback = executable + + if fallback is None: + raise RuntimeError( + "No python executable found in shell environment. Tried: " + + str(allowed_executables) + ) + + return fallback + + def make_bin(self): + if not os.path.exists(POETRY_BIN): + os.mkdir(POETRY_BIN, 0o755) + + python_executable = self._which_python() + + if WINDOWS: + with open(os.path.join(POETRY_BIN, "poetry.bat"), "w") as f: + f.write( + u( + BAT.format( + python_executable=python_executable, + poetry_bin=os.path.join(POETRY_BIN, "poetry").replace( + os.environ["USERPROFILE"], "%USERPROFILE%" + ), + ) + ) + ) + + with open(os.path.join(POETRY_BIN, "poetry"), "w", encoding="utf-8") as f: + if WINDOWS: + python_executable = "python" + + f.write(u("#!/usr/bin/env {}\n".format(python_executable))) + f.write(u(BIN)) + + if not WINDOWS: + # Making the file executable + st = os.stat(os.path.join(POETRY_BIN, "poetry")) + os.chmod(os.path.join(POETRY_BIN, "poetry"), st.st_mode | stat.S_IEXEC) + + def make_env(self): + if WINDOWS: + return + + with open(os.path.join(POETRY_HOME, "env"), "w") as f: + f.write(u(self.get_export_string())) + + def update_path(self): + """ + Tries to update the $PATH automatically. + """ + if not self._modify_path: + return + + if "fish" in SHELL: + return self.add_to_fish_path() + + if WINDOWS: + return self.add_to_windows_path() + + # Updating any profile we can on UNIX systems + export_string = self.get_export_string() + + addition = "\n{}\n".format(export_string) + + profiles = self.get_unix_profiles() + for profile in profiles: + if not os.path.exists(profile): + continue + + with open(profile, "r") as f: + content = f.read() + + if addition not in content: + with open(profile, "a") as f: + f.write(u(addition)) + + def add_to_fish_path(self): + """ + Ensure POETRY_BIN directory is on Fish shell $PATH + """ + current_path = os.environ.get("PATH", None) + if current_path is None: + print( + colorize( + "warning", + "\nUnable to get the PATH value. It will not be updated automatically.", + ) + ) + self._modify_path = False + + return + + if POETRY_BIN not in current_path: + fish_user_paths = subprocess.check_output( + ["fish", "-c", "echo $fish_user_paths"] + ).decode("utf-8") + if POETRY_BIN not in fish_user_paths: + cmd = "set -U fish_user_paths {} $fish_user_paths".format(POETRY_BIN) + set_fish_user_path = ["fish", "-c", "{}".format(cmd)] + subprocess.check_output(set_fish_user_path) + else: + print( + colorize( + "warning", + "\nPATH already contains {} and thus was not modified.".format( + POETRY_BIN + ), + ) + ) + + def add_to_windows_path(self): + try: + old_path = self.get_windows_path_var() + except WindowsError: + old_path = None + + if old_path is None: + print( + colorize( + "warning", + "Unable to get the PATH value. It will not be updated automatically", + ) + ) + self._modify_path = False + + return + + new_path = POETRY_BIN + if POETRY_BIN in old_path: + old_path = old_path.replace(POETRY_BIN + ";", "") + + if old_path: + new_path += ";" + new_path += old_path + + self.set_windows_path_var(new_path) + + def get_windows_path_var(self): + with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root: + with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key: + path, _ = winreg.QueryValueEx(key, "PATH") + + return path + + def set_windows_path_var(self, value): + import ctypes + + with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root: + with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key: + winreg.SetValueEx(key, "PATH", 0, winreg.REG_EXPAND_SZ, value) + + # Tell other processes to update their environment + HWND_BROADCAST = 0xFFFF + WM_SETTINGCHANGE = 0x1A + + SMTO_ABORTIFHUNG = 0x0002 + + result = ctypes.c_long() + SendMessageTimeoutW = ctypes.windll.user32.SendMessageTimeoutW + SendMessageTimeoutW( + HWND_BROADCAST, + WM_SETTINGCHANGE, + 0, + u"Environment", + SMTO_ABORTIFHUNG, + 5000, + ctypes.byref(result), + ) + + def remove_from_path(self): + if "fish" in SHELL: + return self.remove_from_fish_path() + + elif WINDOWS: + return self.remove_from_windows_path() + + return self.remove_from_unix_path() + + def remove_from_fish_path(self): + fish_user_paths = subprocess.check_output( + ["fish", "-c", "echo $fish_user_paths"] + ).decode("utf-8") + if POETRY_BIN in fish_user_paths: + cmd = "set -U fish_user_paths (string match -v {} $fish_user_paths)".format( + POETRY_BIN + ) + set_fish_user_path = ["fish", "-c", "{}".format(cmd)] + subprocess.check_output(set_fish_user_path) + + def remove_from_windows_path(self): + path = self.get_windows_path_var() + + poetry_path = POETRY_BIN + if poetry_path in path: + path = path.replace(POETRY_BIN + ";", "") + + if poetry_path in path: + path = path.replace(POETRY_BIN, "") + + self.set_windows_path_var(path) + + def remove_from_unix_path(self): + # Updating any profile we can on UNIX systems + export_string = self.get_export_string() + + addition = "{}\n".format(export_string) + + profiles = self.get_unix_profiles() + for profile in profiles: + if not os.path.exists(profile): + continue + + with open(profile, "r") as f: + content = f.readlines() + + if addition not in content: + continue + + new_content = [] + for line in content: + if line == addition: + if new_content and not new_content[-1].strip(): + new_content = new_content[:-1] + + continue + + new_content.append(line) + + with open(profile, "w") as f: + f.writelines(new_content) + + def get_export_string(self): + path = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME") + export_string = 'export PATH="{}:$PATH"'.format(path) + + return export_string + + def get_unix_profiles(self): + profiles = [os.path.join(HOME, ".profile")] + + if "zsh" in SHELL: + zdotdir = os.getenv("ZDOTDIR", HOME) + profiles.append(os.path.join(zdotdir, ".zshrc")) + + bash_profile = os.path.join(HOME, ".bash_profile") + if os.path.exists(bash_profile): + profiles.append(bash_profile) + + return profiles + + def display_pre_message(self): + if WINDOWS: + home = POETRY_BIN.replace(os.getenv("USERPROFILE", ""), "%USERPROFILE%") + else: + home = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME") + + kwargs = { + "poetry": colorize("info", "Poetry"), + "poetry_home_bin": colorize("comment", home), + } + + if not self._modify_path: + kwargs["platform_msg"] = PRE_MESSAGE_NO_MODIFY_PATH + else: + if "fish" in SHELL: + kwargs["platform_msg"] = PRE_MESSAGE_FISH + elif WINDOWS: + kwargs["platform_msg"] = PRE_MESSAGE_WINDOWS + else: + profiles = [ + colorize("comment", p.replace(os.getenv("HOME", ""), "$HOME")) + for p in self.get_unix_profiles() + ] + kwargs["platform_msg"] = PRE_MESSAGE_UNIX.format( + rcfiles="\n".join(profiles), plural="s" if len(profiles) > 1 else "" + ) + + print(PRE_MESSAGE.format(**kwargs)) + + def display_pre_uninstall_message(self): + home_bin = POETRY_BIN + if WINDOWS: + home_bin = home_bin.replace(os.getenv("USERPROFILE", ""), "%USERPROFILE%") + else: + home_bin = home_bin.replace(os.getenv("HOME", ""), "$HOME") + + kwargs = { + "poetry": colorize("info", "Poetry"), + "poetry_home_bin": colorize("comment", home_bin), + } + + print(PRE_UNINSTALL_MESSAGE.format(**kwargs)) + + def display_post_message(self, version): + print("") + + kwargs = { + "poetry": colorize("info", "Poetry"), + "version": colorize("comment", version), + } + + if WINDOWS: + message = POST_MESSAGE_WINDOWS + if not self._modify_path: + message = POST_MESSAGE_WINDOWS_NO_MODIFY_PATH + + poetry_home_bin = POETRY_BIN.replace( + os.getenv("USERPROFILE", ""), "%USERPROFILE%" + ) + elif "fish" in SHELL: + message = POST_MESSAGE_FISH + if not self._modify_path: + message = POST_MESSAGE_FISH_NO_MODIFY_PATH + + poetry_home_bin = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME") + else: + message = POST_MESSAGE_UNIX + if not self._modify_path: + message = POST_MESSAGE_UNIX_NO_MODIFY_PATH + + poetry_home_bin = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME") + kwargs["poetry_home_env"] = colorize( + "comment", POETRY_ENV.replace(os.getenv("HOME", ""), "$HOME") + ) + + kwargs["poetry_home_bin"] = colorize("comment", poetry_home_bin) + + print(message.format(**kwargs)) + + def call(self, *args): + return subprocess.check_output(args, stderr=subprocess.STDOUT) + + def _get(self, url): + request = Request(url, headers={"User-Agent": "Python Poetry"}) + + with closing(urlopen(request)) as r: + return r.read() + + +def main(): + parser = argparse.ArgumentParser( + description="Installs the latest (or given) version of poetry" + ) + parser.add_argument( + "-p", + "--preview", + help="install preview version", + dest="preview", + action="store_true", + default=False, + ) + parser.add_argument("--version", help="install named version", dest="version") + parser.add_argument( + "-f", + "--force", + help="install on top of existing version", + dest="force", + action="store_true", + default=False, + ) + parser.add_argument( + "--no-modify-path", + help="do not modify $PATH", + dest="no_modify_path", + action="store_true", + default=False, + ) + parser.add_argument( + "-y", + "--yes", + help="accept all prompts", + dest="accept_all", + action="store_true", + default=False, + ) + parser.add_argument( + "--uninstall", + help="uninstall poetry", + dest="uninstall", + action="store_true", + default=False, + ) + parser.add_argument( + "--file", + dest="file", + action="store", + help="Install from a local file instead of fetching the latest version " + "of Poetry available online.", + ) + + args = parser.parse_args() + + base_url = Installer.BASE_URL + + if args.file is None: + try: + urlopen(Installer.REPOSITORY_URL) + except HTTPError as e: + if e.code == 404: + base_url = Installer.FALLBACK_BASE_URL + else: + raise + + installer = Installer( + version=args.version or os.getenv("POETRY_VERSION"), + preview=args.preview or string_to_bool(os.getenv("POETRY_PREVIEW", "0")), + force=args.force, + modify_path=not args.no_modify_path, + accept_all=args.accept_all + or string_to_bool(os.getenv("POETRY_ACCEPT", "0")) + or not is_interactive(), + file=args.file, + base_url=base_url, + ) + + if args.uninstall or string_to_bool(os.getenv("POETRY_UNINSTALL", "0")): + return installer.uninstall() + + return installer.run() + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file